diff --git a/src/steam/engine/node-wiring-builder.hpp b/src/steam/engine/node-wiring-builder.hpp index c3bc81b71..bd47cd649 100644 --- a/src/steam/engine/node-wiring-builder.hpp +++ b/src/steam/engine/node-wiring-builder.hpp @@ -45,11 +45,14 @@ #include "steam/engine/proc-node.hpp" #include "lib/nocopy.hpp" +#include namespace steam { namespace engine { + using std::move; + /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1367 : Rebuild the Node Invocation class NodeWiringBuilder @@ -57,6 +60,28 @@ namespace engine { { public: + NodeWiringBuilder + inSlots (uint s) + { + UNIMPLEMENTED ("define number of predecessor-source slots"); + return move(*this); + } + + NodeWiringBuilder + outSlots (uint r) + { + UNIMPLEMENTED ("define number of result slots"); + return move(*this); + } + + template + NodeWiringBuilder + createBuffers (ARGS&& ...args) + { + UNIMPLEMENTED ("define builder for all buffers to use"); + return move(*this); + } + /****************************************************//** * Terminal: complete the Connectivity defined thus far. */ diff --git a/src/steam/streamtype.hpp b/src/steam/streamtype.hpp index aefcaa69c..83788cd79 100644 --- a/src/steam/streamtype.hpp +++ b/src/steam/streamtype.hpp @@ -67,7 +67,7 @@ namespace steam { RAW, SOURCE, TARGET, - INTERMEDIARY + TRANSIENT }; struct Prototype; diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 7b6c7dbe3..daf542246 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -1250,20 +1250,20 @@ Beyond that, it can be necessary to associate at least a state flag with //indiv __Note__: while the API to access this service is uniform, conceptually there is a difference between just using the (shared) type information and associating individual metadata, like the buffer state. Type-~IDs, once allocated, will never be discarded (within the lifetime of an Lumiera application instance -- buffer associations aren't persistent). To the contrary, individual metadata //will be discarded,// when releasing the corresponding buffer. According to the ''prototype pattern'', individual metadata is treated as a one-way-off specialisation. -
+
It turns out that --  throughout the render engine implementation -- we never need direct access to the buffers holding actual media data. Buffers are just some entity to be //managed,// i.e. "allocated", "locked" and "released"; the //actual meaning of these operations can be left to the implementation.// The code within the render engine just pushes around ''smart-prt like handles''. These [[buffer handles|BuffHandle]] act as a front-end, being created by and linked to a buffer provider implementation. There is no need to manage the lifecycle of buffers automatically, because the use of buffers is embedded into the render calculation cycle, which follows a rather strict protocol anyway. Relying on the [[capabilities of the scheduler|SchedulerRequirements]], the sequence of individual jobs in the engine ensures...
 * that the availability of a buffer was ensured prior to planning a job ("buffer allocation")
 * that a buffer handle was obtained ("locked") prior to any operation requiring a buffer
 * that buffers are marked as free ("released") after doing the actual calculations.
 
 !operations
-While BufferProvider is an interface meant to be backed by various different kinds of buffer and memory management approaches, there is a common set of operations to be supported by any of them
+While BufferProvider is an interface meant to be backed by various and diverse kinds of buffer and memory management approaches, there is a common set of operations to be supported by any of them
 ;announcing
 :client code may announce beforehand that it expects to get a certain amount of buffers. Usually this causes some allocations to happen right away, or it might trigger similar mechanisms to ensure availability; the BufferProvider will then return the actual number of buffers guaranteed to be available. This announcing step is optional an can happen any time before or even after using the buffers and it can be repeated with different values to adjust to changing requirements. Thus the announced amount of buffers always denotes //additional buffers,// on top of what is actively used at the moment. This safety margin of available buffers usually is accounted separately for each distinct kind of buffer (buffer type). There is no tracking as to which specific client requested buffers, beyond the buffer type.
 ;locking
-:this operation actually makes a buffer available for a specific client and returns a [[buffer handle|BuffHandle]]. The corresponding buffer is marked as used and can't be locked again unless released. If necessary, at that point the BufferProvider might allocate memory to accommodate (especially when the buffers weren't announced beforehand). The locking may fail and raise an exception. You may expect failure to be unlikely when buffers have been //announced beforehand.// To support additional sanity checks, the client may provide a token-ID with the lock-operation. This token may be retrieved later and it may be used to ensure the buffer is actually locked for //this token.//
+:this operation actually makes a buffer available for a specific client and returns a [[buffer handle|BuffHandle]]. The corresponding buffer is marked as used and can't be locked again unless released. If necessary, at that point the BufferProvider might allocate memory to accommodate (especially when the buffers weren't announced beforehand). The locking may fail and raise an exception. Such a failure will be unlikely when buffers have been //announced beforehand.// To support additional sanity checks, the client may provide a token-ID with the lock-operation. This token may be retrieved later and it may be used to ensure the buffer is actually locked for //this token.//
 ;attaching
