In the Lumiera code base, we use C-String constants as unique error-IDs. Basically this allows to create new unique error IDs anywhere in the code. However, definition of such IDs in arbitrary namespaces tends to create slight confusion and ambiguities, while maintaining the proper use statements requires some manual work. Thus I introduce a new **standard scheme** * Error-IDs for widespread use shall be defined _exclusively_ into `namespace lumiera::error` * The shorthand-Macro `LERR_()` can now be used to simplify inclusion and referral * (for local or single-usage errors, a local or even hidden definition is OK)
728 lines
23 KiB
C++
728 lines
23 KiB
C++
/*
|
|
PlacementIndex - tracking individual Placements and their relations
|
|
|
|
Copyright (C) Lumiera.org
|
|
2008, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of
|
|
the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
* *****************************************************/
|
|
|
|
|
|
/** @file placement-index.cpp
|
|
** Implementation of core session storage structure.
|
|
** The PlacementIndex associates IDs to instances, within a nested scope structure.
|
|
** Moreover, it provides and manages the actual Placement instances (storage),
|
|
** considered to be part of the session.
|
|
**
|
|
** Simple hash based implementation. Seems adequate for now (12/09).
|
|
** A main table associates Placement-ID to a Placement \em instance, which is contained
|
|
** and managed within this index. A second hashtable allows to reverse lookup the scope
|
|
** associations, especially for enumerating the contents of a scope. The latter is done
|
|
** by wrapping up an STL iterator range into a "Lumiera Forward Iterator" (adapter).
|
|
** Generally speaking, PlacementIndex is an implementation level facility and provides
|
|
** the basic/low-level functionality. For example, the PlacementIndexQueryResolver
|
|
** provides depth-first exploration of all the contents of an scope, including
|
|
** nested scopes, building on top of the scope iterators from PlacementIndex.
|
|
**
|
|
** PlacementIndex can be seen as the core datastructure of the session. Objects are attached
|
|
** to the session by adding (copying) a Placement instance, which is owned and managed by
|
|
** the PlacementIndex. Adding this Placement instance creates a new Placement-ID, which
|
|
** from then on acts as a shorthand for "the object instance" within the session.
|
|
** The actual storage is provided by an embedded TypedAllocationManager instance, which
|
|
** is planned (as of 12/09) to be backed later by a memory pool based custom allocator.
|
|
**
|
|
** @see PlacementRef
|
|
** @see PlacementIndex_test
|
|
**
|
|
*/
|
|
|
|
|
|
#include "steam/mobject/session/placement-index.hpp"
|
|
#include "steam/mobject/session/session-impl.hpp"
|
|
#include "steam/mobject/session/scope.hpp"
|
|
#include "lib/typed-allocation-manager.hpp"
|
|
#include "lib/iter-adapter-stl.hpp"
|
|
#include "lib/util-foreach.hpp"
|
|
#include "lib/iter-source.hpp"
|
|
#include "include/logging.h"
|
|
|
|
#include <boost/functional/hash.hpp>
|
|
#include <unordered_map>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
namespace lumiera {
|
|
namespace error {
|
|
LUMIERA_ERROR_DEFINE (NOT_IN_SESSION, "referring to a Placement not known to the current session");
|
|
LUMIERA_ERROR_DEFINE (PLACEMENT_TYPE, "requested Placement (pointee) type not compatible with data or context");
|
|
LUMIERA_ERROR_DEFINE (NONEMPTY_SCOPE, "Placement scope (still) contains other elements");
|
|
}}
|
|
|
|
namespace steam {
|
|
namespace mobject {
|
|
namespace session {
|
|
|
|
using boost::hash;
|
|
using std::shared_ptr;
|
|
using std::unordered_map;
|
|
using std::unordered_multimap;
|
|
using lib::TypedAllocationManager;
|
|
using lib::iter_stl::IterSnapshot;
|
|
using lib::iter_stl::eachVal;
|
|
using lib::iter_source::eachMapKey;
|
|
using lib::iter_source::eachDistinctKey;
|
|
using lib::iter_source::eachValForKey;
|
|
using std::placeholders::_1;
|
|
using std::function;
|
|
using std::bind;
|
|
using std::make_pair;
|
|
using std::pair;
|
|
|
|
using util::for_each;
|
|
using util::has_any;
|
|
|
|
|
|
|
|
|
|
/* some type shorthands */
|
|
typedef mobject::PlacementMO PlacementMO;
|
|
typedef PlacementIndex::PRef PRef;
|
|
typedef PlacementIndex::ID ID;
|
|
|
|
|
|
/*****************************************************************//**
|
|
* Storage and implementation backing the PlacementIndex
|
|
* - #placementTab_ is an hashtable mapping IDs to Placement + Scope
|
|
* - #scopeTab_ is an reverse association serving to keep track of
|
|
* any scope's contents
|
|
* - root scope element is stored and maintained explicitly.
|
|
*/
|
|
class PlacementIndex::Table
|
|
{
|
|
typedef shared_ptr<PlacementMO> PPlacement;
|
|
|
|
struct PlacementEntry
|
|
{
|
|
PPlacement element;
|
|
PPlacement scope;
|
|
};
|
|
|
|
// using hashtables to implement the index
|
|
typedef PlacementMO::ID PID;
|
|
typedef unordered_map<PID, PlacementEntry> IDTable;
|
|
typedef std::unordered_multimap<PID,PID> ScopeTable;
|
|
|
|
typedef pair<ScopeIter, ScopeIter> ScopeContents;
|
|
|
|
|
|
TypedAllocationManager allocator_;
|
|
IDTable placementTab_;
|
|
ScopeTable scopeTab_;
|
|
|
|
PPlacement root_;
|
|
|
|
public:
|
|
Table()
|
|
{ }
|
|
|
|
~Table()
|
|
try {
|
|
root_.reset();
|
|
clear();
|
|
}
|
|
catch (lumiera::Error& err)
|
|
{
|
|
WARN (session, "Problem while purging PlacementIndex: %s", err.what());
|
|
lumiera_error(); // discard any error state
|
|
}
|
|
|
|
|
|
size_t
|
|
size() const ///<@note always at least 1 because of root
|
|
{
|
|
return placementTab_.size();
|
|
}
|
|
|
|
size_t
|
|
scope_cnt() const ///<@note root doesn't produce an scope entry
|
|
{
|
|
return scopeTab_.size();
|
|
}
|
|
|
|
size_t
|
|
element_cnt() const
|
|
{
|
|
return allocator_.numSlots<PlacementMO>();
|
|
}
|
|
|
|
bool
|
|
contains (ID id) const
|
|
{
|
|
return util::contains(placementTab_, id);
|
|
}
|
|
|
|
|
|
PlacementMO&
|
|
fetch (ID id) const
|
|
{
|
|
REQUIRE (contains (id));
|
|
PPlacement const& entry = base_entry(id).element;
|
|
|
|
ENSURE (entry);
|
|
ENSURE (id == entry->getID());
|
|
return *entry;
|
|
}
|
|
|
|
PlacementMO&
|
|
fetchScope (ID id) const
|
|
{
|
|
REQUIRE (contains (id));
|
|
PPlacement const& scope = base_entry(id).scope;
|
|
|
|
ENSURE (scope);
|
|
ENSURE (contains (scope->getID()));
|
|
return *scope;
|
|
}
|
|
|
|
PlacementIndex::iterator
|
|
queryScopeContents (ID id) const
|
|
{
|
|
REQUIRE (contains (id));
|
|
ScopeContents contents = scopeTab_.equal_range (id);
|
|
return iterator (ScopeRangeIter(contents.first, contents.second)
|
|
,scopeIndexElementsResolver() );
|
|
}
|
|
|
|
|
|
|
|
void
|
|
clear()
|
|
{
|
|
INFO (session, "Purging Placement Tables...");
|
|
scopeTab_.clear();
|
|
placementTab_.clear();
|
|
|
|
if (root_)
|
|
setupRoot (*root_);
|
|
}
|
|
|
|
|
|
/** insert a specially configured root entry into
|
|
* the yet empty table. Root is it's own scope
|
|
*/
|
|
void
|
|
setupRoot (PlacementMO const& rootDef)
|
|
{
|
|
REQUIRE (0 == placementTab_.size());
|
|
REQUIRE (0 == scopeTab_.size());
|
|
|
|
root_ = allocator_.create<PlacementMO> (rootDef);
|
|
ID rootID = root_->getID();
|
|
placementTab_[rootID].element = root_;
|
|
placementTab_[rootID].scope = root_;
|
|
|
|
ENSURE (contains (rootID));
|
|
ENSURE (scopeTab_.empty());
|
|
ENSURE (1 == size());
|
|
}
|
|
|
|
PlacementMO&
|
|
getRootElement()
|
|
{
|
|
REQUIRE (root_);
|
|
REQUIRE (0 < size());
|
|
REQUIRE (contains (root_->getID()));
|
|
|
|
return *root_;
|
|
}
|
|
|
|
|
|
/** Store a copy of the given Placement as new instance
|
|
* within the index, together with the Scope
|
|
* this Placement belongs to.
|
|
* @note we discard the specific type info.
|
|
* It can be rediscovered later with the help
|
|
* of the pointee's vtable
|
|
* @see Placement#isCompatible
|
|
*/
|
|
ID
|
|
addEntry (PlacementMO const& newObj, ID scopeID)
|
|
{
|
|
REQUIRE (contains (scopeID));
|
|
|
|
PPlacement newEntry = allocator_.create<PlacementMO> (newObj);
|
|
ID newID = newEntry->getID();
|
|
|
|
ASSERT (newID, "invalid");
|
|
ASSERT (!contains (newID));
|
|
placementTab_[newID].element = newEntry;
|
|
placementTab_[newID].scope = placementTab_[scopeID].element;
|
|
scopeTab_.insert (make_pair (scopeID, newID));
|
|
return newID;
|
|
}
|
|
|
|
bool
|
|
removeEntry (ID id)
|
|
{
|
|
if (!contains (id))
|
|
{
|
|
ENSURE (!util::contains(scopeTab_, id));
|
|
return false;
|
|
}
|
|
|
|
if (util::contains(scopeTab_, id))
|
|
throw error::State{"Unable to remove the specified Placement, "
|
|
"because it defines an non-empty scope. "
|
|
"You need to delete any contents first."
|
|
, LERR_(NONEMPTY_SCOPE)}; //////////////////////////////TICKET #197
|
|
|
|
ASSERT (contains (id));
|
|
PlacementEntry toRemove = remove_base_entry (id);
|
|
remove_from_scope (toRemove.scope->getID(), id);
|
|
ENSURE (!util::contains(scopeTab_, id));
|
|
ENSURE (!contains (id));
|
|
return true;
|
|
}
|
|
|
|
void
|
|
removeAll (ID scopeID)
|
|
{
|
|
remove_all_from_scope (scopeID); // recursive
|
|
removeEntry (scopeID); // discard top-level
|
|
|
|
ENSURE (!util::contains(scopeTab_, scopeID));
|
|
ENSURE (!contains (scopeID));
|
|
}
|
|
|
|
|
|
/* == access for self-test == */
|
|
|
|
using IDIter = lib::IterSource<PID>::iterator;
|
|
|
|
PlacementMO* _root_4check () { return root_.get(); }
|
|
PlacementMO* _element_4check (ID id){ return base_entry(id).element.get();}
|
|
PlacementMO* _scope_4check (ID id) { return base_entry(id).scope.get(); }
|
|
IDIter _eachEntry_4check () { return eachMapKey (placementTab_); }
|
|
IDIter _eachScope_4check () { return eachDistinctKey (scopeTab_); }
|
|
IDIter _contents_4check(ID id){ return eachValForKey (scopeTab_,id);}
|
|
|
|
|
|
private:
|
|
typedef IDTable::const_iterator Slot;
|
|
|
|
PlacementEntry const&
|
|
base_entry (ID key) const
|
|
{
|
|
Slot pos = placementTab_.find (key);
|
|
if (pos == placementTab_.end())
|
|
throw error::Logic("lost a Placement expected to be registered within PlacementIndex.");
|
|
|
|
return pos->second;
|
|
}
|
|
|
|
PlacementEntry
|
|
remove_base_entry (ID key)
|
|
{
|
|
Slot pos = placementTab_.find (key);
|
|
REQUIRE (pos != placementTab_.end());
|
|
PlacementEntry dataToRemove (pos->second);
|
|
placementTab_.erase(pos);
|
|
return dataToRemove;
|
|
}
|
|
|
|
void
|
|
remove_from_scope (ID scopeID, ID entryID)
|
|
{
|
|
typedef ScopeTable::const_iterator Pos;
|
|
pair<Pos,Pos> searchRange = scopeTab_.equal_range(scopeID);
|
|
|
|
Pos pos = searchRange.first;
|
|
Pos end = searchRange.second;
|
|
for ( ; pos!=end; ++pos)
|
|
if (pos->second == entryID)
|
|
{
|
|
scopeTab_.erase(pos);
|
|
return;
|
|
}
|
|
|
|
NOTREACHED();
|
|
}
|
|
|
|
void
|
|
remove_all_from_scope (ID scopeID)
|
|
{
|
|
typedef ScopeTable::const_iterator Pos;
|
|
pair<Pos,Pos> scopeEntries = scopeTab_.equal_range(scopeID);
|
|
Pos first = scopeEntries.first;
|
|
Pos end = scopeEntries.second;
|
|
|
|
// take a snapshot of all children to be processed recursively
|
|
typedef IterSnapshot<PID> ChildIDs;
|
|
ChildIDs child (eachVal(first,end));
|
|
|
|
scopeTab_.erase (first,end); // assumed to be NOP for first==end
|
|
|
|
for ( ; child; ++child )
|
|
{
|
|
remove_all_from_scope (*child); // recursive
|
|
remove_base_entry (*child); // discard storage
|
|
|
|
ENSURE (!util::contains(scopeTab_, *child));
|
|
ENSURE (!contains (*child));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** Helper for building a scope exploring iterator
|
|
* for PlacementIndex: our "reverse index" (`scopeTab_`)
|
|
* tracks the contents of each scope as pairs (scopeID,elementID).
|
|
* After fetching the range of matching entries, whenever the client
|
|
* dereferences the iterator, we have to pick up the second ID and
|
|
* resolve it through the main index table (placementTab_).
|
|
*/
|
|
PlacementMO&
|
|
resolveScopeIndexElement(pair<PID,PID> const& entry) const
|
|
{
|
|
ID elemID (entry.second);
|
|
ASSERT (contains (elemID));
|
|
return fetch (elemID);
|
|
}
|
|
|
|
|
|
typedef function<PlacementMO& (pair<PID,PID> const&)> ElementResolver;
|
|
mutable ElementResolver elementResolver_;
|
|
|
|
ElementResolver
|
|
scopeIndexElementsResolver() const ///< @return functor for iterator element access
|
|
{
|
|
if (!elementResolver_)
|
|
elementResolver_ = bind (&Table::resolveScopeIndexElement, this, _1 );
|
|
return elementResolver_;
|
|
}
|
|
};
|
|
//(End) placement index table implementation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ============ PlacementIndex implementation functions ============ */
|
|
|
|
|
|
PlacementIndex::PlacementIndex (PlacementMO const& rootDef)
|
|
: pTab_(new Table)
|
|
{
|
|
INFO (session, "Initialising PlacementIndex...");
|
|
|
|
pTab_->setupRoot(rootDef);
|
|
ENSURE (isValid());
|
|
}
|
|
|
|
PlacementIndex::~PlacementIndex() { }
|
|
|
|
|
|
PlacementMO&
|
|
PlacementIndex::getRoot() const
|
|
{
|
|
return pTab_->getRootElement();
|
|
}
|
|
|
|
|
|
size_t
|
|
PlacementIndex::size() const
|
|
{
|
|
ASSERT (0 < pTab_->size());
|
|
return pTab_->size() - 1; // root not counted
|
|
}
|
|
|
|
|
|
bool
|
|
PlacementIndex::contains (ID id) const
|
|
{
|
|
return pTab_->contains (id);
|
|
}
|
|
|
|
|
|
PlacementMO&
|
|
PlacementIndex::find (ID id) const
|
|
{
|
|
__check_knownID(*this,id);
|
|
return pTab_->fetch (id);
|
|
}
|
|
|
|
|
|
|
|
/** retrieve the Scope information
|
|
* registered alongside with the denoted Placement.
|
|
* @throw error::Invalid when the given ID isn't registered
|
|
* @note root is it's own scope, per definition.
|
|
*/
|
|
PlacementMO&
|
|
PlacementIndex::getScope (ID id) const
|
|
{
|
|
__check_knownID(*this,id);
|
|
return pTab_->fetchScope (id);
|
|
}
|
|
|
|
|
|
/** Retrieve all the elements attached to the given entry (scope)
|
|
* Each element (Placement) can act as a scope, containing other Placements,
|
|
* which will be discovered by this query one level deep (not recursive).
|
|
* @return a Lumiera Forward Iterator, yielding the children,
|
|
* possibly empty if the denoted element is a leaf.
|
|
* @note results are returned in arbitrary order (hashtable)
|
|
*/
|
|
PlacementIndex::iterator
|
|
PlacementIndex::getReferrers (ID id) const
|
|
{
|
|
__check_knownID(*this, id);
|
|
return pTab_->queryScopeContents(id);
|
|
}
|
|
|
|
|
|
/** Add a new Placement (Object "instance") into the index.
|
|
* Usually this means effectively adding this "Object" to the Session.
|
|
* The given Placement is copied into the storage managed within the session.
|
|
* This copy within the storage is what will be "the placement of this object".
|
|
* It can be discovered as index (Session) content, re-accessed by the ID returned
|
|
* from this call and modified in the course of editing the session.
|
|
* @param newObj reference placement pointing to the MObject to be added
|
|
* @param targetScope ref to a placement already added to the index, serving as
|
|
* container "into" which the new placement will be located
|
|
* @return placement ID of the newly added Placement
|
|
* @note the newly added Placement has an identity of its own.
|
|
*/
|
|
ID
|
|
PlacementIndex::insert (PlacementMO const& newObj, ID targetScope)
|
|
{
|
|
if (!contains (targetScope))
|
|
throw error::Logic ("Specified a non-registered Placement as scope "
|
|
"while adding another Placement to the index"
|
|
,LERR_(INVALID_SCOPE)); ////////////////TICKET #197
|
|
|
|
return pTab_->addEntry(newObj, targetScope);
|
|
}
|
|
|
|
|
|
/** Remove and discard a Placement (Object "instance") from the index.
|
|
* Usually this means removing this Object from the session.
|
|
* @return \c true if actually removed an object.
|
|
* @throw error::State if the object to be removed is an non-empty scope
|
|
*/
|
|
bool
|
|
PlacementIndex::remove (ID id)
|
|
{
|
|
if (id == getRoot().getID())
|
|
throw error::Fatal ("Request to kill the model root.");
|
|
|
|
return pTab_->removeEntry (id);
|
|
}
|
|
|
|
|
|
/** recursively kill a complete scope,
|
|
* including the given element and all children.
|
|
* @note as an exception, when specifying model root,
|
|
* any sub-elements are cleared but root is retained
|
|
*/
|
|
void
|
|
PlacementIndex::clear (ID targetScope)
|
|
{
|
|
if (targetScope == getRoot().getID())
|
|
pTab_->clear();
|
|
else
|
|
pTab_->removeAll (targetScope);
|
|
|
|
ENSURE (isValid());
|
|
}
|
|
|
|
|
|
void
|
|
PlacementIndex::clear()
|
|
{
|
|
pTab_->clear();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ====== PlacementIndex validity self-check ====== */
|
|
|
|
namespace { // self-check implementation helpers...
|
|
|
|
LUMIERA_ERROR_DEFINE(INDEX_CORRUPTED, "PlacementIndex corrupted");
|
|
|
|
struct SelfCheckFailure
|
|
: error::Fatal
|
|
{
|
|
SelfCheckFailure (lib::Literal currentTest, string failure)
|
|
: error::Fatal (string("Failed test: ")+currentTest+ " : "+failure
|
|
,LUMIERA_ERROR_INDEX_CORRUPTED)
|
|
{ }
|
|
};
|
|
}
|
|
|
|
|
|
/**
|
|
* PlacementIndex self-verification code
|
|
* Executes all built-in checks automatically
|
|
* on object creation.
|
|
* @throw SelfCheckFailure
|
|
*/
|
|
class PlacementIndex::Validator
|
|
{
|
|
|
|
PlacementIndex::Table& tab;
|
|
|
|
PlacementMO* elm (ID id) { return tab._element_4check (id);}
|
|
PlacementMO* sco (ID id) { return tab._scope_4check (id); }
|
|
|
|
|
|
|
|
#define VERIFY(_CHECK_, CHECK_ID, DESCRIPTION) \
|
|
if (!(_CHECK_)) \
|
|
throw SelfCheckFailure (CHECK_ID, (DESCRIPTION));
|
|
|
|
|
|
void
|
|
checkRoot (PMO* root)
|
|
{
|
|
VERIFY ( root, "(0.1) Basics", "Root element missing");
|
|
VERIFY ( root->isValid(), "(0.2) Basics", "Root Placement invalid");
|
|
VERIFY ( (*root)->isValid(), "(0.3) Basics", "Root MObject self-check failure");
|
|
}
|
|
|
|
void
|
|
checkEntry (ID id)
|
|
{
|
|
VERIFY ( tab.contains(id), "(1.1) Elements", "PlacementIndex main table corrupted");
|
|
VERIFY ( elm(id), "(1.2) Elements", "Entry doesn't hold a Placement");
|
|
VERIFY ( id==elm(id)->getID(), "(1.3) Elements", "Element stored with wrong ID"); ////////////////TICKET #197
|
|
VERIFY ( elm(id)->isValid(), "(1.4) Elements", "Index contains invalid Placement")
|
|
VERIFY ( sco(id), "(1.5) Elements", "Entry has undefined scope");
|
|
VERIFY ( sco(id)->isValid(), "(1.6) Elements", "Entry has invalid scope");
|
|
VERIFY ( tab.contains (sco(id)->getID()),
|
|
"(1.7) Elements", "Element associated with an unknown scope");
|
|
|
|
PMO& theElement = *elm(id);
|
|
ID theScope (sco(id)->getID());
|
|
if (theScope == id
|
|
&& elm(id)==tab._root_4check()
|
|
) // no need to check root,
|
|
return; // because root is it's own scope
|
|
|
|
iterator elementsInScope = tab.queryScopeContents(theScope);
|
|
auto equalsTheElement = [&](PMO& entry) { return entry == theElement; };
|
|
bool properlyRegistered = has_any (elementsInScope, equalsTheElement);
|
|
|
|
VERIFY ( properlyRegistered, "(1.8) Elements", "Element not registered as member of the enclosing scope: "+ theElement);
|
|
}
|
|
|
|
void
|
|
checkScope (ID id)
|
|
{
|
|
VERIFY ( tab.contains(id), "(2.1) Scopes", "Scope not registered in main table");
|
|
VERIFY ( elm(id), "(2.2) Scopes", "Scope entry doesn't hold a Placement");
|
|
VERIFY ( sco(id), "(2.3) Scopes", "Scope entry doesn't hold a containing Scope");
|
|
|
|
PMO* root = tab._root_4check();
|
|
PMO* scope = sco(id);
|
|
while (scope && scope != sco(scope->getID()))
|
|
scope = sco(scope->getID());
|
|
|
|
VERIFY ( root==scope, "(2.4) Scopes", "Found a scope not attached below root.");
|
|
|
|
for_each ( tab._contents_4check(id), &Validator::checkScopeEntry, this, id, _1 );
|
|
}
|
|
|
|
void
|
|
checkScopeEntry (ID scope, ID entry)
|
|
{
|
|
VERIFY ( tab.contains(entry), "(2.5) Scopes", "Scope member not registered in main table");
|
|
VERIFY ( elm(entry), "(2.6) Scopes", "Scope member entry doesn't refer to a valid Placement");
|
|
VERIFY ( sco(entry), "(2.7) Scopes", "Scope member entry is lacking valid scope information");
|
|
VERIFY ( sco(entry)->getID() == scope,
|
|
"(2.8) Scopes", "Scope member registered as belonging to a different scope in main table");
|
|
}
|
|
|
|
void
|
|
checkAllocation ()
|
|
{
|
|
VERIFY ( 0 < tab.size(), "(4.1) Storage", "Implementation table is empty");
|
|
VERIFY ( 0 < tab.element_cnt(),"(4.2) Storage", "No Placement instances allocated");
|
|
VERIFY ( tab.size()==tab.scope_cnt()+1,
|
|
"(4.3) Storage", "Number of elements and scope entries disagree");
|
|
VERIFY ( tab.size()==tab.element_cnt(),
|
|
"(4.4) Storage", "Number of entries doesn't match number of allocated Placement instances");
|
|
}
|
|
|
|
|
|
public:
|
|
Validator (Table& indexTable)
|
|
: tab(indexTable)
|
|
{
|
|
checkRoot (tab._root_4check());
|
|
|
|
for_each ( tab._eachEntry_4check(), &Validator::checkEntry, this, _1 );
|
|
for_each ( tab._eachScope_4check(), &Validator::checkScope, this, _1 );
|
|
|
|
checkAllocation();
|
|
}
|
|
|
|
};//(End) Validator (PlacementIndex self-check implementation)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** validity self-check, used for sanity checks and the session self-check.
|
|
* The following checks are performed (causing at least one full table scan)
|
|
* - root element exists and is valid.
|
|
* - each element
|
|
* - has a known scope
|
|
* - is registered as child of it's scope
|
|
* - can reach root from each scope
|
|
* - element count of the allocator matches table size
|
|
*/
|
|
bool
|
|
PlacementIndex::isValid() const
|
|
{
|
|
try
|
|
{
|
|
VERIFY ( pTab_, "(0) Basics" ,"Implementation tables not initialised");
|
|
|
|
Validator verify(*pTab_);
|
|
return true;
|
|
}
|
|
|
|
catch(SelfCheckFailure& failure)
|
|
{
|
|
lumiera_error();
|
|
ERROR (session, "%s", failure.what());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#undef VERIFY
|
|
|
|
|
|
|
|
}}} // namespace steam::mobject::session
|