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)
320 lines
11 KiB
C++
320 lines
11 KiB
C++
/*
|
|
BufferProvider - Abstraction for Buffer management during playback/render
|
|
|
|
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 buffer-provider.cpp
|
|
** Implementation details related to buffer lifecycle management in the render engine.
|
|
*/
|
|
|
|
|
|
#include "lib/error.hpp"
|
|
#include "steam/engine/buffer-provider.hpp"
|
|
#include "steam/engine/buffer-metadata.hpp"
|
|
#include "lib/util.hpp"
|
|
|
|
using util::isSameObject;
|
|
|
|
namespace steam {
|
|
namespace engine {
|
|
|
|
|
|
namespace { // impl. details and definitions
|
|
|
|
const uint DEFAULT_DESCRIPTOR = 0;
|
|
|
|
}
|
|
|
|
LUMIERA_ERROR_DEFINE (BUFFER_MANAGEMENT, "Problem providing working buffers");
|
|
|
|
|
|
/** build a new provider instance, managing a family of buffers.
|
|
* The metadata of these buffers is organised hierarchically based on
|
|
* chained hash values, using the #implementationID as a seed.
|
|
* @param implementationID symbolic ID setting these family of buffers apart.
|
|
*/
|
|
BufferProvider::BufferProvider (Literal implementationID)
|
|
: meta_(new BufferMetadata (implementationID))
|
|
{ }
|
|
|
|
BufferProvider::~BufferProvider() { }
|
|
|
|
|
|
/** @internal verify the given descriptor.
|
|
* @return true if it corresponds to a buffer
|
|
* currently locked and usable by client code
|
|
*/
|
|
bool
|
|
BufferProvider::verifyValidity (BufferDescriptor const& bufferID) const
|
|
{
|
|
return meta_->isLocked (bufferID);
|
|
}
|
|
|
|
|
|
BufferDescriptor
|
|
BufferProvider::getDescriptorFor (size_t storageSize)
|
|
{
|
|
return BufferDescriptor (*this, meta_->key (storageSize));
|
|
}
|
|
|
|
|
|
BufferDescriptor
|
|
BufferProvider::getDescriptorFor(size_t storageSize, TypeHandler specialTreatment)
|
|
{
|
|
return BufferDescriptor (*this, meta_->key (storageSize, specialTreatment));
|
|
}
|
|
|
|
|
|
size_t
|
|
BufferProvider::getBufferSize (HashVal typeID) const
|
|
{
|
|
metadata::Key& typeKey = meta_->get (typeID);
|
|
return typeKey.storageSize();
|
|
}
|
|
|
|
|
|
/** callback from implementation to build and enrol a BufferHandle,
|
|
* to be returned to the client as result of the #lockBuffer call.
|
|
* Performs the necessary metadata state transition leading from an
|
|
* abstract buffer type to a metadata::Entry corresponding to an
|
|
* actual buffer, which is locked for exclusive use by one client.
|
|
*/
|
|
BuffHandle
|
|
BufferProvider::buildHandle (HashVal typeID, void* storage, LocalKey const& implID)
|
|
{
|
|
metadata::Key& typeKey = meta_->get (typeID);
|
|
metadata::Entry& entry = meta_->markLocked(typeKey, storage, implID);
|
|
|
|
return BuffHandle (BufferDescriptor(*this, entry), storage);
|
|
}
|
|
|
|
|
|
/** BufferProvider API: declare in advance the need for working buffers.
|
|
* This optional call allows client code to ensure the availability of the
|
|
* necessary working space, prior to starting the actual operations. The
|
|
* client may reasonably assume to get the actual number of buffers, as
|
|
* indicated by the return value. A provider may be able to handle
|
|
* various kinds of buffers (e.g. of differing size), which are
|
|
* distinguished by the \em type embodied into the BufferDescriptor.
|
|
* @return maximum number of simultaneously usable buffers of this type,
|
|
* to be retrieved later through calls to #lockBuffer.
|
|
* @throw error::State when no buffer of this kind can be provided
|
|
* @note the returned count may differ from the requested count.
|
|
*/
|
|
uint
|
|
BufferProvider::announce (uint count, BufferDescriptor const& type)
|
|
{
|
|
uint actually_possible = prepareBuffers (count, type);
|
|
if (!actually_possible)
|
|
throw error::State ("unable to fulfil request for buffers"
|
|
,LUMIERA_ERROR_BUFFER_MANAGEMENT);
|
|
return actually_possible;
|
|
}
|
|
|
|
|
|
/** BufferProvider API: retrieve a single buffer for exclusive use.
|
|
* This call actually claims a buffer of this type and marks it for
|
|
* use by client code. The returned handle allows for convenient access,
|
|
* but provides no automatic tracking or memory management. The client is
|
|
* explicitly responsible to invoke #releaseBuffer (which can be done directly
|
|
* on the BuffHandle).
|
|
* @return a copyable handle, representing this buffer and this usage transaction.
|
|
* @throw error::State when unable to provide this buffer
|
|
* @note this function may be used right away, without prior announcing, but then
|
|
* the client should be prepared for exceptions. The #announce operation allows
|
|
* to establish a reliably available baseline.
|
|
*/
|
|
BuffHandle
|
|
BufferProvider::lockBuffer (BufferDescriptor const& type)
|
|
{
|
|
REQUIRE (was_created_by_this_provider (type));
|
|
|
|
return provideLockedBuffer (type);
|
|
} // is expected to call buildHandle() --> state transition
|
|
|
|
|
|
/** BufferProvider API: state transition to \em emitted state.
|
|
* Client code may signal a state transition through this optional operation.
|
|
* The actual meaning of an "emitted" buffer is implementation defined; similarly,
|
|
* some back-ends may actually do something when emitting a buffer (e.g. commit data
|
|
* to cache), while others just set a flag or do nothing at all. This state transition
|
|
* may be invoked at most once per locked buffer.
|
|
* @throw error::Fatal in case of invalid state transition sequence. Only a locked buffer
|
|
* may be emitted, and at most once.
|
|
* @warning by convention, emitting a buffer implies that the contained data is ready and
|
|
* might be used by other parts of the application.
|
|
* An emitted buffer should not be modified anymore.
|
|
*/
|
|
void
|
|
BufferProvider::emitBuffer (BuffHandle const& handle)
|
|
{
|
|
metadata::Entry& metaEntry = meta_->get (handle.entryID());
|
|
mark_emitted (metaEntry.parentKey(), metaEntry.localKey());
|
|
metaEntry.mark(EMITTED);
|
|
}
|
|
|
|
|
|
/** BufferProvider API: declare done and detach.
|
|
* Client code is required to release \em each previously locked buffer eventually.
|
|
* @warning invalidates the BuffHandle, clients mustn't access the buffer anymore.
|
|
* Right after releasing, an access through the handle will throw;
|
|
* yet the buffer might be re-used and the handle become valid
|
|
* later on accidentally.
|
|
* @note EX_FREE
|
|
*/
|
|
void
|
|
BufferProvider::releaseBuffer (BuffHandle const& handle)
|
|
try {
|
|
metadata::Entry& metaEntry = meta_->get (handle.entryID());
|
|
metaEntry.mark(FREE); // might invoke embedded dtor function
|
|
detachBuffer (metaEntry.parentKey(), metaEntry.localKey());
|
|
meta_->release (metaEntry);
|
|
}
|
|
ERROR_LOG_AND_IGNORE (engine, "releasing a buffer from BufferProvider")
|
|
|
|
|
|
|
|
/** @warning this operation locally modifies the metadata entry of a single buffer
|
|
* to attach a TypeHandler taking ownership of an object embedded within the buffer.
|
|
* The client is responsible for actually placement-constructing the object; moreover
|
|
* the client is responsible for any damage done to already existing buffer content.
|
|
* @note the buffer must be in locked state and the underlying buffer type must not define
|
|
* an non-trivial TypeDescriptor, because there is no clean way of superseding an
|
|
* existing TypeDescriptor, which basically is just a functor and possibly
|
|
* could perform any operation on buffer clean-up.
|
|
* @note EX_STRONG
|
|
*/
|
|
void
|
|
BufferProvider::attachTypeHandler (BuffHandle const& target, BufferDescriptor const& reference)
|
|
{
|
|
metadata::Entry& metaEntry = meta_->get (target.entryID());
|
|
metadata::Entry& refEntry = meta_->get (reference);
|
|
REQUIRE (refEntry.isTypeKey());
|
|
REQUIRE (!metaEntry.isTypeKey());
|
|
if (!metaEntry.isLocked())
|
|
throw error::Logic{"unable to attach an object because buffer isn't locked for use"
|
|
, LERR_(LIFECYCLE)};
|
|
|
|
metaEntry.useTypeHandlerFrom (refEntry); // EX_STRONG
|
|
}
|
|
|
|
|
|
/** @internal abort normal lifecycle, reset the underlying buffer and detach from it.
|
|
* This allows to break out of normal usage and reset the handle to \em invalid state
|
|
* @param invokeDtor if possibly the clean-up function of an TypeHandler registered with
|
|
* the buffer metadata should be invoked prior to resetting the metadata state.
|
|
* Default is \em not to invoke anything
|
|
* @note EX_FREE
|
|
*/
|
|
void
|
|
BufferProvider::emergencyCleanup (BuffHandle const& target, bool invokeDtor)
|
|
try {
|
|
metadata::Entry& metaEntry = meta_->get (target.entryID());
|
|
metaEntry.invalidate (invokeDtor);
|
|
detachBuffer (metaEntry.parentKey(), metaEntry.localKey());
|
|
meta_->release (metaEntry);
|
|
}
|
|
ERROR_LOG_AND_IGNORE (engine, "cleanup of buffer metadata while handling an error")
|
|
|
|
|
|
|
|
bool
|
|
BufferProvider::was_created_by_this_provider (BufferDescriptor const& descr) const
|
|
{
|
|
return isSameObject (*this, *descr.provider_);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* === BufferDescriptor and BuffHandle === */
|
|
|
|
bool
|
|
BufferDescriptor::verifyValidity() const
|
|
{
|
|
return provider_->verifyValidity(*this);
|
|
}
|
|
|
|
|
|
size_t
|
|
BufferDescriptor::determineBufferSize() const
|
|
{
|
|
return provider_->getBufferSize (*this);
|
|
}
|
|
|
|
|
|
void
|
|
BuffHandle::emit()
|
|
{
|
|
REQUIRE (isValid());
|
|
descriptor_.provider_->emitBuffer(*this);
|
|
}
|
|
|
|
|
|
void
|
|
BuffHandle::release()
|
|
{
|
|
if (pBuffer_)
|
|
{
|
|
REQUIRE (isValid());
|
|
descriptor_.provider_->releaseBuffer(*this);
|
|
pBuffer_ = 0;
|
|
}
|
|
ENSURE (!isValid());
|
|
}
|
|
|
|
|
|
void
|
|
BuffHandle::emergencyCleanup()
|
|
{
|
|
descriptor_.provider_->emergencyCleanup(*this); // EX_FREE
|
|
pBuffer_ = 0;
|
|
}
|
|
|
|
|
|
/** Install a standard TypeHandler for an already locked buffer.
|
|
* This causes the dtor function to be invoked when releasing this buffer.
|
|
* The assumption is that client code will placement-construct an object
|
|
* into this buffer right away, and thus we're taking ownership on that object.
|
|
* @param type a reference BufferDescriptor defining an embedded TypeHandler to use
|
|
* A copy of this TypeHandler will be stored into the local metadata for
|
|
* this buffer only, not altering the basic buffer type in any way
|
|
* @throw lifecycle error when attempting to treat an buffer not in locked state
|
|
* @throw error::Logic in case of insufficient buffer space to hold the
|
|
* intended target object
|
|
* @note EX_STRONG
|
|
*/
|
|
void
|
|
BuffHandle::takeOwnershipFor(BufferDescriptor const& type)
|
|
{
|
|
if (!this->isValid())
|
|
throw error::Logic ("attaching an object requires an buffer in locked state", LERR_(LIFECYCLE));
|
|
if (this->size() < type.determineBufferSize())
|
|
throw error::Logic ("insufficient buffer size to hold an instance of that type");
|
|
|
|
descriptor_.provider_->attachTypeHandler(*this, type); // EX_STRONG
|
|
}
|
|
|
|
|
|
|
|
|
|
}} // namespace engine
|