Lumiera  0.pre.03
»edit your freedom«
placement-index.cpp
Go to the documentation of this file.
1 /*
2  PlacementIndex - tracking individual Placements and their relations
3 
4  Copyright (C)
5  2008, Hermann Vosseler <Ichthyostega@web.de>
6 
7   **Lumiera** is free software; you can redistribute it and/or modify it
8   under the terms of the GNU General Public License as published by the
9   Free Software Foundation; either version 2 of the License, or (at your
10   option) any later version. See the file COPYING for further details.
11 
12 * *****************************************************************/
13 
14 
48 #include "lib/iter-adapter-stl.hpp"
49 #include "lib/util-foreach.hpp"
50 #include "lib/iter-source.hpp"
51 #include "include/logging.h"
52 
53 #include <boost/functional/hash.hpp>
54 #include <unordered_map>
55 #include <functional>
56 #include <memory>
57 #include <string>
58 
59 namespace lumiera {
60 namespace error {
61  LUMIERA_ERROR_DEFINE (NOT_IN_SESSION, "referring to a Placement not known to the current session");
62  LUMIERA_ERROR_DEFINE (PLACEMENT_TYPE, "requested Placement (pointee) type not compatible with data or context");
63  LUMIERA_ERROR_DEFINE (NONEMPTY_SCOPE, "Placement scope (still) contains other elements");
64 }}
65 
66 namespace steam {
67 namespace mobject {
68 namespace session {
69 
70  using boost::hash;
71  using std::shared_ptr;
72  using std::unordered_map;
73  using std::unordered_multimap;
80  using std::placeholders::_1;
81  using std::function;
82  using std::bind;
83  using std::make_pair;
84  using std::pair;
85 
86  using util::for_each;
87  using util::has_any;
88 
89 
90 
91 
92  /* some type shorthands */
93  typedef mobject::PlacementMO PlacementMO;
94  typedef PlacementIndex::PRef PRef;
95  typedef PlacementIndex::ID ID;
96 
97 
98  /*****************************************************************/
106  {
108 
110  {
111  PPlacement element;
112  PPlacement scope;
113  };
114 
115  // using hashtables to implement the index
116  typedef PlacementMO::ID PID;
117  typedef unordered_map<PID, PlacementEntry> IDTable;
118  typedef std::unordered_multimap<PID,PID> ScopeTable;
119 
120  typedef pair<ScopeIter, ScopeIter> ScopeContents;
121 
122 
123  TypedAllocationManager allocator_;
124  IDTable placementTab_;
125  ScopeTable scopeTab_;
126 
127  PPlacement root_;
128 
129  public:
130  Table()
131  { }
132 
133  ~Table()
134  try {
135  root_.reset();
136  clear();
137  }
138  catch (lumiera::Error& err)
139  {
140  WARN (session, "Problem while purging PlacementIndex: %s", err.what());
141  lumiera_error(); // discard any error state
142  }
143 
144 
145  size_t
146  size() const
147  {
148  return placementTab_.size();
149  }
150 
151  size_t
152  scope_cnt() const
153  {
154  return scopeTab_.size();
155  }
156 
157  size_t
158  element_cnt() const
159  {
160  return allocator_.numSlots<PlacementMO>();
161  }
162 
163  bool
164  contains (ID id) const
165  {
166  return util::contains(placementTab_, id);
167  }
168 
169 
170  PlacementMO&
171  fetch (ID id) const
172  {
173  REQUIRE (contains (id));
174  PPlacement const& entry = base_entry(id).element;
175 
176  ENSURE (entry);
177  ENSURE (id == entry->getID());
178  return *entry;
179  }
180 
181  PlacementMO&
182  fetchScope (ID id) const
183  {
184  REQUIRE (contains (id));
185  PPlacement const& scope = base_entry(id).scope;
186 
187  ENSURE (scope);
188  ENSURE (contains (scope->getID()));
189  return *scope;
190  }
191 
193  queryScopeContents (ID id) const
194  {
195  REQUIRE (contains (id));
196  ScopeContents contents = scopeTab_.equal_range (id);
197  return iterator (ScopeRangeIter(contents.first, contents.second)
198  ,scopeIndexElementsResolver() );
199  }
200 
201 
202 
203  void
204  clear()
205  {
206  INFO (session, "Purging Placement Tables...");
207  scopeTab_.clear();
208  placementTab_.clear();
209 
210  if (root_)
211  setupRoot (*root_);
212  }
213 
214 
218  void
219  setupRoot (PlacementMO const& rootDef)
220  {
221  REQUIRE (0 == placementTab_.size());
222  REQUIRE (0 == scopeTab_.size());
223 
224  root_ = allocator_.create<PlacementMO> (rootDef);
225  ID rootID = root_->getID();
226  placementTab_[rootID].element = root_;
227  placementTab_[rootID].scope = root_;
228 
229  ENSURE (contains (rootID));
230  ENSURE (scopeTab_.empty());
231  ENSURE (1 == size());
232  }
233 
234  PlacementMO&
235  getRootElement()
236  {
237  REQUIRE (root_);
238  REQUIRE (0 < size());
239  REQUIRE (contains (root_->getID()));
240 
241  return *root_;
242  }
243 
244 
253  ID
254  addEntry (PlacementMO const& newObj, ID scopeID)
255  {
256  REQUIRE (contains (scopeID));
257 
258  PPlacement newEntry = allocator_.create<PlacementMO> (newObj);
259  ID newID = newEntry->getID();
260 
261  ASSERT (newID, "invalid");
262  ASSERT (!contains (newID));
263  placementTab_[newID].element = newEntry;
264  placementTab_[newID].scope = placementTab_[scopeID].element;
265  scopeTab_.insert (make_pair (scopeID, newID));
266  return newID;
267  }
268 
269  bool
270  removeEntry (ID id)
271  {
272  if (!contains (id))
273  {
274  ENSURE (!util::contains(scopeTab_, id));
275  return false;
276  }
277 
278  if (util::contains(scopeTab_, id))
279  throw error::State{"Unable to remove the specified Placement, "
280  "because it defines an non-empty scope. "
281  "You need to delete any contents first."
282  , LERR_(NONEMPTY_SCOPE)};
283 
284  ASSERT (contains (id));
285  PlacementEntry toRemove = remove_base_entry (id);
286  remove_from_scope (toRemove.scope->getID(), id);
287  ENSURE (!util::contains(scopeTab_, id));
288  ENSURE (!contains (id));
289  return true;
290  }
291 
292  void
293  removeAll (ID scopeID)
294  {
295  remove_all_from_scope (scopeID); // recursive
296  removeEntry (scopeID); // discard top-level
297 
298  ENSURE (!util::contains(scopeTab_, scopeID));
299  ENSURE (!contains (scopeID));
300  }
301 
302 
303  /* == access for self-test == */
304 
306 
307  PlacementMO* _root_4check () { return root_.get(); }
308  PlacementMO* _element_4check (ID id){ return base_entry(id).element.get();}
309  PlacementMO* _scope_4check (ID id) { return base_entry(id).scope.get(); }
310  IDIter _eachEntry_4check () { return eachMapKey (placementTab_); }
311  IDIter _eachScope_4check () { return eachDistinctKey (scopeTab_); }
312  IDIter _contents_4check(ID id){ return eachValForKey (scopeTab_,id);}
313 
314 
315  private:
316  typedef IDTable::const_iterator Slot;
317 
318  PlacementEntry const&
319  base_entry (ID key) const
320  {
321  Slot pos = placementTab_.find (key);
322  if (pos == placementTab_.end())
323  throw error::Logic("lost a Placement expected to be registered within PlacementIndex.");
324 
325  return pos->second;
326  }
327 
329  remove_base_entry (ID key)
330  {
331  Slot pos = placementTab_.find (key);
332  REQUIRE (pos != placementTab_.end());
333  PlacementEntry dataToRemove (pos->second);
334  placementTab_.erase(pos);
335  return dataToRemove;
336  }
337 
338  void
339  remove_from_scope (ID scopeID, ID entryID)
340  {
341  typedef ScopeTable::const_iterator Pos;
342  pair<Pos,Pos> searchRange = scopeTab_.equal_range(scopeID);
343 
344  Pos pos = searchRange.first;
345  Pos end = searchRange.second;
346  for ( ; pos!=end; ++pos)
347  if (pos->second == entryID)
348  {
349  scopeTab_.erase(pos);
350  return;
351  }
352 
353  NOTREACHED();
354  }
355 
356  void
357  remove_all_from_scope (ID scopeID)
358  {
359  typedef ScopeTable::const_iterator Pos;
360  pair<Pos,Pos> scopeEntries = scopeTab_.equal_range(scopeID);
361  Pos first = scopeEntries.first;
362  Pos end = scopeEntries.second;
363 
364  // take a snapshot of all children to be processed recursively
365  typedef IterSnapshot<PID> ChildIDs;
366  ChildIDs child (eachVal(first,end));
367 
368  scopeTab_.erase (first,end); // assumed to be NOP for first==end
369 
370  for ( ; child; ++child )
371  {
372  remove_all_from_scope (*child); // recursive
373  remove_base_entry (*child); // discard storage
374 
375  ENSURE (!util::contains(scopeTab_, *child));
376  ENSURE (!contains (*child));
377  }
378  }
379 
380 
381 
389  PlacementMO&
390  resolveScopeIndexElement(pair<PID,PID> const& entry) const
391  {
392  ID elemID (entry.second);
393  ASSERT (contains (elemID));
394  return fetch (elemID);
395  }
396 
397 
398  typedef function<PlacementMO& (pair<PID,PID> const&)> ElementResolver;
399  mutable ElementResolver elementResolver_;
400 
401  ElementResolver
403  {
404  if (!elementResolver_)
405  elementResolver_ = bind (&Table::resolveScopeIndexElement, this, _1 );
406  return elementResolver_;
407  }
408  };
409  //(End) placement index table implementation
410 
411 
412 
413 
414 
415 
416 
417  /* ============ PlacementIndex implementation functions ============ */
418 
419 
420  PlacementIndex::PlacementIndex (PlacementMO const& rootDef)
421  : pTab_(new Table)
422  {
423  INFO (session, "Initialising PlacementIndex...");
424 
425  pTab_->setupRoot(rootDef);
426  ENSURE (isValid());
427  }
428 
429  PlacementIndex::~PlacementIndex() { }
430 
431 
432  PlacementMO&
433  PlacementIndex::getRoot() const
434  {
435  return pTab_->getRootElement();
436  }
437 
438 
439  size_t
440  PlacementIndex::size() const
441  {
442  ASSERT (0 < pTab_->size());
443  return pTab_->size() - 1; // root not counted
444  }
445 
446 
447  bool
448  PlacementIndex::contains (ID id) const
449  {
450  return pTab_->contains (id);
451  }
452 
453 
454  PlacementMO&
455  PlacementIndex::find (ID id) const
456  {
457  __check_knownID(*this,id);
458  return pTab_->fetch (id);
459  }
460 
461 
462 
468  PlacementMO&
469  PlacementIndex::getScope (ID id) const
470  {
471  __check_knownID(*this,id);
472  return pTab_->fetchScope (id);
473  }
474 
475 
484  PlacementIndex::getReferrers (ID id) const
485  {
486  __check_knownID(*this, id);
487  return pTab_->queryScopeContents(id);
488  }
489 
490 
503  ID
504  PlacementIndex::insert (PlacementMO const& newObj, ID targetScope)
505  {
506  if (!contains (targetScope))
507  throw error::Logic ("Specified a non-registered Placement as scope "
508  "while adding another Placement to the index"
509  ,LERR_(INVALID_SCOPE));
510 
511  return pTab_->addEntry(newObj, targetScope);
512  }
513 
514 
520  bool
521  PlacementIndex::remove (ID id)
522  {
523  if (id == getRoot().getID())
524  throw error::Fatal ("Request to kill the model root.");
525 
526  return pTab_->removeEntry (id);
527  }
528 
529 
535  void
536  PlacementIndex::clear (ID targetScope)
537  {
538  if (targetScope == getRoot().getID())
539  pTab_->clear();
540  else
541  pTab_->removeAll (targetScope);
542 
543  ENSURE (isValid());
544  }
545 
546 
547  void
548  PlacementIndex::clear()
549  {
550  pTab_->clear();
551  }
552 
553 
554 
555 
556 
557  /* ====== PlacementIndex validity self-check ====== */
558 
559  namespace { // self-check implementation helpers...
560 
561  LUMIERA_ERROR_DEFINE(INDEX_CORRUPTED, "PlacementIndex corrupted");
562 
564  : error::Fatal
565  {
566  SelfCheckFailure (lib::Literal currentTest, string failure)
567  : error::Fatal (string("Failed test: ")+currentTest+ " : "+failure
568  ,LUMIERA_ERROR_INDEX_CORRUPTED)
569  { }
570  };
571  }
572 
573 
581  {
582 
584 
585  PlacementMO* elm (ID id) { return tab._element_4check (id);}
586  PlacementMO* sco (ID id) { return tab._scope_4check (id); }
587 
588 
589 
590 #define VERIFY(_CHECK_, CHECK_ID, DESCRIPTION) \
591  if (!(_CHECK_)) \
592  throw SelfCheckFailure (CHECK_ID, (DESCRIPTION));
593 
594 
595  void
596  checkRoot (PMO* root)
597  {
598  VERIFY ( root, "(0.1) Basics", "Root element missing");
599  VERIFY ( root->isValid(), "(0.2) Basics", "Root Placement invalid");
600  VERIFY ( (*root)->isValid(), "(0.3) Basics", "Root MObject self-check failure");
601  }
602 
603  void
604  checkEntry (ID id)
605  {
606  VERIFY ( tab.contains(id), "(1.1) Elements", "PlacementIndex main table corrupted");
607  VERIFY ( elm(id), "(1.2) Elements", "Entry doesn't hold a Placement");
608  VERIFY ( id==elm(id)->getID(), "(1.3) Elements", "Element stored with wrong ID");
609  VERIFY ( elm(id)->isValid(), "(1.4) Elements", "Index contains invalid Placement")
610  VERIFY ( sco(id), "(1.5) Elements", "Entry has undefined scope");
611  VERIFY ( sco(id)->isValid(), "(1.6) Elements", "Entry has invalid scope");
612  VERIFY ( tab.contains (sco(id)->getID()),
613  "(1.7) Elements", "Element associated with an unknown scope");
614 
615  PMO& theElement = *elm(id);
616  ID theScope (sco(id)->getID());
617  if (theScope == id
618  && elm(id)==tab._root_4check()
619  ) // no need to check root,
620  return; // because root is it's own scope
621 
622  iterator elementsInScope = tab.queryScopeContents(theScope);
623  auto equalsTheElement = [&](PMO& entry) { return entry == theElement; };
624  bool properlyRegistered = has_any (elementsInScope, equalsTheElement);
625 
626  VERIFY ( properlyRegistered, "(1.8) Elements", "Element not registered as member of the enclosing scope: "+ theElement);
627  }
628 
629  void
630  checkScope (ID id)
631  {
632  VERIFY ( tab.contains(id), "(2.1) Scopes", "Scope not registered in main table");
633  VERIFY ( elm(id), "(2.2) Scopes", "Scope entry doesn't hold a Placement");
634  VERIFY ( sco(id), "(2.3) Scopes", "Scope entry doesn't hold a containing Scope");
635 
636  PMO* root = tab._root_4check();
637  PMO* scope = sco(id);
638  while (scope && scope != sco(scope->getID()))
639  scope = sco(scope->getID());
640 
641  VERIFY ( root==scope, "(2.4) Scopes", "Found a scope not attached below root.");
642 
643  for_each ( tab._contents_4check(id), &Validator::checkScopeEntry, this, id, _1 );
644  }
645 
646  void
647  checkScopeEntry (ID scope, ID entry)
648  {
649  VERIFY ( tab.contains(entry), "(2.5) Scopes", "Scope member not registered in main table");
650  VERIFY ( elm(entry), "(2.6) Scopes", "Scope member entry doesn't refer to a valid Placement");
651  VERIFY ( sco(entry), "(2.7) Scopes", "Scope member entry is lacking valid scope information");
652  VERIFY ( sco(entry)->getID() == scope,
653  "(2.8) Scopes", "Scope member registered as belonging to a different scope in main table");
654  }
655 
656  void
657  checkAllocation ()
658  {
659  VERIFY ( 0 < tab.size(), "(4.1) Storage", "Implementation table is empty");
660  VERIFY ( 0 < tab.element_cnt(),"(4.2) Storage", "No Placement instances allocated");
661  VERIFY ( tab.size()==tab.scope_cnt()+1,
662  "(4.3) Storage", "Number of elements and scope entries disagree");
663  VERIFY ( tab.size()==tab.element_cnt(),
664  "(4.4) Storage", "Number of entries doesn't match number of allocated Placement instances");
665  }
666 
667 
668  public:
669  Validator (Table& indexTable)
670  : tab(indexTable)
671  {
672  checkRoot (tab._root_4check());
673 
674  for_each ( tab._eachEntry_4check(), &Validator::checkEntry, this, _1 );
675  for_each ( tab._eachScope_4check(), &Validator::checkScope, this, _1 );
676 
677  checkAllocation();
678  }
679 
680  };//(End) Validator (PlacementIndex self-check implementation)
681 
682 
683 
684 
685 
686 
696  bool
697  PlacementIndex::isValid() const
698  {
699  try
700  {
701  VERIFY ( pTab_, "(0) Basics" ,"Implementation tables not initialised");
702 
703  Validator verify(*pTab_);
704  return true;
705  }
706 
707  catch(SelfCheckFailure& failure)
708  {
709  lumiera_error();
710  ERROR (session, "%s", failure.what());
711  }
712  return false;
713  }
714 
715 #undef VERIFY
716 
717 
718 
719 }}} // namespace steam::mobject::session
_MapT< MAP >::ValIter eachValForKey(MAP &map, typename _MapT< MAP >::Key key)
Abstract foundation for building custom allocation managers.
AnyPair entry(Query< TY > const &query, typename WrapReturn< TY >::Wrapper &obj)
helper to simplify creating mock table entries, wrapped correctly
inline string literal This is a marker type to indicate that
Definition: symbol.hpp:76
virtual CStr what() const noexcept override
std::exception interface : yield a diagnostic message
This header is for including and configuring NoBug.
Steam-Layer implementation namespace root.
Namespace of Session and user visible high-level objects.
Definition: sequence.hpp:65
bool has_any(CON const &elements, FUN function, P1 &&bind1, ARGS &&...args)
Accept binding for arbitrary function arguments.
Derived specific exceptions within Lumiera&#39;s exception hierarchy.
Definition: error.hpp:190
PlacementMO & resolveScopeIndexElement(pair< PID, PID > const &entry) const
Helper for building a scope exploring iterator for PlacementIndex: our "reverse index" (scopeTab_) tr...
Core of the session implementation datastructure.
Placement< MObject > PlacementMO
Definition: placement.hpp:260
Iterator tool treating pulled data by a custom transformation (function)
Definition: itertools.hpp:754
lumiera_err lumiera_error(void)
Get and clear current error state.
Definition: error-state.c:115
void for_each(CON const &elements, FUN function, P1 &&bind1, ARGS &&...args)
Accept binding for arbitrary function arguments.
_MapT< MAP >::KeyIter eachMapKey(MAP &map)
Foundation for a custom allocation manager, tracking the created objects by smart-ptrs.
_MapT< MAP >::KeyIter eachDistinctKey(MAP &map)
PlacementIndex self-verification code Executes all built-in checks automatically on object creation...
Lumiera public interface.
Definition: advice.cpp:104
Session and SessionServices Implementation classes.
Accessing a STL element range through a Lumiera forward iterator, An instance of this iterator adapte...
virtual bool isValid() const =0
MObject self-test (usable for asserting)
materialised iterator contents.
Storage and implementation backing the PlacementIndex.
Preconfigured adapters for some STL container standard usage situations.
ID addEntry(PlacementMO const &newObj, ID scopeID)
Store a copy of the given Placement as new instance within the index, together with the Scope this Pl...
void setupRoot(PlacementMO const &rootDef)
insert a specially configured root entry into the yet empty table.
Extension module to build an opaque data source, accessible as Lumiera Forward Iterator.
Perform operations "for each element" of a collection.
string contents(Rec const &object)
render the child elements as string data for test/verification
Interface and Base definition for all Lumiera Exceptions.
Definition: error.hpp:62
_MapIterT< IT >::ValIter eachVal(IT const &begin, IT const &end)
#define LUMIERA_ERROR_DEFINE(err, msg)
Definition and initialisation of an error constant.
Definition: error.h:71