From 0ccc2d0b89f3ecf91e6b7927aecef50a78fdd2f4 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 20 Dec 2024 22:05:23 +0100 Subject: [PATCH] Invocation: complete rework of the `FeedManifold` This completes a deep and very challenging series of refactorings with the goal to introduce support for **Parameters** into the Render invocation code. A secondary goal was to re-assess the prototype code written thus far and thereby to establish a standard processing scheme. With these rearrangements, the `FeedManifold` is poised to act as **central link** between the Render-Node invocation code and the actual Media-Processing code in a Library Plug-in Up to this point, the existing code from the Prototype is still compilable, yet broken. The __next step__ will be to harness the possible simplifications and enable the actual invocation to work on arbitrary combinations of buffers and parameters, enabled by the **compile-time use-case classification** now provided by `FeedManifold` --- src/steam/engine/feed-manifold.hpp | 84 ++- tests/core/steam/engine/node-base-test.cpp | 75 ++- wiki/thinkPad.ichthyo.mm | 726 ++++++++++++++++----- 3 files changed, 717 insertions(+), 168 deletions(-) diff --git a/src/steam/engine/feed-manifold.hpp b/src/steam/engine/feed-manifold.hpp index 40a4f5a9b..8f1f034e0 100644 --- a/src/steam/engine/feed-manifold.hpp +++ b/src/steam/engine/feed-manifold.hpp @@ -80,7 +80,6 @@ #include "lib/error.hpp" #include "lib/nocopy.hpp" -//#include "steam/engine/proc-node.hpp" #include "steam/engine/buffhandle.hpp" #include "lib/uninitialised-storage.hpp" #include "lib/meta/function.hpp" @@ -91,8 +90,6 @@ #include "lib/test/test-helper.hpp" //#include "lib/several.hpp" -//#include -//#include #include @@ -101,14 +98,10 @@ namespace steam { namespace engine { -// using std::pair; -// using std::vector; - namespace {// Introspection helpers.... using lib::meta::_Fun; using lib::meta::enable_if; - using lib::meta::disable_if_self; using lib::meta::is_UnaryFun; using lib::meta::is_BinaryFun; using lib::meta::is_TernaryFun; @@ -117,14 +110,14 @@ namespace engine { using lib::meta::ElmTypes; using lib::meta::NullType; using lib::meta::Tagged; - using lib::meta::TySeq; - using std::declval; using std::is_pointer; using std::is_reference; - using std::remove_reference_t; + using std::is_convertible; + using std::is_constructible; + using std::is_copy_constructible; using std::remove_pointer_t; using std::tuple_element_t; - using std::void_t; + using std::add_pointer_t; using std::__and_; using std::__not_; @@ -267,13 +260,17 @@ namespace engine { template using Res = typename _Fun::Ret; + template + using SigP = add_pointer_t::Sig>; template - using isSuitable = std::is_constructible>; + using isSuitable = is_constructible>; template - using isConfigurable = std::is_constructible; - + using isConfigurable = __and_ + ,__not_>> + >; // non-capture-λ are convertible via function-ptr to bool + // yet we want to detect a real built-in bool-conversion. template static bool isActivated (PF const& paramFun) @@ -547,6 +544,18 @@ namespace engine { * for each Node invocation. * @tparam FUN type of the data processing-functor * @tparam PAM type of an optional parameter-setup functor (defaults to deactivated) + * + * # Usage + * The Prototype is typically first built solely from a processing-functor. + * It can even be constructed as type only, by `FeedManifold::Prototype`. + * In this form, any parameter handling will be _disabled._ However, by adding a + * parameter-functor with the **cross-builder-API**, a _new instance_ of the prototype + * is created _as a replacement_ of the old one (note: we move the processing functor). + * This adds a parameter-functor to the configuration, which will then be invoked + * _whenever a new FeedManifold instance_ [is created](\ref #createFeed); the result of + * this parameter-functor invocation should be a parameter value, which can be passed + * into the constructor of FeedManifold, together with a copy of the proc-functor. + * @see NodeBase_test::verify_FeedPrototype() */ template class FeedPrototype @@ -574,21 +583,32 @@ namespace engine { static constexpr bool hasParamFun() { return _Trait::template isParamFun(); } static constexpr bool canActivate() { return _Trait::template canActivate(); } - /** - * build suitable Feed(Manifold) for processing Node invocation + /** @return runtime test: there is actually usable parameter-functor to invoke? */ + bool isActivated() const { return _Trait::isActivated(paramFun_); } + + + + /************************************************************//** + * build suitable Feed(Manifold) for processing a Node invocation */ Feed createFeed (TurnoutSystem& turnoutSys) { if constexpr (hasParamFun()) - if (_Trait::isActivated(paramFun_)) + if (isActivated()) return Feed(paramFun_(turnoutSys), procFun_); return Feed{procFun_}; } + + /* ======= cross-builder API ======= */ + + using ProcFun = FUN; + using ParamFun = PAM; + template - using Adapted = FeedPrototype; + using Adapted = FeedPrototype; /** * Cross-Builder to add configuration with a given parameter-functor. @@ -601,11 +621,37 @@ namespace engine { */ template auto - moveAdapted (PFX otherParamFun) + moveAdapted (PFX otherParamFun =PFX{}) { using OtherParamFun = std::decay_t; return Adapted{move(procFun_), move(otherParamFun)}; } + + + /** build a clone-copy of this prototype, holding the same functors + * @note possible only if both proc-functor and param-functor are copyable + */ + enable_if<__and_ + ,is_copy_constructible>, FeedPrototype> + clone() const + { + return FeedPrototype{FUN(procFun_), PAM(paramFun_)}; + } + + /** + * Change the current parameter-functor setup by assigning some value. + * @param paramFunDef something that is assignable to \a PAM + * @note possible only if the param-functor accepts this kind of assignment; + * especially when \a PAM was defined to be a `std::function`, then + * the param-functor can not only be reconfigured, but also disabled. + */ + template>> + FeedPrototype&& + assignParamFun (PFX&& paramFunDef =PAM{}) + { + paramFun_ = forward (paramFunDef); + return move(*this); + } }; diff --git a/tests/core/steam/engine/node-base-test.cpp b/tests/core/steam/engine/node-base-test.cpp index 36e200ae0..f81abdd3c 100644 --- a/tests/core/steam/engine/node-base-test.cpp +++ b/tests/core/steam/engine/node-base-test.cpp @@ -332,6 +332,9 @@ namespace test { BufferProvider& provider = DiagnosticBufferProvider::build(); BuffHandle buff = provider.lockBufferFor (-55); + + //_______________________________________ + // Case-1: Prototype without param-functor auto fun_singleParamOut = [](short param, Buffer* buff) { *buff = param-1; }; using M1 = FeedManifold; using P1 = M1::Prototype; @@ -357,8 +360,8 @@ namespace test { - //_____________________________________ - // Reconfigure to attach a param-functor + //_____________________________________________ + // Case-2: Reconfigure to attach a param-functor long rr{11}; // ▽▽▽▽ Note: side-effect auto fun_paramSimple = [&](TurnoutSystem&){ return rr += 1+rani(100); }; using P1x = P1::Adapted; @@ -395,6 +398,74 @@ namespace test { CHECK (calcResult == r1 - 1); // FeedManifold on the stack, since invocations are m1.invoke(); // performed concurrently, each with its own set of CHECK (calcResult == 0 - 1); // buffers and parameters. + + + + //_______________________________ + // Case-3: Integrate std::function + using ParamSig = short(TurnoutSystem&); + using ParamFunction = std::function; + // a Prototype to hold such a function... + using P1F = P1::Adapted; + CHECK ( P1F::hasParam()); + CHECK ( P1F::hasParamFun()); + CHECK ( P1F::canActivate()); + + P1F p1f = p1x.clone() // if (and only if) the embedded functors allow clone-copy + .moveAdapted(); // then we can fork-off and then adapt a cloned prototype + + // Need to distinguish between static capability and runtime state... + CHECK (not p1 .canActivate()); // Case-1 had no param functor installed... + CHECK (not p1 .isActivated()); // and thus also can not invoke such a functor at runtime + CHECK (not p1x.canActivate()); // Case-2 has a fixed param-λ, which can not be activated/deactivated + CHECK ( p1x.isActivated()); // yet at runtime this functor is always active and callable + CHECK ( p1f.canActivate()); // Case-3 was defined to hold a std::function, which thus can be toggled + CHECK (not p1f.isActivated()); // yet in current runtime configuration, the function is empty + + // create a FeedManifold instance from this prototype + M1 m1f1 = p1f.createFeed(turSys); // no param-functor invoked, + CHECK (m1f1.param == short{}); // so this FeedManifold will use the default-constructed parameter + + // but since std::function is assignable, we can activate it... + CHECK (not p1f.isActivated()); + p1f.assignParamFun ([](TurnoutSystem&){ return 47; }); + CHECK ( p1f.isActivated()); + M1 m1f2 = p1f.createFeed(turSys); // ◁————————— param-functor invoked here + CHECK (m1f2.param == 47); // ...surprise: we got number 47... + p1f.assignParamFun(); + CHECK (not p1f.isActivated()); // can /deactivate/ it again... + M1 m1f3 = p1f.createFeed(turSys); // so no param-functor invoked here + CHECK (m1f3.param == short{}); + + // done with buffer + buff.release(); + + + + //_____________________________________ + // Addendum: type conversion intricacies + auto lambdaSimple = [ ](TurnoutSystem&){ return short(47); }; + auto lambdaCapture = [&](TurnoutSystem&){ return short(47); }; + using LambdaSimple = decltype(lambdaSimple); + using LambdaCapture = decltype(lambdaCapture); + CHECK ( (std::is_constructible::value)); + CHECK ( (std::is_constructible::value)); + CHECK (not (std::is_constructible::value)); + // Surprise! a non-capture-λ turns out to be bool convertible, + // which however is also true for std::function, + // yet for quite different reasons: While the latter has a + // built-in conversion operator to detect /inactive/ state, + // the simple λ decays to a function pointer, which makes it + // usable as implementation for plain-C callback functions. + using FunPtr = short(*)(TurnoutSystem&); + CHECK (not (std::is_convertible::value)); + CHECK ( (std::is_convertible::value)); + CHECK (not (std::is_convertible::value)); + // ..which allows to distinguish these cases.. + // + CHECK (true == _ParamFun::isConfigurable::value); + CHECK (false == _ParamFun::isConfigurable::value); + CHECK (false == _ParamFun::isConfigurable::value); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 433b51247..fbfd0a054 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -27061,9 +27061,7 @@ - - - +

