diff --git a/src/vault/mem/simple-buffer-state-registry.hpp b/src/vault/mem/simple-buffer-state-registry.hpp index 07b1ed975..610755ca3 100644 --- a/src/vault/mem/simple-buffer-state-registry.hpp +++ b/src/vault/mem/simple-buffer-state-registry.hpp @@ -75,7 +75,7 @@ namespace mem { mark_locked (ID typeKey, BuffAlloc alloc) override { auto& [storage,size,implMark] = alloc; - return metadata_.markLocked (typeKey, storage, implMark); + return metadata_.markLocked (typeKey, storage, size, implMark); } ID diff --git a/src/vault/out/output-slot-connection.hpp b/src/vault/out/output-slot-connection.hpp index a5aafd60f..5b344f4ac 100644 --- a/src/vault/out/output-slot-connection.hpp +++ b/src/vault/out/output-slot-connection.hpp @@ -147,6 +147,12 @@ namespace out { UNIMPLEMENTED ("a lot regarding the Timings is still not clear..."); //////////////////////////TICKET #831 } + OpenedSinks + connect (PAlloc& lifecycleManager) override + { + /////////////////OOO use an iterExplorer-Pipeline to create the functor closure here + } + /* == Callback API for BufferProxy == */ diff --git a/src/vault/out/output-slot.cpp b/src/vault/out/output-slot.cpp index 2e60c619b..f60b6559e 100644 --- a/src/vault/out/output-slot.cpp +++ b/src/vault/out/output-slot.cpp @@ -48,7 +48,7 @@ namespace out { * type of the PImpl, i.e. the Allocation subclass passed in as argument. The OutputSlot, * together with the DataSink handles, will take shared ownership of the connection setup. */ - shared_ptr + OutputSlot::PAlloc OutputSlot::connect (unique_ptr allocation) { REQUIRE (allocation); @@ -74,14 +74,15 @@ namespace out { OutputSlot::OpenedSinks OutputSlot::getOpenedSinks() { - + REQUIRE (alloc_); + return alloc_->connect (alloc_); } Timings OutputSlot::timingConstraints() { - REQUIRE(alloc_); + REQUIRE (alloc_); return alloc_->getTimings(); } diff --git a/src/vault/out/output-slot.hpp b/src/vault/out/output-slot.hpp index 38256edc7..2806238cb 100644 --- a/src/vault/out/output-slot.hpp +++ b/src/vault/out/output-slot.hpp @@ -85,7 +85,6 @@ namespace out { using vault::mem::BuffHandle; using lib::time::FrameCnt; - using std::shared_ptr; using std::unique_ptr; @@ -160,23 +159,31 @@ namespace out { protected: /** @internal interface for the allocated state */ + class Allocation; + using PAlloc = std::shared_ptr; + + PAlloc alloc_; + class Allocation { public: virtual ~Allocation(); ///< this is an interface - virtual void release() =0; - virtual Timings getTimings() =0; + virtual void release() =0; + virtual Timings getTimings() =0; + virtual OpenedSinks connect (PAlloc&) =0; }; - /** active connections through this OutputSlot */ + PAlloc + connect (unique_ptr); + + /** + * @internal Implementation subclass: + * manage active connections through this OutputSlot + */ template class AllocState; - shared_ptr alloc_; - - shared_ptr - connect (unique_ptr); public: /** by default marked as inactive/defunct */ @@ -194,5 +201,5 @@ namespace out { }; -}} // namespace vault::out +}}// namespace vault::out #endif /*VAULT_OUT_OUTPUT_SLOT_H*/ diff --git a/wiki/renderengine.html b/wiki/renderengine.html index ade7ee57b..a6ef79898 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -5214,11 +5214,11 @@ Thus there are two serious problem situations &rarr; OutputSlotImpl -
+
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
-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 (&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 full screen, 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)
 
 !design possibilities
 !!properties as a starting point
@@ -5237,10 +5237,10 @@ Data is handed over by the client invoking an {{{emit(time,...)}}} function on t
 ;buffer handover model
 :the client owns the data buffer and cares for allocation and de-allocation. The {{{emit()}}}-call just propagates a pointer to the buffer holding the data ready for output. The output slot implementation in turn has the liability to copy or otherwise use this data within a given time limit.
 ;shared buffer model
-: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 signals 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
-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, &raquo;latency&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.
+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, »latency« 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 frame rate 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.
 
 !!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.
@@ -5278,7 +5278,7 @@ The planned delivery time of a frame, or (equivalently) the frame-number on a we
 &rarr; OutputSlotImpl
 
 -----
-!!! Delegation and state managment
+!!! Delegation and state management
 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//
@@ -5286,17 +5286,23 @@ However, the general structure of a BufferProvider was elaborated to quite some
 * 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}}}