-:optionally the client may attach an object to a locked buffer. This object is placement-constructed into the buffer and will be destroyed automatically when releasing the buffer. Alternatively, the client may provide a pair of constructor- / destructor-functors, to be invoked in a similar way. This allows e.g. to install descriptor structures within the buffer, as required by an external library.
+:optionally the client may attach an object to a locked buffer. This object is placement-constructed into the buffer and will be destroyed automatically when releasing the buffer. Alternatively, the client may provide a pair of constructor- / destructor-functors, to be invoked in a similar way. This allows e.g. to install descriptor structures within the buffer, as required by an external media handling library.
 ;emitting
 :the client //may optionally mark a state transition// -- whose precise meaning remains implicit and implementation dependent. From the client's perspective, emitting and releasing may seem equivalent, since the buffer should not be touched after that point. However, conceivably there are usages where it matters for //the consumer// to be sure an expected result was actually achieved, since the producer may well acquire the buffer and then fail to complete the required work, prompting some clean-up safety mechanism to merely release the resources.
 ;releasing
@@ -1279,8 +1279,10 @@ __see also__
 → RenderMechanics for details on the buffer management within the node invocation for a single render step
 
-
-
The invocation of individual [[render nodes|ProcNode]] uses an ''buffer table'' internal helper data structure to encapsulate technical details of the allocation, use, re-use and feeing of data buffers for the media calculations. Here, the management of the physical data buffers is delegated through a BufferProvider, which typically is implemented relying on the ''frame cache'' in the Vault. Yet some partially quite involved technical details need to be settled for each invocation: We need input buffers, maybe provided as external input, while in other cases to be filled by a recursive call. We need storage to prepare the (possibly automated) parameters, and finally we need a set of output buffers. All of these buffers and parameters need to be rearranged for invoking the (external) processing function, followed by releasing the input buffers and commiting the output buffers to be used as result.
+
+
{{red{⚠ In-depth rework underway as of 7/2024...}}}
+^^┅┅┅┅┅┅the following text is ''superseded''┅┅┅┅┅┅┅┅┅^^
+The invocation of individual [[render nodes|ProcNode]] uses an ''buffer table'' internal helper data structure to encapsulate technical details of the allocation, use, re-use and feeing of data buffers for the media calculations. Here, the management of the physical data buffers is delegated through a BufferProvider, which typically is implemented relying on the ''frame cache'' in the Vault. Yet some partially quite involved technical details need to be settled for each invocation: We need input buffers, maybe provided as external input, while in other cases to be filled by a recursive call. We need storage to prepare the (possibly automated) parameters, and finally we need a set of output buffers. All of these buffers and parameters need to be rearranged for invoking the (external) processing function, followed by releasing the input buffers and commiting the output buffers to be used as result.
 
 Because there are several flavours of node wiring, the building blocks comprising such a node invocation will be combined depending on the circumstances. Performing all these various steps is indeed the core concern of the render node -- with the help of BufferTable to deal with the repetitive, tedious and technical details.
 
@@ -4808,8 +4810,10 @@ Moreover, the design of coordinate matching and resolving incurs a structure sim
 In the most general case the render network may be just a DAG (not just a tree). Especially, multiple exit points may lead down to the same node, and following each of this possible paths the node may be at a different depth on each. This rules out a simple counter starting from the exit level, leaving us with the possibility of either employing a rather convoluted addressing scheme or using arbitrary ID numbers.{{red{...which is what we do for now}}}
 
-
-
The [[nodes|ProcNode]] are wired to form a "Directed Acyclic Graph"; each node knows its predecessor(s), but not its successor(s).  The RenderProcess is organized according to the ''pull principle'', thus we find an operation {{{pull()}}} at the core of this process. Meaning that there isn't a central entity to invoke nodes consecutively. Rather, the nodes themselves contain the detailed knowledge regarding prerequisites, so the calculation plan is worked out recursively. Yet still there are some prerequisite resources to be made available for any calculation to happen. So the actual calculation is broken down into atomic chunks of work, resulting in a 2-phase invocation whenever "pulling" a node. For this to work, we need the nodes to adhere to a specific protocol:
+
+
{{red{⚠ In-depth rework underway as of 7/2024...}}}
+^^┅┅┅┅┅┅the following text is ''superseded''┅┅┅┅┅┅┅┅┅^^
+The [[nodes|ProcNode]] are wired to form a "Directed Acyclic Graph"; each node knows its predecessor(s), but not its successor(s).  The RenderProcess is organized according to the ''pull principle'', thus we find an operation {{{pull()}}} at the core of this process. Meaning that there isn't a central entity to invoke nodes consecutively. Rather, the nodes themselves contain the detailed knowledge regarding prerequisites, so the calculation plan is worked out recursively. Yet still there are some prerequisite resources to be made available for any calculation to happen. So the actual calculation is broken down into atomic chunks of work, resulting in a 2-phase invocation whenever "pulling" a node. For this to work, we need the nodes to adhere to a specific protocol:
 ;planning phase
 :when a node invocation is foreseeable to be required for getting a specific frame for a specific nominal and actual time, the engine has to find out the actual operations to happen
 :# the planning is initiated by issuing an "get me output" request, finally resulting in a JobTicket