Das UI-Design ist vom konkreten Einzelfall-Nutzen ausgegangen, nicht von einem stimmigen Gesamtentwurf. Alles ist auf das Konstruieren von 3D-Bauteilen in typischem handlichen Format ausgelegt: eine kleine Zahl an Einzelobjekten im cm-Bereich.... @@ -27825,9 +27823,7 @@ - - - +

...denn es muß die obere und die untere Kappe gezeichnet werden, und deren Höhe ergibt sich qua Konstruktion aus der Basisbreite @@ -27838,9 +27834,7 @@ - - - +

➩ Skala für gesamte Klammer verkleinern @@ -28465,9 +28459,7 @@ - - - +

Problem: muß immer sichtbar sein @@ -28637,9 +28629,7 @@ - - - +

nämlich hier der Visitor, der den Aufruf letztlich emfpängt @@ -29213,9 +29203,7 @@ - - - +

sondern die Argumente sind in einem Tuple eingewickelt, @@ -30595,9 +30583,7 @@ - - - +

grundsätzlich: es zeichnet der Canvas @@ -31109,9 +31095,7 @@ - - - +

...zum Beispiel um einen "Wall" auch expressiv zu schattieren @@ -32421,9 +32405,7 @@ - - - +

weil er dann mehr oder weniger hartverdrahtet ist @@ -33463,9 +33445,7 @@ - - - +

