diff --git a/src/steam/engine/feed-manifold.hpp b/src/steam/engine/feed-manifold.hpp index 60e3d097b..fb4804901 100644 --- a/src/steam/engine/feed-manifold.hpp +++ b/src/steam/engine/feed-manifold.hpp @@ -25,7 +25,7 @@ ** the aforementioned »network of Render Nodes.« At this point, external functionality must actually ** be connected to internal structures: this purpose is served by the FeedManifold. ** - ** This amounts to an tow-stage adaptation process. Firstly, the plug-in for an external library + ** This amounts to an two-stage adaptation process. Firstly, the plug-in for an external library ** has to wrap-up and package the library functions into an _invocation functor_ — which thereby ** creates a _low-level Specification_ of the functionality to invoke. This functor is picked up ** and stored as a prototype within the associated render node. More specifically, each node can @@ -46,7 +46,7 @@ ** might have to create a wrapper type in cases where the external library requires to use a ** specific constructor function for buffers (if this requirement turns out as problematic, ** there is leeway to pass constructor arguments to such a wrapper — yet Lumiera will insist - ** on managing the memory, so frameworks insisting on their own memory management will have + ** on managing the memory, so frameworks enforcing their own memory management will have ** to be broken up and side-stepped, in order to be usable with Lumiera). ** - when several and even mixed types of a kind must be given, e.g. several buffers or ** several parameters, then the processing functor should be written such as to @@ -56,12 +56,12 @@ ** A suitable storage layout is chosen at compile type, based on the given functor type. ** - essentially, FeedManifold is structured storage with some default-wiring. ** - the trait functions #hasInput() and #hasParam() should be used by downstream code - ** to find out if some part of the storage is present and branch accordingly + ** to find out if some part of the storage is present and branch accordingly... ** @todo 12/2024 figure out how constructor-arguments can be passed flexibly ** @remark in the first draft version of the Render Engine from 2009/2012, there was an entity - ** called `BuffTable`, which however provided additional buffer-management functionality. + ** called `BuffTable`, which however provided additional buffer-management capabilities. ** This name describes well the basic functionality, which can be hard to see with all - ** this additional meta-programming related to the flexible functor signature. When it + ** the additional meta-programming related to the flexible functor signature. When it ** comes to actual invocation, we collect input buffers from predecessor nodes and ** we prepare output buffers, and then we pass both to a processing function. ** @@ -70,6 +70,7 @@ ** We have still to reach the point were the engine becomes operational!!! ** @see NodeBase_test ** @see weaving-pattern-builder.hpp + ** @see lib::meta::ElmTypes in variadic-helper.hpp : uniform processing of »tuple-like« data */ @@ -158,6 +159,7 @@ namespace engine { * which all may be simple types, tuples or arrays. * @note »Buffers« are always accepted by pointer, which allows * to distinguish parameter and data «slots« + * @see VariadicHelper_test::rebuild_variadic() */ template struct _ProcFun @@ -247,33 +249,46 @@ namespace engine { + /** + * Configuration context for a FeedManifold. + * This type-rebinding helper provides a storage configuration + * specifically tailored to serve the invocation of \a FUN + * @note storage segments for input and parameters are only present + * when the given function is classified by _ProcFun + * as handling input and / or parameters. + * @remark since BuffHandle is not default-constructible, but must be + * retrieved from a BufferProvider rather, a chunk of uninitialised + * storage is used to accept the `BuffHandle`s allocated and populated + * with results from preceding Nodes. + */ template struct _StorageSetup { using _Trait = _ProcFun; - enum{ FAN_I = _Trait::FAN_I - , FAN_O = _Trait::FAN_O - }; static constexpr bool hasInput() { return _Trait::hasInput(); } static constexpr bool hasParam() { return _Trait::hasParam(); } - using ParSig = typename _Trait::SigP; + enum{ FAN_P = hasParam()? _Trait::FAN_P : 0 + , FAN_I = hasInput()? _Trait::FAN_I : 0 + , FAN_O = _Trait::FAN_O + }; template using BuffS = lib::UninitialisedStorage; - + // ^^^ can not be default-constructed using BuffI = BuffS; using BuffO = BuffS; - using ArgI = typename _Trait::SigI; + using Param = std::conditional_t>; + using ArgI = std::conditional_t>; using ArgO = typename _Trait::SigO; /** FeedManifold building block: hold parameter data */ struct ParamStorage { - ParSig param; + Param param; ParamStorage() = default; @@ -283,12 +298,14 @@ namespace engine { { } }; + /** FeedManifold building block: hold input buffer pointers */ struct BufferSlot_Input { BuffI inBuff; ArgI inArgs{}; }; + /** FeedManifold building block: hold output buffer pointers */ struct BufferSlot_Output { BuffO outBuff; @@ -296,7 +313,7 @@ namespace engine { }; template - using enable_if_hasParam = typename lib::meta::enable_if_c<_ProcFun>::hasParam()>::Type; + using enable_if_hasParam = typename lib::meta::enable_if_c<_ProcFun::hasParam()>::type; template using NotProvided = Tagged; @@ -304,13 +321,14 @@ namespace engine { template using Provide_if = std::conditional_t>; - using FeedOutput = BufferSlot_Output; - using FeedInput = Provide_if; - using FeedParam = Provide_if; + using FeedOutput = BufferSlot_Output; + using FeedInput = Provide_if; + using FeedParam = Provide_if; /** * Data Storage block for the FeedManifold * Flexibly configured based on the processing function. + * @remark mixed-in as base-class */ struct Storage : util::NonCopyable @@ -325,10 +343,9 @@ namespace engine { : process{forward (fun)} { } - template - , typename =enable_if_hasParam> - Storage (F&& fun, INIT&& ...paramInit) - : FeedParam{forward (paramInit)...} + template> + Storage (Param p, F&& fun) + : FeedParam{move (p)} , process{forward (fun)} { } }; @@ -339,10 +356,33 @@ namespace engine { * Adapter to connect input/output buffers to a processing functor backed by an external library. * Essentially, this is structured storage tailored specifically to a given functor signature. * Tables of buffer handles are provided for the downstream code to store results received from - * preceding odes or to pick up calculated data after the invocation. From these BuffHandle entries, + * preceding nodes or to pick up calculated data after invocation. From these BuffHandle entries, * buffer pointers are retrieved and packaged suitably for use by the wrapped invocation functor. - * This setup is used by a »weaving pattern« within the invocation of a processing node for the - * purpose of media processing or data calculation. + * This setup is intended for use by a »weaving pattern« within the invocation of a processing node + * for the purpose of media processing or data calculation. + * + * # Interface exposed to down-stream code + * Data fields are typed to suit the given functor \a FUN, and are present only when needed + * - `param` holds a parameter value or tuple of values, as passed to the constructor + * - `inBuff` and `outBuff` are chunks of \ref UninitialisedStorage with suitable dimension + * to hold an array of \ref BuffHandle to organise input- and output-buffers + * - the constants `FAN_P`, `FAN_I` and `FAN_O` reflect the number of individual elements + * connected for parameters, inputs and outputs respectively. + * - `inBuff.array()` and `outBuff.array()` expose the storage for handles as std::array, + * with suitable dimension, subscript-operator and iteration. Note however that the + * storage itself is _uninitialised_ and existing handles must be _emplaced_ by + * invoking copy-construction e.g. `outBuff.createAt (idx, givenHandle)` + * - after completely populating all BuffHandle slots this way, FeedManifold::connect() + * will pick up buffer pointers and transfer them into the associated locations in + * the input and output arguments `inArgs` and `outArgs` + * - finally, FeedManifold::invoke() will trigger the stored processing functor, + * passing `param`, `inArgs` and `outArgs` as appropriate. + * The `constexpr` functions #hasInput() and #hasParam() can be used to find out + * if the functor was classified to take inputs and / or parameters. + * @note destructors of parameter values will be invoked, but nothing will be done + * for the BuffHandle elements; the caller is responsible to perform the + * buffer management protocol, i.e. invoke BuffHandle::emit() + * and BuffHandle::release() * * @todo WIP-WIP 12/24 now adding support for arbitrary parameters /////////////////////////////////////TICKET #1386 */ @@ -369,8 +409,10 @@ namespace engine { using ArgI = typename _S::ArgI; using ArgO = typename _S::ArgO; + using Param = typename _S::Param; enum{ FAN_I = _S::FAN_I , FAN_O = _S::FAN_O + , FAN_P = _S::FAN_P }; static constexpr bool hasInput() { return _S::hasInput(); } @@ -415,10 +457,16 @@ namespace engine { void invoke() { - if constexpr (hasInput()) - _F::process (_F::inArgs, _F::outArgs); + if constexpr (hasParam()) + if constexpr (hasInput()) + _F::process (_F::param, _F::inArgs, _F::outArgs); + else + _F::process (_F::param, _F::outArgs); else - _F::process (_F::outArgs); + if constexpr (hasInput()) + _F::process (_F::inArgs, _F::outArgs); + else + _F::process (_F::outArgs); } }; diff --git a/tests/core/steam/engine/node-base-test.cpp b/tests/core/steam/engine/node-base-test.cpp index 523c83d9f..5bf21eba2 100644 --- a/tests/core/steam/engine/node-base-test.cpp +++ b/tests/core/steam/engine/node-base-test.cpp @@ -37,6 +37,7 @@ using std::tuple; using std::array; using util::isSameAdr; using lib::test::showType; +using lib::izip; namespace steam { @@ -75,6 +76,23 @@ namespace test { /** @test the FeedManifold as adapter between Engine and processing library + * - bind local λ with various admissible signatures + * - construct specifically tailored FeedManifold types + * - use the DiagnosticBufferProvider for test buffers + * - create FeedManifold instance, passing the λ and additional parameters + * - connect BuffHandle for these buffers into the FeedManifold instance + * - trigger invocation of the function + * - look into the buffers and verify effect + * @remark within each Render Node, a FeedManifold is used as junction + * to tap into processing functionality provided by external libraries. + * Those will be adapted by a Plug-in, to be loaded by the Lumiera core + * application. The _signature of a functor_ linked to the FeedManifold + * is used as kind of a _low-level-specification_ how to invoke external + * processing functions. Obviously this must be complemented by a more + * high-level descriptor, which is interpreted by the Builder to connect + * a suitable structure of Render Nodes. + * @see feed-manifold.h + * @see NodeLinkage_test */ void verify_FeedManifold() @@ -84,11 +102,15 @@ namespace test { using Buffer = long; + //______________________________________________________________ // Example-1: a FeedManifold to adapt a simple generator function auto fun_singleOut = [&](Buffer* buff) { *buff = r1; }; using M1 = FeedManifold; CHECK (not M1::hasInput()); CHECK (not M1::hasParam()); + CHECK (0 == M1::FAN_P); + CHECK (0 == M1::FAN_I); + CHECK (1 == M1::FAN_O); // instantiate... M1 m1{fun_singleOut}; CHECK (1 == m1.outBuff.array().size()); @@ -113,6 +135,7 @@ namespace test { CHECK (buff.accessAs() == r1); // result: the random number r1 was written into the buffer. + //_____________________________________________________________ // Example-2: adapt a function to process input -> output buffer auto fun_singleInOut = [](Buffer* in, Buffer* out) { *out = *in + 1; }; using M2 = FeedManifold; @@ -151,6 +174,7 @@ namespace test { CHECK (buffOut.accessAs() == r1+1); + //______________________________________ // Example-3: accept complex buffer setup using Sequence = array; using Channels = array; @@ -158,7 +182,7 @@ namespace test { auto fun_complexInOut = [](Channels in, Compound out) { auto [seq,extra] = out; - for (auto [i,b] : lib::izip(in)) + for (auto [i,b] : izip(in)) { (*seq)[i] = *b + 1; *extra += *b; @@ -216,6 +240,76 @@ namespace test { CHECK ( o01 == *ia1+1); CHECK ( o02 == *ia2+1); CHECK (*oa1 == -55 + *ia0+*ia1+*ia2); ///////////////////////////////////////////OOO should be -33 + + + //_________________________________ + // Example-4: pass a parameter tuple + using Params = tuple; + // Note: demonstrates mix of complex params, an array for input, but just a simple output buffer + auto fun_ParamInOut = [](Params param, Channels in, Buffer* out) + { + auto [s,l] = param; + *out = 0; + for (Buffer* b : in) + *out += (s+l) * (*b); + }; + using M4 = FeedManifold; + CHECK (M4::hasInput()); + CHECK (M4::hasParam()); + CHECK (2 == M4::FAN_P); + CHECK (3 == M4::FAN_I); + CHECK (1 == M4::FAN_O); + CHECK (showType() == "array"_expect); + CHECK (showType() == "long *"_expect); + CHECK (showType() == "tuple"_expect); + + // Note: instantiate passing param values as extra arguments + short r2 = 1+rani(10); + long r3 = rani(1000); + M4 m4{Params{r2,r3}, fun_ParamInOut}; // parameters directly given by-value + auto& [p0,p1] = m4.param; + CHECK (p0 == r2); // parameter values exposed through manifold + CHECK (p1 == r3); + + // wire-in existing buffers for this example + m4.inBuff.createAt (0, buffI0); + m4.inBuff.createAt (1, buffI1); + m4.inBuff.createAt (2, buffI2); + m4.outBuff.createAt(0, buffO1); + CHECK (*ia0 == r1 ); // existing values in the buffers.... + CHECK (*ia1 == r1+1); + CHECK (*ia2 == -55 ); /////////////////////////////////////////////////////OOO should be -22 + CHECK (*oa1 == -55 + *ia0+*ia1+*ia2); ///////////////////////////////////////////OOO should be -33 + + m4.connect(); + m4.invoke(); // processing combines input buffers with parameters + CHECK (*oa1 == (r2+r3) * (r1 + r1+1 -55)); /////////////////////////////////////OOO should be -22 + + + //______________________________________ + // Example-5: simple parameter and output + auto fun_singleParamOut = [&](short param, Buffer* buff) { *buff = param-1; }; + using M5 = FeedManifold; + CHECK (not M5::hasInput()); + CHECK ( M5::hasParam()); + CHECK (1 == M5::FAN_P); + CHECK (0 == M5::FAN_I); + CHECK (1 == M5::FAN_O); + CHECK (showType() == "tuple<>"_expect); + CHECK (showType() == "long *"_expect); + CHECK (showType() == "short"_expect); + + // instantiate, directly passing param value + M5 m5{r2, fun_singleParamOut}; + // wire with one output buffer + m5.outBuff.createAt(0, buffO1); + m5.connect(); + CHECK (m5.param == r2); // the parameter value passed to the ctor +// CHECK (m5.inArgs ); // does not compile because storage field is not provided + CHECK (*m5.outArgs == *oa1); // still previous value sitting in the buffer... + + m5.invoke(); + CHECK (*oa1 == r2 - 1); // processing has placed result based on param into output buffer } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 11c8af763..f9e5a67eb 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -26196,9 +26196,7 @@ - - - +

