OutputSlot: look for alternative ways to implement the proxy

After a lot of pondering over various solution schemes,
it seems that `BufferProxyAdaptor` is too rigid for the task at hand.
It could be retained as a technology demonstration, and used as blueprint
for a custom-made implementation. In a nutshell, we need a `BufferStore`
that directly delegates to the respective `Connection` — which also implies
to use the `OutputSlot::AllocState` as an intermediate to hendle that delegation.
This commit is contained in:
Fischlurch 2026-03-18 23:23:06 +01:00
parent 9b6933b572
commit 2ee6bb3b04
5 changed files with 360 additions and 26 deletions

View file

@ -14,10 +14,14 @@
/** @file buffer-proxy-adaptor.hpp /** @file buffer-proxy-adaptor.hpp
** Adapter to expose a given memory block through a BuffHandle. ** Adapter to expose a given memory block through a BuffHandle.
** This implementation building-block allows to integrate some specific data access ** This implementation building-block allows to integrate some specific data access
** (e.g. related to input / output) through the buffer lifecycle protocol as defined by BufferProvider. ** through the buffer lifecycle protocol as defined by BufferProvider.
** @todo BROKEN as of 12/2024 //////////////////////////////////////////////////////////////////////////////TICKET #1387 : can not properly compose BufferProvider ** @remark 3/2026 this solution was developed as pathfinder to resolve a mismatch
** @see output-slot.hpp ** between the »Buffer Provider Protocol« and the »Output Slot Protocol«.
** Ultimately, however, it was not used; instead, a similar, specially
** tailored construct was directly integrated.
** @see output-proxy-provider-test.cpp ** @see output-proxy-provider-test.cpp
** @see output-buffer-proxy.hpp
** @see output-slot.hpp
*/ */
#ifndef VAULT_MEM_BUFFER_PROXY_ADAPTOR_H #ifndef VAULT_MEM_BUFFER_PROXY_ADAPTOR_H
@ -46,7 +50,7 @@ namespace mem {
/** /**
* Adapter to expose access controlled access to some memory resource * Adapter to expose controlled access to some memory resource
* through a [»Buffer Type«](\ref BuffDescr) and a BuffHandle as front-end, * through a [»Buffer Type«](\ref BuffDescr) and a BuffHandle as front-end,
* in accordance with the BufferProvider protocol. * in accordance with the BufferProvider protocol.
* *
@ -55,8 +59,7 @@ namespace mem {
* - what a Buffer Type means * - what a Buffer Type means
* - is there only one resource or is this setup statefull? * - is there only one resource or is this setup statefull?
* - 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 WIP-WIP 2/2026 used as prototype to forge a path ahead to resolve * @todo 3/2026 this is prototyping code and was retained for demonstration purposes.
* the structural problems with OutputSlot vs. BufferProvider ///////////////////////////////////////TICKET #1415 : prototyping to resolve structural mismatch between OutputSlot / DataSink / BufferProvider
*/ */
template<class CONF> template<class CONF>
class BufferProxyAdaptor class BufferProxyAdaptor

View file

@ -0,0 +1,173 @@
/*
OUTPUT-BUFFER-PROXY.hpp - Adaptor to access existing allocation via buffer handling protocol
Copyright (C)
2026 Hermann Vosseler <Ichthyostega@web.de>
  **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.
*/
/** @file output-buffer-proxy.hpp
** Adapter to expose some buffer memory through a BuffHandle as front-end.
** This implementation building-block allows to integrate the output buffers
** managed by a specific output mechanism with the generic implementation of
** OutputSlote, so that only a BuffHandle needs to be exposed to the client.
** @see output-proxy-provider-test.cpp
** @see output-buffer-proxy.hpp
** @see output-slot.hpp
*/
#ifndef VAULT_OUT_OUTPUT_BUFFER_PROXY_H
#define VAULT_OUT_OUTPUT_BUFFER_PROXY_H
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/symbol.hpp"
#include "lib/meta/util.hpp"
#include "vault/mem/buffer-provider.hpp"
#include "vault/mem/buffer-provider-setup.hpp"
#include "vault/mem/heap-mem-buffer-store.hpp"
#include "vault/mem/simple-buffer-state-registry.hpp"
#include <functional>
#include <utility>
namespace vault{
namespace out {
using std::move;
using lib::Literal;
using lib::HashVal;
using vault::mem::Buff;
using vault::mem::LocalTag;
using vault::mem::BuffAlloc;
using vault::mem::BuffDescr;
using vault::mem::BuffHandle;
using vault::mem::TypeHandler; ///////////////////////////OOO Rly?
using vault::mem::BufferProviderSetup;
/**
* Adapter to expose controlled access to some memory resource
* through a [»Buffer Type«](\ref BuffDescr) and a BuffHandle as front-end,
* in accordance with the BufferProvider protocol.
*
* This template is an implementation building block and needs to be instantiated
* with a policy or configuration to define the flexible parts of the behaviour
* - what a Buffer Type means
* - is there only one resource or is this setup statefull?
* - 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.
*/
template<class CONF>
class OutputBufferProxy
: util::NonCopyable
{
class ProxyBufferStore
: public BufferProviderSetup::Store
, private CONF
{
Buff*
asBuffer (LocalTag targetMarker)
{
void* buffMem{targetMarker};
return static_cast<Buff*> (buffMem);
}
uint
prepareBuffers (HashVal,uint,size_t) override
{
WARN (engine, "Announce invoked on a Proxy Buffer Provider; "
"Implies misuse as generic BufferProvider");
return 1;
}
BuffAlloc
provideBuffer (HashVal,size_t siz,LocalTag targetMarker) override
{
BuffAlloc storageSlot{asBuffer(targetMarker), siz, targetMarker};
CONF::on_lock (storageSlot);
return storageSlot;
}
void
mark_emitted (HashVal, BuffAlloc storageSlot) override
{
CONF::on_emit (storageSlot);
}
void
detachBuffer (HashVal, BuffAlloc storageSlot) override
{
CONF::on_release (storageSlot);
}
public:
ProxyBufferStore (CONF policy)
: CONF{move(policy)}
{ }
};
struct Setup
: CONF
{
auto buildStage() { return std::make_unique<vault::mem::SimpleBufferStateRegistry>("OutputBufferProxy"); }
auto buildStore() { return std::make_unique<ProxyBufferStore> (move(*this)); }
}; // Note: possible since Setup does not use CONF
class PassThroughBufferProvider
: public BufferProviderSetup
{
public:
PassThroughBufferProvider (Setup setup)
: BufferProviderSetup{setup}
{ }
BuffDescr
registerBuffer (void* buff, size_t siz)
{
REQUIRE (siz);
REQUIRE (buff);
auto& typeKey = bufferStage_->defineBufferType (siz, TypeHandler::RAW, LocalTag(buff));
return buildDescriptor (typeKey);
}
};
PassThroughBufferProvider proxyProvider_;
public:
OutputBufferProxy(CONF policy)
: proxyProvider_{Setup{move(policy)}}
{ }
template<typename TAR>
BuffDescr
getDescriptorFor (TAR& dataBlock)
{
return proxyProvider_.registerBuffer(&dataBlock, sizeof(TAR));
}
template<typename TAR>
BuffHandle
lockBuffer (TAR& dataBlock)
{
BuffDescr buffType = getDescriptorFor<TAR> (dataBlock);
ENSURE (buffType.isValid());
return buffType.lockBuffer();
}
};
}} // namespace vault::out
#endif /*VAULT_OUT_OUTPUT_BUFFER_PROXY_H*/

View file

@ -33,6 +33,7 @@
#include "lib/error.hpp" #include "lib/error.hpp"
#include "lib/nocopy.hpp" #include "lib/nocopy.hpp"
#include "vault/out/output-slot.hpp" #include "vault/out/output-slot.hpp"
#include "vault/out/output-buffer-proxy.hpp"
#include "lib/scoped-collection.hpp" #include "lib/scoped-collection.hpp"
#include "lib/iter-adapter-stl.hpp" #include "lib/iter-adapter-stl.hpp"
#include "lib/iter-source.hpp" #include "lib/iter-source.hpp"

View file

@ -5214,11 +5214,11 @@ Thus there are two serious problem situations
&amp;rarr; OutputSlotImpl &amp;rarr; OutputSlotImpl
</pre> </pre>
</div> </div>
<div title="OutputSlotDesign" modifier="Ichthyostega" created="201112190322" tags="design decision"> <div title="OutputSlotDesign" modifier="Ichthyostega" created="201112190322" modified="202603190010" tags="design decision" changecount="7">
<pre>The OutputSlot interface describes a point where generated media data can actually be sent to the external world. It is expected to be implemented by adapters and bridges to existing drivers or external interface libraries, like a viewer widget in the GUI, ALSA or Jack sound output, rendering to file, using an external media format library. The design of this core facility was rather difficult and stretched out over quite some time span -- this page documents the considerations and the decisions taken. <pre>The OutputSlot interface describes a point where generated media data can actually be sent to the external world. It is expected to be implemented by adapters and bridges to existing drivers or external interface libraries, like a viewer widget in the GUI, ALSA or Jack sound output, rendering to file, using an external media format library. The design of this core facility was rather difficult and stretched out over quite some time span -- this page documents the considerations and the decisions taken.
!intention !intention
OutpotSlot is a metaphor to unify 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, sound, Jack, rendering to file) can be added and removed dynamically from various components (Vault, Stage), all using the same resolution and mapping mechanisms (&amp;rarr; OutputManagement) OutputSlot is a metaphor to unify 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, sound, Jack, rendering to file) can be added and removed dynamically from various components (Vault, Stage), all using the same resolution and mapping mechanisms (&amp;rarr; OutputManagement)
!design possibilities !design possibilities
!!properties as a starting point !!properties as a starting point
@ -5240,16 +5240,16 @@ Data is handed over by the client invoking an {{{emit(time,...)}}} function on t
:here the output mechanism owns the buffer. Within a certain time window prior to the expected time of the {{{emit()}}}-call, the client may obtain this buffer (pointer) to fill in the data. The slot implementation won't touch this buffer until the {{{emit()}}} handover, which in this case just provides the time and signalles the client is done with that buffer. If the data emitting handshake doesn't happen at all, it counts as late and superseded by the next handshake. :here the output mechanism owns the buffer. Within a certain time window prior to the expected time of the {{{emit()}}}-call, the client may obtain this buffer (pointer) to fill in the data. The slot implementation won't touch this buffer until the {{{emit()}}} handover, which in this case just provides the time and signalles the client is done with that buffer. If the data emitting handshake doesn't happen at all, it counts as late and superseded by the next handshake.
!!relation to timing !!relation to timing
Besides the sink handles, allocation of an output slot defines some timing constraints, which are binding for the client. These include a grid of deadlines for each frame to deliver, plus a fixed //latency.// The output slot implementation itself is bound by external constraints to deliver data at a fixed framerate and aligned to an externally defined timing grid, plus the data needs to be handed over ahead of these time points by an time amount given by the latency. Depending on the data exchange model, there is an additional time window limiting the buffer management. Besides the sink handles, allocation of an output slot defines some timing constraints, to which the client must comply. These [[Timings]] are detailed and explicit, including a grid of deadlines for each frame to deliver, plus a fixed //latency.// Within this context, &amp;raquo;latency&amp;laquo; implies a requirement to be ahead of the nominal time by a certain amount, to compensate for the processing time that is necessary to propagate the media to the physical output pin. The output slot implementation itself is bound by external constraints to deliver data at a fixed framerate and aligned to an externally defined timing grid, and furthermore the data needs to be handed over ahead of these time points by an time amount given by the latency. Depending on the data exchange model, additional timing constraints may be imposed to further limit the time window when the buffer must be provided, transferred and released.
The assumption is for the client to have elaborate timing capabilities at his disposal. More specifically, the client is assumed to be a job running within the engine scheduler and thus can be configured to run //after// another job has finished, and to run within certain time limits. Thus the client is able to provide a //current nominal time// -- which is suitably close to the actual wall clock time. The output slot implementation can be written such as to work out from this time specification if the call is timely or overdue -- and react accordingly. !!relation to buffer management
{{red{WIP 11/11}}} meanwhile I have worked out the BufferProvider interface in detail, including an explicit buffer handover protocol that is is backed by a tiny state engine to track the BufferMetadata. This mechanism provides an additional transition to {{{BLOCKED}}} state when order or timing constraints are being violated. Yet how to detect and resolve such a floundered state from the engine point of view still remains to be addressed.
{{red{TODO 6/11}}}in this spec, both data exchange models exhibit a weakness regarding the releasing of buffers. At which time is it safe to release a buffer, when the handover didn't happen? Do we need an explicit callback, and how could this callback be triggered? This is similar to the problem of closing a network connection, i.e. the problem is generally unsolvable, but can be handled pragmatically within certain limits. {{red{WIP 3/2026}}} after a long break, we are now facing the problem how to make BufferProvider and OutputSlot work together. Both define a protocol and data exchange models -- and both exhibit a weakness regarding the releasing of buffers. At which time is it safe to release a buffer, when the handover didn't happen? This is similar to the problem of closing a network connection, i.e. the problem is generally unsolvable, but can be handled pragmatically within certain limits.
{{red{WIP 11/11}}}meanwhile I've worked out the BufferProvider interface in deail. There's now a deatiled buffer handover protocol defined, which is supported by a little state engine tracking BufferMetadata. This mechanism provides a transition to {{{BLOCKED}}} state when order or timing constraints are being violated, which practically solves this problem. How to detect and resolve such a floundered state from the engine point of view still remains to be addressed. * For handles in the Render Engine we can generally assume that the regular call path is always completed, which ensures that every handle gets released. Exceptions may happen, but are //not handled// -- rather the Engine will be shut down in such a case, which could be arranged to discard all buffer memory unconditionally
* Regarding the »Output Slot Protocol« it is not clear yet which part will be responsible for checking the [[timing constraints|Timings]]; the actual {{{Connection}}} implementation looks like a plausible candidate. Furthermore, it is not clear yet {{red{3/26}}} where precisely an underlying buffer provider will be integrated, and if doing so is mandatory
!!!Lifecycle and storage At this point, the initial design draft from 2012 became bogged down in a thicket of confused implementation. Based on, and driven by a placeholder implementation at that time, a {{{BufferProvider}}} utilised to simplify building the test setup was conflated with the //interface// exposed towards prospective clients. Notably the prototype implementation relied on passing a BuffHandle from a backing BufferProvider directly to the client. Yet only the BuffHandle itself can be considered part of the protocol, and acts as client interface, necessitating a mechanism that propagates the //lifecycle events// (locking, emitting, releasing) into a state coordinator within the opened OutputSlot.
The concrete OutputSlot implementation is owned and managed by the facility actually providing the output possibility in question. For example, the GUI provides viewer widgets, while some sound output backend provides sound ports. The associated OutputSlot implementation object is required to stay alive as long as it's registered with some OutputManager. It needs to be de-registered explicitly prior to destruction -- and this deregistration may block until all clients using this slot did terminate. Beyond that, an output slot implementation is expected to handle all kinds of failures gracefully -- preferably just emitting a signal (callback functor).
{{red{TODO 7/11: Deregistration is an unsolved problem....}}}
----- -----
!Implementation / design problems !Implementation / design problems
@ -5259,23 +5259,38 @@ How to handle the selection of those two data exchange models!
Thus: Client gets an {{{OutputSlot&amp;}}}, without knowing the exact implementation type &amp;rArr; how can the client pick up the right strategy? Thus: Client gets an {{{OutputSlot&amp;}}}, without knowing the exact implementation type &amp;rArr; how can the client pick up the right strategy?
Solving this problem through //generic programming// -- i.e coding both cases effectively different, but similar -- would require to provide both implementation options already at //compile time!// Solving this problem through //generic programming// -- i.e coding both cases effectively different, but similar -- would require to provide both implementation options already at //compile time!//
{{red{currently}}} I see two possible, yet quite different approaches... Generally speaking, I see two possible different approaches...
;generic ;generic
:when creating individual jobs, we utilise a //factory obtained from the output slot.// :when creating individual jobs, we utilise a //factory obtained from the output slot.//
;unified ;unified
:extend and adapt the protocol such to make both models similar; concentrate all differences //within a separate buffer provider.// :extend and adapt the protocol such to make both models similar; concentrate all differences //within a separate buffer provider.//
!!!discussion !!!discussion
the generic approach looks as it's becoming rather convoluted in practice. We'd need to hand over additional parameters to the factory, which passes them through to the actual job implementation created. And there would be a coupling between slot and job (the slot is aware it's going to be used by a job, and even provides the implementation). Obviously, a benefit is that the actual code path executed within the job is without indirections, and all written down in a single location. Another benefit is the possibility to extend this approach to cover further buffer handling models -- it doesn't pose any requirements on the structure of the buffer handling -- the generic approach looks as it's becoming rather convoluted in practice. We'd need to hand over additional parameters to the factory, which passes them through to the actual job implementation created. And there would be a coupling between slot and job (the slot is aware it's going to be used by a job, and even provides the implementation). Obviously, a benefit is that the actual code path executed within the job is without indirections, and all written down in a single location. Another benefit is the possibility to extend this approach to cover further buffer handling models -- it doesn't pose any requirements on the structure of the buffer handling --
On the other hand, if we accept to retrieve the buffer(s) via an indirection, which we kind of do anyway //within the render node implementation// -- the unified model looks more like a clean solution. It's more like doing away with some local optimisations possible if we handle the models explicitly, so it's not much of a loss, given that the majority of the processing time will be spent within the inner pixel calculation loops for frame processing anyway. When following this approach, the BufferProvider becomes a third, independent partner, and the slot cooperates tightly with this buffer provider, while the client (processing node) still just talks to the slot. Basically, this unified solution works like extending the shared buffer model to both cases. On the other hand, if we accept to retrieve the buffer(s) via an indirection, which we kind of do anyway //within the render node implementation// -- the unified model looks more like a clean solution. It's more like doing away with some local optimisations possible if we handle the models explicitly, so it's not much of a loss, given that the majority of the processing time will be spent within the inner pixel calculation loops for frame processing anyway. When following this approach, the BufferProvider becomes a third, independent partner, and the slot cooperates tightly with this buffer provider, while the client (processing node) still just talks to a front-end handle. Basically, this unified solution works like extending the shared buffer model to both cases. It is possible to go even one step further and dress up the output handling such that it looks similar to the normal buffer handling for the client.
&amp;rArr; __conclusion__: go for the unified approach! &amp;rArr; __conclusion__: go for the unified approach!
!!!unified data exchange cycle !!!unified data exchange cycle
The planned delivery time of a frame is used as an ID throughout that cycle The planned delivery time of a frame, or (equivalently) the frame-number on a well defined time grid is used as an ID throughout that cycle
# within a defined time window prior to delivery, the client can ''allocate and retrieve the buffer'' from the BufferProvider. # within a defined time window prior to delivery, the client can ''allocate and retrieve a working buffer'' in the form of a BuffHandle.
# the client has to ''emit'' within a (short) time window prior to deadline # the client has to ''emit'' within a (short) time window prior to deadline
# now the slot gets exclusive access to the buffer for output, signalling the buffer release to the buffer provider when done. # now the output mechanism behind the OutputSlot gets exclusive access to the buffer for output, signalling the buffer release when done.
&amp;rarr; OutputSlotImpl &amp;rarr; OutputSlotImpl
-----
!!! Delegation and state managment
In 2026, the prototype implementation had to be reworked, due to [[#1395|https://issues.lumiera.org/ticket/1395]].
However, the general structure of a BufferProvider was elaborated to quite some degree, and seems plausible and roughly final. Thus it becomes difficult to integrate the very specific requirements imposed by output handling into an implementation framework concerned primarily with memory management.
* the output format dictates the buffer layout; whenever an OutputSlot was allocated, there is only a single //buffer type//
* the client requests a buffer //for a specific frame// (observing the given [[timing constraints|Timings]] however)
* at that point, the actual connection implementation has to hand-out a buffer pointer
* so we need to create metadata and track state transitions for a buffer that is managed elsewhere
* furthermore, calls from the client through the BuffHandle, to signal {{{emit}}} and {{{release}}} must be routed to the proper connection.
So the problem is that BufferProvider is conceived as a central service facility, while what we need here is a thin abstraction proxy. Fortunately, the {{{BufferProvider}}} implementation scheme offers some wiggle room, insofar a {{{LocalTag}}} can be stored with the metadata. So the task is to provide a suitable implememtation of {{{BufferProvider::BufferStore}}}, such that
* the index number of the connection / data feed is embedded into a {{{LocalTag}}}
* an actual output buffer, provided by the {{{Connection}}} back-end, is registered with the metadata and represented as BuffHandle
* calls on that BuffHandle will invoke the {{{BufferStore}}} API and must be delegated to the corresponding {{{Connection}}}.
Taken together, this implies that the {{{OutputSlot::AllocState&lt;CON&gt;}}} turns into a delegation hub. It has to maintain the active connections and can thus be used to back a custom made {{{BufferStore}}} implementation, using the information embedded into the {{{LocalTag}}} to select the proper {{{Connection}}} for delegation -- which provides the actual implementation of all buffer management and state handling for the specific output mechanism.
</pre> </pre>
</div> </div>
<div title="OutputSlotImpl" modifier="Ichthyostega" created="201107102343" modified="201109232235" tags="spec operational"> <div title="OutputSlotImpl" modifier="Ichthyostega" created="201107102343" modified="201109232235" tags="spec operational">

View file

@ -114315,7 +114315,7 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</node> </node>
</node> </node>
</node> </node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1771536058707" ID="ID_1312596627" MODIFIED="1771537193386"> <node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1771536058707" ID="ID_1312596627" MODIFIED="1773870715330" STYLE="bubble">
<richcontent TYPE="NODE"><html> <richcontent TYPE="NODE"><html>
<head/> <head/>
<body> <body>
@ -114327,9 +114327,26 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</p> </p>
</body> </body>
</html></richcontent> </html></richcontent>
<edge COLOR="#a3026d"/>
<arrowlink COLOR="#ff0020" DESTINATION="ID_83611307" ENDARROW="Default" ENDINCLINATION="-142;6;" ID="Arrow_ID_47613106" STARTARROW="None" STARTINCLINATION="6;-31;"/> <arrowlink COLOR="#ff0020" DESTINATION="ID_83611307" ENDARROW="Default" ENDINCLINATION="-142;6;" ID="Arrow_ID_47613106" STARTARROW="None" STARTINCLINATION="6;-31;"/>
<arrowlink COLOR="#8f00c1" DESTINATION="ID_580664253" ENDARROW="Default" ENDINCLINATION="181;-320;" ID="Arrow_ID_553405939" STARTARROW="None" STARTINCLINATION="-172;19;"/> <arrowlink COLOR="#8f00c1" DESTINATION="ID_580664253" ENDARROW="Default" ENDINCLINATION="181;-320;" ID="Arrow_ID_553405939" STARTARROW="None" STARTINCLINATION="-172;19;"/>
<linktarget COLOR="#fd4e1f" DESTINATION="ID_1312596627" ENDARROW="Default" ENDINCLINATION="521;57;" ID="Arrow_ID_75459964" SOURCE="ID_797224940" STARTARROW="None" STARTINCLINATION="879;-27;"/>
<icon BUILTIN="yes"/> <icon BUILTIN="yes"/>
<node BACKGROUND_COLOR="#4e273a" COLOR="#fcdf7d" CREATED="1773870620479" HGAP="45" ID="ID_493983467" MODIFIED="1773871608024" TEXT="indirekte Konsequenz aus den Design-Entw&#xfc;rfen" VSHIFT="3">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
Ich hab zu Beginn ein &#187;Buffer Provider Protocol&#171; konzipiert, auf Basis einer abstrakten Analyse der vorhersehbaren Verwendung. Das enth&#228;lt bereits die Idee von Lifecycle-Phasen (und das folgt praktisch direkt aus <i>first principles...</i>). Dann habe ich, etwas sp&#228;ter das &#187;Output Slot Protocol&#171; konzipiert, und mich dabei &#8212; mutma&#223;lich unbewu&#223;t &#8212; an die damit bereits abgesteckte, gedankliche Schablone gehalten. Dieses Design wurde viele Jahre nicht &#252;berpr&#252;ft; erst jetzt, als die Integration der Engine als Ziel gesezt wurde, und dann die Architektur &#187;gedreht&#171; (Stichwort &#187;Domain Ontology&#171;), erst dann zeigte sich, da&#223; beide Entw&#252;rfe im Prinzip kongruent sind, aber praktisch nicht zusammenpassen. In dieser Situation bin ich <i>nicht im Stande, meine Designs einfach wegzuferfen </i>&#8212; denn beide Designs beruhen auf einer Einsicht, die ich immer noch f&#252;r zutreffend halte und nicht ungeschehen machen kann.
</p>
<p>
Allerdings ist dadurch noch nicht die konkrete Form der BufferProvider-Implementierung festgelegt; im Besonderen nicht die Idee, wie ich mit den Metadaten umgehe, und da&#223; wir eine Metadaten-Tabelle haben, die auch die Funktion der State-Engine mit &#252;bernimmt. Zwar k&#246;nnte ich diese bestehende Implementierung &#8222;einfach&#8220; wegwerfen &#8212; ich hab aber das glatte Gegenteil getan: ich hab in sie jetzt viel Zeit investiert. Was hat mich dazu motiviert? Es war nur die Intuition, da&#223; die Idee mit den Metadaten in die richtige Richtung weist, und einen Spielraum &#246;ffnent; demgegen&#252;ber w&#228;re eine Implementierung, die von konkreten Anforderungen getrieben wird, bereits komplett festgelegt. Der Preis daf&#252;r ist allerdings, da&#223; ich mich damit auf das Design-Schema im BufferProvider festgelegt habe, und jetzt die Anfroderung, einen Buffer<i>&#160; lediglich durchzureichen,</i>&#160;um dieses Schema herum einf&#228;deln mu&#223;
</p>
</body>
</html></richcontent>
<edge COLOR="#e60076"/>
<icon BUILTIN="info"/>
</node>
</node> </node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1771536236498" ID="ID_606028629" MODIFIED="1771536297629" TEXT="Nah-Ziel"> <node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1771536236498" ID="ID_606028629" MODIFIED="1771536297629" TEXT="Nah-Ziel">
<icon BUILTIN="yes"/> <icon BUILTIN="yes"/>
@ -114991,6 +115008,7 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</p> </p>
</body> </body>
</html></richcontent> </html></richcontent>
<arrowlink COLOR="#fd4e1f" DESTINATION="ID_1312596627" ENDARROW="Default" ENDINCLINATION="521;57;" ID="Arrow_ID_75459964" STARTARROW="None" STARTINCLINATION="879;-27;"/>
</node> </node>
<node CREATED="1772129627997" ID="ID_1533127720" MODIFIED="1772129874152" TEXT="weitere Analyse zeigt: die konkrtete Storage-Size weicht ab (kann gr&#xf6;&#xdf;er sein)"> <node CREATED="1772129627997" ID="ID_1533127720" MODIFIED="1772129874152" TEXT="weitere Analyse zeigt: die konkrtete Storage-Size weicht ab (kann gr&#xf6;&#xdf;er sein)">
<richcontent TYPE="NOTE"><html> <richcontent TYPE="NOTE"><html>
@ -116978,6 +116996,26 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</node> </node>
</node> </node>
</node> </node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1773871637641" ID="ID_1200164165" MODIFIED="1773871656095" TEXT="das wird jetzt alles ziemlich komplex">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1773871659011" ID="ID_678216230" MODIFIED="1773871668297" TEXT="es wirkt auf den ersten Blick wie ein &#xbb;Hack&#xab;"/>
<node CREATED="1773871671125" ID="ID_660604335" MODIFIED="1773871709556" TEXT="der Grund ist: ich habe mich auf das Schema des BufferProviders festgelegt"/>
<node CREATED="1773871721534" ID="ID_563946551" MODIFIED="1773871785505" TEXT="dieses Schema sieht nicht vor, die Koordinierungs-Logik gegen etwas anderes auszutauschen"/>
<node CREATED="1773871799787" ID="ID_1447337642" MODIFIED="1773871816862" TEXT="dem entsprechend sind die Virtual-Functions eine Ebene nach hinten gewandert"/>
<node CREATED="1773871818105" ID="ID_1249815623" MODIFIED="1773871903659" TEXT="und ich mu&#xdf; sie nun implementieren">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
schlimmer noch, nach der j&#252;ngsten &#220;berarbeitung sind auch die Datentypen aus der Metadaten-Verwaltung auf das Implementierungs-API gewandert (insofern das BufferStage-API nun dedizierte Lookups macht zu gegebenem typeKey)
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1773871912684" ID="ID_229835695" MODIFIED="1773872053431" TEXT="einziger verbleibender Freiheitsgrad: den BufferStore re-interpretieren">
<arrowlink COLOR="#3c2aa0" DESTINATION="ID_1447615516" ENDARROW="Default" ENDINCLINATION="140;-650;" ID="Arrow_ID_502898501" STARTARROW="None" STARTINCLINATION="1105;47;"/>
</node>
</node>
</node> </node>
</node> </node>
<node CREATED="1770590176675" ID="ID_1189656993" MODIFIED="1770590203848" TEXT="nicht mehr ben&#xf6;tigte Instrumentierung im HeapMemProvider zur&#xfc;ckbauen"> <node CREATED="1770590176675" ID="ID_1189656993" MODIFIED="1770590203848" TEXT="nicht mehr ben&#xf6;tigte Instrumentierung im HeapMemProvider zur&#xfc;ckbauen">
@ -138022,18 +138060,122 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
<icon BUILTIN="pencil"/> <icon BUILTIN="pencil"/>
<node CREATED="1773703751137" ID="ID_340628735" MODIFIED="1773703998020" TEXT="&#xdc;berlegung am Rande: l&#xe4;ngerfristig wollen wir einen globalen Metadaten-Store"> <node CREATED="1773703751137" ID="ID_340628735" MODIFIED="1773703998020" TEXT="&#xdc;berlegung am Rande: l&#xe4;ngerfristig wollen wir einen globalen Metadaten-Store">
<richcontent TYPE="NOTE"><html> <richcontent TYPE="NOTE"><html>
<head> <head/>
</head>
<body> <body>
<p> <p>
In der aktuellen Konfiguration hat jeder BufferProvider seine eigenes BufferStage-Implementierung, und darin eingebettet eine eigene Metadaten-Hashtable. Im Falle des BufferProxyAdaptors bedeutet das, f&#252;r jede Allokation eines OutputSlot erneut eine Metadaten-Verwaltung zu konstruieren, die dann aber nur f&#252;r wenige Buffer-Eintr&#228;ge genutzt wird. Da Output-Slots durchaus l&#228;ngerfristig leben, stellt das kein Performance-Problem da, aber k&#246;nnte letztlich schon zu einigem Bloat f&#252;hren In der aktuellen Konfiguration hat jeder BufferProvider seine eigenes BufferStage-Implementierung, und darin eingebettet eine eigene Metadaten-Hashtable. Im Falle des BufferProxyAdaptors bedeutet das, f&#252;r jede Allokation eines OutputSlot erneut eine Metadaten-Verwaltung zu konstruieren, die dann aber nur f&#252;r wenige Buffer-Eintr&#228;ge genutzt wird. Da Output-Slots durchaus l&#228;ngerfristig leben, stellt das kein Performance-Problem da, aber k&#246;nnte letztlich schon zu einigem Bloat f&#252;hren
</p> </p>
</body> </body>
</html> </html></richcontent>
</richcontent>
<arrowlink COLOR="#76516c" DESTINATION="ID_1415408255" ENDARROW="Default" ENDINCLINATION="779;39;" ID="Arrow_ID_1408352744" STARTARROW="None" STARTINCLINATION="468;17;"/> <arrowlink COLOR="#76516c" DESTINATION="ID_1415408255" ENDARROW="Default" ENDINCLINATION="779;39;" ID="Arrow_ID_1408352744" STARTARROW="None" STARTINCLINATION="468;17;"/>
</node> </node>
<node CREATED="1773704083017" ID="ID_446456909" MODIFIED="1773704096521" TEXT="Anordnung und Zugriffe sind problematisch in der bestehenden Architektur">
<node CREATED="1773704097997" ID="ID_1516975322" MODIFIED="1773704108791" TEXT="man will nur einen einzigen BufferProxyAdaptor"/>
<node CREATED="1773704125817" ID="ID_1780250199" MODIFIED="1773704161930" TEXT="aber die &#xbb;Typ&#xab;-Eintr&#xe4;ge werden in der Connection konfiguriert"/>
<node CREATED="1773704176910" ID="ID_1110317988" MODIFIED="1773704206640">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
und die Lebenszyklus-Callbacks m&#252;ssen in diese Connection&#160;<i>zur&#252;ckfinden</i>
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1773704250352" ID="ID_1517610825" MODIFIED="1773704374037" TEXT="zudem hab ich das LocalTag bereits &#x201e;verbraucht&#x201c;">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
da steckt die Buffer-Adresse drinnen, die dann konkret vom BufferStore auch wieder zur&#252;ckgeliefert werden soll; denn dieser ist hier in dem speziellen Setup eigentlich nur ein &#187;stub&#171; und dient dazu einen bereits bestehenden und <i>woanders verwalteten</i>&#160;Buffer lediglich durchzureichen
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1773704375888" ID="ID_1885968236" MODIFIED="1773704395616" TEXT="das l&#xe4;uft auf eine ekelige Verdrahtungs-Orgie hinaus">
<icon BUILTIN="smiley-oh"/>
<node CREATED="1773704412274" ID="ID_1170367903" MODIFIED="1773704427349" TEXT="Connection / oder DataSink brauchen back-Pointer auf den AllocState"/>
<node CREATED="1773704467940" ID="ID_1643292500" MODIFIED="1773704485525" TEXT="alle Lifecycle-Callbacks kommen im AllocState an"/>
<node CREATED="1773704486520" ID="ID_1324797839" MODIFIED="1773704500571" TEXT="sie haben nur den Buffer-Ptr, nicht die dazugeh&#xf6;rige Connection"/>
</node>
</node>
<node CREATED="1773797100647" ID="ID_425446315" MODIFIED="1773797118509" TEXT="&#xdc;berlegung: wie k&#xf6;nnt man das Problem &#xfc;berhaupt &#xbb;knacken&#xab;">
<node CREATED="1773797120055" ID="ID_1455782398" MODIFIED="1773797126986" TEXT="mit Gewalt?">
<node CREATED="1773797138856" ID="ID_1366418982" MODIFIED="1773797219006" TEXT="jede DataSink bekommt auch einen Basis-Descriptor(&#x27fc;backlink)"/>
<node CREATED="1773797234722" ID="ID_928593244" MODIFIED="1773797318457" TEXT="jedesmal wenn eine Connection einen Buffer ausgibt, meldet sie das beim AllocState"/>
<node CREATED="1773797320138" ID="ID_364292629" MODIFIED="1773797363035" TEXT="der AllocState f&#xfc;hrt eine Index-Tabelle Buff-Adr &#x27fc; Connection">
<font NAME="SansSerif" SIZE="12"/>
</node>
<node CREATED="1773797364278" ID="ID_1994968229" MODIFIED="1773797379039" TEXT="bei jedem Callback macht AllocState einen lookup"/>
<node CREATED="1773797379839" ID="ID_1614645519" MODIFIED="1773797430350" TEXT="und delegiert dann an ein callback-Interface in der Connection"/>
</node>
<node CREATED="1773797578422" ID="ID_1046290416" MODIFIED="1773797592886" TEXT="das Proxy-Callback-Interface &#xbb;aufbohren&#xab;">
<node CREATED="1773797596517" ID="ID_1912686522" MODIFIED="1773797622368" TEXT="wir legen einen lokalen Responder in das LocalTag"/>
<node CREATED="1773797623708" ID="ID_139729210" MODIFIED="1773797661678" TEXT="man ruft diesen auf, um den tats&#xe4;chlichen buffer zu bekommen"/>
<node CREATED="1773797677498" ID="ID_47941498" MODIFIED="1773797692733" TEXT="dieser Responder bekommt auch direkt die callbacks"/>
</node>
<node CREATED="1773866734065" ID="ID_1762550767" MODIFIED="1773866797883">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
<i>Nur das</i>&#160;<font face="Monospaced" color="#523333">LocalTag</font>&#160;ist von Anfang bis Ende da
</p>
</body>
</html></richcontent>
<font NAME="SansSerif" SIZE="12"/>
<node CREATED="1773866907525" ID="ID_835297439" MODIFIED="1773866922823" TEXT="direkt im AllocState w&#xfc;rde ein Connection-Index gen&#xfc;gen">
<node CREATED="1773866937489" ID="ID_75609998" MODIFIED="1773867041053" TEXT="sofern alle Aufrufe jeweils (implizit) gesondert daherkommen">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
...was hei&#223;t, <i>im jeweiligen Aufruf</i>&#160;ist bekannt um welchen Aufruf es sich handelt; sodann gen&#252;gt der Index der Connection als Zusatz-Info, um das jeweils einschl&#228;gige Connection-API aufzurufen
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1773867052378" ID="ID_1791077110" MODIFIED="1773867068140" TEXT="Also r&#xfc;ckt der AllocState in den Mittelpunkt ">
<node CREATED="1773867077554" ID="ID_1292849096" MODIFIED="1773867090761" TEXT="die DataSinks verweisen auf diesen (nicht die Connection)"/>
<node CREATED="1773867128982" ID="ID_1869189490" MODIFIED="1773867191531" TEXT="oder sie verweisen sogar auf den OutputSlot (und dieser geht &#xfc;ber seinen PImpl)">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
das w&#228;ren dann aber zwei Indirektions-Schritte (somit komplexer) und au&#223;erdem br&#228;uchte es dann ein Implementierungs-API
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1773867095492" ID="ID_1479457348" MODIFIED="1773867534654" TEXT="die DataSinks bekommen stattdessen einen Connection-Index eingebettet mit"/>
</node>
<node CREATED="1773867587451" ID="ID_1716621019" MODIFIED="1773867642960" TEXT="die DataSinks k&#xf6;nnten &#xbb;unique&#xab; sein &#x27f6; flexible direkte Implementierung">
<icon BUILTIN="idea"/>
</node>
</node>
<node CREATED="1773867267493" ID="ID_1405576784" MODIFIED="1773867294678" TEXT="wenn dann ein Buffer f&#xfc;r einen bestimmten Frame angefordert wird...">
<node CREATED="1773867302097" ID="ID_689493546" MODIFIED="1773867317107" TEXT="mu&#xdf; als Response direkt ein BuffHandle geliefert werden"/>
<node CREATED="1773867317823" ID="ID_1995974743" MODIFIED="1773867329597" TEXT="insofern f&#xe4;llt der Umweg &#xfc;ber einen Typ hier sowiso weg"/>
<node CREATED="1773867673872" ID="ID_1438192515" MODIFIED="1773867688584" TEXT="es tritt aber die Frame-Nr als zus&#xe4;tzlicher Parameter auf">
<icon BUILTIN="messagebox_warning"/>
</node>
<node CREATED="1773872350927" ID="ID_1455769657" MODIFIED="1773872371190" TEXT="und hinter dem Callback(s) m&#xfc;&#xdf;te nochmal eine Indirektion folgen"/>
</node>
</node>
<node CREATED="1773872010905" ID="ID_1655981568" MODIFIED="1773872027169" TEXT="Nun steht die inzwischen festgezurrte BufferProvider-Struktur im Weg">
<node CREATED="1773871942945" ID="ID_1447615516" MODIFIED="1773872053431" TEXT="l&#xe4;uft darauf hinaus, den BufferStore anders zu interpretieren">
<linktarget COLOR="#3c2aa0" DESTINATION="ID_1447615516" ENDARROW="Default" ENDINCLINATION="140;-650;" ID="Arrow_ID_502898501" SOURCE="ID_229835695" STARTARROW="None" STARTINCLINATION="1105;47;"/>
</node>
<node CREATED="1773872076215" ID="ID_1282818549" MODIFIED="1773872198594" TEXT="mein Ansatz war bis jetzt: das ist &#xbb;ein Dummy&#xab; mit Diagnose-Callbacks"/>
<node CREATED="1773872206676" ID="ID_1935549810" MODIFIED="1773872220263" TEXT="tats&#xe4;chlich replizieren die Callbacks aber das BufferStore-API"/>
<node CREATED="1773875187789" ID="ID_666457397" MODIFIED="1773875217280" TEXT="unn&#xf6;tige Indirektion und Komplexit&#xe4;t &#x27f9; brauche keinen BufferProxyAdaptor">
<node CREATED="1773875219107" ID="ID_1212468658" MODIFIED="1773875229312" TEXT="ich kann den ja f&#xfc;r andere Zwecke mal bestehen lassen"/>
<node CREATED="1773875230170" ID="ID_1330974689" MODIFIED="1773875244308" TEXT="er war wichtig, um den Mismatch in den APIs zu erkennen und dann zu beheben"/>
<node CREATED="1773875245243" ID="ID_1639451717" MODIFIED="1773875299381" TEXT="die dort erstmals verwendeten generierten Datentypen sind anderweitig n&#xfc;tzlich"/>
<node CREATED="1773875306072" ID="ID_1024103205" MODIFIED="1773875342503" TEXT="was wir brauchen: eine direkt an AllocState delegierende BufferStore-Implementierung"/>
</node>
</node>
</node>
</node> </node>
</node> </node>
</node> </node>