dann muß ich aber die zwei Bezugssyteme wirklich auseinanderhalten @@ -34090,9 +34070,7 @@ - - - +

entweder der ClipPresenter delegiert explizit an einen Service des Display-Managers @@ -34685,9 +34663,7 @@ - - - +

Stand 10/22 : Fragen grundsätzlich geklärt @@ -88796,13 +88772,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + - - - + @@ -88814,6 +88790,35 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + + + +

+ das genügt mir hier — gehe davon aus daß es dann mit Tupeln auch funktioniert +

+ +
+ +
+ + + @@ -91526,11 +91531,19 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + +

+ Processing-Funktion kann Parameter verlangen +

+ +
+ - - + + @@ -91580,9 +91593,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - + + + + @@ -91631,7 +91645,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -91671,10 +91685,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - + + @@ -91797,8 +91811,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + @@ -91846,6 +91861,17 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + +

+ jetzt bin ich bereits so weit gekommen, und hab den größten Teil der Komplexität in die FeedManifold verlagert.... +

+ + +
+
@@ -91870,7 +91896,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -91881,13 +91908,25 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + +

+ Dieser Trait erbringt keine so hohe Abstraktiosleistung wie _ProcFun; im Grunde könnte man auch allen diesen Code direkt in FeedPrototype schieben — es ist also mehr ein Vekikel zur Code-Organisation, räumt die low-level-Details weg und macht sie auch leichter testbar +