@@ -6387,8 +6391,10 @@ In 2018, the middle Layer was renamed into → Steam-Layer
 
 
-
+
A data processing node within the Render Engine. Its key feature is the possibility to pull from it one (freely addressable) [[Frame]] of calculated data. Further, each ~ProcNode has the ability to be wired with other nodes and [[Parameter Providers|ParamProvider]]
+{{red{⚠ In-depth rework underway as of 7/2024...}}}
+^^┅┅┅┅┅┅the following text is ''superseded''┅┅┅┅┅┅┅┅┅^^
 
 !! {{red{open questions}}}
 * how to address a node
@@ -6939,8 +6945,10 @@ Currently the Render/Playback is beeing targetted for implementation; almost eve
 [img[Entities comprising the Render Engine|uml/fig128389.png]]
 
-
-
Below are some notes regarding details of the actual implementation of the render process and processing node operation. In the description of the [[render node operation protocol|NodeOperationProtocol]] and the [[mechanics of the render process|RenderMechanics]], these details were left out deliberately.
+
+
{{red{⚠ In-depth rework underway as of 7/2024...}}}
+^^┅┅┅┅┅┅the following text is ''superseded''┅┅┅┅┅┅┅┅┅^^
+Below are some notes regarding details of the actual implementation of the render process and processing node operation. In the description of the [[render node operation protocol|NodeOperationProtocol]] and the [[mechanics of the render process|RenderMechanics]], these details were left out deliberately.
 {{red{WIP as of 9/11 -- need to mention the planning phase more explicitly}}}
 
 !Layered structure of State
@@ -7002,8 +7010,10 @@ Prerequisite data for the media calculations can be considered just part of that
 * all it needs to know is the ''effective nominal time'' and an ''invocation instance ID''
 
-
-
While the render process, with respect to the dependencies, the builder and the processing function is sufficiently characterized by referring to the ''pull principle'' and by defining a [[protocol|NodeOperationProtocol]] each node has to adhere to — for actually get it coded we have to care for some important details, especially //how to manage the buffers.// It may well be that the length of the code path necessary to invoke the individual processing functions is finally not so important, compared with the time spent at the inner pixel loop within these functions. But my guess is (as of 5/08), that the overall number of data moving and copying operations //will be//  of importance.
+
+
{{red{⚠ In-depth rework underway as of 7/2024...}}}
+^^┅┅┅┅┅┅the following text is ''superseded''┅┅┅┅┅┅┅┅┅^^
+While the render process, with respect to the dependencies, the builder and the processing function is sufficiently characterized by referring to the ''pull principle'' and by defining a [[protocol|NodeOperationProtocol]] each node has to adhere to — for actually get it coded we have to care for some important details, especially //how to manage the buffers.// It may well be that the length of the code path necessary to invoke the individual processing functions is finally not so important, compared with the time spent at the inner pixel loop within these functions. But my guess is (as of 5/08), that the overall number of data moving and copying operations //will be//  of importance.
 {{red{WIP as of 9/11 -- need to mention the planning phase more explicitly}}}
 
 !requirements
@@ -7144,8 +7154,10 @@ An Activity is //performed// by invoking its {{{activate(now, ctx)}}} function -
 In a similar vein, also ''dependency notifications'' need to happen decoupled from the activity chain from which they originate; thus the Post-mechanism is also used for dispatching notifications. Yet notifications are to be treated specially, since they are directed towards a receiver, which in the standard case is a {{{GATE}}}-Activity and will respond by //decrementing its internal latch.// Consequently, notifications will be sent through the ''λ-post'' -- which operationally re-schedules a continuation as a follow-up job. Receiving such a notification may cause the Gate to become opened; in this case the trigger leads to //activation of the chain// hooked behind the Gate, which at some point typically enters into another calculation job. Otherwise, if the latch (in the Gate) is already zero (or the deadline has passed), nothing happens. Thus the implementation of state transition logic ensures the chain behind a Gate can only be //activated once.//
 
-
-
For each segment (of the effective timeline), there is a Processor holding the exit node(s) of a processing network, which is a "Directed Acyclic Graph" of small, preconfigured, stateless [[processing nodes|ProcNode]]. This network is operated according to the ''pull principle'', meaning that the rendering is just initiated by "pulling" output from the exit node, causing a cascade of recursive downcalls or prerequisite calculations to be scheduled as individual [[jobs|RenderJob]]. Each node knows its predecessor(s), thus the necessary input can be pulled from there. Consequently, there is no centralized "engine object" which may invoke nodes iteratively or table driven — rather, the rendering can be seen as a passive service provided for the Vault, which may pull from the exit nodes at any time, in any order (?), and possibly multithreaded.
+
+
{{red{⚠ In-depth rework underway as of 7/2024...}}}
+^^┅┅┅┅┅┅the following text is ''superseded''┅┅┅┅┅┅┅┅┅^^
+For each segment (of the effective timeline), there is a Processor holding the exit node(s) of a processing network, which is a "Directed Acyclic Graph" of small, preconfigured, stateless [[processing nodes|ProcNode]]. This network is operated according to the ''pull principle'', meaning that the rendering is just initiated by "pulling" output from the exit node, causing a cascade of recursive downcalls or prerequisite calculations to be scheduled as individual [[jobs|RenderJob]]. Each node knows its predecessor(s), thus the necessary input can be pulled from there. Consequently, there is no centralized "engine object" which may invoke nodes iteratively or table driven — rather, the rendering can be seen as a passive service provided for the Vault, which may pull from the exit nodes at any time, in any order (?), and possibly multithreaded.
 All State necessary for a given calculation process is encapsulated and accessible by a StateProxy object, which can be seen as the representation of "the process". At the same time, this proxy provides the buffers holding data to be processed and acts as a gateway to the Vault to handle the communication with the Cache. In addition to this //top-level State,// each calculation step includes a small [[state adapter object|StateAdapter]] (stack allocated), which is pre-configured by the builder and serves the purpose to isolate the processing function from the detals of buffer management.
 
 