und zwar, genauer gesagt, eine Konsequenz der Entscheidung, nicht nur einen ViewHook, sondern ein ViewHooked zu machen. Ich hab die Beziehung in's Strukturelle hinen genommen. Damit muß auch die Wurzel diese Struktur unterstützen, und damit wird an dieser Stelle die Abstraktion undicht. @@ -26850,9 +26848,7 @@ - - - +

Ich habe beim Upgrade auf Debian-Stretch mal nachgemessen: tatsächlich hat mein Display 94dpi. Demnach wäre der andere weithin übliche Wert von 96dpi präziser. Jedoch bin ich nach mehreren Experimenten bei 90dpi geblieben, da für mich so die Schriftarten die „richtige Größe“ haben — das mag auch daran liegen, daß ich leicht kurzsichtig bin und typischeweise etwas näher am Bildschirm sitze. @@ -27481,9 +27477,7 @@ - - - +

...aber der Code schaut komplett richtig aus; man muß ja die Differenz zwischen oberem und unterem Anker als Höhe ansetzen, und man muß dabei bedenken, daß die Zeichnung des Balkens dann skaliert wird, jedoch nach Skalierung genau bis zum unteren Anker reichen soll (und eben dieses funktioniert nicht) @@ -28069,9 +28063,7 @@ - - - +

