From 3fe6d5f54c30def4c7cce3c31872e31936a114db Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 23 Jan 2026 22:16:13 +0100 Subject: [PATCH] BufferProvider: define standard usage as test case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This picks up the work for [ticket:1395 resolve integration of BufferProvider with DataSink (#1395)]: Almost a year ago, before the long hiatus due to important general clean-up and maintenance work, and the research regarding Video output — a discrepancy was discovered between the »Buffer Provider Protocol« and the »Output Slot Protocol«. This mismatch prevents us from building a simple preliminary `BufferProvider` to be used for the engine integration as part of the [ticket:1228 »Playback Vertical Slice«]. After re-establisihing the work context, I have read through the new Render Node code built last year, and distilled the sequence of buffer management operations that actually happen during a recursive `pull()` call. With this kowledge, I am now able to fill in the ''standard case for rendering'' — which I was unable to formulate during the first attempt of building the Render Engine in 2011. __Next Steps__: Redesign of the standard implementation base of the `BufferProvider` to prepare for a massively concurrent usage. --- src/steam/engine/engine-ctx.hpp | 5 - .../engine/buffer-provider-protocol-test.cpp | 114 +++-- tests/core/steam/engine/node-base-test.cpp | 16 +- .../core/steam/engine/test-rand-ontology.cpp | 8 +- tests/core/steam/engine/testframe.cpp | 4 +- tests/core/steam/engine/testframe.hpp | 6 +- wiki/thinkPad.ichthyo.mm | 446 +++++++++++++++++- 7 files changed, 536 insertions(+), 63 deletions(-) diff --git a/src/steam/engine/engine-ctx.hpp b/src/steam/engine/engine-ctx.hpp index 863e59a09..17db1a9dd 100644 --- a/src/steam/engine/engine-ctx.hpp +++ b/src/steam/engine/engine-ctx.hpp @@ -37,16 +37,12 @@ #include "steam/engine/buffhandle.hpp" #include "lib/nocopy.hpp" -//#include #include namespace steam { namespace engine { -// using lib::Literal; -// using std::unique_ptr; -// using std::forward; class EngineCtx : util::NonCopyable @@ -57,7 +53,6 @@ namespace engine { public: BufferProvider& mem; BufferProvider& cache; -// BufferProvider& output; /////////////////////////OOO presumably no longer necessary static lib::Depend access; diff --git a/tests/core/steam/engine/buffer-provider-protocol-test.cpp b/tests/core/steam/engine/buffer-provider-protocol-test.cpp index 425b66607..105b3b708 100644 --- a/tests/core/steam/engine/buffer-provider-protocol-test.cpp +++ b/tests/core/steam/engine/buffer-provider-protocol-test.cpp @@ -22,6 +22,7 @@ #include "lib/test/tracking-dummy.hpp" #include "lib/util-foreach.hpp" #include "steam/engine/testframe.hpp" +#include "steam/engine/test-rand-ontology.hpp" #include "steam/engine/diagnostic-buffer-provider.hpp" #include "steam/engine/buffhandle-attach.hpp" #include "steam/engine/bufftable.hpp" @@ -43,29 +44,34 @@ namespace test { namespace { // Test fixture - const uint TEST_SIZE = 1024*1024; const uint TEST_ELMS = 20; - void - do_some_calculations (BuffHandle const& buffer) + HashVal + do_some_computation (TestFrame const& src, TestFrame& work, int64_t* data) { - UNIMPLEMENTED ("some verifiable test/dummy buffer accessing operations"); + REQUIRE (data); + ont::Param param = src.getChecksum(); + ont::manipulateFrame (&work, &src, param); + for (uint i=0; i(); // note: implies also sizeof(TestFrame) - BuffDescr desc2 = provider.getDescriptorFor(TEST_SIZE); - CHECK (desc1.verifyValidity()); - CHECK (desc2.verifyValidity()); + using DataBuff = std::array; + constexpr auto BUFFSIZ = sizeof(DataBuff); - uint num1 = provider.announce(TEST_ELMS, desc1); - uint num2 = provider.announce(TEST_ELMS, desc2); + BuffDescr desc1 = provider.getDescriptor(); + BuffDescr desc2 = provider.getDescriptor(); // note: implies also sizeof(TestFrame) + BuffDescr desc3 = provider.getDescriptorFor(BUFFSIZ); // Can also request fixed sized raw storage. +// CHECK (desc1.verifyValidity()); /////////////////////////////////////////////////////////////////////////////OOO fails (which is wrong) +// CHECK (desc2.verifyValidity()); +// CHECK (desc3.verifyValidity()); + + uint num1 = desc1.announce(TEST_ELMS); + uint num2 = desc1.announce(TEST_ELMS + 1); + uint num3 = desc2.announce(TEST_ELMS * 2); CHECK (num1 == TEST_ELMS); - CHECK (0 < num2 && num2 <=TEST_ELMS); + CHECK (num2 == TEST_ELMS + 1); + CHECK (num3 == TEST_ELMS * 2); - const size_t STORAGE_SIZE = BuffTable::Storage<2*TEST_ELMS>::size; - char storage[STORAGE_SIZE]; - BuffTable& tab = - BuffTable::prepare(STORAGE_SIZE, storage) - .announce(num1, desc1) - .announce(num2, desc2) - .build(); - - tab.lockBuffers(); - for_each (tab.buffers(), do_some_calculations); - tab.releaseBuffers(); + // emulate a progressive processing + BuffHandle workBuff = desc2.lockBuffer(); + workBuff.accessAs() = testData(0); + for (uint i=0; i(); + size_t& mark = markBuff.accessAs(); + TestFrame& work = workBuff.accessAs(); + DataBuff& data = dataBuff.accessAs(); + + mark = do_some_computation (src,work, &data[0]); + + srcBuff.emit(); + dataBuff.emit(); + markBuff.emit(); + + srcBuff.release(); + dataBuff.release(); + markBuff.release(); + // note : workBuff passed to next iteration + } + CHECK (workBuff.isValid()); + auto endSum = workBuff.accessAs().getChecksum(); + workBuff.release(); // note: not every buffer need be emitted. DiagnosticBufferProvider& checker = DiagnosticBufferProvider::access(provider); - CHECK (checker.all_buffers_released()); -#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #829 + CHECK (checker.buffer_was_used (0)); + CHECK (checker.buffer_was_closed (0)); +// CHECK (checker.all_buffers_released()); ///////////////OOO do we need this API? } diff --git a/tests/core/steam/engine/node-base-test.cpp b/tests/core/steam/engine/node-base-test.cpp index 5ec96dcfc..c90ac35c6 100644 --- a/tests/core/steam/engine/node-base-test.cpp +++ b/tests/core/steam/engine/node-base-test.cpp @@ -90,10 +90,10 @@ namespace test { verify_TurnoutSystem() { Time nomTime{rani(10'000),0}; // drive test with a random »nominal Time« <10s with ms granularity - TurnoutSystem turoSys{nomTime}; // a time spec is mandatory, all further parameters are optional + TurnoutSystem tuSys{nomTime}; // a time spec is mandatory, all further parameters are optional - CHECK (turoSys.getNomTime() == nomTime); // can access those basic params from within the render invocation. - CHECK (turoSys.getProcKey() == ProcessKey{}); + CHECK (tuSys.getNomTime() == nomTime); // can access those basic params from within the render invocation. + CHECK (tuSys.getProcKey() == ProcessKey{}); /* == That's all required for basic usage. == */ @@ -108,14 +108,14 @@ namespace test { {// Build and connect extension storage block auto dataBlock = // ...typically placed locally into a nested stack frame spec.makeBlockBuilder() - .buildParamDataBlock(turoSys); + .buildParamDataBlock(tuSys); - turoSys.attachChainBlock (dataBlock); // link extension data block into the TurnoutSystem - CHECK (turoSys.get(acc0) == someVal); // now able to retrieve data from extension block - turoSys.detachChainBlock (dataBlock); + tuSys.attachChainBlock (dataBlock); // link extension data block into the TurnoutSystem + CHECK (tuSys.get(acc0) == someVal); // now able to retrieve data from extension block + tuSys.detachChainBlock (dataBlock); } // base block continues to be usable... - CHECK (turoSys.getNomTime() == nomTime); + CHECK (tuSys.getNomTime() == nomTime); } diff --git a/tests/core/steam/engine/test-rand-ontology.cpp b/tests/core/steam/engine/test-rand-ontology.cpp index d2cb8dfb3..e8a4ac0cf 100644 --- a/tests/core/steam/engine/test-rand-ontology.cpp +++ b/tests/core/steam/engine/test-rand-ontology.cpp @@ -66,7 +66,7 @@ namespace ont { * @param chanCnt size of the array of frames to generate * @param buffArry pointer to an allocation sufficiently sized to hold `TestFrame[chanCnt]` * @param frameNr the frame of the »source feed« to use commonly on all those frames in the output - * @param flavour a further seed parameter used as starting offest for the output's `family` parameters + * @param flavour a further seed parameter used as starting offset for the output's `family` parameters * @remark this is a variation of the [dummy data generator](\ref #generateFrame), * which immediately generates a planar block of related frames with random data, * all seeded with the _same_ `frameNr` and _consecutive_ `family` parameters, @@ -98,9 +98,9 @@ namespace ont { * @param chanCnt size of the array of frames to manipulate * @param buffArry pointer to an array of several frames (channels) * @param param parameter to control or »mark« the data manipulation (hash-combining) - * @remark this function in-place processing of several channels in one step: data is processed - * in 64-bit words, by hash-chaining with \a param and then joining in the data items. - * All data buffers will be manipulated and marked with as valid with a new checksum. + * @remark this function imitates in-place processing of several channels in one step: data is + * processed in 64-bit words, by hash-chaining with \a param and then joining in the data items. + * All given data buffers will be manipulated and marked as valid with a new checksum. */ void manipulateMultichan (TestFrame* buffArry, ChaNo chanCnt, Param param) diff --git a/tests/core/steam/engine/testframe.cpp b/tests/core/steam/engine/testframe.cpp index 098d933b0..f0c7d550e 100644 --- a/tests/core/steam/engine/testframe.cpp +++ b/tests/core/steam/engine/testframe.cpp @@ -367,9 +367,9 @@ namespace test { } /** access current checksum without recomputing. */ HashVal - TestFrame::getChecksum() + TestFrame::getChecksum() const { - return accessHeader().checksum; + return unConst(this)->accessHeader().checksum; } bool diff --git a/tests/core/steam/engine/testframe.hpp b/tests/core/steam/engine/testframe.hpp index d3802a4b6..8a7aba91f 100644 --- a/tests/core/steam/engine/testframe.hpp +++ b/tests/core/steam/engine/testframe.hpp @@ -67,11 +67,13 @@ namespace test { */ class TestFrame { + public: enum StageOfLife { CREATED, EMITTED, DISCARDED }; - static constexpr size_t BUFFSIZ = 1024; + + private: using _Arr = std::array; using _A64 = std::array; @@ -105,7 +107,7 @@ namespace test { /** recompute and store checksum based on current contents */ HashVal markChecksum(); - HashVal getChecksum(); + HashVal getChecksum() const; /** Helper to verify that a given memory location holds * an active TestFrame instance (created, not yet destroyed) diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 7cc0cf4f2..a01081d0a 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -99858,7 +99858,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + @@ -99902,6 +99904,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

+ +
@@ -100257,7 +100261,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + @@ -109692,6 +109698,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + @@ -109992,7 +110002,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + + + @@ -110264,6 +110277,427 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + + + + + + + + + +

+ Ich hatte zwar eine Grund-Mechanik deduktiv entwickelt, bin aber daran gescheitert, diese in ein vollständiges Nutzungsmuster zu überführen, denn mir fehlte überall die Implementierung des Builders, und dahinter stand das Problem, daß mein damaliger Entwurf die Struktur des Rendervorgangs komplett und erschöpfend im Voraus planen  mußte, da ich mit einem tabellengetriebenen Ansatz und fertig vorcompilierten Aufruf-Bausteinen gearbeitet habe. Zunächst konnte ich mich um dieses Problem noch elegant herumnavigieren (und viel labern), aber dann bei der DummyPlayConnection hätte ich konkret werden müssen, und hab dann gemerkt, daß ich nichts konkretes in der Hand habe, und nur sagen kann, daß muß man dann sehen, wenn alles ausimplementiert ist. Und in diesem Dilemma habe ich wohl begonnen, zu spekulieren, und mir die BuffTable als Hilfsmittel ausgedacht. Im Rückblick war das der richtige Ansatz, denn er führt zu einem Baukasten-System auf einem niedrigeren Level, das man vermutlich durch allgemeine Analsyse zumindest für häufige Standard-Fälle schaffen kann. Aber das allgemeine Framework war so verwirrend geworden (auch für mich selbst, wie meine Notizen zeigen), daß ich nicht "Tritt fassen" konnte. Später dann habe ich einen ganz anderen Ansatz verfolgt, der letztlich zielführend war: nämlich vom Play-Prozess her zu denken (dieser Weg wurde nur für mehrere Jahre unterbrochen, da ich mich zunächst um das GUI und die GUI-Session-Verbindung gekümmert habe) +

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

+ die letzten großen Errungenschaften vor fast einem Jahr ☹ +

+ +
+ + + + + + +

+ ...natürlich vom DiagnosticBufferProvider.... +

+

+ +

+
+
+

+ BufferProvider& provider =  DiagnosticBufferProvider::build(); +

+

+ const BuffDescr buffDescr  = provider.getDescriptor<TestFrame>(); +

+

+ BuffHandle buff = provider.lockBuffer(buffDescr); +

+

+ +

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

+ void +

+

+ generateFrame (TestFrame*  buff, FraNo frameNr, Flavr  flavour) +

+

+ { +

+

+ REQUIRE (buff); +

+

+ new(buff) TestFrame{uint(frameNr),  flavour}; +

+

+ } +

+

+ +

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

+ void +

+

+ shed (Feed& feed,  TurnoutSystem&, OptionalBuff  outBuff) +

+

+ { +

+

+   for (uint i=0;  i<outTypes_.size(); ++i) +

+

+     { +

+

+       BuffHandle resultData = +

+

+           i == resultSlot_ and  outBuff? *outBuff +

+

+                                        : outTypes_[i].lockBuffer(); +

+

+       feed.outBuff.createAt(i, move(resultData)); +

+

+     } +

+

+   feed.connect(); +

+

+ } +

+

+ +

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

+ dort wird natürlich ein DiagnosticBufferProvider verwendet +

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

+ darin liegen Funktoren, die dann in der terminalen Build-Operation auf einen geeigneten BufferProvider zugreifen, um darauf getDescriptor<BU>() aufzurufen. +

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

+ Interface: engine-ctx.hpp +

+

+ Impl: engine-ctx-facilities.hpp|cpp +

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

+ EngineCtx::Facilities::Facilities() +

+

+   : memProvider_{make_unique<TrackingHeapBlockProvider>()} +

+

+   , cacheProvider_{}         ////////////////////////////////TICKET  #1367 : only suitable for first prototype +

+

+   { } +

+

+ +

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

+ der DiagnosticBufferProvider verwendet als PImpl einen TrackingHeapBlockProvider +

+ + +
+
+ + + + + +

+ weil der DiagnosticBufferProvider nur »seine« Instanz beobachtet +

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

+ und für alle nicht-output-buffer auch release() +

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

+ 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 +

+ + +
+
+ + + + +

+ derzeit wird direkt auf der Descriptor das lock() aufgerufen +

+ + +
+
+ + + + +

+ 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. +

+ + +
+
+ + + + +

+ 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 +

+ + +
+
+
+ + + + +

+ ...das war der Fehler beim ersten Anlauf (deshalb die BuffTable); +

+

+ ....aktuell wäre eine solche Integration im NodeStorage_test zu zeigen. +

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

+ 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 +

+ + +
+
+ + + + +

+ und auch dann müßte es ein Test sein, der das System unter Druck setzt, um die Wahrscheinlichkeit des Versagens zu erhöhen; typischerweise würde ich das auch gleich mit concurrency-Tests verbinden. +

+ +
+
+ + + + +

+ ...was den Test enorm vereinfacht; das Verketten haben wir bereits im NodeLink_test geprüft, und werden es wohl im Operations-Test noch mehr und unter Druck prüfen +

+ +
+
+
+ + + + + + + +
+
+
+ + + + + + + + + + + + +
@@ -110345,6 +110779,12 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+ + + + + +