@@ -11180,8 +11192,10 @@ On a more global level, this LowLevelModel within the engine exposes a number of
 !Control connections
 
-
-
Each [[processing node|ProcNode]] contains a stateless ({{{const}}}) descriptor detailing the inputs, outputs and predecessors. Moreover, this descriptor contains the configuration of the call sequence yielding the »data pulled from predecessor(s)«. The actual type of this object is composed out of several building blocks (policy classes) and placed by the builder as a template parameter on the WiringDescriptor of the individual ProcNode. This happens in the WiringFactory in file {{{nodewiring.cpp}}}, which consequently contains all the possible combinations (pre)generated at compile time.
+
+
{{red{⚠ In-depth rework underway as of 7/2024...}}}
+^^┅┅┅┅┅┅the following text is ''superseded''┅┅┅┅┅┅┅┅┅^^
+Each [[processing node|ProcNode]] contains a stateless ({{{const}}}) descriptor detailing the inputs, outputs and predecessors. Moreover, this descriptor contains the configuration of the call sequence yielding the »data pulled from predecessor(s)«. The actual type of this object is composed out of several building blocks (policy classes) and placed by the builder as a template parameter on the WiringDescriptor of the individual ProcNode. This happens in the WiringFactory in file {{{nodewiring.cpp}}}, which consequently contains all the possible combinations (pre)generated at compile time.
 
 !building blocks
 * ''Caching'': whether the result frames of this processing step will be communicated to the Cache and thus could be fetched from there instead of actually calculating them.
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 39e14e3c9..2961b30dc 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -68346,6 +68346,57 @@
 
 
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  
+  
+    

+ ...man muß also abstrakte Einstiegspunkte für einige vordefinierte Aktions- und Themenkomplexe schaffen; innerhalb des Callbacks kann sich dann eine Library in ihrer eigenen Domain-Ontology bewegen +

+ +
+
+
+
+
+
+
+ + + + + + + @@ -81553,16 +81604,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

dieser speichert einen HashVal subClassification_

- -
+
@@ -81572,16 +81620,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

siehe type-handler.hpp

- -
+
@@ -81591,8 +81636,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
dieser zielt darauf ab, Strukturen in den Buffer zu pflanzen

- -
+
@@ -81609,9 +81653,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

und dieser gehört zu einer DomainOntology @@ -81640,16 +81682,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Konsequenz ⟹ die Spec legt fest was in den Buffer gelegt wird

- -
+ @@ -81657,9 +81696,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

leider setzt das bereits die Domain-Ontology vorraus @@ -81669,7 +81706,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -81679,22 +81716,28 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...und zur Laufzeit wird nur noch ein Stück von diesem »was« angefordert

- -
+
+ + + + + + + + - + + @@ -81704,6 +81747,31 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + + + + + + + + + @@ -81713,6 +81781,21 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + +

+ Problem: wie kommt man von Layer-3 in Layer-2? +

+ + +
+ + +
+
+
@@ -85446,6 +85529,58 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + +

+ Der erste Entwurf von 2009 / 2012 baute im Grunde noch darauf auf, einen void*  etwas geschickter zu „verpacken“ — ich konnte mir Parametrisierung nicht anders vorstellen, als eine Ansammlung von Daten in einem Record; Type Erasure war damals für mich noch ein schwer zu fassendes Konzept, weil ich im Grunde die Idee von Typ-Kontext und higher kinded Types noch nicht wirklich erfaßt hatte. Ich sah zwar die Möglichkeit, mit Templates in einem »Baukasten-System« eine paßgenaue Aufruf-Mechanik zusammenzustellen, aber mein Entwurf lief sehr schnell gegen die „Schallmauer“ zwischen Laufzeit-Parametrisierung und Code-Generierung im Compiler. Zudem fehlte es mir noch an Phantasie, was man tatsächlich mit Templates anstellen kann, indem man eigenständige Code-Bausteine per Kontext aufgreift.... +

+ +
+
+ + + + +

+ Inzwischen habe ich verstanden, daß es im Kern um Ontology-Mapping  geht, und der Typ-Kontext hierfür die Implementierung bereitstellt. Das bedeutet, ich denke den Typ-Kontext nicht mehr als eine feste Struktur, die bestimmte Parameter abliefern muß. Das hat aber auch zur Konsequenz, daß ich explizit darauf verzichte, eine feste Struktur oder gar eine Meta-Ontologie vorzugeben, auf der dann der Build-Prozeß als Interpreter läuft. Vielmehr muß bereits der Build-Prozeß auf einem hohen Level — nachdem der Render-Path grundsätzlich feststeht (und damit auch, welche Library die Arbeit machen muß) — alsbald in die Domäne eben dieser Library einsteigen und mit dem Ontology-Mapping interagieren, um das Baumuster der einzelnen Node festzulegen. Scheinbar wie zufällig entsteht genau dadurch für jede Library ein System von Code, welches exakt alle benötigten Code-Bausteine instantiiert und somit konkretisiert aufrufen kann. Ich brauche damit gar keine zentrale Registy von Invocation-Varianten mehr, und die Template-Instanzen liegen dann im Code des jewiligen Binding-Plug-ins für diese Library... +

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

+ dann sind unendlich viele Template-Instanzen die Folge +

+ + +
+
+
+ + + + + + + + + + @@ -85492,11 +85627,24 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - + + + + + + + + + + + + + + + + @@ -85550,11 +85698,11 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + @@ -85586,9 +85734,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+
- + @@ -85597,8 +85746,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -85661,7 +85810,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -85670,6 +85819,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

+
@@ -85681,6 +85831,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + @@ -85692,10 +85845,374 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Auf der Ebene der Implementierung ist die Domain-Ontology so etwas wie eine Typklasse — wie bildet man das in C++17 ab — und wie in C++20 ? +

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

+ das heißt, zur Aufruf-Zeit sind keine Meta-Informationen mehr notwendig, zum Zeitpunkt des Zusammenstellens der Render-Nodes dagegen sehr wohl +

+ +
+
+ + + + +

+ in der Tat berühren sich hier zwei Ebenen, und das könnte man für eine Trennung in verschiedene Domänen nutzen. Und zwar zum Einen die introspektive selbst-Darstellung für eine Media-Library und zum Anderen der Aufruf einer Processing-Function mit paßgenauen Argumenten; zu bedenken ist zudem, daß nicht jede Library auch eine Form von Discovery bietet — insofern kann diese Mapping- und Adapter-Schicht einiges an Fleißarbeit erfordern +

+ +
+ + + + +
    +
  • + der Builder bezieht Introspektion von der Adapter-Schicht +
  • +
  • + Antwort ist eine generische Meta-Beschreibung +
  • +
  • + diese wird vom Builder interpretiert +
  • +
  • + und es demzufolge werden direkte Bau-Instruktionen gegeben +
  • +
+ +
+
+ + + + +
    +
  • + der Builder triggert mit ID-Informationen den Binding-Layer an +
  • +
  • + dieser erzeugt daraufhin direkt die Ausführungs-Parameter +
  • +
  • + welche dann lediglich in die Nodes eingebettet werden +
  • +
+ +
+
+
+ + + + +

+ ...insofern ist das eindeutig die elegantere Lösung. Auf den ersten Blick mag die komplette Trennung „sauberer“ wirken, jedoch muß dazu eine Meta-Repräsentation geschaffen werden, die für jedwede Art von Medium gilt, und es muß dafür ein Interpreter im Builder bereitstehen. Da auf der anderen Seite ohnehin ein Adapter für jede Library geschrieben werden muß, besteht die Möglichkeit, für jede Library einen eigenständige Weg zu verwenden, um letztlich die Aufrufparameter auszugeben; das bedeutet, man kann in der jeweiligen Fach-Ontologie bleiben, und das Ontology-Mapping beschränkt sich auf eine Übersetzung in das sehr rudimentäre Muster von Node-Aufrufen, wie von der Render-Engine gefordert +

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

+ das hängt sehr stark davon ab, zu welchem Grad die jeweilige Media-Library überhaupt Flexibilität zuläßt. Im schlimmsten Fall nimmt die Library einen Dateinamen und liefert direkt eine Pipeline an, die in einer Library-internen Engine läuft. Eine etwas bessere Variante wäre, wenn die Library einen Filenamen (oder offenes File-Handle) akzeptiert und dann decodierte Daten in einer Folge von Buffern bereitstellt. Idealerweise kann man der Library einen Rohdatenblock geben, dann den Header interpretieren lassen, und daraufhin dann einen Teildatenblock an die LIbrary zum Decodieren geben. +

+ +
+
+ + + + + +

+ ⟹ wie viele Kanäle stehen bereit +

+

+ welches Buffer-Format (Größe) +

+

+ ggfs Metadaten-Adapter? +

+ +
+
+
+ + + + + + +

+ ...aber diese Info muß aus dem Library-Adapter bezogen werden können, und zwar schon viel früher, wenn das entspr. Processing-Asset erstellt wird; das kann allerdings auch eine dynamischen Rückruf in die LIbrary implizieren +

+ + +
+
+ + + + + +

+ das ist ein besonders kniffeliges Thema... +

