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) Lumiera.org
5  2008, Hermann Vosseler <Ichthyostega@web.de>
6 
7  This program is free software; you can redistribute it and/or
8  modify it under the terms of the GNU General Public License as
9  published by the Free Software Foundation; either version 2 of
10  the License, or (at your option) any later version.
11 
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 
21 * *****************************************************/
22 
23 
57 #include "lib/iter-adapter-stl.hpp"
58 #include "lib/util-foreach.hpp"
59 #include "lib/iter-source.hpp"
60 #include "include/logging.h"
61 
62 #include <boost/functional/hash.hpp>
63 #include <unordered_map>
64 #include <functional>
65 #include <memory>
66 #include <string>
67 
68 namespace lumiera {
69 namespace error {
70  LUMIERA_ERROR_DEFINE (NOT_IN_SESSION, "referring to a Placement not known to the current session");
71  LUMIERA_ERROR_DEFINE (PLACEMENT_TYPE, "requested Placement (pointee) type not compatible with data or context");
72  LUMIERA_ERROR_DEFINE (NONEMPTY_SCOPE, "Placement scope (still) contains other elements");
73 }}
74 
75 namespace steam {
76 namespace mobject {
77 namespace session {
78 
79  using boost::hash;
80  using std::shared_ptr;
81  using std::unordered_map;
82  using std::unordered_multimap;
89  using std::placeholders::_1;
90  using std::function;
91  using std::bind;
92  using std::make_pair;
93  using std::pair;
94 
95  using util::for_each;
96  using util::has_any;
97 
98 
99 
100 
101  /* some type shorthands */
102  typedef mobject::PlacementMO PlacementMO;
103  typedef PlacementIndex::PRef PRef;
104  typedef PlacementIndex::ID ID;
105 
106 
107  /*****************************************************************/
115  {
117 
119  {
120  PPlacement element;
121  PPlacement scope;
122  };
123 
124  // using hashtables to implement the index
125  typedef PlacementMO::ID PID;
126  typedef unordered_map<PID, PlacementEntry> IDTable;
127  typedef std::unordered_multimap<PID,PID> ScopeTable;
128 
129  typedef pair<ScopeIter, ScopeIter> ScopeContents;
130 
131 
132  TypedAllocationManager allocator_;
133  IDTable placementTab_;
134  ScopeTable scopeTab_;
135 
136  PPlacement root_;
137 
138  public:
139  Table()
140  { }
141 
142  ~Table()
143  try {
144  root_.reset();
145  clear();
146  }
147  catch (lumiera::Error& err)
148  {
149  WARN (session, "Problem while purging PlacementIndex: %s", err.what());
150  lumiera_error(); // discard any error state
151  }
152 
153 
154  size_t
155  size() const
156  {
157  return placementTab_.size();
158  }
159 
160  size_t
161  scope_cnt() const
162  {
163  return scopeTab_.size();
164  }
165 
166  size_t
167  element_cnt() const
168  {
169  return allocator_.numSlots<PlacementMO>();
170  }
171 
172  bool
173  contains (ID id) const
174  {
175  return util::contains(placementTab_, id);
176  }
177 
178 
179  PlacementMO&
180  fetch (ID id) const
181  {
182  REQUIRE (contains (id));
183  PPlacement const& entry = base_entry(id).element;
184 
185  ENSURE (entry);
186  ENSURE (id == entry->getID());
187  return *entry;
188  }
189 
190  PlacementMO&
191  fetchScope (ID id) const
192  {
193  REQUIRE (contains (id));
194  PPlacement const& scope = base_entry(id).scope;
195 
196  ENSURE (scope);
197  ENSURE (contains (scope->getID()));
198  return *scope;
199  }
200 
202  queryScopeContents (ID id) const
203  {
204  REQUIRE (contains (id));
205  ScopeContents contents = scopeTab_.equal_range (id);
206  return iterator (ScopeRangeIter(contents.first, contents.second)
207  ,scopeIndexElementsResolver() );
208  }
209 
210 
211 
212  void
213  clear()
214  {
215  INFO (session, "Purging Placement Tables...");
216  scopeTab_.clear();
217  placementTab_.clear();
218 
219  if (root_)
220  setupRoot (*root_);
221  }
222 
223 
227  void
228  setupRoot (PlacementMO const& rootDef)
229  {
230  REQUIRE (0 == placementTab_.size());
231  REQUIRE (0 == scopeTab_.size());
232 
233  root_ = allocator_.create<PlacementMO> (rootDef);
234  ID rootID = root_->getID();
235  placementTab_[rootID].element = root_;
236  placementTab_[rootID].scope = root_;
237 
238  ENSURE (contains (rootID));
239  ENSURE (scopeTab_.empty());
240  ENSURE (1 == size());
241  }
242 
243  PlacementMO&
244  getRootElement()
245  {
246  REQUIRE (root_);
247  REQUIRE (0 < size());
248  REQUIRE (contains (root_->getID()));
249 
250  return *root_;
251  }
252 
253 
262  ID
263  addEntry (PlacementMO const& newObj, ID scopeID)
264  {
265  REQUIRE (contains (scopeID));
266 
267  PPlacement newEntry = allocator_.create<PlacementMO> (newObj);
268  ID newID = newEntry->getID();
269 
270  ASSERT (newID, "invalid");
271  ASSERT (!contains (newID));
272  placementTab_[newID].element = newEntry;
273  placementTab_[newID].scope = placementTab_[scopeID].element;
274  scopeTab_.insert (make_pair (scopeID, newID));
275  return newID;
276  }
277 
278  bool
279  removeEntry (ID id)
280  {
281  if (!contains (id))
282  {
283  ENSURE (!util::contains(scopeTab_, id));
284  return false;
285  }
286 
287  if (util::contains(scopeTab_, id))
288  throw error::State{"Unable to remove the specified Placement, "
289  "because it defines an non-empty scope. "
290  "You need to delete any contents first."
291  , LERR_(NONEMPTY_SCOPE)};
292 
293  ASSERT (contains (id));
294  PlacementEntry toRemove = remove_base_entry (id);
295  remove_from_scope (toRemove.scope->getID(), id);
296  ENSURE (!util::contains(scopeTab_, id));
297  ENSURE (!contains (id));
298  return true;
299  }
300 
301  void
302  removeAll (ID scopeID)
303  {
304  remove_all_from_scope (scopeID); // recursive
305  removeEntry (scopeID); // discard top-level
306 
307  ENSURE (!util::contains(scopeTab_, scopeID));
308  ENSURE (!contains (scopeID));
309  }
310 
311 
312  /* == access for self-test == */
313 
315 
316  PlacementMO* _root_4check () { return root_.get(); }
317  PlacementMO* _element_4check (ID id){ return base_entry(id).element.get();}
318  PlacementMO* _scope_4check (ID id) { return base_entry(id).scope.get(); }
319  IDIter _eachEntry_4check () { return eachMapKey (placementTab_); }
320  IDIter _eachScope_4check () { return eachDistinctKey (scopeTab_); }
321  IDIter _contents_4check(ID id){ return eachValForKey (scopeTab_,id);}
322 
323 
324  private:
325  typedef IDTable::const_iterator Slot;
326 
327  PlacementEntry const&
328  base_entry (ID key) const
329  {
330  Slot pos = placementTab_.find (key);
331  if (pos == placementTab_.end())
332  throw error::Logic("lost a Placement expected to be registered within PlacementIndex.");
333 
334  return pos->second;
335  }
336 
338  remove_base_entry (ID key)
339  {
340  Slot pos = placementTab_.find (key);
341  REQUIRE (pos != placementTab_.end());
342  PlacementEntry dataToRemove (pos->second);
343  placementTab_.erase(pos);
344  return dataToRemove;
345  }
346 
347  void
348  remove_from_scope (ID scopeID, ID entryID)
349  {
350  typedef ScopeTable::const_iterator Pos;
351  pair<Pos,Pos> searchRange = scopeTab_.equal_range(scopeID);
352 
353  Pos pos = searchRange.first;
354  Pos end = searchRange.second;
355  for ( ; pos!=end; ++pos)
356  if (pos->second == entryID)
357  {
358  scopeTab_.erase(pos);
359  return;
360  }
361 
362  NOTREACHED();
363  }
364 
365  void
366  remove_all_from_scope (ID scopeID)
367  {
368  typedef ScopeTable::const_iterator Pos;
369  pair<Pos,Pos> scopeEntries = scopeTab_.equal_range(scopeID);
370  Pos first = scopeEntries.first;
371  Pos end = scopeEntries.second;
372 
373  // take a snapshot of all children to be processed recursively
374  typedef IterSnapshot<PID> ChildIDs;
375  ChildIDs child (eachVal(first,end));
376 
377  scopeTab_.erase (first,end); // assumed to be NOP for first==end
378 
379  for ( ; child; ++child )
380  {
381  remove_all_from_scope (*child); // recursive
382  remove_base_entry (*child); // discard storage
383 
384  ENSURE (!util::contains(scopeTab_, *child));
385  ENSURE (!contains (*child));
386  }
387  }
388 
389 
390 
398  PlacementMO&
399  resolveScopeIndexElement(pair<PID,PID> const& entry) const
400  {
401  ID elemID (entry.second);
402  ASSERT (contains (elemID));
403  return fetch (elemID);
404  }
405 
406 
407  typedef function<PlacementMO& (pair<PID,PID> const&)> ElementResolver;
408  mutable ElementResolver elementResolver_;
409 
410  ElementResolver
412  {
413  if (!elementResolver_)
414  elementResolver_ = bind (&Table::resolveScopeIndexElement, this, _1 );
415  return elementResolver_;
416  }
417  };
418  //(End) placement index table implementation
419 
420 
421 
422 
423 
424 
425 
426  /* ============ PlacementIndex implementation functions ============ */
427 
428 
429  PlacementIndex::PlacementIndex (PlacementMO const& rootDef)
430  : pTab_(new Table)
431  {
432  INFO (session, "Initialising PlacementIndex...");
433 
434  pTab_->setupRoot(rootDef);
435  ENSURE (isValid());
436  }
437 
438  PlacementIndex::~PlacementIndex() { }
439 
440 
441  PlacementMO&
442  PlacementIndex::getRoot() const
443  {
444  return pTab_->getRootElement();
445  }
446 
447 
448  size_t
449  PlacementIndex::size() const
450  {
451  ASSERT (0 < pTab_->size());
452  return pTab_->size() - 1; // root not counted
453  }
454 
455 
456  bool
457  PlacementIndex::contains (ID id) const
458  {
459  return pTab_->contains (id);
460  }
461 
462 
463  PlacementMO&
464  PlacementIndex::find (ID id) const
465  {
466  __check_knownID(*this,id);
467  return pTab_->fetch (id);
468  }
469 
470 
471 
477  PlacementMO&
478  PlacementIndex::getScope (ID id) const
479  {
480  __check_knownID(*this,id);
481  return pTab_->fetchScope (id);
482  }
483 
484 
493  PlacementIndex::getReferrers (ID id) const
494  {
495  __check_knownID(*this, id);
496  return pTab_->queryScopeContents(id);
497  }
498 
499 
512  ID
513  PlacementIndex::insert (PlacementMO const& newObj, ID targetScope)
514  {
515  if (!contains (targetScope))
516  throw error::Logic ("Specified a non-registered Placement as scope "
517  "while adding another Placement to the index"
518  ,LERR_(INVALID_SCOPE));
519 
520  return pTab_->addEntry(newObj, targetScope);
521  }
522 
523 
529  bool
530  PlacementIndex::remove (ID id)
531  {
532  if (id == getRoot().getID())
533  throw error::Fatal ("Request to kill the model root.");
534 
535  return pTab_->removeEntry (id);
536  }
537 
538 
544  void
545  PlacementIndex::clear (ID targetScope)
546  {
547  if (targetScope == getRoot().getID())
548  pTab_->clear();
549  else
550  pTab_->removeAll (targetScope);
551 
552  ENSURE (isValid());
553  }
554 
555 
556  void
557  PlacementIndex::clear()
558  {
559  pTab_->clear();
560  }
561 
562 
563 
564 
565 
566  /* ====== PlacementIndex validity self-check ====== */
567 
568  namespace { // self-check implementation helpers...
569 
570  LUMIERA_ERROR_DEFINE(INDEX_CORRUPTED, "PlacementIndex corrupted");
571 
573  : error::Fatal
574  {
575  SelfCheckFailure (lib::Literal currentTest, string failure)
576  : error::Fatal (string("Failed test: ")+currentTest+ " : "+failure
577  ,LUMIERA_ERROR_INDEX_CORRUPTED)
578  { }
579  };
580  }
581 
582 
590  {
591 
593 
594  PlacementMO* elm (ID id) { return tab._element_4check (id);}
595  PlacementMO* sco (ID id) { return tab._scope_4check (id); }
596 
597 
598 
599 #define VERIFY(_CHECK_, CHECK_ID, DESCRIPTION) \
600  if (!(_CHECK_)) \
601  throw SelfCheckFailure (CHECK_ID, (DESCRIPTION));
602 
603 
604  void
605  checkRoot (PMO* root)
606  {
607  VERIFY ( root, "(0.1) Basics", "Root element missing");
608  VERIFY ( root->isValid(), "(0.2) Basics", "Root Placement invalid");
609  VERIFY ( (*root)->isValid(), "(0.3) Basics", "Root MObject self-check failure");
610  }
611 
612  void
613  checkEntry (ID id)
614  {
615  VERIFY ( tab.contains(id), "(1.1) Elements", "PlacementIndex main table corrupted");
616  VERIFY ( elm(id), "(1.2) Elements", "Entry doesn't hold a Placement");
617  VERIFY ( id==elm(id)->getID(), "(1.3) Elements", "Element stored with wrong ID");
618  VERIFY ( elm(id)->isValid(), "(1.4) Elements", "Index contains invalid Placement")
619  VERIFY ( sco(id), "(1.5) Elements", "Entry has undefined scope");
620  VERIFY ( sco(id)->isValid(), "(1.6) Elements", "Entry has invalid scope");
621  VERIFY ( tab.contains (sco(id)->getID()),
622  "(1.7) Elements", "Element associated with an unknown scope");
623 
624  PMO& theElement = *elm(id);
625  ID theScope (sco(id)->getID());
626  if (theScope == id
627  && elm(id)==tab._root_4check()
628  ) // no need to check root,
629  return; // because root is it's own scope
630 
631  iterator elementsInScope = tab.queryScopeContents(theScope);
632  auto equalsTheElement = [&](PMO& entry) { return entry == theElement; };
633  bool properlyRegistered = has_any (elementsInScope, equalsTheElement);
634 
635  VERIFY ( properlyRegistered, "(1.8) Elements", "Element not registered as member of the enclosing scope: "+ theElement);
636  }
637 
638  void
639  checkScope (ID id)
640  {
641  VERIFY ( tab.contains(id), "(2.1) Scopes", "Scope not registered in main table");
642  VERIFY ( elm(id), "(2.2) Scopes", "Scope entry doesn't hold a Placement");
643  VERIFY ( sco(id), "(2.3) Scopes", "Scope entry doesn't hold a containing Scope");
644 
645  PMO* root = tab._root_4check();
646  PMO* scope = sco(id);
647  while (scope && scope != sco(scope->getID()))
648  scope = sco(scope->getID());
649 
650  VERIFY ( root==scope, "(2.4) Scopes", "Found a scope not attached below root.");
651 
652  for_each ( tab._contents_4check(id), &Validator::checkScopeEntry, this, id, _1 );
653  }
654 
655  void
656  checkScopeEntry (ID scope, ID entry)
657  {
658  VERIFY ( tab.contains(entry), "(2.5) Scopes", "Scope member not registered in main table");
659  VERIFY ( elm(entry), "(2.6) Scopes", "Scope member entry doesn't refer to a valid Placement");
660  VERIFY ( sco(entry), "(2.7) Scopes", "Scope member entry is lacking valid scope information");
661  VERIFY ( sco(entry)->getID() == scope,
662  "(2.8) Scopes", "Scope member registered as belonging to a different scope in main table");
663  }
664 
665  void
666  checkAllocation ()
667  {
668  VERIFY ( 0 < tab.size(), "(4.1) Storage", "Implementation table is empty");
669  VERIFY ( 0 < tab.element_cnt(),"(4.2) Storage", "No Placement instances allocated");
670  VERIFY ( tab.size()==tab.scope_cnt()+1,
671  "(4.3) Storage", "Number of elements and scope entries disagree");
672  VERIFY ( tab.size()==tab.element_cnt(),
673  "(4.4) Storage", "Number of entries doesn't match number of allocated Placement instances");
674  }
675 
676 
677  public:
678  Validator (Table& indexTable)
679  : tab(indexTable)
680  {
681  checkRoot (tab._root_4check());
682 
683  for_each ( tab._eachEntry_4check(), &Validator::checkEntry, this, _1 );
684  for_each ( tab._eachScope_4check(), &Validator::checkScope, this, _1 );
685 
686  checkAllocation();
687  }
688 
689  };//(End) Validator (PlacementIndex self-check implementation)
690 
691 
692 
693 
694 
695 
705  bool
706  PlacementIndex::isValid() const
707  {
708  try
709  {
710  VERIFY ( pTab_, "(0) Basics" ,"Implementation tables not initialised");
711 
712  Validator verify(*pTab_);
713  return true;
714  }
715 
716  catch(SelfCheckFailure& failure)
717  {
718  lumiera_error();
719  ERROR (session, "%s", failure.what());
720  }
721  return false;
722  }
723 
724 #undef VERIFY
725 
726 
727 
728 }}} // 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:85
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:74
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:199
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:269
Iterator tool treating pulled data by a custom transformation (function)
Definition: itertools.hpp:763
lumiera_err lumiera_error(void)
Get and clear current error state.
Definition: error-state.c:124
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:113
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:71
_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:80