daraus absolute Font-Size und weiter wie [Fall-1] @@ -29182,9 +29174,7 @@ - - - +

sollten z.B. die Konstruktor-Funktionen nicht unmittelbar mit definiert werden? @@ -30387,9 +30377,7 @@ - - - +

Beispiel: @@ -31389,9 +31377,7 @@ - - - +

WidgetPath::~WidgetPath() noexcept @@ -32585,9 +32571,7 @@ - - - +

da wird ggfs eine neue CSS-Kaskade erstellt, oder die bestehende Kaskade erweitert. @@ -34027,9 +34011,7 @@ - - - +

oder umgekeht.... @@ -34939,9 +34921,7 @@ - - - +

Lösung: Interface DisplayMetric im CanvasHook @@ -35300,9 +35280,7 @@ - - - +

...da das ElementBoxWidget nicht die ownership für sein Kind-Widget (den ContentRenderer) übernimmt; daher muß das Kind länger leben als das ElementBoxWidget, aber genau das ist nicht gewährleistet, wenn das Clip-Delegate von ElementBoxWidget erbt. @@ -36009,9 +35987,7 @@ - - - +

erinnere mich, diverse Mechanismen gesehen zu haben, @@ -88412,10 +88388,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - + + @@ -88428,8 +88404,12 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + + + @@ -88796,6 +88776,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + +
@@ -92097,16 +92081,16 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
...und die neueren Compiler können sich auch nicht beschweren, daß wir anonyme Typen in die Storage binden, und obendrein sind so die ganzen Meta-Definitionen wirklich downstream nicht mehr sichtbar

