diff --git a/src/lib/meta/function.hpp b/src/lib/meta/function.hpp index e22e1544b..abefd1159 100644 --- a/src/lib/meta/function.hpp +++ b/src/lib/meta/function.hpp @@ -143,6 +143,12 @@ namespace meta{ : _Fun { }; + /** Specialisation to strip spurious const for type analysis */ + template + struct _Fun + : _Fun + { }; + /** Specialisation when using a function reference */ template struct _Fun diff --git a/src/lib/meta/util.hpp b/src/lib/meta/util.hpp index 52c112eb5..9165a5d22 100644 --- a/src/lib/meta/util.hpp +++ b/src/lib/meta/util.hpp @@ -135,6 +135,12 @@ namespace meta { + /** helper to prevent a template constructor from shadowing inherited copy ctors */ + template + using disable_if_self = disable_if, SELF>>; + + + /** detect possibility of a conversion to string. * Naive implementation, which first attempts to build a string instance by * implicit conversion, and then tries to invoke an explicit string conversion. diff --git a/src/lib/random-draw.hpp b/src/lib/random-draw.hpp index 827081c81..1e660dc69 100644 --- a/src/lib/random-draw.hpp +++ b/src/lib/random-draw.hpp @@ -69,9 +69,23 @@ ** specialisations for various function signatures. These adaptors are used to ** conform any mapping function and thus allow to simplify or widen the ** possible configurations at usage site. + ** + ** ## Copy inhibition + ** The configuration of the RandomDraw processing pipeline makes heavy use of function composition + ** and adaptation to handle a wide selection of input types and usage patterns. Unfortunately this + ** requires to like the generated configuration-λ to the object instance (capturing by reference); + ** not allowing this would severely limit the possible configurations. This implies that an object + ** instance must not be moved anymore, once the processing pipeline has been configured. And this + ** in turn would severely limit it's usage in a DSL. As a compromise, RandomDraw relies on + ** [lazy on-demand initialisation](\ref lazy-init.hpp): as long as the processing function has + ** not been invoked, the internal pipeline is unconfigured, and the object can be moved and copied. + ** Once invoked, the prepared configuration is assembled and the function »engaged«; from this point + ** on, any attempt to move or copy the object will throw an exception, while it is still possible + ** to assign other RandomDraw instances to this object. ** @todo 11/2023 This is a first draft and was extracted from an actual usage scenario. ** It remains to be seen if the scheme as defined is of any further use henceforth. ** @see RandomDraw_test + ** @see lazy-init.hpp ** @see TestChainLoad_test ** @see SchedulerStress_test */ @@ -82,7 +96,7 @@ #include "lib/error.h" -#include "lib/nocopy.hpp" +#include "lib/lazy-init.hpp" #include "lib/meta/function.hpp" #include "lib/meta/function-closure.hpp" #include "lib/util-quant.hpp" @@ -95,6 +109,7 @@ namespace lib { namespace err = lumiera::error; + using lib::meta::disable_if_self; using lib::meta::_Fun; using std::function; using std::forward; @@ -165,9 +180,11 @@ namespace lib { */ template class RandomDraw - : public POL - , util::MoveOnly + : public LazyInit { + using Lazy = LazyInit; + using Disabled = typename Lazy::MarkDisabled; + using Sig = typename _Fun::Sig; using Fun = function; using Tar = typename _Fun::Ret; @@ -176,11 +193,6 @@ namespace lib { Tar minResult_{Tar::minVal()}; ///< minimum result val actually to produce > min double probability_{0}; ///< probability that value is in [min .. max] \ neutral - /** due to function binding, this object - * should actually be non-copyable, - * which would defeat DLS use */ - bool fixedLocation_{false}; - /** @internal quantise into limited result value */ Tar @@ -253,8 +265,10 @@ namespace lib { * Drawing is _disabled_ by default, always yielding "zero" */ RandomDraw() - : POL{adaptOut(POL::defaultSrc)} - { } + : Lazy{Disabled()} + { + mapping (POL::defaultSrc); + } /** * Build a RandomDraw by attaching a value-processing function, @@ -265,32 +279,14 @@ namespace lib { * - `void(RandomDraw&, ...)` : the function manipulates the current * instance, to control parameters dynamically, based on input. */ - template + template> RandomDraw(FUN&& fun) - : POL{} + : Lazy{Disabled()} , probability_{1.0} - , fixedLocation_{true} // prevent move-copy (function binds to *this) { mapping (forward (fun)); } - /** - * Object can be move-initialised, unless - * a custom processing function was installed. - */ - RandomDraw(RandomDraw && rr) - : POL{adaptOut(POL::defaultSrc)} - , maxResult_{move(rr.maxResult_)} - , minResult_{move(rr.minResult_)} - , probability_{rr.probability_} - , fixedLocation_{rr.fixedLocation_} - { - if (fixedLocation_) - throw err::State{"RandomDraw was already configured with a processing function, " - "which binds into a fixed object location. It can not be moved anymore." - , err::LUMIERA_ERROR_LIFECYCLE}; - } - /* ===== Builder API ===== */ @@ -325,8 +321,12 @@ namespace lib { mapping (FUN&& fun) { Fun& thisMapping = static_cast (*this); - thisMapping = adaptOut(adaptIn(std::forward (fun))); - fixedLocation_ = true; // function will bind to *this + Lazy::installInitialiser (thisMapping + ,[theFun = forward (fun)] + (RandomDraw* self) + { // when lazy init is performed.... + self->installAdapted (theFun); + }); return move (*this); } @@ -345,6 +345,16 @@ namespace lib { + /** @internal adapt a function and install it to control drawing and mapping */ + template + void + installAdapted (FUN&& fun) + { + Fun& thisMapping = static_cast (*this); + thisMapping = adaptOut(adaptIn(std::forward (fun))); + } + + /** @internal adapt input side of a given function to conform to the * global input arguments as defined in the Policy base function. * @return a function pre-fitted with a suitable Adapter from the Policy */ @@ -359,7 +369,6 @@ namespace lib { using Sig = typename _Fun::Sig; using Args = typename _Fun::Args; using BaseIn = typename lib::meta::_Fun::Args; - using Adaptor = typename POL::template Adaptor; if constexpr (std::is_same_v) // function accepts same arguments as this RandomDraw @@ -369,7 +378,10 @@ namespace lib { // function wants to manipulate *this dynamically... return adaptIn (applyFirst (forward (fun), *this)); else - return Adaptor::build (forward (fun)); + {// attempt to find a custom adaptor via Policy template + using Adaptor = typename POL::template Adaptor; + return Adaptor::build (forward (fun)); + } } /** @internal adapt output side of a given function, allowing to handle it's results @@ -429,7 +441,7 @@ namespace lib { getCurrMapping() { Fun& currentProcessingFunction = *this; - if (fixedLocation_ and currentProcessingFunction) + if (currentProcessingFunction) return Fun{currentProcessingFunction}; else return Fun{adaptOut(POL::defaultSrc)}; diff --git a/tests/library/lazy-init-test.cpp b/tests/library/lazy-init-test.cpp index 523ada2a2..83ed1d05d 100644 --- a/tests/library/lazy-init-test.cpp +++ b/tests/library/lazy-init-test.cpp @@ -41,11 +41,8 @@ namespace test{ using util::isSameObject; using lib::meta::isFunMember; - using lib::meta::disable_if; + using lib::meta::disable_if_self; using err::LUMIERA_ERROR_LIFECYCLE; - - using std::is_same; - using std::remove_reference_t; using std::make_unique; @@ -360,7 +357,7 @@ namespace test{ installInitialiser(fun, buildInit([](int){ return 0; })); } // prevent this ctor from shadowing the copy ctors //////TICKET #963 - template, LazyDemo>>> + template> LazyDemo (FUN&& someFun) : LazyInit{MarkDisabled()} , fun{} diff --git a/tests/vault/gear/test-chain-load-test.cpp b/tests/vault/gear/test-chain-load-test.cpp index c990754c4..14aef8770 100644 --- a/tests/vault/gear/test-chain-load-test.cpp +++ b/tests/vault/gear/test-chain-load-test.cpp @@ -222,7 +222,7 @@ namespace test { { auto graph = TestChainLoad<32>{}; - graph.expansionRule([](size_t h,double){ return Cap{8, h%16, 63}; }) + graph.expansionRule(graph.rule().probability(0.5).maxVal(4)) .buildToplolgy() .printTopologyDOT(); } diff --git a/tests/vault/gear/test-chain-load.hpp b/tests/vault/gear/test-chain-load.hpp index 64748df3f..9d416f3ff 100644 --- a/tests/vault/gear/test-chain-load.hpp +++ b/tests/vault/gear/test-chain-load.hpp @@ -49,7 +49,10 @@ ** predecessor nodes; additionally, new chains can be spawned (to simulate the effect of ** data loading Jobs without predecessor). The computation always begins with the _root ** node_, proceeds over the node links and finally leads to the _top node,_ which connects - ** all chains of computation, leaving no dead end. + ** all chains of computation, leaving no dead end. The probabilistic rules controlling the + ** topology can be configured using the lib::RandomDraw component, allowing either just + ** to set a fixed probability or to define elaborate dynamic configurations based on the + ** graph height or node connectivity properties. ** ** ## Usage ** A TestChainLoad instance is created with predetermined maximum fan factor and a fixed @@ -68,6 +71,7 @@ ** ** @see TestChainLoad_test ** @see SchedulerStress_test + ** @see random-draw.hpp */ @@ -131,49 +135,14 @@ namespace test { namespace { // Default definitions for topology generation const size_t DEFAULT_FAN = 16; const size_t DEFAULT_SIZ = 256; - - const double CAP_EPSILON = 0.001; ///< tiny bias to absorb rounding problems } - /** - * Helper to cap and map to a value range. - */ - struct Cap - { - double lower{0}; - double value{0}; - double upper{1}; - - Cap (int i) : value(i){ } - Cap (size_t s) : value(s){ } - Cap (double d) : value{d}{ } - - template - Cap (NL l, NV v, NU u) - : lower(l) - , value(v) - , upper(u) - { } - - size_t - mapped (size_t scale) - { - if (value==lower) - return 0; - value -= lower; - value /= upper-lower; - value *= scale; - value += CAP_EPSILON; - value = limited (size_t(0), value, scale); - return size_t(value); - } - }; - /** + /***********************************************************************//** * A Generator for synthetic Render Jobs for Scheduler load testing. - * Allocates a fixed set of #numNodes and generates connecting toplology. + * Allocates a fixed set of #numNodes and generates connecting topology. * @tparam maxFan maximal fan-in/out from a node, also limits maximal parallel strands. * @see TestChainLoad_test */ @@ -282,13 +251,12 @@ namespace test { private: using NodeTab = typename Node::Tab; using NodeStorage = std::array; - using CtrlRule = std::function; std::unique_ptr nodes_; - CtrlRule seedingRule_ {[](size_t, double){ return 0; }}; - CtrlRule expansionRule_{[](size_t, double){ return 0; }}; - CtrlRule reductionRule_{[](size_t, double){ return 0; }}; + Rule seedingRule_ {}; + Rule expansionRule_{}; + Rule reductionRule_{}; public: TestChainLoad() @@ -313,24 +281,26 @@ namespace test { /* ===== topology control ===== */ + static Rule rule() { return Rule(); } + TestChainLoad&& - seedingRule (CtrlRule r) + seedingRule (Rule&& r) { - seedingRule_ = r; + seedingRule_ = move(r); return move(*this); } TestChainLoad&& - expansionRule (CtrlRule r) + expansionRule (Rule&& r) { - expansionRule_ = r; + expansionRule_ = move(r); return move(*this); } TestChainLoad&& - reductionRule (CtrlRule r) + reductionRule (Rule&& r) { - reductionRule_ = r; + reductionRule_ = move(r); return move(*this); } @@ -346,7 +316,11 @@ namespace test { *next{&b}; // the next set of nodes connected to current Node* node = &nodes_->front(); size_t level{0}; - size_t expectedLevel = max (1u, numNodes/maxFan); // guess, typically too low + + // local copy of all rules (they are non-copyable, once engaged) + Rule expansionRule = expansionRule_; + Rule reductionRule = reductionRule_; + Rule seedingRule = seedingRule_; // prepare building blocks for the topology generation... auto moreNext = [&]{ return next->size() < maxFan; }; @@ -358,14 +332,9 @@ namespace test { n->level = level; return n; }; - auto height = [&](double level) + auto apply = [&](Rule& rule, Node* n) { - return level/expectedLevel; - }; - auto apply = [&](CtrlRule& rule, Node* n) - { - Cap param = rule (n->hash, height(level)); - return param.mapped (maxFan); + return rule(n); }; addNode(); // prime next with root node @@ -381,8 +350,8 @@ namespace test { for (Node* o : *curr) { // follow-up on all Nodes in current level... o->calculate(); - size_t toSeed = apply (seedingRule_, o); - size_t toExpand = apply (expansionRule_,o); + size_t toSeed = apply (seedingRule, o); + size_t toExpand = apply (expansionRule,o); while (0 < toSeed and spaceLeft()) { // start a new chain from seed Node* n = addNode(); @@ -398,7 +367,7 @@ namespace test { if (not toReduce) { // carry-on chain from o r = spaceLeft()? addNode():nullptr; - toReduce = apply (reductionRule_, o); + toReduce = apply (reductionRule, o); } else --toReduce; @@ -503,8 +472,9 @@ namespace test { */ template class TestChainLoad::NodeControlBinding - : protected std::function + : public std::function { + protected: /** by default use Node-hash directly as source of randomness */ static size_t defaultSrc (Node* node) diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 1c4e20a50..461008de1 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -96262,9 +96262,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + @@ -98410,8 +98410,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -98439,11 +98439,14 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + +
- + @@ -98465,6 +98468,50 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + +

+ löst das Problem fast... +

+ +
+ + + + + +

+ nachdem man nämlich buildTopology() aufgerufen hat, +

+

+ explodiert TestChainLoad am Ende der DSL-Methode. +

+

+ Denn da steht ganz verträumt... +

+

+ return move(*this) +

+ +
+
+ + + + + + + +

+ nun gut: dann verwendet buildTopology() eben lokale Kopien +

+ +
+
+ +