lumiera_/src/common/query/defs-registry.hpp

344 lines
11 KiB
C++
Raw Normal View History

2008-02-29 18:58:29 +01:00
/*
DEFS-REGISTRY.hpp - implementation of the default object store
2010-12-17 23:28:49 +01:00
Copyright: clarify and simplify the file headers * Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
Copyright (C)
2008, Hermann Vosseler <Ichthyostega@web.de>
2010-12-17 23:28:49 +01:00
Copyright: clarify and simplify the file headers * Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
  **Lumiera** 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. See the file COPYING for further details.
2010-12-17 23:28:49 +01:00
2008-02-29 18:58:29 +01:00
*/
/** @file defs-registry.hpp
** A piece of implementation code factored out into a separate header (include).
** Only used through defs-manager-impl.hpp and for the unit tests. We can't place it
** into a separate compilation unit, because config-resolver.cpp defines some explicit
** template instantiation, which cause the different Slots of the DefsrRegistry#table_
** to be filled with data and defaults for the specific Types.
**
** Basically, this piece of code defines a specialised index / storage table to hold
** Queries-for-default objects. This allows to remember what actually was used as
** "default" solution for some query and to oder possible default solutions.
** @remarks as of 2017, we're still using a fake implementation of the resolution,
** no real resolution engine. While the basic idea of this "defaults registry"
** is likely to stay, the actual order relation and maybe even the components
2025-06-07 23:59:57 +02:00
** to be stored in this registry might be subject to change.
**
** @see mobject::session::DefsManager
** @see DefsRegistryImpl_test
**
*/
#ifndef LUMIERA_QUERY_DEFS_REGISTRY_H
#define LUMIERA_QUERY_DEFS_REGISTRY_H
2008-02-29 18:58:29 +01:00
#include "lib/p.hpp"
#include "lib/util.hpp"
#include "lib/util-foreach.hpp"
#include "lib/sync-classlock.hpp"
#include "lib/format-string.hpp"
2012-12-03 00:18:18 +01:00
#include "lib/query-util.hpp"
#include "common/query.hpp"
#include "lib/nocopy.hpp"
2008-02-29 18:58:29 +01:00
#include <set>
#include <vector>
#include <memory>
2008-02-29 18:58:29 +01:00
namespace lumiera{
namespace query {
using lib::P;
using lib::ClassLock;
using std::weak_ptr;
using std::string;
namespace impl {
namespace {
using util::_Fmt;
uint maxSlots (0); ///< number of different registered Types
_Fmt dumpRecord ("%2i| %64s --> %s\n");
}
2025-06-07 23:59:57 +02:00
struct TableEntry
{
virtual ~TableEntry() {};
};
/** we maintain an independent defaults registry
* for every participating kind of object. */
using Table = std::vector< P<TableEntry> >;
/**
2025-06-07 23:59:57 +02:00
* holding a single "default object" entry
*/
template<class TAR>
struct Record
{
QueryKey queryKey;
weak_ptr<TAR> objRef;
uint degree;
Record (Query<TAR> const& q, P<TAR> const& obj)
: queryKey (q)
, objRef (obj)
, degree(queryKey.degree())
{ }
struct Search ///< Functor searching for a specific object
{
Search (P<TAR> const& obj)
: obj_(obj) { }
P<TAR> const& obj_;
2025-06-07 23:59:57 +02:00
bool
operator() (Record const& rec)
{
P<TAR> storedObj (rec.objRef.lock());
2025-06-07 23:59:57 +02:00
return storedObj and (storedObj == obj_);
}
};
friend bool
operator< (Record one, Record two) ///< @note doesn't touch the objRef
{
return one.queryKey < two.queryKey;
}
2012-12-22 22:01:51 +01:00
operator string () const { return dumpRecord % degree % queryKey.display() % dumpObj(); }
string dumpObj () const { P<TAR> o (objRef.lock()); return o? string(*o):"dead"; }
};
/** every new kind of object (Type) creates a new
2025-06-07 23:59:57 +02:00
* slot in the main Table holding all registered
* default objects. Each slot actually holds a
* separate tree (set) of registry entries
*/
template<class TAR>
struct Slot
: public TableEntry
{
using Registry = std::set<Record<TAR>>;
Registry registry;
static size_t index; ///< where to find this Slot in every Table
static Registry&
access (Table& table)
{
2025-06-07 23:59:57 +02:00
if ( not index
or index > table.size()
or not table[index-1])
createSlot (table);
2025-06-07 23:59:57 +02:00
ASSERT (0 < index and index<=table.size() and table[index-1]);
Slot* item = static_cast<Slot*> (table[index-1].get());
return item->registry;
}
private:
static void
createSlot (Table& table)
{
ClassLock<TableEntry> guard;
if (!index)
index = ++maxSlots;
if (index > table.size())
table.resize (index);
2025-06-07 23:59:57 +02:00
table[index-1].reset(new Slot);
}
};
// static vars to organise one Table Slot per type....
template<class TAR>
size_t Slot<TAR>::index (0);
2008-02-29 18:58:29 +01:00
/**
* @internal Helper for organising preconfigured default objects.
* Maintains a collection of objects known or encountered as "default"
* for a given type. This collection is ordered by "degree of constriction",
* which is implemented by counting the number of predicates in the query
* used to define or identify each object.
* Accessing an object identified by a query causes the query to be resolved
* (executed in prolog), which may result in some additional properties of
* the object being bound or specified.
* @todo as of 3/2008 the real query implementation is missing, and the
* exact behaviour has to be defined.
2008-02-29 18:58:29 +01:00
*/
class DefsRegistry
: util::NonCopyable
2008-02-29 18:58:29 +01:00
{
Table table_;
2008-02-29 18:58:29 +01:00
public:
/** used for enumerating solutions */
template<class TAR>
2025-06-07 23:59:57 +02:00
class Iter
{
friend class DefsRegistry;
using II = Slot<TAR>::Registry::iterator;
II p,i,e;
P<TAR> next, ptr;
2025-06-07 23:59:57 +02:00
Iter (II from, II to) ///< just enumerates the given range
: p(from), i(from), e(to)
{
if (i!=e) ++i; // p is next to be tested, i always one ahead
operator++ ();
}
2025-06-07 23:59:57 +02:00
Iter (II match, II from, II to) ///< returns direct match first, then enumerates
: p(match), i(from), e(to)
2025-06-07 23:59:57 +02:00
{
operator++ (); // init to first element (or to null if empty)
}
P<TAR>
findNext () ///< EX_FREE
{
while (!next)
{
if (p==e) break;
next = p->objRef.lock();
p = i++;
}
return next;
}
public:
P<TAR> operator* () { return ptr; }
2025-06-07 23:59:57 +02:00
bool hasNext () { return next or findNext(); }
Iter& operator++ ()
2025-06-07 23:59:57 +02:00
{
ptr=findNext();
next.reset();
2025-06-07 23:59:57 +02:00
return *this;
}
};
/** find a sequence of "default" objects possibly matching the query.
* If there was a registration for some object of the given kind with
* the \em same query, this one will be first in the sequence. Besides,
* the sequence will yield all still existing registered "default" objects
* of this kind, ordered ascending by "degree of constriction", i.e. starting
* with the object registered together with the shortest query.
* @return a forward input iterator yielding this sequence
* @note none of the queries will be evaluated (we're just counting predicates)
*/
template<class TAR>
Iter<TAR>
candidates (Query<TAR> const& query)
{
P<TAR> dummy;
Record<TAR> entry (query, dummy);
using Registry = Slot<TAR>::Registry;
Registry& registry = Slot<TAR>::access(table_);
// try to get a possible direct match (same query)
auto pos = registry.find (entry);
auto end = registry.end();
if (pos==end)
return Iter<TAR> (registry.begin(), end); // just enumerate contents
else
return Iter<TAR> (pos, registry.begin(), end); // start with direct match
}
/** register the object as being "default" when searching something
* similar as designated by the given query. Only a weak ref is stored.
2012-12-24 03:20:52 +01:00
* @param obj to be recorded as "default". Only a weak pointer will be stored
* @retval true if object has actually been stored
* @retval false if another object is registered for exactly the same query
* Nothing is changed in this case
*/
template<class TAR>
bool
2012-12-24 03:20:52 +01:00
put (P<TAR> const& obj, Query<TAR> const& query)
{
Record<TAR> entry (query, obj);
using Registry = Slot<TAR>::Registry;
using RIter = Registry::iterator;
Registry& registry = Slot<TAR>::access(table_);
RIter pos = registry.lower_bound (entry);
2025-06-07 23:59:57 +02:00
if (pos!=registry.end() and
pos->queryKey == query)
{
P<TAR> storedObj (pos->objRef.lock());
if (storedObj)
return (storedObj == obj);
else
2012-12-24 03:20:52 +01:00
// use the opportunity to purge the expired entry
registry.erase(pos++);
}
// no existing entry....
registry.insert(pos, entry);
ENSURE (registry.find (entry) != registry.end());
return true;
}
2008-02-29 18:58:29 +01:00
/** if this object is registered as "default" in some way, drop the registration.
* @return false if the object wasn't registered at all.
*/
template<class TAR>
bool
forget (P<TAR> const& obj)
{
using Registry = Slot<TAR>::Registry;
using SearchFunc = Record<TAR>::Search;
Registry& registry = Slot<TAR>::access(table_);
return util::remove_if(registry, SearchFunc (obj));
}
/** helper for diagnostics.
* @note to use it, your objects need an operator string()
*/
template<class TAR>
string
dump ()
{
string res;
util::for_each ( Slot<TAR>::access(table_)
, [&] (Record<TAR>& entry)
{
res += string(entry);
}
);
return res;
}
2008-02-29 18:58:29 +01:00
};
} // (End) impl namespace
using impl::DefsRegistry;
}} // namespace lumiera::query
2008-02-29 18:58:29 +01:00
#endif