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`
This commit is contained in:
Fischlurch 2024-12-20 22:05:23 +01:00
parent 72703f70c9
commit 0ccc2d0b89
3 changed files with 717 additions and 168 deletions

View file

@ -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 <utility>
//#include <array>
#include <tuple>
@ -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<class PF>
using Res = typename _Fun<PF>::Ret;
template<class PF>
using SigP = add_pointer_t<typename _Fun<PF>::Sig>;
template<class PF>
using isSuitable = std::is_constructible<Param, Res<PF>>;
using isSuitable = is_constructible<Param, Res<PF>>;
template<class PF>
using isConfigurable = std::is_constructible<bool, PF&>;
using isConfigurable = __and_<is_constructible<bool, PF&>
,__not_<is_convertible<PF&, SigP<PF>>>
>; // non-capture-λ are convertible via function-ptr to bool
// yet we want to detect a real built-in bool-conversion.
template<class PF>
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<FUN>::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 FUN, class PAM>
class FeedPrototype
@ -574,21 +583,32 @@ namespace engine {
static constexpr bool hasParamFun() { return _Trait::template isParamFun<PAM>(); }
static constexpr bool canActivate() { return _Trait::template canActivate<PAM>(); }
/**
* 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<typename PFX>
using Adapted = FeedPrototype<FUN, PFX>;
using Adapted = FeedPrototype<FUN,PFX>;
/**
* Cross-Builder to add configuration with a given parameter-functor.
@ -601,11 +621,37 @@ namespace engine {
*/
template<typename PFX>
auto
moveAdapted (PFX otherParamFun)
moveAdapted (PFX otherParamFun =PFX{})
{
using OtherParamFun = std::decay_t<PFX>;
return Adapted<OtherParamFun>{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<FUN>
,is_copy_constructible<PAM>>, 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<typename PFX =PAM, typename = enable_if<std::is_assignable<PAM,PFX>>>
FeedPrototype&&
assignParamFun (PFX&& paramFunDef =PAM{})
{
paramFun_ = forward<PFX> (paramFunDef);
return move(*this);
}
};

View file

@ -332,6 +332,9 @@ namespace test {
BufferProvider& provider = DiagnosticBufferProvider::build();
BuffHandle buff = provider.lockBufferFor<Buffer> (-55);
//_______________________________________
// Case-1: Prototype without param-functor
auto fun_singleParamOut = [](short param, Buffer* buff) { *buff = param-1; };
using M1 = FeedManifold<decltype(fun_singleParamOut)>;
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<decltype(fun_paramSimple)>;
@ -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<ParamSig>;
// a Prototype to hold such a function...
using P1F = P1::Adapted<ParamFunction>;
CHECK ( P1F::hasParam());
CHECK ( P1F::hasParamFun());
CHECK ( P1F::canActivate());
P1F p1f = p1x.clone() // if (and only if) the embedded functors allow clone-copy
.moveAdapted<ParamFunction>(); // 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<bool,ParamFunction>::value));
CHECK ( (std::is_constructible<bool,LambdaSimple >::value));
CHECK (not (std::is_constructible<bool,LambdaCapture>::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<ParamFunction,FunPtr>::value));
CHECK ( (std::is_convertible<LambdaSimple ,FunPtr>::value));
CHECK (not (std::is_convertible<LambdaCapture,FunPtr>::value));
// ..which allows to distinguish these cases..
//
CHECK (true == _ParamFun<P1::ProcFun>::isConfigurable<ParamFunction>::value);
CHECK (false == _ParamFun<P1::ProcFun>::isConfigurable<LambdaSimple >::value);
CHECK (false == _ParamFun<P1::ProcFun>::isConfigurable<LambdaCapture>::value);
}
};

File diff suppressed because it is too large Load diff