lumiera_/src/proc/engine/buffer-metadata.hpp

794 lines
27 KiB
C++

/*
BUFFER-METADATA.hpp - internal metadata for data buffer providers
Copyright (C) Lumiera.org
2011, 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 buffer-metadate.hpp
** Metadata for managing and accessing buffers. The Lumiera Engine uses the
** Abstraction of an BufferProvider to handle various kinds of buffer organisation
** and access in a uniform way. Actually, buffers can be exposed and provided by several
** facilities, which might even be implemented through an external library. Thus the engine
** and the abstraction placed in between needs a common set of control data, to be able to
** expose the correct buffer for each request. Typically -- and independent of the actual
** implementation -- the following properties need to be tracked
** - that overall storage size available within the buffer
** - a pair of custom \em creator and \em destructor functions to use together with this buffer
** - an additional client key to distinguish otherwise otherwise identical client requests
** These three distinctions are applied in sequence, thus forming a type tree with 3 levels.
** Only the first distinguishing level (the size) is mandatory. The others are provided,
** because some of the foreseeable buffer providers allow to re-access the data placed
** into the buffer, by assigning an internally managed ID to the buffer. The most
** prominent example is the frame cache, which obviously needs to keep track of
** the buffers after the render engine is finished, while the engine code
** just accesses yet another buffer to place the results of calculations.
**
** These additional distinctions and properties are associated with the help of the
** BufferDescriptor, embedded into each BuffHandle. While the engine just uses these
** handles in the way of a pointer, the buffer descriptor acts as an additional tag
** attached to the buffer access, allowing to re-access a context within the
** buffer provider implementation.
**
** @see buffer-provider.hpp
** @see BufferMetadata_test
** @see BufferProviderProtocol_test
*/
#ifndef PROC_ENGINE_BUFFR_METADATA_H
#define PROC_ENGINE_BUFFR_METADATA_H
#include "lib/error.hpp"
#include "lib/symbol.hpp"
#include "lib/util-foreach.hpp"
#include "include/logging.h"
#include "proc/engine/type-handler.hpp"
#include "proc/engine/buffer-local-key.hpp"
#include <tr1/unordered_map>
#include <boost/noncopyable.hpp>
namespace proc {
namespace engine {
using lib::HashVal;
using lib::Literal;
using util::for_each;
namespace error = lumiera::error;
namespace metadata {
class Key;
class Entry;
}
class BufferMetadata;
/**
* Buffer states
* usable within BufferProvider
* and stored within the metadata
*/
enum BufferState
{ NIL, ///< abstract entry, not yet allocated
FREE, ///< allocated buffer, no longer in use
LOCKED, ///< allocated buffer actively in use
EMITTED, ///< allocated buffer, returned from client
BLOCKED ///< allocated buffer blocked by protocol failure
};
namespace { // internal constants to mark the default case
const LocalKey UNSPECIFIC;
const TypeHandler RAW_BUFFER;
inline bool
nontrivial (TypeHandler const& toVerify)
{
return RAW_BUFFER != toVerify;
}
inline bool
nontrivial (LocalKey const& toVerify)
{
return UNSPECIFIC != toVerify;
}
}
/* === Metadata Implementation === */
namespace metadata {
using error::LUMIERA_ERROR_LIFECYCLE;
using error::LUMIERA_ERROR_BOTTOM_VALUE;
namespace { // details of hash calculation
template<typename VAL>
HashVal
chainedHash(HashVal accumulatedHash, VAL changedValue)
{
boost::hash_combine (accumulatedHash, changedValue);
return accumulatedHash;
}
}
/**
* Description of a Buffer-"type".
* Key elements will be used to generate hash IDs,
* to be embedded into a BufferDescriptor.
* Keys are chained hierarchically.
*/
class Key
{
HashVal parent_;
HashVal hashID_;
protected:
size_t storageSize_;
TypeHandler instanceFunc_;
LocalKey specifics_;
public:
/** build a standard basic key describing a kind of Buffer.
* @param familyID basic hash seed value to distinguish
* families of buffer types managed by
* different BufferProvider instances
* @param storageSize fundamental info: buffer size
*/
Key (HashVal familyID, size_t storageSize)
: parent_(familyID)
, hashID_(chainedHash (familyID, storageSize))
, storageSize_(storageSize)
, instanceFunc_(RAW_BUFFER)
, specifics_(UNSPECIFIC)
{ }
// standard copy operations permitted
/** create a derived buffer type description.
* Using a different storage size than the parent type,
* all else remaining the same
*/
Key (Key const& parent, size_t differingStorageSize)
: parent_(parent.hashID_)
, hashID_(chainedHash (parent_, differingStorageSize))
, storageSize_(differingStorageSize) // differing from parent
, instanceFunc_(parent.instanceFunc_)
, specifics_(parent.specifics_)
{ }
/** create a derived buffer type description.
* Using different ctor and dtor functions,
* all else remaining the same as with parent
*/
Key (Key const& parent, TypeHandler const& differingTypeHandlerFunctions)
: parent_(parent.hashID_)
, hashID_(chainedHash (parent_, differingTypeHandlerFunctions))
, storageSize_(parent.storageSize_)
, instanceFunc_(differingTypeHandlerFunctions) // differing from parent
, specifics_(parent.specifics_)
{ }
/** create a derived buffer type description.
* Using a different private ID than the parent type,
* all else remaining the same
*/
Key (Key const& parent, LocalKey anotherTypeSpecificInternalID)
: parent_(parent.hashID_)
, hashID_(chainedHash (parent_, anotherTypeSpecificInternalID))
, storageSize_(parent.storageSize_)
, instanceFunc_(parent.instanceFunc_)
, specifics_(anotherTypeSpecificInternalID) // differing from parent
{ }
/** build derived Key for a concrete buffer Entry
* @param parent type key to subsume this buffer
* @param bufferAddr pointer to the concrete buffer
* @return Child key with hashID based on the buffer address.
* For NULL buffer a copy of the parent is returned.
*/
static Key
forEntry (Key const& parent, const void* bufferAddr, LocalKey const& implID =UNSPECIFIC)
{
Key newKey(parent);
if (bufferAddr)
{
newKey.parent_ = HashVal(parent);
newKey.hashID_ = chainedHash(parent, bufferAddr);
if (nontrivial(implID))
{
REQUIRE (!newKey.specifics_.isDefined(),
"Implementation defined local key should not be overridden. "
"Underlying buffer type already defines a nontrivial LocalKey");
newKey.specifics_ = implID;
} }
return newKey;
}
void
useTypeHandlerFrom (Key const& ref)
{
if (nontrivial(this->instanceFunc_))
throw error::Logic ("unable to supersede an already attached TypeHandler"
, LUMIERA_ERROR_LIFECYCLE);
instanceFunc_ = ref.instanceFunc_;
}
LocalKey const& localKey() const { return specifics_;}
size_t storageSize() const { return storageSize_; }
HashVal parentKey() const { return parent_;}
operator HashVal() const { return hashID_;}
};
/**
* A complete metadata Entry, based on a Key.
* This special Key element usually describes an actual Buffer.
* Entries are to be managed in a hashtable, which is "the metadata table".
* As a special case, an entry without a concrete buffer storage pointer
* can be created. This corresponds to a (plain) key and describes just
* a buffer type. Such type-only entries are fixed to the NIL state.
* All other entries allow for state transitions.
*
* The "metadata table" with its entries is maintained by an engine::BufferMetadata
* instance. For the latter, Entry serves as representation and access point
* to the individual metadata; this includes using the TypeHandler for
* building and destroying buffer structures.
*/
class Entry
: public Key
{
BufferState state_;
void* buffer_;
protected:
Entry (Key const& parent, void* bufferPtr =0, LocalKey const& implID =UNSPECIFIC)
: Key (Key::forEntry (parent, bufferPtr, implID))
, state_(bufferPtr? LOCKED:NIL)
, buffer_(bufferPtr)
{ }
/// BufferMetadata is allowed to create
friend class engine::BufferMetadata;
// standard copy operations permitted
public:
/** is this Entry currently associated to a
* concrete buffer? Is this buffer in use? */
bool
isLocked() const
{
ASSERT (!buffer_ || (NIL != state_ && FREE != state_));
return bool(buffer_);
}
/** is this Entry just an (abstract) placeholder for a type?
* @return false if it's a real entry corresponding to a concrete buffer
*/
bool
isTypeKey() const
{
return NIL == state_ && !buffer_;
}
BufferState
state() const
{
return state_;
}
void*
access()
{
__must_not_be_NIL();
__must_not_be_FREE();
ENSURE (buffer_);
return buffer_;
}
/** Buffer state machine */
Entry&
mark (BufferState newState)
{
__must_not_be_NIL();
if ( (state_ == FREE && newState == LOCKED)
||(state_ == LOCKED && newState == EMITTED)
||(state_ == LOCKED && newState == BLOCKED)
||(state_ == LOCKED && newState == FREE)
||(state_ == EMITTED && newState == BLOCKED)
||(state_ == EMITTED && newState == FREE)
||(state_ == BLOCKED && newState == FREE))
{
// allowed transition
if (newState == FREE)
invokeEmbeddedDtor_and_clear();
if (newState == LOCKED)
invokeEmbeddedCtor();
state_ = newState;
return *this;
}
throw error::Fatal ("Invalid buffer state transition.");
}
Entry&
lock (void* newBuffer)
{
__must_be_FREE();
buffer_ = newBuffer;
return mark (LOCKED);
}
Entry&
invalidate (bool invokeDtor =true)
{
if (buffer_ && invokeDtor)
invokeEmbeddedDtor_and_clear();
buffer_ = 0;
state_ = FREE;
return *this;
}
protected:
/** @internal maybe invoke a registered TypeHandler's
* constructor function, which typically builds some
* content object into the buffer by placement new. */
void
invokeEmbeddedCtor()
{
__buffer_required();
if (nontrivial (instanceFunc_))
instanceFunc_.createAttached (buffer_);
}
/** @internal maybe invoke a registered TypeHandler's
* destructor function, which typically clears up some
* content object living within the buffer */
void
invokeEmbeddedDtor_and_clear()
{
__buffer_required();
if (nontrivial (instanceFunc_))
instanceFunc_.destroyAttached (buffer_);
buffer_ = 0;
}
private:
void
__must_not_be_NIL() const
{
if (NIL == state_)
throw error::Fatal ("Buffer metadata entry with state==NIL encountered."
"State transition logic broken (programming error)"
, LUMIERA_ERROR_LIFECYCLE);
}
void
__must_not_be_FREE() const
{
if (FREE == state_)
throw error::Logic ("Buffer is inaccessible (marked as free). "
"Need a new buffer pointer in order to lock an entry. "
"You should invoke markLocked(buffer) prior to access."
, LUMIERA_ERROR_LIFECYCLE );
}
void
__must_be_FREE() const
{
if (FREE != state_)
throw error::Logic ("Buffer already in use"
, LUMIERA_ERROR_LIFECYCLE );
REQUIRE (!buffer_, "Buffer marked as free, "
"but buffer pointer is set.");
}
void
__buffer_required() const
{
if (!buffer_)
throw error::Fatal ("Need concrete buffer for any further operations");
}
};
/**
* (Hash)Table to store and manage buffer metadata.
* Buffer metadata entries are comprised of a Key part and an extended
* Entry, holding the actual management and housekeeping metadata. The
* Keys are organised hierarchically and denote the "kind" of buffer.
* The hash values for lookup are based on the key part, chained with
* the actual memory location of the concrete buffer corresponding
* to the metadata entry to be retrieved.
*/
class Table
{
typedef std::tr1::unordered_map<HashVal,Entry> MetadataStore;
MetadataStore entries_;
public:
~Table() { verify_all_buffers_freed(); }
/** fetch metadata record, if any
* @param hashID for the Key part of the metadata entry
* @return pointer to the entry in the table or NULL
*/
Entry*
fetch (HashVal hashID)
{
MetadataStore::iterator pos = entries_.find (hashID);
if (pos != entries_.end())
return &(pos->second);
else
return NULL;
}
const Entry*
fetch (HashVal hashID) const
{
MetadataStore::const_iterator pos = entries_.find (hashID);
if (pos != entries_.end())
return &(pos->second);
else
return NULL;
}
/** store a copy of the given new metadata entry.
* The hash key for lookup is retrieved from the given Entry, by conversion to HashVal.
* Consequently, this will be the hashID of the parent Key (type), when the entry holds
* a NULL buffer (i.e a "pseudo entry"). Otherwise, it will be this parent Key hash,
* extended by hashing the actual buffer address.
* @return reference to relevant entry for this Key. This might be a copy
* of the new entry, or an already existing entry with the same Key
*/
Entry&
store (Entry const& newEntry)
{
using std::make_pair;
REQUIRE (!fetch (newEntry), "duplicate buffer metadata entry");
MetadataStore::iterator pos = entries_.insert (make_pair (HashVal(newEntry), newEntry))
.first;
ENSURE (pos != entries_.end());
return pos->second;
}
void
remove (HashVal hashID)
{
uint cnt = entries_.erase (hashID);
ENSURE (cnt, "entry to remove didn't exist");
}
private:
void
verify_all_buffers_freed()
try
{
for_each (entries_, verify_is_free);
}
ERROR_LOG_AND_IGNORE (engine,"Shutdown of BufferProvider metadata store")
static void
verify_is_free (std::pair<HashVal, Entry> const& e)
{
WARN_IF (e.second.isLocked(), engine,
"Buffer still in use while shutting down BufferProvider? ");
}
};
}//namespace metadata
/* ===== Buffer Metadata Frontend ===== */
/**
* Registry for managing buffer metadata.
* This is an implementation level service,
* used by the standard BufferProvider implementation.
* Each metadata registry (instance) defines and maintains
* a family of "buffer types"; beyond the buffer storage size,
* the concrete meaning of those types is tied to the corresponding
* BufferProvider implementation and remains opaque. These types are
* represented as hierarchically linked hash keys. The implementation
* may bind a TypeHandler to a specific type, allowing automatic invocation
* of a "constructor" and "destructor" function on each buffer of this type,
* when \em locking or \em freeing the corresponding buffer.
*/
class BufferMetadata
: boost::noncopyable
{
Literal id_;
HashVal family_;
metadata::Table table_;
public:
typedef metadata::Key Key;
typedef metadata::Entry Entry;
/** establish a metadata registry.
* Such will maintain a family of buffer type entries
* and provide a service for storing and retrieving metadata
* for concrete buffer entries associated with these types.
* @param implementationID to distinguish families of
* type keys belonging to different registries.
*/
BufferMetadata (Literal implementationID)
: id_(implementationID)
, family_(hash_value(id_))
{ }
/** combine the distinguishing properties
* into a single type key, which will be known/remembered
* from that point on. Properties are combined according to
* a fixed type specialisation order, with the buffer size
* forming the base level, possible TypeHandler functors the
* second level, and implementation defined LocalKey entries
* the third level. All these levels describe abstract type
* keys, not entries for concrete buffers. The latter are
* always created as children of a known type key.
*/
Key
key ( size_t storageSize
, TypeHandler instanceFunc =RAW_BUFFER
, LocalKey specifics =UNSPECIFIC)
{
REQUIRE (storageSize);
Key typeKey = trackKey (family_, storageSize);
if (nontrivial(instanceFunc))
typeKey = trackKey (typeKey, instanceFunc);
if (nontrivial(specifics))
typeKey = trackKey (typeKey, specifics);
return typeKey;
}
/** create a sub-type, using a different type/handler functor */
Key
key (Key const& parentKey, TypeHandler const& instanceFunc)
{
return trackKey (parentKey, instanceFunc);
}
/** create a sub-type,
* using a different private-ID (implementation defined) */
Key
key (Key const& parentKey, LocalKey specifics)
{
return trackKey (parentKey, specifics);
}
/** shortcut to access the Key part of a (probably new) Entry
* describing a concrete buffer at the given address
* @note might create/register a new Entry as a side-effect
*/
Key const&
key (Key const& parentKey, void* concreteBuffer, LocalKey const& implID =UNSPECIFIC)
{
Key derivedKey = Key::forEntry (parentKey, concreteBuffer);
Entry* existing = table_.fetch (derivedKey);
return existing? *existing
: markLocked (parentKey,concreteBuffer,implID);
}
/** core operation to access or create a concrete buffer metadata entry.
* The hashID of the entry in question is built, based on the parentKey,
* which denotes a buffer type, and the concrete buffer address. If yet
* unknown, a new concrete buffer metadata Entry is created and initialised
* to LOCKED state. Otherwise just the existing Entry is fetched.
* @note this function really \em activates the buffer.
* In case the type (Key) involves a TypeHandler (functor),
* its constructor function will be invoked, if actually a new
* entry gets created. Typically this mechanism will be used
* to placement-create an object into the buffer.
* @param parentKey a key describing the \em type of the buffer
* @param concreteBuffer storage pointer, must not be NULL
* @param onlyNew disallow fetching an existing entry
* @throw error::Logic when #onlyNew is set, but an equivalent entry
* was registered previously. This indicates a serious error
* in buffer lifecycle management.
* @throw error::Invalid when invoked with NULL buffer. Use the #key
* functions instead to register and track type keys.
* @return reference to the entry stored in the metadata table.
* @warning the exposed reference might become invalid when the
* buffer is released or re-used later.
*/
Entry&
lock (Key const& parentKey
,void* concreteBuffer
,LocalKey const& implID =UNSPECIFIC
,bool onlyNew =false)
{
if (!concreteBuffer)
throw error::Invalid ("Attempt to lock a slot for a NULL buffer"
, error::LUMIERA_ERROR_BOTTOM_VALUE);
Entry newEntry(parentKey, concreteBuffer, implID);
Entry* existing = table_.fetch (newEntry);
if (existing && onlyNew)
throw error::Logic ("Attempt to lock a slot for a new buffer, "
"while actually the old buffer is still locked"
, error::LUMIERA_ERROR_LIFECYCLE );
if (existing && existing->isLocked())
throw error::Logic ("Attempt to re-lock a buffer still in use"
, error::LUMIERA_ERROR_LIFECYCLE );
if (!existing)
return store_and_lock (newEntry); // actual creation
else
return existing->lock (concreteBuffer);
}
/** access the metadata record registered with the given hash key.
* This might be a pseudo entry in case of a Key describing a buffer type.
* Otherwise, the entry associated with a concrete buffer pointer is returned
* by reference, an can be modified (e.g. state change)
* @param hashID which can be calculated from the Key
* @throw error::Invalid when there is no such entry
* @note use #isKnown to check existence
*/
Entry&
get (HashVal hashID)
{
Entry* entry = table_.fetch (hashID);
if (!entry)
throw error::Invalid ("Attempt to access an unknown buffer metadata entry");
return *entry;
}
bool
isKnown (HashVal key) const
{
return bool(table_.fetch (key));
}
bool
isLocked (HashVal key) const
{
const Entry* entry = table_.fetch (key);
return entry
&& entry->isLocked();
}
/* == memory management operations == */
/** combine the type (Key) with a concrete buffer,
* thereby marking this buffer as locked. Store a concrete
* metadata Entry to account for this fact. This might include
* invoking a constructor function, in case the type (Key)
* defines a (nontrivial) TypeHandler.
* @throw error::Fatal when locking a NULL buffer
* @throw exceptions which might be raised by a TypeHandler's
* constructor function. In this case, the Entry remains
* created, but is marked as FREE
*/
Entry&
markLocked (Key const& parentKey, void* buffer, LocalKey const& implID =UNSPECIFIC)
{
if (!buffer)
throw error::Fatal ("Attempt to lock for a NULL buffer. Allocation floundered?"
, error::LUMIERA_ERROR_BOTTOM_VALUE);
return this->lock(parentKey, buffer, implID, true); // force creation of a new entry
}
/** purge the bare metadata Entry from the metadata tables.
* @throw error::Logic if the entry isn't marked FREE already
*/
void
release (HashVal key)
{
Entry* entry = table_.fetch (key);
if (!entry) return;
ASSERT (entry && (key == HashVal(*entry)));
release (*entry);
}
void
release (Entry const& entry)
{
if (FREE != entry.state())
throw error::Logic ("Attempt to release a buffer still in use"
, error::LUMIERA_ERROR_LIFECYCLE);
table_.remove (HashVal(entry));
}
private:
template<typename PAR, typename DEF>
Key
trackKey (PAR parent, DEF specialisation)
{
Key newKey (parent,specialisation);
maybeStore (newKey);
return newKey;
}
void
maybeStore (Key const& key)
{
if (isKnown (key)) return;
table_.store (Entry (key, NULL));
}
Entry&
store_and_lock (Entry const& metadata)
{
Entry& newEntry = table_.store (metadata);
try
{
newEntry.invokeEmbeddedCtor();
ENSURE (LOCKED == newEntry.state());
ENSURE (newEntry.access());
}
catch(...)
{
newEntry.mark(FREE);
throw;
}
return newEntry;
}
};
}} // namespace proc::engine
#endif