+ + +
+
- - + + @@ -92011,8 +92050,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -92089,8 +92128,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -92101,6 +92140,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ @@ -92218,11 +92258,22 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - + + + + +

+ Zwar war der Anlaß für diesen sehr kompleten Umbau ein Anderer, aber ich habe bei der Gelegenheit gerne auch noch eine komplette Überarbeitung des ersten Entwurfs gemacht. In diesem konnte und wollte ich mich zu Beginn noch nicht festlegen, und habe daher Extension-Points auf mehreren Ebenen vorgesehen; im Grunde bin ich mit diesem Ansatz stecken geblieben und habe dann einen »Durchmasch« per Prototyping gemacht. In dieser ersten Lösung war teilweise noch eine Steuerung aus dem Weaving-Pattern vorgesehen (in Anlehung an den Ausgangspunkt, den ersten Entwurf von 2009) — aber tatsächlich in der Implementierung auf der Strecke geblieben. Der Code war also so noch nicht ausreichend, er konnte nur einheitliche Buffer in wenigen, festen Konfigurationen handhaben, und eben (das war der Anlaß) überhaupt keine Parameter +

+ +
+ +
+ + @@ -92234,8 +92285,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -92266,7 +92317,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -92308,7 +92360,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -92317,8 +92369,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

+ +
- + @@ -92356,30 +92410,76 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + + + - + + + + + + + + + + + + + + + +

+ prototype.moveAdapted (paramFun) ⟶ move in neue FeedPrototype-Instanz +

+ + +
- + + + + + +

+ prototype.clone() ⟶ Kopie falls beide Funktoren das erlauben +

+ + +
+
+ + + + + + + + + +
+
+
+ - - - + + + @@ -92400,8 +92500,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -92418,7 +92518,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -92446,29 +92547,186 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + - + - - + + - - + + + - + - + + + + + + + + + +

+ „glücklicherweise“ sollte man das seit C++11 auch stets so machen, was die diversen safe-bool idioms überflüssig gemacht hat +

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

+ ⟶ bool +

+
+

+ conv +

+
+

+ cons +

+
+

+ * +

