OutputSlot: recast the proxy BufferProvider to use the Connection directly
Reaping the benefits of the preceding refactorings, most of the complex wiring within `OutputBufferProxy` can be omitted, and the proxy calls can be delegated directly to the actual `Connection` There is a catch however: we need to create an additional pathway to feed the frame-number information into the `BufferStore` backend.
This commit is contained in:
parent
7bae52d1f8
commit
eeedb7a028
12 changed files with 173 additions and 63 deletions
|
|
@ -134,6 +134,9 @@ namespace mem {
|
||||||
* automatic tracking and clean-up. The client is explicitly
|
* automatic tracking and clean-up. The client is explicitly
|
||||||
* responsible to invoke #releaseBuffer (either on the
|
* responsible to invoke #releaseBuffer (either on the
|
||||||
* BufferProvider, or directly on the BuffHandle).
|
* BufferProvider, or directly on the BuffHandle).
|
||||||
|
* @param type the _generic buffer type_ to base this buffer allocation on;
|
||||||
|
* at minimum this implicitly defines the buffer size, possibly also a TypeHandler
|
||||||
|
* @param arg (optional) custom argument to pass-through to the backing buffer manager
|
||||||
* @return a copyable handle, representing this buffer and this usage transaction.
|
* @return a copyable handle, representing this buffer and this usage transaction.
|
||||||
* @throw error::State when unable to provide this buffer
|
* @throw error::State when unable to provide this buffer
|
||||||
* @note this function may be used right away, without prior announcing, but then
|
* @note this function may be used right away, without prior announcing, but then
|
||||||
|
|
@ -146,11 +149,11 @@ namespace mem {
|
||||||
* return a metadata::Entry
|
* return a metadata::Entry
|
||||||
*/
|
*/
|
||||||
BuffHandle
|
BuffHandle
|
||||||
BufferProvider::lockBuffer (BuffDescr const& type)
|
BufferProvider::lockBuffer (BuffDescr const& type, int64_t arg)
|
||||||
{
|
{
|
||||||
REQUIRE (was_created_by_this_provider (type));
|
REQUIRE (was_created_by_this_provider (type));
|
||||||
auto& typeKey = bufferStage_->lookup (type);
|
auto& typeKey = bufferStage_->lookup (type);
|
||||||
BuffAlloc alloc= bufferStore_->provideBuffer (type,typeKey.storageSize(),typeKey.localTag());
|
BuffAlloc alloc= bufferStore_->provideBuffer (type,typeKey.storageSize(),typeKey.localTag(),arg);
|
||||||
auto& stateKey = bufferStage_->mark_locked (typeKey, alloc);
|
auto& stateKey = bufferStage_->mark_locked (typeKey, alloc);
|
||||||
|
|
||||||
return BuffHandle (buildDescriptor(stateKey), getAddr(alloc));
|
return BuffHandle (buildDescriptor(stateKey), getAddr(alloc));
|
||||||
|
|
@ -299,15 +302,15 @@ namespace mem {
|
||||||
BuffDescr::announce (uint count)
|
BuffDescr::announce (uint count)
|
||||||
{
|
{
|
||||||
ENSURE (provider_);
|
ENSURE (provider_);
|
||||||
return provider_->announce(count, *this);
|
return provider_->announce (count, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
BuffHandle
|
BuffHandle
|
||||||
BuffDescr::lockBuffer()
|
BuffDescr::lockBuffer (int64_t arg)
|
||||||
{
|
{
|
||||||
ENSURE (provider_);
|
ENSURE (provider_);
|
||||||
return provider_->lockBuffer(*this);
|
return provider_->lockBuffer (*this, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ namespace mem {
|
||||||
uint announce (uint count, BuffDescr const&);
|
uint announce (uint count, BuffDescr const&);
|
||||||
|
|
||||||
/** claim storage for exclusive use, based on a descriptor */
|
/** claim storage for exclusive use, based on a descriptor */
|
||||||
BuffHandle lockBuffer (BuffDescr const&);
|
BuffHandle lockBuffer (BuffDescr const&, int64_t=0);
|
||||||
|
|
||||||
/** directly claim storage and construct an object,
|
/** directly claim storage and construct an object,
|
||||||
* without preparing a descriptor beforehand */
|
* without preparing a descriptor beforehand */
|
||||||
|
|
@ -236,10 +236,10 @@ namespace mem {
|
||||||
public:
|
public:
|
||||||
virtual ~BufferStore() { } ///< this is an interface
|
virtual ~BufferStore() { } ///< this is an interface
|
||||||
|
|
||||||
virtual uint prepareBuffers (HashVal typeID, uint cnt, size_t) =0;
|
virtual uint prepareBuffers (HashVal typeID, uint cnt,size_t) =0;
|
||||||
virtual BuffAlloc provideBuffer (HashVal typeID, size_t, LocalTag) =0;
|
virtual BuffAlloc provideBuffer (HashVal typeID, size_t,LocalTag,int64_t) =0;
|
||||||
virtual void mark_emitted (HashVal typeID, BuffAlloc) =0;
|
virtual void mark_emitted (HashVal typeID, BuffAlloc) =0;
|
||||||
virtual void detachBuffer (HashVal typeID, BuffAlloc) =0;
|
virtual void detachBuffer (HashVal typeID, BuffAlloc) =0;
|
||||||
};
|
};
|
||||||
|
|
||||||
unique_ptr<BufferStage> bufferStage_;
|
unique_ptr<BufferStage> bufferStage_;
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ namespace mem {
|
||||||
}
|
}
|
||||||
|
|
||||||
BuffAlloc
|
BuffAlloc
|
||||||
provideBuffer (HashVal,size_t siz,LocalTag targetMarker) override
|
provideBuffer (HashVal,size_t siz,LocalTag targetMarker, int64_t customArg) override
|
||||||
{
|
{
|
||||||
BuffAlloc storageSlot{asBuffer(targetMarker), siz, targetMarker};
|
BuffAlloc storageSlot{asBuffer(targetMarker), siz, targetMarker};
|
||||||
CONF::on_lock (storageSlot);
|
CONF::on_lock (storageSlot);
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,11 @@ namespace mem {
|
||||||
|
|
||||||
/** callback to engage buffer handling cycle */
|
/** callback to engage buffer handling cycle */
|
||||||
uint announce (uint count);
|
uint announce (uint count);
|
||||||
BuffHandle lockBuffer();
|
|
||||||
|
/** claim an actual buffer for exclusive use.
|
||||||
|
* @param arg a custom argument to pass to the buffer manager
|
||||||
|
*/
|
||||||
|
BuffHandle lockBuffer (int64_t arg=0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,7 @@ namespace mem {
|
||||||
|
|
||||||
|
|
||||||
BuffAlloc
|
BuffAlloc
|
||||||
HeapMemBufferStore::provideBuffer (HashVal typeID, size_t buffSiz, LocalTag)
|
HeapMemBufferStore::provideBuffer (HashVal typeID, size_t buffSiz, LocalTag, int64_t)
|
||||||
{
|
{
|
||||||
BlockPool& blocks = getBlockPoolFor (buffSiz, typeID);
|
BlockPool& blocks = getBlockPoolFor (buffSiz, typeID);
|
||||||
Block& newBlock = blocks.createBlock();
|
Block& newBlock = blocks.createBlock();
|
||||||
|
|
|
||||||
|
|
@ -66,10 +66,10 @@ namespace mem {
|
||||||
public:
|
public:
|
||||||
/* === BufferStore interface === */
|
/* === BufferStore interface === */
|
||||||
|
|
||||||
virtual uint prepareBuffers (HashVal typeID, uint cnt, size_t) override;
|
uint prepareBuffers (HashVal typeID, uint cnt, size_t) override;
|
||||||
virtual BuffAlloc provideBuffer (HashVal,size_t,LocalTag) override;
|
BuffAlloc provideBuffer (HashVal,size_t,LocalTag,int64_t) override;
|
||||||
virtual void mark_emitted (HashVal typeID, BuffAlloc) override;
|
void mark_emitted (HashVal typeID, BuffAlloc) override;
|
||||||
virtual void detachBuffer (HashVal typeID, BuffAlloc) override;
|
void detachBuffer (HashVal typeID, BuffAlloc) override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~HeapMemBufferStore();
|
~HeapMemBufferStore();
|
||||||
|
|
|
||||||
|
|
@ -135,19 +135,21 @@ namespace out {
|
||||||
|
|
||||||
/* === Connection API === */
|
/* === Connection API === */
|
||||||
|
|
||||||
BuffHandle
|
Buff*
|
||||||
claimBufferFor(FrameID frameNr)
|
claimBufferFor (FrameID frameNr) override
|
||||||
{
|
{
|
||||||
|
#if false //////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : disabled code due to OutputSlot refactoring
|
||||||
TRACE (test, "Con=%p : lock buffer for frame-#%lu", this, frameNr);
|
TRACE (test, "Con=%p : lock buffer for frame-#%lu", this, frameNr);
|
||||||
REQUIRE (!closed_);
|
REQUIRE (!closed_);
|
||||||
|
|
||||||
return trackFrame (frameNr,
|
return trackFrame (frameNr,
|
||||||
buffProvider_.lockBuffer (bufferType_));
|
buffProvider_.lockBuffer (bufferType_));
|
||||||
|
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (end) disabled code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
isTimely (FrameID frameNr, TimeValue currentTime)
|
isTimely (FrameID frameNr, TimeValue currentTime) override
|
||||||
{
|
{
|
||||||
TRACE (test, "Con=%p : timely? frame-#%lu"
|
TRACE (test, "Con=%p : timely? frame-#%lu"
|
||||||
, this, frameNr);
|
, this, frameNr);
|
||||||
|
|
@ -159,32 +161,38 @@ namespace out {
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
transfer (BuffHandle & filledBuffer)
|
transfer (Buff* filledBuffer) override
|
||||||
{
|
{
|
||||||
|
#if false //////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : disabled code due to OutputSlot refactoring
|
||||||
TRACE (test, "Con=%p : transfer buffer %zu"
|
TRACE (test, "Con=%p : transfer buffer %zu"
|
||||||
, this, HashVal(filledBuffer));
|
, this, HashVal(filledBuffer));
|
||||||
REQUIRE (!closed_);
|
REQUIRE (!closed_);
|
||||||
|
|
||||||
pushout (filledBuffer);
|
pushout (filledBuffer);
|
||||||
|
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (end) disabled code
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
pushout (BuffHandle & data4output)
|
pushout (Buff* data4output) override
|
||||||
{
|
{
|
||||||
|
#if false //////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : disabled code due to OutputSlot refactoring
|
||||||
REQUIRE (!closed_);
|
REQUIRE (!closed_);
|
||||||
buffProvider_.emitBuffer (data4output);
|
buffProvider_.emitBuffer (data4output);
|
||||||
buffProvider_.releaseBuffer(data4output);
|
buffProvider_.releaseBuffer(data4output);
|
||||||
|
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (end) disabled code
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
discard (BuffHandle & superseededData)
|
discard (Buff* superseededData) override
|
||||||
{
|
{
|
||||||
|
#if false //////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : disabled code due to OutputSlot refactoring
|
||||||
REQUIRE (!closed_);
|
REQUIRE (!closed_);
|
||||||
buffProvider_.releaseBuffer (superseededData);
|
buffProvider_.releaseBuffer (superseededData);
|
||||||
|
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (end) disabled code
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
shutDown ()
|
shutDown() override
|
||||||
{
|
{
|
||||||
closed_ = true;
|
closed_ = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ namespace out {
|
||||||
* - callback functors for the lifecycle stages related to the client's access
|
* - callback functors for the lifecycle stages related to the client's access
|
||||||
* @todo 3/2026 this is prototyping code and was retained for demonstration purposes.
|
* @todo 3/2026 this is prototyping code and was retained for demonstration purposes.
|
||||||
*/
|
*/
|
||||||
template<class HUB>
|
template<class CON>
|
||||||
class OutputBufferProxy
|
class OutputBufferProxy
|
||||||
: public BufferProviderSetup
|
: public BufferProviderSetup
|
||||||
{
|
{
|
||||||
|
|
@ -74,13 +74,11 @@ namespace out {
|
||||||
class OutputBufferStore
|
class OutputBufferStore
|
||||||
: public BufferProviderSetup::Store
|
: public BufferProviderSetup::Store
|
||||||
{
|
{
|
||||||
HUB& output_;
|
CON&
|
||||||
|
asTarget (LocalTag targetMarker)
|
||||||
Buff*
|
|
||||||
asBuffer (LocalTag targetMarker)
|
|
||||||
{
|
{
|
||||||
void* buffMem{targetMarker};
|
void* targetAdr{targetMarker};
|
||||||
return static_cast<Buff*> (buffMem);
|
return * static_cast<CON*> (targetAdr);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint
|
uint
|
||||||
|
|
@ -91,9 +89,10 @@ namespace out {
|
||||||
}
|
}
|
||||||
|
|
||||||
BuffAlloc
|
BuffAlloc
|
||||||
provideBuffer (HashVal,size_t siz,LocalTag targetMarker) override
|
provideBuffer (HashVal,size_t siz, LocalTag targetMarker, int64_t frameNr) override
|
||||||
{
|
{
|
||||||
BuffAlloc storageSlot{asBuffer(targetMarker), siz, targetMarker};
|
Buff* buffer = asTarget(targetMarker).claimBufferFor (frameNr);
|
||||||
|
BuffAlloc storageSlot{buffer, siz, targetMarker};
|
||||||
TODO ("delegate buffer lock");
|
TODO ("delegate buffer lock");
|
||||||
return storageSlot;
|
return storageSlot;
|
||||||
}
|
}
|
||||||
|
|
@ -109,19 +108,12 @@ namespace out {
|
||||||
{
|
{
|
||||||
TODO ("delegate buffer release");
|
TODO ("delegate buffer release");
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
|
||||||
OutputBufferStore(HUB& outputHub)
|
|
||||||
: output_{outputHub}
|
|
||||||
{ }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Setup
|
struct Setup
|
||||||
{
|
{
|
||||||
HUB& outputHub_;
|
|
||||||
|
|
||||||
auto buildStage() { return std::make_unique<vault::mem::SimpleBufferStateRegistry>("OutputBufferProxy"); }
|
auto buildStage() { return std::make_unique<vault::mem::SimpleBufferStateRegistry>("OutputBufferProxy"); }
|
||||||
auto buildStore() { return std::make_unique<OutputBufferStore> (outputHub_); }
|
auto buildStore() { return std::make_unique<OutputBufferStore>(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
BuffDescr
|
BuffDescr
|
||||||
|
|
@ -135,8 +127,8 @@ namespace out {
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
OutputBufferProxy(HUB& outputHub)
|
OutputBufferProxy()
|
||||||
: BufferProviderSetup{Setup{outputHub}}
|
: BufferProviderSetup{Setup{}}
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@
|
||||||
namespace vault {
|
namespace vault {
|
||||||
namespace out {
|
namespace out {
|
||||||
|
|
||||||
|
using vault::mem::Buff;
|
||||||
using vault::mem::BuffHandle;
|
using vault::mem::BuffHandle;
|
||||||
using vault::mem::BufferProvider;
|
using vault::mem::BufferProvider;
|
||||||
using lib::time::TimeValue; /////////////////OOO Rly?
|
using lib::time::TimeValue; /////////////////OOO Rly?
|
||||||
|
|
@ -94,12 +95,12 @@ namespace out {
|
||||||
public:
|
public:
|
||||||
virtual ~Connection();
|
virtual ~Connection();
|
||||||
|
|
||||||
virtual BuffHandle claimBufferFor(FrameID) =0;
|
virtual Buff* claimBufferFor(FrameID) =0;
|
||||||
virtual bool isTimely (FrameID, TimeValue) =0;
|
virtual bool isTimely (FrameID, TimeValue) =0;
|
||||||
virtual void transfer (BuffHandle &) =0;
|
virtual void transfer (Buff*) =0;
|
||||||
virtual void pushout (BuffHandle &) =0;
|
virtual void pushout (Buff*) =0;
|
||||||
virtual void discard (BuffHandle &) =0;
|
virtual void discard (Buff*) =0;
|
||||||
virtual void shutDown () =0;
|
virtual void shutDown () =0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -127,7 +128,7 @@ namespace out {
|
||||||
{
|
{
|
||||||
using Connections = lib::ScopedCollection<CON>;
|
using Connections = lib::ScopedCollection<CON>;
|
||||||
using OpenedSinks = OutputSlot::OpenedSinks;
|
using OpenedSinks = OutputSlot::OpenedSinks;
|
||||||
using BufferProxy = OutputBufferProxy<AllocState>;
|
using BufferProxy = OutputBufferProxy<Connection>;
|
||||||
|
|
||||||
Connections connections_;
|
Connections connections_;
|
||||||
BufferProxy bufferProxy_;
|
BufferProxy bufferProxy_;
|
||||||
|
|
@ -171,7 +172,7 @@ namespace out {
|
||||||
template<class CTOR>
|
template<class CTOR>
|
||||||
AllocState(uint numChannels, CTOR&& populator)
|
AllocState(uint numChannels, CTOR&& populator)
|
||||||
: connections_(numChannels, std::forward<CTOR>(populator))
|
: connections_(numChannels, std::forward<CTOR>(populator))
|
||||||
, bufferProxy_{*this}
|
, bufferProxy_{}
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ namespace play {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
using fixture::ModelPortRegistry;
|
using fixture::ModelPortRegistry;
|
||||||
|
using vault::mem::Buff;
|
||||||
using vault::mem::BuffHandle;
|
using vault::mem::BuffHandle;
|
||||||
using vault::out::DataSink;
|
using vault::out::DataSink;
|
||||||
using vault::out::FrameID;
|
using vault::out::FrameID;
|
||||||
|
|
@ -79,11 +80,11 @@ namespace test {
|
||||||
class UnimplementedConnection
|
class UnimplementedConnection
|
||||||
: public vault::out::OutputSlot::Connection
|
: public vault::out::OutputSlot::Connection
|
||||||
{
|
{
|
||||||
BuffHandle claimBufferFor (FrameID) override;
|
Buff* claimBufferFor (FrameID) override;
|
||||||
bool isTimely (FrameID, TimeValue) override { return true; }
|
bool isTimely (FrameID, TimeValue) override { return true; }
|
||||||
void transfer (BuffHandle &) override { UNIMPLEMENTED ("transfer (BuffHandle const&)"); }
|
void transfer (Buff*) override { UNIMPLEMENTED ("transfer (BuffHandle const&)"); }
|
||||||
void pushout (BuffHandle &) override { UNIMPLEMENTED ("pushout (BuffHandle const&)"); }
|
void pushout (Buff*) override { UNIMPLEMENTED ("pushout (BuffHandle const&)"); }
|
||||||
void discard (BuffHandle &) override { UNIMPLEMENTED ("discard (BuffHandle const&)"); }
|
void discard (Buff*) override { UNIMPLEMENTED ("discard (BuffHandle const&)"); }
|
||||||
void shutDown () override { UNIMPLEMENTED ("shutDown() Connection"); }
|
void shutDown () override { UNIMPLEMENTED ("shutDown() Connection"); }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -199,7 +200,7 @@ namespace test {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
inline vault::mem::BuffHandle UnimplementedConnection::claimBufferFor (FrameID)
|
inline vault::mem::Buff* UnimplementedConnection::claimBufferFor (FrameID)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED ("claimBufferFor(FrameID)");
|
UNIMPLEMENTED ("claimBufferFor(FrameID)");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5157,17 +5157,17 @@ Thus the mapping is a copyable value object, using an associative array. It may
|
||||||
First and foremost, mapping can be seen as a //functional abstraction.// As it's used at implementation level, encapsulation of detail types in't the primary concern, so it's a candidate for generic programming: For each of those use cases outlined above, a distinct mapping type is created by instantiating the {{{OutputMapping<DEF>}}} template with a specifically tailored definition context ({{{DEF}}}), which takes on the role of a strategy. Individual instances of this concrete mapping type may be default created and copied freely. This instantiation process includes picking up the concrete result type and building a functor object for resolving on the fly. Thus, in the way typical for generic programming, the more involved special details are moved out of sight, while being still in scope for the purpose of inlining. But there //is// a concern better to be encapsulated and concealed at the usage site, namely accessing the rules system. Thus mapping leads itself to the frequently used implementation pattern where there is a generic frontend as header, calling into opaque functions embedded within a separate compilation unit.
|
First and foremost, mapping can be seen as a //functional abstraction.// As it's used at implementation level, encapsulation of detail types in't the primary concern, so it's a candidate for generic programming: For each of those use cases outlined above, a distinct mapping type is created by instantiating the {{{OutputMapping<DEF>}}} template with a specifically tailored definition context ({{{DEF}}}), which takes on the role of a strategy. Individual instances of this concrete mapping type may be default created and copied freely. This instantiation process includes picking up the concrete result type and building a functor object for resolving on the fly. Thus, in the way typical for generic programming, the more involved special details are moved out of sight, while being still in scope for the purpose of inlining. But there //is// a concern better to be encapsulated and concealed at the usage site, namely accessing the rules system. Thus mapping leads itself to the frequently used implementation pattern where there is a generic frontend as header, calling into opaque functions embedded within a separate compilation unit.
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
<div title="OutputSlot" modifier="Ichthyostega" created="201106162339" modified="202603142327" tags="def Concepts Player spec img" changecount="23">
|
<div title="OutputSlot" modifier="Ichthyostega" created="201106162339" modified="202603220010" tags="def Concepts Player spec img" changecount="26">
|
||||||
<pre>Within the Lumiera player and output subsystem, actually sending data to an external output requires to allocate an ''output slot''
|
<pre>Within the Lumiera player and output subsystem, actually sending data to an external output requires to allocate an ''output slot''
|
||||||
This is the central metaphor for the organisation of actual (system level) outputs; using this concept allows to separate and abstract the data calculation and the organisation of playback and rendering from the specifics of the actual output sink. Actual output possibilities (video in GUI window, video fullscreen, using a beamer, sound, Jack, rendering to file) can be added and removed dynamically from various components (Vault, Stage), all using the same resolution and mapping mechanisms (&rarr; OutputManagement)
|
This is the central metaphor for the organisation of actual (system level) outputs; using this concept allows to separate and abstract the data calculation and the organisation of playback and rendering from the specifics of the actual output sink. Actual output possibilities (video in GUI window, video fullscreen, using a beamer, sound, Jack, rendering to file) can be added and removed dynamically from various components (Vault, Stage), all using the same resolution and mapping mechanisms (&rarr; OutputManagement)
|
||||||
|
|
||||||
!Properties of an output slot
|
!Properties of an output slot
|
||||||
Each OutputSlot is an unique and distinguishable entity. It corresponds explicitly to an external output, or a group of such outputs (e.g. left and right soundcard output channels), or an output file or similar capability accepting media content. First off, an output slot needs to be provided, configured and registered, using an implementation for the kind of media data to be output (sound, video) and the special circumstances of the output capability (render a file, display video in a GUI widget, send video to a full screen display, establish a Jack port, just use some kind of "sound out"). An output slot is always limited to a single kind of media, and to a single connection unit, but this connection may still be comprised of multiple channels (stereoscopic video, multichannel sound).
|
Each OutputSlot is an unique and distinguishable entity. It corresponds explicitly to an external output, or a group of such outputs (e.g. left and right sound card output channels), or an output file or similar capability accepting media content. The setup for an {{{OutputSlot}}} is provided, configured and registered with an OutputManager, using an implementation suitable for the kind of media data to be sent (sound, video) and also suitable for the desired kind if output capability (render to file, display video in a GUI widget, send video to a full screen display, establish a Jack port or use any kind of "sound out"). Thus it is always limited to a single kind of media, and to a single connection unit, but this connection may still comprise several data feeds (e.g. stereoscopic video, multichannel sound). The number of such feeds and the expected data format depends on the _media stream type_ used for configuring or retrieving this OutputSlot. For example, a stereo sound output may expect only a single data feed, where data blocks for both channels are sent in interleaved format.
|
||||||
|
|
||||||
In order to be usable as //output sink,// an output slot needs to be //allocated,// i.e. tied to and locked for a specific client. At any time, there may be only a single client using a given output slot this way. To stress this point: output slots don't provide any kind of inherent mixing capability; any adaptation, mixing, overlaying and sharing needs to be done within the nodes network producing the output data fed to the slot. (in special cases, some external output capabilities -- e.g. the Jack audio connection system -- may still provide additional mixing capabilities, but that's beyond the scope of the Lumiera application)
|
Once the client retrieves the OutputSlot for some desired output capability, the slot was already _allocated_ for exclusive use for that client. At any time, there may be only a single client using a given output capability this way. It should be noted thus that output slots don't provide any kind of //inherent mixing capability;// any adaptation, mixing, overlaying and sharing needs to be done within the Render Node Network that produces the data to be output. (As an aside, //some// external output frameworks — e.g. the Jack audio connection system — may still provide additional mixing capabilities, yet these are beyond the scope of the Lumiera application)
|
||||||
|
|
||||||
[>img[Outputslot implementation structures|uml/OutputSlot_components.svg]]
|
[>img[Outputslot implementation structures|uml/OutputSlot_components.svg]]
|
||||||
Once allocated, the output slot returns a set of concrete ''sink handles'' (one for each physical channel expecting data). The calculating process feeds its results into those handles. Size and other characteristics of the data frames are assumed to be suitable, which typically won't be verified at that level anymore (but the sink handle provides a hook for assertions). Besides that, the allocation of an output slot reveals detailed ''timing expectations''. The client is required to comply to these [[timings|Timings]] when ''emitting'' data -- he's even required to provide a //current time specification,// alongside with the data. Based on this information, the output slot has the ability to handle timing failures gracefully; the concrete output slot implementation is expected to provide some kind of de-click or de-flicker facility, which kicks in automatically when a timing failure is detected.
|
The {{{OutputSlot}}} object is a (copyable) front-end handle, that should be stored by the client and discarded when further output is no longer required. From the OutputSlot, a set of actual ''data sink handles'' can be retrieved (one for each possible data feed). Starting from these handles, a ''two-phase output protocol'' can be executed for each frame of data to send to the output, which includes to obtain a BuffHandle, similar to what the BufferProvider exposes. The suitability of the buffer and the data format is not validated in any way at that level, since the setup works under the assumption that the StreamType and further metadata has been validated or configured suitably at a higher level. However, the OutputSlot exposes a definition of [[timing constraints|Timings]] to describe when data must be provided, and what deadline to observe for each frame. The client is required to comply to these {{{vault::out::Timings}}} both when acquiring the BuffHandle for some frame and when ''emitting'' data -- any use outside these limits will cause that frame to be treated as a glitch. Furthermore, the client is required to invoke {{{BuffHandle::release()}}} as soon as no further access to the buffer is required (and after invoking `emit()`). Failure to do so will cause a blocked buffer and typically leads to abort of the output and play process. However, individual frames that miss the deadline are handled gracefully; the actual implementation is expected to provide some kind of de-click or de-flicker facility, which kicks in automatically whenever a timing failure is detected.
|
||||||
|
|
||||||
!!!usage and implementation
|
!!!usage and implementation
|
||||||
Clients retrieve a reference to an output slot by asking a suitable OutputManager for an output possibility that supports some specific media and format. Usually, they just "claim" this slot by invoking {{{allocate()}}}, which causes actual output connections to be built and data handover mechanisms to be established, behind the scenes. For each such connection -- corresponding to a single distinct data feed within the media format handled by this ~OutputSlot -- the client gets a smart-handle {{{DataSink}}}. The feed may, or may not correspond to a nominal channel in the media format, since some output interfaces require to deliver the data in separate buffers per channel, while others require data to be interleaved. Generally speaking, the concrete ~OutputSlot implementation performs operations rather specific to the kind of output and external interface in question -- thereby calling directly into a specific connection implementation. The »''Output Slot Protocol''« however is designed in a way to abstract from all those technical details.
|
Clients retrieve a reference to an output slot by asking a suitable OutputManager for an output possibility that supports some specific media and format. Usually, they just "claim" this slot by invoking {{{allocate()}}}, which causes actual output connections to be built and data handover mechanisms to be established, behind the scenes. For each such connection -- corresponding to a single distinct data feed within the media format handled by this ~OutputSlot -- the client gets a smart-handle {{{DataSink}}}. The feed may, or may not correspond to a nominal channel in the media format, since some output interfaces require to deliver the data in separate buffers per channel, while others require data to be interleaved. Generally speaking, the concrete ~OutputSlot implementation performs operations rather specific to the kind of output and external interface in question -- thereby calling directly into a specific connection implementation. The »''Output Slot Protocol''« however is designed in a way to abstract from all those technical details.
|
||||||
|
|
@ -6265,7 +6265,7 @@ This is the core service provided by the player subsystem. The purpose is to cre
|
||||||
:any details of this processing remain opaque for the clients; even the player subsystem just accesses the EngineFaçade
|
:any details of this processing remain opaque for the clients; even the player subsystem just accesses the EngineFaçade
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
<div title="PlaybackVerticalSlice" creator="Ichthyostega" modifier="Ichthyostega" created="202303272236" modified="202601230245" tags="overview impl discuss draft" changecount="50">
|
<div title="PlaybackVerticalSlice" creator="Ichthyostega" modifier="Ichthyostega" created="202303272236" modified="202603212353" tags="overview impl discuss draft" changecount="51">
|
||||||
<pre>//Integration effort to promote the development of rendering, playback and video display in the GUI//
|
<pre>//Integration effort to promote the development of rendering, playback and video display in the GUI//
|
||||||
This IntegrationSlice was started in {{red{2023}}} as [[Ticket #1221|https://issues.lumiera.org/ticket/1221]] to coordinate the completion and integration of various implementation facilities, planned, drafted and built during the last years; this effort marks the return of development focus to the lower layers (after years of focussed UI development) and will implement the asynchronous and time-bound rendering coordinated by the [[Scheduler]] in the [[Vault|Vault-Layer]]
|
This IntegrationSlice was started in {{red{2023}}} as [[Ticket #1221|https://issues.lumiera.org/ticket/1221]] to coordinate the completion and integration of various implementation facilities, planned, drafted and built during the last years; this effort marks the return of development focus to the lower layers (after years of focussed UI development) and will implement the asynchronous and time-bound rendering coordinated by the [[Scheduler]] in the [[Vault|Vault-Layer]]
|
||||||
|
|
||||||
|
|
@ -6312,6 +6312,8 @@ __February.25__: built extended verification mechanisms, including some Node met
|
||||||
|
|
||||||
__January.26__: During the last nine months, unfortunately I was occupied with generic clean-up, upgrade, infrastructure and research work -- and so there was ''no progress'' regarding the PlaybackVerticalSlice. Just before that recess, an important breakthrough was achieved: a complete node graph was built and invoked end to end in a unit test setting. The further integration up to a RenderJob invocation is still blocked by some yet unresolved [[design mismatch (#1395)|https://issues.lumiera.org/ticket/1395]] related to the //»BufferProvider Protocol«// vs the //»OutputSlot Protocol«//; this is where I'll pick up the development work stalled almost a year ago.
|
__January.26__: During the last nine months, unfortunately I was occupied with generic clean-up, upgrade, infrastructure and research work -- and so there was ''no progress'' regarding the PlaybackVerticalSlice. Just before that recess, an important breakthrough was achieved: a complete node graph was built and invoked end to end in a unit test setting. The further integration up to a RenderJob invocation is still blocked by some yet unresolved [[design mismatch (#1395)|https://issues.lumiera.org/ticket/1395]] related to the //»BufferProvider Protocol«// vs the //»OutputSlot Protocol«//; this is where I'll pick up the development work stalled almost a year ago.
|
||||||
|
|
||||||
|
__March.26__: Meanwhile I was able to get at the root of the confusion related to BufferProvider and OutputSlot. This insight allows to rectify and realign the architecture, while largely keeping the APIs and both »Protocols« intact. As part of this design revalidation, I did an in-depth re-assessment of buffer metadata handling and got a much better understanding of the requirements for an actual buffer manager implementation -- and it is clear now that at least such a basic implementation is required to achieve a first engine integration. Furthermore, I was indeed able to build a proxy-BufferProvider that can be used as façade within the OutputSlot Protocol, so that both Protocols can be reconciled. The integration of this new proxy implementation necessitates a deeper refactoring within OutputSlot though, leading to a much better and cleaner design overall.
|
||||||
|
|
||||||
!Decisions
|
!Decisions
|
||||||
;Scheduler
|
;Scheduler
|
||||||
:is understood as a high-level Service, not a bare bone implementation mechanism
|
:is understood as a high-level Service, not a bare bone implementation mechanism
|
||||||
|
|
|
||||||
|
|
@ -138551,7 +138551,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
||||||
</node>
|
</node>
|
||||||
<node CREATED="1774122219137" ID="ID_656953886" MODIFIED="1774122240553" TEXT="⟹ alle weiteren Protokollschritte erfolgen direkt von dort"/>
|
<node CREATED="1774122219137" ID="ID_656953886" MODIFIED="1774122240553" TEXT="⟹ alle weiteren Protokollschritte erfolgen direkt von dort"/>
|
||||||
</node>
|
</node>
|
||||||
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1774122365597" ID="ID_292817548" MODIFIED="1774122379717" TEXT="Abwägung: wohin delegieren die Proxy-Callbacks?">
|
<node COLOR="#435e98" CREATED="1774122365597" ID="ID_292817548" MODIFIED="1774131392789" TEXT="Abwägung: wohin delegieren die Proxy-Callbacks?">
|
||||||
<icon BUILTIN="help"/>
|
<icon BUILTIN="help"/>
|
||||||
<node CREATED="1774122388349" ID="ID_1459974960" MODIFIED="1774122391728" TEXT="möglich wäre...">
|
<node CREATED="1774122388349" ID="ID_1459974960" MODIFIED="1774122391728" TEXT="möglich wäre...">
|
||||||
<node CREATED="1774122410671" ID="ID_1178347692" MODIFIED="1774124507440" TEXT="an eine Connection (Interface)">
|
<node CREATED="1774122410671" ID="ID_1178347692" MODIFIED="1774124507440" TEXT="an eine Connection (Interface)">
|
||||||
|
|
@ -138655,7 +138655,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
||||||
</body>
|
</body>
|
||||||
</html></richcontent>
|
</html></richcontent>
|
||||||
</node>
|
</node>
|
||||||
<node CREATED="1774124482636" ID="ID_99417390" MODIFIED="1774124502539" TEXT="Beschluß: Direk-Aufruf auf das Connection-Interface">
|
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#17196b" CREATED="1774124482636" ID="ID_99417390" MODIFIED="1774131406831" TEXT="Beschluß: Direkt-Aufruf auf das Connection-Interface">
|
||||||
<icon BUILTIN="forward"/>
|
<icon BUILTIN="forward"/>
|
||||||
<icon BUILTIN="full-1"/>
|
<icon BUILTIN="full-1"/>
|
||||||
</node>
|
</node>
|
||||||
|
|
@ -138689,6 +138689,105 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
||||||
<node CREATED="1774126881885" ID="ID_899991315" MODIFIED="1774126887766" TEXT="der Proxy-BufferProvider"/>
|
<node CREATED="1774126881885" ID="ID_899991315" MODIFIED="1774126887766" TEXT="der Proxy-BufferProvider"/>
|
||||||
</node>
|
</node>
|
||||||
</node>
|
</node>
|
||||||
|
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1774132649643" ID="ID_1759796259" MODIFIED="1774132670265" TEXT="Problem übersehen: die Frame-Nummer">
|
||||||
|
<icon BUILTIN="broken-line"/>
|
||||||
|
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1774132731585" ID="ID_396286799" MODIFIED="1774136559514">
|
||||||
|
<richcontent TYPE="NODE"><html>
|
||||||
|
<head/>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
das Connection-Interface (Entwurf) verlangt eine Frame-Nr für <font face="Monospaced"><b>claimBufferFor</b>(frame)</font>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html></richcontent>
|
||||||
|
<icon BUILTIN="messagebox_warning"/>
|
||||||
|
</node>
|
||||||
|
<node CREATED="1774132780386" ID="ID_837938632" MODIFIED="1774132808434" TEXT="dafür fehlt mir ein Kommunikations-Kanal ⟶ OutputBufferProxy">
|
||||||
|
<node CREATED="1774132820987" ID="ID_1358245383" MODIFIED="1774132823828" TEXT="LocalTag">
|
||||||
|
<node CREATED="1774132825036" ID="ID_588976778" MODIFIED="1774132964077" TEXT="ist entweder die Connection">
|
||||||
|
<richcontent TYPE="NOTE"><html>
|
||||||
|
<head/>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
das wäre die schöne und elegante Lösung die ich mir heute ausgedacht habe (und dabei die Frame-Nr übersehen)
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html></richcontent>
|
||||||
|
</node>
|
||||||
|
<node CREATED="1774132833155" ID="ID_484094660" MODIFIED="1774133019220" TEXT="oder die Connection-Nr + verdrahtete Back-Ref">
|
||||||
|
<richcontent TYPE="NOTE"><html>
|
||||||
|
<head/>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
das wäre die offensichtliche und dämliche Lösung: man verdrahtet einen back-Link auf den AllocState, und gibt einen Index in die Connection-Tafel über das LocalTag durch.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html></richcontent>
|
||||||
|
</node>
|
||||||
|
<node COLOR="#5b280f" CREATED="1774132862617" ID="ID_588130028" MODIFIED="1774133294975" TEXT="Buffer geht nicht (brauche dann Index)">
|
||||||
|
<richcontent TYPE="NOTE"><html>
|
||||||
|
<head/>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
Deshalb habe ich die zunächst gebaute Lösung mit dem <font color="#794c4c" face="Monospaced">BufferProxyAdaptor</font> aufgegeben: denn in den einzelnen Lifecycle-Methoden weiß man dann nicht, zu welcher Connection der Callback gehört. Man müßte dann im AllocState eine reverse-Index-Tabelle aufbauen, mit der man von der Buffer-Adresse zur passenden Connection zurückfindet. Prinzipiell wäre das möglich (da Buffer-Adressen global eindeutig sind und daher niemals gleichzeitig von zwei verschiedenen Connections verwendet werden können .... allerdings damit hörts auch schon auf, denn <i>später könnte durchaus eine andere Connection den gleichen Buffer verwenden</i>)
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html></richcontent>
|
||||||
|
<icon BUILTIN="button_cancel"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node CREATED="1774133338567" ID="ID_1915597180" MODIFIED="1774133354752" TEXT="völlig egal wie, in jedem Fall ist die Frame-Nummer eine zusätzliche Info"/>
|
||||||
|
</node>
|
||||||
|
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1774133516471" ID="ID_1070291197" MODIFIED="1774136481626" TEXT="⟹ bleibt nur, einen Kanal zu schaffen">
|
||||||
|
<icon BUILTIN="yes"/>
|
||||||
|
<node CREATED="1774133549299" ID="ID_437134456" MODIFIED="1774134318162" TEXT="Lösung-1 : zusätzliches Argument durchfädeln">
|
||||||
|
<icon BUILTIN="forward"/>
|
||||||
|
<node CREATED="1774133599076" ID="ID_1639698129" MODIFIED="1774133622157" TEXT="das wäre dann im öffentlichen BufferProviderAPI">
|
||||||
|
<node CREATED="1774133628078" ID="ID_1884544207" MODIFIED="1774133676414" TEXT="BufferProvider::lockBuffer (BuffDescr, int64_t customArg =0)"/>
|
||||||
|
<node CREATED="1774133685170" ID="ID_1491964539" MODIFIED="1774133704827" TEXT="da der Aufruf hier direkt aus dem Funktor kommt wäre das machbar"/>
|
||||||
|
<node CREATED="1774133713301" ID="ID_1681738826" MODIFIED="1774133778916" TEXT="müßte dann zusätzlich nochmal einen Pointer auf den OutputBufferProxy in die Closure binden"/>
|
||||||
|
</node>
|
||||||
|
<node CREATED="1774133855977" ID="ID_247819999" MODIFIED="1774133873835" TEXT="könnte auch ein spezialAPI auf OutputBufferProxy schaffen">
|
||||||
|
<node COLOR="#5b280f" CREATED="1774133898723" ID="ID_407416009" MODIFIED="1774133923914" TEXT="das würde dann zudem noch die interne BufferProvider::lockBuffer aufdoppeln">
|
||||||
|
<icon BUILTIN="button_cancel"/>
|
||||||
|
<node CREATED="1774133925648" ID="ID_1503448137" MODIFIED="1774133930448" TEXT="Implementation-reuse"/>
|
||||||
|
</node>
|
||||||
|
<node CREATED="1774133931949" ID="ID_1017504193" MODIFIED="1774133950185" TEXT="und obendrein müßten wir auch in dem Fall einen zusätzlichen Pointer mit binden"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node CREATED="1774133954237" ID="ID_1833902029" MODIFIED="1774134322458" TEXT="Lösung-2 : kontextuell übermitteln">
|
||||||
|
<icon BUILTIN="button_cancel"/>
|
||||||
|
<node CREATED="1774133994727" ID="ID_702943419" MODIFIED="1774134019424" TEXT="das Connection-API wird dadurch (etwas mehr) stateful"/>
|
||||||
|
<node CREATED="1774134039902" ID="ID_1189428583" MODIFIED="1774134064802" TEXT="es muß dann einen »current frame« geben"/>
|
||||||
|
<node CREATED="1774134118631" ID="ID_1354625599" MODIFIED="1774134133569" TEXT="und dazu eine Konvention, daß der Aufruf unmittelbar im gleichen Thread erfolgt"/>
|
||||||
|
<node CREATED="1774134140492" ID="ID_260619767" MODIFIED="1774134260873" TEXT="die weiteren API-Funktionen sind ohnehin implizit stateful (⟶ Buffer-Adresse)">
|
||||||
|
<richcontent TYPE="NOTE"><html>
|
||||||
|
<head/>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
Das API <i>ist nicht stateless,</i> da Buffer belegt und verwaltet werden. Das ist essntieller Zustand. Insofern liefert der claimBufferFor(frame)-Aufruf eine Buffer-Adresse, und die weiteren Aufrufe beziehen sich darauf.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html></richcontent>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node BACKGROUND_COLOR="#ccb59b" COLOR="#6e2a38" CREATED="1774134324929" ID="ID_1370520896" MODIFIED="1774134730585" TEXT="Entscheidung ist offensichtlich">
|
||||||
|
<richcontent TYPE="NOTE"><html>
|
||||||
|
<head/>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
Auf den ersten Blick ist natürlich Lösung-2 viel attraktiver, denn das ist schnell reingehackt, und dann funktioniert alles wie bisher. Hinsichtlich Interface-Design wäre das aber eine Bankrott-Erklärung; jetzt geb ich mir so viel Mühe, da ein klares Design zu schaffen, und dann führe ich eine solche faule Konvention ein, die beim Implementatior jede Menge Fragen aufwirft, und praktisch dazu zwingt, die Implementierung des Aufrufers (OutputSlot::AllocState<CON> bzw. OutputBufferProxy) im Detail zu durchdringen (⟹ Kopplung).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Dem gegenüber ist Lösung-1 zwar nicht schön, aber akzeptabel; letztlich schaffen wir uns ein Interface immer zu einem Zweck, und hier verlangt der Zweck eben, eine weitere Info zum Buffer-Manager durchzuleiten (ohne sie direkt in die Metadaten zu speichern). SowaskommtindenbestenFamilienvor. Zudem kann ich das Argument defaulten. Ich bin sogar geneigt, das ganz offiziell auch auf das Buffer-Descripter-API zu heben: aus Gründen der Symmetrie, und weil wir uns damit den zusätzlichen Pointer sparen können)
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html></richcontent>
|
||||||
|
<font ITALIC="true" NAME="SansSerif" SIZE="14"/>
|
||||||
|
<icon BUILTIN="yes"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
</node>
|
</node>
|
||||||
</node>
|
</node>
|
||||||
</node>
|
</node>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue