diff --git a/src/steam/engine/engine-ctx.hpp b/src/steam/engine/engine-ctx.hpp index 17db1a9dd..fd6b6fb34 100644 --- a/src/steam/engine/engine-ctx.hpp +++ b/src/steam/engine/engine-ctx.hpp @@ -21,9 +21,11 @@ ** provides a naive self-contained implementation suitable for demonstration and test. ** @todo who is responsible for setup of the services for the actual render engine? ** Might be closely related to brining up façade interfaces. - ** @todo WIP-WIP 2/2025 provide actual service implementation and find a way - ** how to populate the Facilities with these actual services... + ** @todo WIP-WIP 1/2026 provide a default implementation for test + ** and find a way to populate the Facilities with the + ** production variant of the services... ** + ** @see engine-ctx-test.cpp ** @see engine-ctx-facilities.hpp implementation ** @see buffer-provider.hpp ** @see buffhandle.hpp diff --git a/tests/core/steam/engine/buffer-metadata-key-test.cpp b/tests/core/steam/engine/buffer-metadata-key-test.cpp index 71f4d6604..143c01787 100644 --- a/tests/core/steam/engine/buffer-metadata-key-test.cpp +++ b/tests/core/steam/engine/buffer-metadata-key-test.cpp @@ -389,7 +389,7 @@ namespace test { /** Register this test class... */ - LAUNCHER (BufferMetadataKey_test, "unit player"); + LAUNCHER (BufferMetadataKey_test, "unit engine"); diff --git a/tests/core/steam/engine/buffer-metadata-test.cpp b/tests/core/steam/engine/buffer-metadata-test.cpp index 05044a0dd..d3f6de398 100644 --- a/tests/core/steam/engine/buffer-metadata-test.cpp +++ b/tests/core/steam/engine/buffer-metadata-test.cpp @@ -403,7 +403,7 @@ namespace test { /** Register this test class... */ - LAUNCHER (BufferMetadata_test, "unit player"); + LAUNCHER (BufferMetadata_test, "unit engine"); diff --git a/tests/core/steam/engine/buffer-provider-protocol-test.cpp b/tests/core/steam/engine/buffer-provider-protocol-test.cpp index 105b3b708..73615da0c 100644 --- a/tests/core/steam/engine/buffer-provider-protocol-test.cpp +++ b/tests/core/steam/engine/buffer-provider-protocol-test.cpp @@ -31,7 +31,7 @@ using util::isSameObject; using util::for_each; -namespace steam { +namespace steam { namespace engine{ namespace test { @@ -260,7 +260,7 @@ namespace test { /** Register this test class... */ - LAUNCHER (BufferProviderProtocol_test, "unit player"); + LAUNCHER (BufferProviderProtocol_test, "unit engine"); diff --git a/tests/core/steam/engine/engine-ctx-test.cpp b/tests/core/steam/engine/engine-ctx-test.cpp new file mode 100644 index 000000000..abdc4daca --- /dev/null +++ b/tests/core/steam/engine/engine-ctx-test.cpp @@ -0,0 +1,102 @@ +/* + EngineCtx(Test) - calculation of (internal) buffer metadata type keys + + Copyright (C) + 2026, Hermann Vosseler + +  **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 engine-ctx-test.cpp + ** unit test \ref EngineCtx_test + */ + + +#include "lib/error.hpp" +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "steam/engine/engine-ctx.hpp" +#include "steam/engine/buffer-provider.hpp" +//#include "steam/engine/engine-ctx-facilities.hpp" //////////TODO required? +#include "lib/util.hpp" + +//#include + +//using util::isnil; +using util::isSameObject; + + +namespace steam { +namespace engine{ +namespace test { + + namespace { // Test fixture + + + }//(End) Test helpers + + using LERR_(LIFECYCLE); + + + + + /*****************************************************************************//** + * @test verify context with generic management services for Dependency Injection, + * used within the Render Node Network to hide global setup state. + */ + class EngineCtx_test : public Test + { + + + virtual void + run (Arg) + { +// seedRand(); + simpleUsage(); + } + + + void + simpleUsage() + { + auto& ctx = EngineCtx::access; + BufferProvider& provider = ctx().mem; + + // in the default config (for tests) there is no separate cache provider + CHECK (isSameObject (provider, ctx().cache)); + + BuffHandle buff = provider.lockBufferFor(); + CHECK (buff.isValid()); + CHECK (sizeof(int) <= buff.size()); + + buff.accessAs() = -LIFE_AND_UNIVERSE_4EVER; + CHECK (buff.accessAs() == -int(LIFE_AND_UNIVERSE_4EVER)); + + uint alternativeTruth = buff.accessAs(); + CHECK (alternativeTruth == -LIFE_AND_UNIVERSE_4EVER); + + buff.release(); + CHECK (not buff.isValid()); + VERIFY_ERROR (LIFECYCLE, buff.accessAs() ); + } + + + /** @todo automagically post on X with lots of UPERCASE LETTERS */ + void + verify_disaster() + { + UNIMPLEMENTED ("general meltdown"); + } + }; + + + /** Register this test class... */ + LAUNCHER (EngineCtx_test, "unit engine"); + + + +}}} // namespace steam::engine::test diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 853063cce..306616a17 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -1262,28 +1262,30 @@ 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.
+
+
Analysis of requirement shows that direct access to the buffers holding actual media data is not necessary -- at leas within the entire implementation of the Render Nodes Network. This finding is based on the assumption, that Lumiera does not perform any media processing on its own; all processing is delegated to external libraries. When taking this approach, buffers can be reduced to just some entity to be //managed,// i.e. "allocated", "locked" and "released"; the //actual meaning of these operations can be left to the implementation.// So the code within the render engine just pushes around ''smart-ptr like handles''. These [[buffer handles|BuffHandle]] act as a front-end, being created by and are linked to a {{{BufferProvider}}} 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 strict call scheme, which ensures that all buffers are provided before invoking an operation and that buffers are released afterwards. Buffer management can thus be reduced to the following primitive operations...
+* declaring the "type", i.e. the size of a buffer
+* (optionally) a pre-announcement that a given amount of buffers will be required soon
+* the next step is to ensure the necessary buffers are available, by obtaining ("locking") a buffer handle
+* (optionally) the contents of a buffer can be marked as complete ("emitting" a buffer handle)
+* finally the buffer is marked as free ("released") and client code will not touch the buffer handle thereafter.
 
 !operations
-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
+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 conceivable implementation
 ;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.
+: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 using the buffers. Subsequent calls are considered //cummulative:// 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 or accounting as to which specific client requested buffers, beyond the buffer type. The purpose of this announcing mechanism is to reduce latency, as it allows to rebalance memory requirement between various threads in advance, asynchronously.
 ;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. 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.//
+:this operation actually makes a buffer available for a specific client (thread) 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.//
 ;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 media handling 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. Notably, these lifecycle functions are considered part of the »buffer type«
 ;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 content should not be altered 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
-:buffers need to be released explicitly by the client code. This renders the corresponding BuffHandle invalid, (optionally) invokes a destructor function of an attached object and maybe reclaims the buffer memory
+:buffers need to be released explicitly by the client code, without any exception. This renders the corresponding BuffHandle invalid, (optionally) invokes a destructor function of an attached object and allows the backing buffer manager to redeem the buffer memory.
 
 !!type metadata service
 In addition to the basic operations, clients may associate BufferMetadata with individual buffers;
-in the basic form, this means just maintaining a type tag describing the kind of buffer, while optionally this service might be extended to e.g. associating a state flag.
+in the basic form, this means just to maintain a client-defined type tag that describes the kind of buffer. It might be conceivable that this service could be extended, allowing e.g. to associate a flag.
 
 __see also__
 &rarr; OutputSlot relying on a buffer provider to deal with frame output buffers
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index a01081d0a..50d6bcbe9 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -110275,7 +110275,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
 
 
 
-
+
 
 
 
@@ -110429,8 +110429,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
       
- - + @@ -110444,8 +110443,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) dort wird natürlich ein DiagnosticBufferProvider verwendet

- - + @@ -110463,8 +110461,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) darin liegen Funktoren, die dann in der terminalen Build-Operation auf einen geeigneten BufferProvider zugreifen, um darauf getDescriptor<BU>() aufzurufen.

- - + @@ -110480,8 +110477,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) Impl: engine-ctx-facilities.hpp|cpp

- - + @@ -110509,8 +110505,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + @@ -110523,8 +110518,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) der DiagnosticBufferProvider verwendet als PImpl einen TrackingHeapBlockProvider

- - + @@ -110535,8 +110529,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) weil der DiagnosticBufferProvider nur »seine« Instanz beobachtet

- - +
@@ -110566,8 +110559,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) und für alle nicht-output-buffer auch release()

- - +
@@ -110587,8 +110579,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) und zwar wegen der concurrency; wir können es uns nicht leisten, große Reserve-Speicherblöcke in jedem Thread zu haben; daher müssen wir zu einem gewissen Grad den Buffer-Speicher zwischen den Threads ausgleichen. Sinnvollerweise passiert das über asynchrones Messaging, und deshalb ist es sinnvoll, zu Beginn der Call-Sequenz eine Vorwarnung abzusetzen; in einer späteren, elaborierteren Version des Buffer-Managements könnte dann eine Heuristik entscheiden, ob man im aktuellen Thread (der ja exclusiv diesen Job bearbeitet) eine neue Allokation beginnt, oder ob man einen Request an einen generischen Speicherpool absetzt, der dann ggfs. aber eine gewisse Laufzeit hat, und asynchron ankommt. Das ist natürlich dann ein ziemlich komplexes Schema — aber eines, bei dem sich genaue Beobachtung und Feinjustierung lohnt

- - +
@@ -110598,8 +110589,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) derzeit wird direkt auf der Descriptor das lock() aufgerufen

- -
+
@@ -110609,8 +110599,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) Und zwar müßte die Proc-Node den Callgraphen evaluieren, um dadurch die maximal zu einem Zeitpunkt gleichzeitig erforderliche  Allokation zu ermitteln. Es geht ja nicht darum, naiv die Zahl der Eingänge / Ausgänge zu summieren, da Daten in erheblichem Umfang direkt von Node zu Node weitergereicht werden.

- -
+
@@ -110620,8 +110609,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) Das wäre erst mal naheliegend so; wann und wie tatsächlich diese Evaluierung stattfinden kann (zur Build-Zeit oder erst zur Aufruf-Zeit) ist noch völlig offen. Zu bedenken ist auch, daß der Cache eine änliche Vorbereitungs-Anfrage braucht; möglicherweise werden wir sogar diese Vorbereitung als eigenen Job im Vorausgang absetzen, oder zumindest in einen IO-Job mit integrieren

- -
+
@@ -110635,8 +110623,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) ....aktuell wäre eine solche Integration im NodeStorage_test zu zeigen.

- - +
@@ -110651,8 +110638,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) zum einen, weil dann tatsächlich auch die Struktur nachgebildet werden müßte, in der die Buffer belegt, befüllt, weitergereicht, ausgewertet und verworfen werden, alles auch noch verschachtelt. Und zum anderen arbeiten wir hier mit dem DiagnosticBufferProvider, der alle Buffer einmal im Heap alloziert und niemals Buffer verwirft; das Testziel wäre hierdurch trivial gegeben, und dennoch wäre die eigentliche Zusicherung des Protokolls nicht überprüft, abgesehen von dem Grenzfall, daß Buffer verwechselt und dadurch korrumpiert würden