+
    +
  • + welche der Eingabe-Buffer sind inplace? Anzahl, Position in der nominellen Ordnung? +
  • +
  • + die FeedManifold muß diese speziell behandeln und auf die Ausgabeseite durchmappen (wohin?) +
  • +
  • + besondere Komplikation: der Builder muß wissen, ob eine Node in-Place-fähig ist, muß das aber u.U auch noch auf sehr spezielle Weise für den Aufruf instruieren (besondere Funktions-Variante aufrufen, zusätzliche Flag-Parameter übergeben, spezielle Marker für die Buffer setzen) +
  • +
+ + +
+
+ + + + +

+ ein ganz besonders lästiges Thema: in der C-Welt gilt es als Feature, und nicht als Bug, wenn eine Library dem Aufrufer das Memory-Management abnimmt; Libraries sind in der Hinsicht oft unsäglich „kreativ“. Typischerweise bedeutet das, daß man sich dann an ein ganz spezielles Protokoll halten muß; in Lumiera würden wir so etwas als besonderen BufferProvider verkapseln, mit einem speziellen »Inlay-Typ«, der dann lifecycle-Events an die Library weitergibt +

+ + +
+
+ + + + +

+ Typ des Invocation-Adapters +

+ +
+ + + +

+ Das ist ein spezielles Konstrukt, das sich direkt aus dem Setup mit BufferProvider ergibt: man sieht einen speziellen Buffer-»slot« vor, in den ein Adapter-Typ inplantiert wird; dieser Adapter bekommt eine Referenz auf die FeedManifold und wird dann getriggert, was die eigentliche externe Berechnung in der Library bewirkt. Da dieser Typ maßgeschneidert ist, kann er ein zusätzliches Kontroll- und Informations-API bieten +

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