+
+

+ trivial-λ +

+
+

+ +

+
+

+ +

+
+

+ +

+
+

+ capture-λ +

+
+

+ +

+
+

+ +

+
+

+ +

+
+

+ function +

+
+

+ +

+
+

+ +

+
+

+ +

+
+ +
+ +
+ + + + +

+ template<class PF> +

+

+ using SigP = add_pointer_t<typename _Fun<PF>::Sig>; +

+

+ +

+

+ template<class PF> +

+

+ using isConfigurable = __and_<std::is_constructible<bool, PF&> +

+

+                              ,__not_<std::is_convertible<PF&, SigP<PF>>> +

+

+                              >; +

+ + +
+
+
+ + + +
+ + + + + +
-
- - - + + + @@ -92514,12 +92772,14 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
Name: prototype.moveAdapted (paramFun)

- - + + + + @@ -92527,10 +92787,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - + + @@ -92562,10 +92822,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - + + + + @@ -92577,22 +92837,34 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + +
- - + + - + - + + + + +

+ die interne Logik ist so aufgebaut, daß sie sich dann durchgehend korrekt verhält, ohne daß dafür viel getan werden müßte; es muß lediglich eine Funktion sein, und kein brauchbares Ergebnis liefern +

+ + +
+ +
+ @@ -92612,7 +92884,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + @@ -92630,11 +92904,11 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - - + + + @@ -92650,10 +92924,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - + + + + @@ -92819,9 +93093,28 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + +

+ das war brutal +

+ +
+
- - + + + + + + + + @@ -92833,21 +93126,39 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + + + + + + + - - - + + + + + + + + - - - + + + + + + + - + @@ -92902,7 +93213,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -92921,6 +93232,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + +
@@ -92931,7 +93245,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -92974,20 +93288,23 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + +
- + - +
- + - - + + @@ -93015,7 +93332,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -93043,7 +93360,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -93062,15 +93379,15 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - - - - + + + + @@ -93088,7 +93405,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -93100,16 +93418,16 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - - - + + + + @@ -144239,6 +144557,119 @@ std::cout << tmpl.render({"what", "World"}) << s
+ + + + + + + + + + + + + + + + + +

+ man konnte sich nicht
zwischen zwei Auslegungen +

+

+ entscheiden +

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

+ Das bedeutet: ein Funktionspointer mit passender Signatur kann von einem λ initialisiert werden und kann dann die dahinter stehende Funktion aufrufen. Vermutlich als Kompatibilität zu C-Callbacks gedacht.... +

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

+ das ist problematisch wegen std::function +

+ +
+ + + +

+ Denn std::function kann auch ⟘ sein — genauso übrigens wie ein Funktionspointer — und es ist ein durchaus übliches Pattern, auf diese Weise eine zusätzliche Flag-Info zu transportieren +

+ +
+
+ + + + +

+ normalerweise harmlos — aber gefährlich wenn man Traits darauf aufbaut +

+ +
+ + + +

+ Solange man sich an die implizite Semantik eines nullptr hält, ist diese Konversion harmlos, wenngleich auch nicht sonderlich nützlich. Denn zwar ist ein λ grundsätzlich immer und ohne +

+

+ Einschränkungen aufrufbar, aber diesen Umstand kann man nur ausnützen, solange das Lambda keine captures hat; denn andernfalls wird es ein wirkliches Objekt mit Storage und Lebenszyklus. +

+

+ Gefährlich wird es aber, wenn man auf den Umkehrschluß aufbaut, mit der Annahme, eine bool-konvertierbare Funktion sei deaktivierbar. Also wenn man dieser Flag eine zusätzliche Bedeutung zuweist +

+ +
+
+ + + + + + + + + + @@ -144249,6 +144680,7 @@ std::cout << tmpl.render({"what", "World"}) << s +
@@ -144404,7 +144836,7 @@ std::cout << tmpl.render({"what", "World"}) << s - +