+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 implementation of {{{BufferProvider::BufferStore}}}, such that
+* a suitable access path to the given connection / data feed is embedded into a {{{LocalTag}}} in the metadata
 * 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<CON>}}} 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.
 
------
 !!! Establish a proper Lifecycle
-While the original design for OutputSlot seems //elegant, on a theoretical level,// the implementation was constantly plagued with complexities related to the lifecycle. Some relevant aspects could not be settled at all -- which was not noticed until the recent attempts to integrate the OutputSlot into the (likewise reworked) Render Engine implementation. A turning point in all this evolution of design is to remove the //state abstraction// -- in favour of representing the progression of state directly as a sequence of context data frames. The deeper change, which motivated this shift, was to abandon the idea of a single, global, overarching and final scheme of »Rendering«. It is acknowledged rather that the actual rendering for some unspecified »rendering« will be done by an external library -- and each such library establishes its own terminology, processing scheme and conventions, which finally are rooted in some specific ''Domain Onotology''. The Lumiera Render Engine is now understood as a connected graph of //adaptors//, organised around some minimal common denominator, like the fact that data frames are calculated, and that each such frame can be addressed by a media type and a point in time.
+While the original design for OutputSlot seems //elegant, on a theoretical level,// the implementation was constantly plagued with complexities related to the lifecycle. Some relevant aspects could not be settled at all -- which was not noticed until the recent attempts to integrate the OutputSlot into the (likewise reworked) Render Engine implementation. A turning point in all this evolution of design is to remove the //state abstraction// -- in favour of representing the progression of state directly as a sequence of context data frames. The deeper change, which motivated this shift, was to abandon the idea of a single, global, overarching and final scheme of »Rendering«. It is acknowledged rather that the actual rendering for some unspecified »rendering« will be done by an external library -- and each such library establishes its own terminology, processing scheme and conventions, which finally are rooted in some specific ''Domain Ontology''. The Lumiera Render Engine is now understood as a connected graph of //adaptors//, organised around some minimal common denominator, like the fact that data frames are calculated, and that each such frame can be addressed by a media type and a point in time.
 
 In line with this shift in focus, the OutputSlot is seen as yet another element of representational state: it is a token to embody an allocated output connection. This implies to unify the notion of the {{{OutputSlot}}} with the {{{Allocation}}}, and turn both into a handle passed to the client process. All aspects of lifecycle can be managed automatically with such ref-counting handles. This has some cost in terms of performance (thread contention when accessing the atomic ref count), but that is deemed acceptable, since output is handled on a rather slow time scale, like producing a data frame every 10 milliseconds. The specific output mechanism remains »allocated« thus as long as some element of the client processing still holds onto any such handle, either the {{{OutputSlot}}} or some {{{DataSink}}}. The latter is reduced into a //named function//, which however also embeds a shared handle to the common allocation state. With this rearrangement in the design, there is now a cohesive domain of connection management, while the actual, technical connection is separated out and packaged behind the {{{Connection}}} interface.