+ ...wenn ich will, daß das Ergebnis der Berechnung an der gewünschten Stelle anfällt. Falls das nicht geht, weil z.B. die Library selber den Speicher gemanaged hat, weil es sich um das Ergebnis eines Pipeline-Aufrufs handelt, dann muß das bereits vorher der Builder festgestellt haben, denn in dem Fall ist eine Adapter-Node notwendig, die das Ergebnis in meinen Zielpuffer umkopiert — oder zumindest muß die Ausgabe als spezieller BufferProvider verpackt werden (beispielsweise weil die Library mir einen von ihr gemanageten Buffer gibt, ich diesen aber an IO_URING weiterreichen möchte und dann folglich später asynchron die Freigabe an die Library zurücksignalisieren muß. +

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

+ ...was bedeutet, das ist ein Schema zur Vereinheitlichung +

+
    +
  • + es gibt eine lineare Folge von N Eingaben +
  • +
  • + und ebenso M reguläre Ausgaben +
  • +
  • + bestimmte Eingaben sind tatsächlich in-Place-Buffer +
  • +
  • + diese erscheinen stets vor den regulären Ausgaben +
  • +
  • + es gibt einen Invocation-Adapter +
  • +
+ +
+
+ + + + + + + + + + + + + +

+ ...hab ich doch alles schon mehrfach durchdiskutiert, und sogar die Ergebnisse aufgeschrieben... +

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

+ das sind Bindings ≙ »Weichenstellungen« +

+ +
+
+ + + + +

+ ...man könnte ein System von nested scopes aufbauen, auf Basis einer persistenten Datenstruktur. Es ist aber noch nicht klar, ob soetwas jemals gebraucht wird (YAGNI); im Besonderen bräuchte man dann auch einen Propagations-Mechanismus für Bindings, und damit wird die Sache komplex und potentiell aufwendig. Als ein anderes Modell könnte man lediglich einen Verweis auf einen Key-Value-Store durchgeben, der dann auf dem Einstiegs-Stackframe liegen würde. Und als minimal-Version würden wir nur einen Koordinaten-Record per Funktionsparameter nach unten durchreichen — bräuchten dann aber einen offband-channel  um Muting und Aktivierungen (z.B. Switchboard) durchzugeben; letztere müssen übrigens auf Ebene der Job-Invocation atomar aufgegriffen werden (wie genau ist noch nicht klar) +

+ + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + @@ -85759,14 +86276,14 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + @@ -85778,7 +86295,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -85797,65 +86314,549 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
-
- - - - - - - - - - - - - - - - - - - - - - - - - - + - + + +

- Auf der Ebene der Implementierung ist die Domain-Ontology so etwas wie eine Typklasse — wie bildet man das in C++17 ab — und wie in C++20 ? + hängt ganz stark mit der internen Repräsentation eines Turnout-System-Elements zusammen: wie werden die dynamischen Array-Container implementiert? das entscheidet, ob alles in einem Aufruf-Scope passieren muß

+ +
+ + + + + + + + + + + + + + + + + + + + +
    +
  • + Ausgabe: ja die müssen wir wissen, weil wir die entsprechende Anzahl empfangender Buffer bereitstellen müssen +
  • +
  • + Eingabe: ähnliches Argument; es kann sein daß wir nicht alle Leeds für diesen Port verwenden, oder umgekehrt daß wir M der N Ports von K Leeds verwenden +
  • +
+ +
+ +
+ + + + + + + + + + + + + + +

+ Ich sage nicht, daß das immer so ist, oder gar daß es besonders relevant wäre. Aber leider ist das hier die Festlegung eines komplett generischen Schemas, mit Partizipanten, über die nahezu nichts bekannt ist. Rein grundsätzlich könnte es schon sein, daß einige dieser Konstruktor-Parameter etwas Spezielles aus dem konkreten Call-Kontext aufgreifen müssen, denn genau dazu sind sie da. Ich denke da an die Wirkung einer Schalterstellung, oder einen Automations-Parameter, oder auch eine spezielle Service-Instanz, die vom Buffer-Inlay benötigt wird, z.B. Allokator-Instanz, Handle auf einen Hardware-Prozessor etc. +

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

+ das heißt, die Reihenfolge der »slots« entspricht genau der Anzahl der Eingangs/Ausgangs-»slots« der ProcNode; mithin wäre die FeedManifold eine Aufdoppelung der Node-Struktur, nur daß dann hier, auf dem Stack, die konkreten BuffHandles liegen +

+ +
+
+ + + + +

+ ...dann wäre die FeedManifold etwas, was man direkt an den InvocationAdapter weiterreicht; möglicherweise wäre die Form der FeedManifold dann sogar von der DomänenOntologie zu bestimmen: sie würde das widerspiegeln, was die konkrete Funktion zum Aufruf braucht, und das wäre im Extremfall sogar ein Typ, der von der Media-Library vorgegeben ist (wenn diese beispielsweise erwartet, daß sie die Buffer-Pointer in einer bestimmten Struct bekommt, ggfs mit zusätzlichen Flags) +

+ +
+ +
+
+ + + + +

+ wenn man beide Gedanken zuende denkt... +

+
    +
  • + dann brauchen wir sowiso und stets irgend eine Struktur auf dem Stack, in dem die BuffHandles liegen; möglicherweise sogar über mehrere Stack-Frames hinweg durchgereicht +
  • +
  • + andererseits kann eine Library ganz gewiß nix mit Lumiera-BuffHandles anfangen, sondern wird ein spezielles Datenformat wollen; das bedeutet, man muß das BuffHandle in jedem Fall belegen, dann dereferenzieren und den resultierenden Pointer irgendwohin kopieren, um die Zielfunktion aufrufen zu können (das war auch die Idee hinter der »BuffTable« im ersten Entwurf) +
  • +
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + +

+ dieser wird am Ende weitergegeben und nicht geschlossen +

+ +
+
+
+ + + + + + +

+ ...das ist ein interessanter Freiheitsgrad: wir können gleichartige Buffer als Array ausgestalten und damit in einen »Slot« zusammenfassen. Wir müssen das sogar tun, wenn wir Mehrfach-Outputs haben +

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

+ hoppla: weiterer Freiheits-Grad entdeckt +

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

+ Eigentlich hätte ich die Storage gerne »inline« — aber das geht nicht ohne Weiteres in C++, denn die Größe jeden Objektes muß zur Compilezeit bekannt sein. Es gibt eine non-Standard-Extension in GCC, so daß auch in C++ die »dynamischen Arrays« aus C erlaubt sind. Tatsächlich liegen diese aber nicht garantiert auf dem Stack, sondern der Compiler reserviert einen gewissen Puffer und wenn es mehr Elemente sind, macht er stillschweigend eine Heap-Allokation +

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

+ damit würden wir ähnlich vorgehen, wie ein Garbage-Colector mit »Eden-Space«: man müllt hemmongslos voll, weil man weiß daß man alles nach der Rückkehr aus dem Aufruf komplett wegwerfen kann +

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

+ man bekommt von jeder Kopie den gleichen Zielpuffer, und auch die Lebenszyklus-Metoden lassen sich von jedem Handle aufrufen +

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

+ kann man von Außen das Resultat-Array überhaupt sehen? Ist es ein Zugriff  oder wird das Resultat geliefert? +

+ +
+
+ + + + +

+ eingangsseitig / ausgangsseitig? +

+ +
+
+
+ + + + + +

+ ...dieses Schema nimmt an, daß man der Reihe nach alle Eingänge »pullt«. Tatsächlich ist es aber so, daß man nur einen bestimmten ersten Eingang einer Serie pullen muß, und dann liegen bereits mehrere Ergebnisse in den Ausgabepuffern; es fehlt aber das API, um auf diese direkt zugreifen zu können, und außerdem ist auch nicht automatisch klar, auf welche Eingangsposition des Nachfolgers diese gemappt werden +

+ +
+
+ + + + +

+ ...und das ist ein besonders lästiger Grenzfall, der höchstwahrscheinlich sehr selten auftritt, dann aber ein knock-out sein könnte; das bedeutet, für diesen seltenen Fall muß die Möglichkeit für freies Mapping vorgesehen werden +

+ + +
+
+ + + + +

+ ...dazu müßten wir aber wissen, daß der Cache hinreichend zuverlässig funktioniert, und andererseits der Fallback auf wiederholte Berechnung nicht prohibitiv aufwendig wird. Daher erscheint diese Lösung zunächst sehr attraktiv (man muß nämlich erst mal gar nix machen und die Dinge regeln sich von selbst), aber bei genauerer Betrachtung erscheint das Risiko schwer quantifizierbar, schon weil die Bedeutung dieses Falles nicht eingeschätzt werden kann ohne in die Breite gehende, praktische Erfahrungen. +

+

+ Als Analogie sehe ich das Thema »Renaming« in SCMs. Subversion hat versucht, dieses Thema explizit zu modellieren; das Ergebnis wurde komplex. Git hat stattdessen beschlossen, das Thema im Modell komplett zu ignorieren, und bietet nur eine Heuristik. Diese Lösung funktioniert erst mal erstaunlich gut, stellt sich aber längerfristig als ein permanentes Ärgernis heraus +

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

+ da diese zusammen anfallen und verwendet werden, sind sie ein Frame +

+ +
+ +
+ + + + +

+ für sie wird nur ein BuffHandle vorgehalten, nur im Buffer liegt eben ein Array +

+ +
+
+ + + + + + + +

+ ...wobei hier zwei Aspekte offen bleiben... +

+
    +
  • + zum Einen wäre es auch möglich, das Array-wertige Resultat zu beziehen, aber nur einen (teil)Puffer zu verwenden +
  • +
  • + was dann auf das andere Problem hinweist: wie entscheidet der Builder, was zu tun ist? +
  • +
+ +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + +

+ das erscheint aber praktisch schwer umsetzbar.... +

+
    +
  • + es wäre eine beliebige Anzahl an Argumenten jeweils einzeln zu repräsentieren +
  • +
  • + man müßte entweder auch die Mapping-Position mit in den Typ-Kontext aufnehmen, wodurch eine gefährliche Kombinatorik entsteht — oder man müßte mit indizierten »Typ-Slots« arbeiten +
  • +
+ +
+
+ + + + +

+ Diese Lösung erscheint sehr viel einfacher und ziemlich elegant; allerdings führt sie zu einer erheblichen Zahl an indirekten Calls (und die Relevanz dieses Hebels ist schwer einschätzbar) +

+ +
+
+
+ - + + + + + + + + +

+ auch das Caching (als orthogonaler Belang) bestimmt mit, +

+

+ was für ein BufferProvider konkret angebunden wird +

+ +
+
+
+ + + + + - - - - - + + + + + + + + + + +

+ dieser führt den »shed«-Schritt aus +

+ +
+ + + + + + + +

+ Das ist eine Entscheidung. Man könnte ganz im Gegenteil jetzt anfangen, einen generischen Media-Function-Invoker zu schreiben, der von einer Routing-Tabelle gesteuert wird. Das werde ich nicht tun, da dies den  Schein einer Generizität erzeugt, die gar nicht gegeben ist.  Vielmehr gehe ich davon aus, daß die meisten Fälle offensichtlich-einfach sind, und in komplexeren Fällen muß man ohnehin wissen, was die jeweilige Library braucht; es wäre reine Verschwendung, das in ein Meta-Routing-Format zu pressen...  +

+ +
+ +
+ + + + - - - - - - - - + + + + + + + + + + + + + + +

+ ...weil der btr. Buffer anderweitig noch weitergereicht wird; das gilt im Besonderen für den Ergebnis-»Slot«, und es gilt aber auch für Eingabe-»Slots«, die tatsächlich InOut-Parameter darstellen und daher auf die Ausgabeseite aufgedoppelt wurden. +

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

+ muß aber auch festlegen wie aufgerufen wird +

+ +
+ + + + + + + + + + + + + + + + + + + + + +
@@ -85908,9 +86909,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - + + + @@ -85955,12 +86956,16 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - + + + + + @@ -85984,6 +86989,27 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + + + + +
@@ -86042,6 +87068,45 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + +

+ Es ist offensichtlich, daß die Berechnung eines einzigen Video-Frames um Größenordnungen aufwendiger ist, als alle Einzel-Aspekte der Ablaufsteuerung. Aber, gilt das auch noch, wenn wir Sound berechnen? oder MIDI-Verarbeitung machen? Oder gar symbolische Repräsentationen generieren, wie z.B. Musik-Notation, API-Orchestrierung etc? Also die »nicht-offensichtlichen Medien«. Außerdem: wie feingranular wird die Zerlegung von Rechenvorgängen im Modell? Das ist auch eine Abwägung Code-Komplexität vs. Ausführungskosten. Eine sehr feingranulare Zerlegung könnte dazu führen, daß ein gefährlicher Hebel entsteht (im Besonderen, da es sich um eine Baumstruktur handelt). Dadurch könnten Einzel-Aufwände, die für sich allein genommen komplett irrelevant erscheinen, in der Gesamtsumme eben doch wirksam werden +

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

+ Die Frage ist: kann es in dieser komplexen Verarbeitung dazu kommen, daß Buffer-Lebenszyklus-Übergänge redundant getriggert werden? Ein möglicher Problemfall wäre denkbar als Folge der Behandlung von InOut-Parametern durch Kopieren eines BuffHandle (was ansonsten eine sehr elegante Lösung wäre) +

+ +
+ + +
+