diff --git a/doc/devel/uml/OutputSlot_components.svg b/doc/devel/uml/OutputSlot_components.svg index 98dca4bee..91077e005 100644 --- a/doc/devel/uml/OutputSlot_components.svg +++ b/doc/devel/uml/OutputSlot_components.svg @@ -11,27 +11,20 @@ xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
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 (→ OutputManagement) @@ -5237,7 +5237,7 @@ Thus there are two serious problem situations → 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
@@ -5272,7 +5272,8 @@ Besides the sink handles, allocation of an output slot defines some timing const
{{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.
* 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
+* 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. The latter is also responsible for providing the actual data buffer, which is deemed an implementation detail.
+* The {{{DataSink}}} handle obtained for each data feed is the //actual access point// for clients to deliver data for output; in this case it seems adequate to rely on a ref-count, in order to ensure that the output configuration is available as long as any client processing is underway
-----
!Implementation / design problems
@@ -5317,18 +5318,16 @@ So the problem is that BufferProvider is conceived as a central service facility
!!! 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 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.
+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 a copyable value, which however also embeds a shared handle to the common allocation state. From a given {{{DataSink}}}, it is thus possible to start the computation cycle for each frame within the individual data feed of the corresponding {{{Connection}}}. 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}}}.//
+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}}} -- in fact it is sufficient to forward the buffer lifecycle calls directly to the //corresponding connection//, which is implicitly available through the {{{LocalTag}}} in the buffer type. Besides that, this buffer type (which is a hash), together with the identity of the reference counting handle, constitutes an //identity of the {{{DataSink}}}, so that equality comparisons can be supported.
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“
OutputSlot is an abstraction, allowing unified treatment of various physical output connections from within the render jobs.
Each distinct output capability must be registered through an OutputManager. When a client wants to output data, it obtains a suitable OutputSlot through the OutputDirector. This OutputSlot is created and initialised properly for this single use by the {{{OutputManager}}} in charge for that kind of output capability; notably it must be outfitted with an actual implementation of the {{{OutputSlot::Allocation}}} interface, and typically {{{OutputSlot::AllocState<CON>}}} will be used for that purpose.
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index f0f327d7a..649116ea1 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -90478,7 +90478,7 @@ Date: Thu Apr 20 18:53:17 2023 +0200
-
+
@@ -90497,11 +90497,12 @@ Date: Thu Apr 20 18:53:17 2023 +0200