+
+!!! How actually to arrange the delegation
+Taken together, this implies that the {{{OutputSlot::AllocState<CON>}}} 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. Yet this does not imply to route every actual call through the {{{AllocState}}} -- while this is possible, and could be characterised as the naive solution, it would lead to several delegation chains, with a set of delegation methods at each intermediary step. Furthermore, it would require to wire a back-reference to this {{{AllocState}}} through the proxy-{{{BufferProvider}}}.
+
+Since the entrance point to each buffer usage cycle is the functor in the {{{DataSink}}}, a direct call-path can be established. For one, the C++ standard library offers an [[aliasing-constructor|https://cppreference.com/w/cpp/memory/shared_ptr/shared_ptr.html]]; this is a rather obscure feature, yet allows to setup a subordinate shared-ptr, that participates in the lifecycle management (ref-count) of the main pointer, while pointing at some //alias object//, which typically is a subclass, sub-object or otherwise a part of the automatically managed compound. In the case at hand here, a shared-pointer //to the individual connection// can be established within the λ-capture. Furthermore, the {{{OutputBufferProxy}}} (the proxy-{{{BufferProvider}}}) can be constructed such as to delegate //directly to that {{{Connection}}}.//
+
+And this raises another intricate question of design: //shall {{{Connection}}} be a ''concept''?//
+This would be a very elegant, and modern solution, offering much flexibility for the Implementer of said {{{Connection}}}. The {{{OutputBufferProxy}}} could be templated to that {{{Connection}}} type and thus allow the optimiser to inline and reduce the callback functionality. However -- C++ concepts are a rather new and arcane feature, as they rely on very special syntax and semantics that still requires some time in practical use to catch on and prove its viability. A further objection that can be raised is the fact that compliance to a concept leaves no traces within the implementation -- quite contrary to a classical ~OO-style interface, where the implementation is very obviously marked as such. These considerations bring me to the decision to //reject the use of a concept// here, and to rely on a conventional interface for {{{Connection}}}. Notably it can be expected that new kinds of connection will be added frequently, and so the task for a developer not familiar with all fine points of this design should be reasonably straight forward: „You have to implement the {{{Connection}}} interface“
 
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 278722590..ec683524d 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -114711,7 +114711,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -114956,6 +114956,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + @@ -118997,8 +118998,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -138294,7 +138295,12 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + + + + + @@ -138363,9 +138369,47 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + + + + + + + + +

+ ...das ist eine absehbare, zukünftige Anforderung — für den Frame-Cache; letztlich geht es darum, sparsam mit der Storage für Job-Parameter umzugehen, da wir sehr viele Jobs unter hohem Druck durch das System schicken. Das bedeutet, alle Paramter, die nicht beweglich sind (sich pro Frame ändern), müssen möglichst zusammengefaßt repräsentiert werden. Die DataSink muß dem entsprechend auch alles weitere repräsentieren, was sich hinter eine bestehende Output-Connection an Metadaten hängen läßt, und was für den Cache-Key relevant wird +

+ +
+
+ + + + + + +

+ Diese Funktionalität hier, die Ausgabe von Frames, liegt zweifellost bereits weit in der Zone, in der Premature Optimisation ein echtes Problem ist: Selbst wenn etwas alle 5 Millisekunden einmal passiert, sind +100µs praktisch nicht feststellbar; die absolute Schmerzgrenze für Optimierungen in einer solchen Lage wäre, wenn man dadurch eine ganze Millisekunde herausholen könnte, und zudem die Operation unter Druck und permanent abläuft! +

+

+
Schon erstaunlich, daß es so schwer ist, dieser Einsicht gemäß zu handeln... +

+ +
+ + + + + +
+ + + + +
- @@ -138412,11 +138456,14 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + - + + + @@ -138425,10 +138472,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + - + @@ -138439,18 +138486,210 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + + + + + + + + + + + + + + + + + + + - - + + + + + + +

+ ...das ist ein wenig bekanntes (aber bisweilen nützliches) Feature der Standardlib: man kann einen Alias-Pointer an den gleichen shared-State ankoppeln; wohin dieser Alias zeigt, ist komplett beliebig (muß aber während der ganzen Lebensdauer existieren, denn dieser Zielort wird nicht eigens gemanaged). +

+

+ Hier könnten wir dieses Feature nutzen, um direkt auf die jeweilige Connection zu zeigen, oder (alternativ) auf den OutputBufferProxy den konkret getypten AllocState (Subklasse) +

+ +
+
+ + + + +

+ in dem stecken zwei Informationen: +

+
    +
  • + ein Pointer auf den OutputBufferProxy (Buffer-Provider) +
  • +
  • + eine vorbereitete Typ-Info, die die Buffer-Size, aber auch das LocalTag enthält, über das die jeweils zuständige Connection angesteuert wird +
  • +
+ +
+ + + + + + + + + + + + +

+ Das wäre dann auch der tatsächliche Empfänger, der letztlich die Entscheidungen trifft — das wäre sauberes Design +

+ +
+ +
+ + + + +

+ Das wäre kohäsives Design — würde aber eine zusätzliche Indirektion durch die Connection-VTable einsparen ⟹ damit wird das Connection-Interface praktisch wertlos; sinnvollerweise müßte man es durch ein Concept ersetzen +

+ +
+ +
+ + + + +

+ Das wäre die naheliegende (und dämliche) Lösung — insofern spricht für eine solche Lösung daß sie nicht viel »Grips« verlangt; man stiefelt sich von Delegation zu Delegation. Das ist leicht zu schreiben, und einigermaßen gut lesbar, wenn die Delegationen gut benannt sind. Insofern setzt diese Lösung den Baseline-Standard  +

+ +
+ +
+
+ + + + + + + + +

+ weil es sich um eine sehr zentrale Struktur handelt, welche erwartungsgemäß auch viel in Zukunft erweitert und angepaßt werden muß +

+ +
+
+ + + + +

+ Es fällt dadurch nämlich eine weitere, generische Indirektion weg, die dann auch nicht mehr trickreich durch drei Konstruktoren durchgefädelt werden muß +

+ +
+
+ + + + +

+ Konkrete Connection + Concept wäre saugeil +

+ +
+ + + + + +

+ zumindest in der BufferStore-Implementierung; ein cleverer Optimiser kann u.U. sogar die monomorphic optimisation machen? (fraglich, denn der Aufrufer hat ja nur ein BuffHandler. Wenngleich auch kontextuelll klar ist, daß dieses BuffHandle von einer ganz bestimmten Implementierung stammen muß; fragt sich, wie lange es noch dauert, bis Optimiser sowas auch „sehen“ order gar beweisen können, und ob sich dafür der Aufwand lohnt) +

+ +
+
+ + + + + + +

+ Ich bin skeptisch: für wen ist das »lesbar«? +

+ +
+ + + +

+ Concepts sind ein relativ komplexes Gebilde, und sie haben eine spezielle Notierung, die in der Form nur in C++ gegeben ist. Nur ein Bruchteil der C++ - Entwickler wir mit Konzepten vertraut werden (auf lange Zeit). Zudem hinterläßt ein Konzept in der Implementierung keine Spuren. Man könnte sich da zwar etwas einfallen lassen, aber das wäre dann noch mehr ein Spezial-Konstrukt. Das ist etwas, was sich erst noch herausbilden muß (auch die Tool-Unterstützung ist ein Aspekt). +

+

+ Demgegenüber ist ein klassisches OO-Interface ein seit 30 Jahren universell bekanntes und weithin unterstütztes Schema, das in den allermeisten Sprachen in irgend einer Form eingesetzt wird (selbst wenn die Sprachen sonst nicht OO sind). Wenn ich einem durchschnittlichen Entwickler heute sage: „Du mußt das Interface Connection implementieren“, dann setzt der sich einfach hin und klopft das runter. +

+ +
+
+ + + + +
+
+ + + + + + + + +

+ Ja ich weiß, irgenwie will ich diese Idee — und kann dann immer kein gutes Argument dafür herbeizaubern. So auch in dem Fall; es ist nämlich hier trotzdem so, daß wir ein ganzes Tupel von Argumenten für jede einzelne Connection aufsammeln müssen. Ein Type-Refinement würde beduten, zwei dieser Argumente zusammen in einem generischen Buffer-Descriptor „zu verstecken“ (nämlich den Link auf den OutputBufferProxy, sowie die Buffer-Size). Deshalb bleiben trotzdem weitere Argumente übrig, und auch für die Verständlichkeit ist es abträglich, wenn die Abhängigkeit auf den Proxy-BufferProvider verborgen wird +

+ +
+ + + + + + +
+
+ + + + + + + +
@@ -166204,6 +166443,20 @@ std::cout << tmpl.render({"what", "World"}) << s
+ + + + + + +

+ Im Zusammenhang mit der »Test-Chain-Load« (wo ich mich wieder einmal wunderbar im Design verlaufen hatte) habe ich ein »trojanisches Lambda« bauen müssen wollen. Dieser Programmiertrick funktioniert nur, wenn die Storage für das Capture noch »inline« liegt. Empirisch habe ich gefunden, daß ein char[16] oder ein unique-ptr noch inline liegt, aber ein shared-ptr einen Heap-Overflow bewirkt. +

+ +
+
+ +