- - +
@@ -110681,6 +110667,12 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + +
@@ -110699,9 +110691,70 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + +

+ sondern nur ein einziges kohäsives Bündel an Implementierungs-Code, der direkt hinter dem Interface klebt +

+ +
+ + + + + + + +

+ das heißt, dieser »Zustand« wird nicht auf Protokoll-Ebene sichtbar (und genau desshalb entsteht die Verwirrung auf Protokoll-Ebene) +

+ +
+
+ + + + +

+ Denn es verhält sich so, daß ein Metadaten-Prototyp irgendwann ("eventually") in einer zentralen Metadaten-Registry auftauchen muß; dagegen die daraus abgeleiteten lokalen Zustände könnten im aktuellen Thread bleiben. Die Frage ist, auf welchen "Typ" verweist ein BuffHandle? Wozu ist dieser Verweis überhaupt notwendig? Von da aus (also ein operationaler Gesichtspunkt) bestimmt sich, wie wir Metadaten überhaupt speichern +

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

+ ...ich weiß sogar noch (von meiner ersten Untersuchung vor einem Jahr), daß vonseiten der Implementierung relativ klar ist, warum man nicht direkt delegieren kann; also muß der Grund gefunden werden, warum ich in diese Sackgasse gegangen bin... +

+ +
+
+
+ + + + +

+ Das allein entwertet nicht die Protokolle als Solche — aber es macht deutlich, daß die Implementierung (zumindest des BufferProvider) komplett auseinandergenommen werden muß; ich hatte mich seinerzeit vom DiagnosticBufferProvider treiben lassen, und dann einfach alles was noch offen war, irgendwie hingebogen (zumindest sieht der Code danach aus) +

+ +
+ +
+
@@ -110766,7 +110819,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -110775,16 +110828,49 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)

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