- -
+
- - - + + + + @@ -92121,10 +92105,64 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + + + + + + + + + +

+ enable_if_hasParam : verwendet statt Type statt type +

+ + +
+ +
+ + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -92187,8 +92225,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
beide Funktoren müssen in den Typ eingehen

- - +
@@ -92212,8 +92249,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
wenngleich auch lediglich indirekt, denn der sichtbare Parameter ist FUN, der Typ der Processing-Function

- - +
@@ -92229,8 +92265,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
neuer Name: FeedPrototype

- - +
@@ -92245,8 +92280,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
Wenn es also ein Param-Tupel gibt, entscheidet es sich im Aufruf-Kontext, ob dafür ein Init-Wert geliefert wird. Wenn nicht, dann findet Default-Initialisierung statt. Ganz einfach

- - +
@@ -92352,8 +92386,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
⟹ geht in eine Builder-Klasse FeedPrototype<FUN,PAM>

- - +
@@ -92722,8 +92755,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
in den Turnout wird ein Prototyp der FeedManifold eingebettet

- - + @@ -92737,8 +92769,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
das heißt, idealerweise ist dieses ganze komplexe Konfigurations-Thema optional und transparent

- - +
@@ -92794,8 +92825,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -92806,8 +92837,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
bekommt ggfs. ein zusätzliches Parameter-Tupel als ctor-Wert

- - +
@@ -92827,16 +92857,30 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + - + + + + + + + + + + + + + + @@ -137811,7 +137855,12 @@ std::cout << tmpl.render({"what", "World"}) << s - + + + + + +