2024-07-23 03:52:44 +02:00
/*
WEAVING - PATTERN - BUILDER . hpp - build an invocation pattern for media calculations
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
Copyright ( C )
2024 , Hermann Vosseler < Ichthyostega @ web . de >
2024-07-23 03:52:44 +02:00
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
* * Lumiera * * is free software ; you can redistribute it and / or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation ; either version 2 of the License , or ( at your
option ) any later version . See the file COPYING for further details .
2024-07-23 03:52:44 +02:00
*/
/** @file weaving-pattern-builder.hpp
Invocation: work out solution for a precisely fitting allocation
Conduct in-depth analysis to handle a secondary, implementation-related
(and frankly quite challenging) concern regarding the placement of node
and port connectivity data in memory. The intention is for the low-level
model to use a custom data structure based on `lib::Several`, allowing for
flexible and compact arrangement of the connectivity descriptors within
tiled memory blocks, which can then later be discarded in bulk, whenever
a segment of the render graph is superseded. Yet since the generated
descriptors are heterogeneous and, due to virtual functions, can not be
trivially copied, the corresponding placement invocations on the
data builder API must not be mixed, but rather given in ordered strikes
and preceded by a dimensioning call to pre-reserve a bulk of storage
However, doing so directly would jeopardise the open and flexible nature
of the node builder API, thereby creating a dangerous coupling between
the implementation levels of the node graph and of prospective library
wrapper plug-ins in charge of controlling details of the graph layout.
The solution devised here entails a functional helper data structure
created temporarily within the builder API stack frames; the detailed
and local type information provided from within the library plug-in
can thereby be embedded into opaque builder functors, allowing to
delay the actual data generation up until the final builder step,
at which point the complete number and size requirements of
connectivity data is known and can be used for dimensioning.
2024-10-20 21:51:34 +02:00
* * Construction kit to establish an invocation scheme for media calculations .
2024-10-22 05:59:00 +02:00
* * Adapters and configuration is provided to invoke the actual _media processing function_
* * in accordance to a fixed _wiring scheme : _
* * - the function takes two arguments
* * - these are an array of input and output buffer pointers
* * - buffer sizes or types are assumed to be uniform over all » slots «
* * - yet the input side my use another type than the output side
* * @ todo as of 10 / 2024 , this scheme is established as prototype to explore how processing nodes
* * can be build , connected and invoked ; the expectation is however that this simple scheme
* * is suitable to adapt and handle many common cases of invoking media processing functions ,
* * because the given _functor_ is constructed within a plug - in tailored to a specific
* * media processing library ( e . g . FFmpeg ) and thus can be a lambda to forward to the
* * actual function .
2024-12-14 03:50:10 +01:00
* * @ note steam : : engine : : Turnout mixes - in the steam : : engine : : MediaWeavingPattern , which in turn
2024-10-22 05:59:00 +02:00
* * inherits from an * Invocation Adapter * given as template parameter . So this constitutes
* * an * extension point * where other , more elaborate invocation schemes could be integrated .
* *
2024-10-23 16:27:09 +02:00
* *
2024-10-22 05:59:00 +02:00
* * # Interplay of NodeBuider , PortBuilder and WeavingBuilder
* *
2024-10-23 16:27:09 +02:00
* * The steam : : engine : : WeavingBuilder defined here serves as the low - level builder and adapter
* * to prepare the wiring and invocation . The builder - API allows to setup the wiring of input
* * and output - » slots « and control some detail aspects like caching . However , without defining
* * any connections explicitly , a simple 1 : 1 wiring scheme is employed
2024-10-22 05:59:00 +02:00
* * - each _input slot_ of the function gets an input buffer , which is filled by _pulling_
* * ( i . e . invoking ) a predecessor node ( a so called » lead « ) .
* * - for each _output slot_ a buffer is allocated for the processing function to drop off
* * the calculated media data
* * - only one of these output buffers is used as actual result , while the other buffers
* * are just discarded ( but may possibly be fed to the frame cache ) .
* *
2024-10-23 16:27:09 +02:00
* * Each [ Processing Node ] ( \ ref ProcNode ) represents one specific processing functionality on a
* * logical level ; yet such a node may be able to generate several „ flavours “ of this processing ,
2024-10-22 05:59:00 +02:00
* * which are represented as * ports * on this node . Actually , each such port stands for one specific
* * setup of a function invocation , with appropriate _wiring_ of input and output connections .
* * For example , an audio filtering function may be exposed on port - # 1 for stereo sound , while
* * port - # 2 may process the left , and port - # 3 the right channel in isolation . It is entirely
* * up to the library - adapter - plug - in what processing functions to expose , and in which flavours .
* * The WeavingBuilder is used to generate a single \ ref Turnout object , which corresponds to
* * the invocation of a single port and thus one flavour of processing .
* *
2024-10-23 16:27:09 +02:00
* * At one architectural level above , the \ ref NodeBuilder exposes the ability to set up a
2024-10-22 05:59:00 +02:00
* * ProcNode , complete with several ports and connected to possibly several predecessor nodes .
2024-10-23 16:27:09 +02:00
* * Using a sequence of NodeBuilder invocations , the _processing node graph_ can be built gradually ,
* * starting from the source ( predecessors ) and moving up to the _exit nodes , _ which produce the
* * desired calculation results . The NodeBuilder offers a function to define the predecessor nodes
* * ( also designated as _lead nodes_ ) , and it offers an [ entrance point ] ( \ ref NodeBuilder : : preparePort )
* * to descend into a \ ref PortBuilder , allowing to add the port definitions for this node step by step .
2024-10-22 05:59:00 +02:00
* *
* * On the implementation level , the PortBuilder inherits from the NodeBuilder and embeds a
* * WeavingBuilder instance . Moreover , the actual parametrisations of the NodeBuilder template
* * are chained to create a _functional data structure . _ This intricate setup is necessary because
* * the actual data structure of the node graph comprises several small descriptor arrays and
* * interconnected pointers , which are all placed into consecutive chunks of memory , using a
* * custom allocator , the AllocationCluster . The lib : : Several is used as front - end to access
* * these small collections of related objects , and the associated lib : : SeveralBuilder provides
* * the low - level memory allocation and object creation functionality . The purpose of this
* * admittedly quite elaborate scheme is to generate a compact data structure , with high
* * cache locality and without wasting too much memory . Since the exact number of elements
2024-10-23 16:27:09 +02:00
* * and the size of those elements can be deduced only after the builder - API usage has
2024-10-22 05:59:00 +02:00
* * been completed , the aforementioned functional datastructure is used to collect the
* * parametrisation information for all ports , while delaying the actual object creation .
* * With this technique , it is possible to generate all descriptors or entries of one
* * kind in a single run , and placed optimally and compact into the memory allocation .
2024-07-23 03:52:44 +02:00
* *
* * @ see turnout . hpp
* * @ see node - builder . hpp
2024-12-06 23:43:18 +01:00
* * @ see NodeLink_test
2024-07-23 03:52:44 +02:00
* *
2024-10-22 05:59:00 +02:00
* * @ todo WIP - WIP - WIP as of 10 / 2024 prototyping how to build and invoke render nodes /////////////////////////TICKET #1371
2024-07-23 03:52:44 +02:00
* *
*/
# ifndef STEAM_ENGINE_WEAVING_PATTERN_BUILDER_H
# define STEAM_ENGINE_WEAVING_PATTERN_BUILDER_H
//#include "steam/common.hpp"
2024-10-26 03:03:11 +02:00
# include "lib/error.hpp"
2024-10-31 23:50:59 +01:00
# include "lib/symbol.hpp"
2024-07-23 03:52:44 +02:00
//#include "steam/engine/channel-descriptor.hpp"
//#include "vault/gear/job.h"
# include "lib/several-builder.hpp"
2024-11-02 23:54:41 +01:00
# include "steam/engine/proc-id.hpp"
2024-08-03 03:21:59 +02:00
# include "steam/engine/engine-ctx.hpp"
2024-12-14 03:50:10 +01:00
# include "steam/engine/weaving-pattern.hpp"
2024-07-24 20:29:37 +02:00
# include "steam/engine/buffer-provider.hpp"
2024-10-14 04:07:47 +02:00
# include "steam/engine/buffhandle-attach.hpp" /////////////////OOO why do we need to include this? we need the accessAs<TY>() template function
2024-10-26 03:03:11 +02:00
# include "lib/test/test-helper.hpp" ////////////////////////////OOO TODO added for test
# include "lib/format-string.hpp"
2024-07-23 03:52:44 +02:00
//#include "lib/util-foreach.hpp"
//#include "lib/iter-adapter.hpp"
//#include "lib/meta/function.hpp"
//#include "lib/itertools.hpp"
Invocation: work out solution for a precisely fitting allocation
Conduct in-depth analysis to handle a secondary, implementation-related
(and frankly quite challenging) concern regarding the placement of node
and port connectivity data in memory. The intention is for the low-level
model to use a custom data structure based on `lib::Several`, allowing for
flexible and compact arrangement of the connectivity descriptors within
tiled memory blocks, which can then later be discarded in bulk, whenever
a segment of the render graph is superseded. Yet since the generated
descriptors are heterogeneous and, due to virtual functions, can not be
trivially copied, the corresponding placement invocations on the
data builder API must not be mixed, but rather given in ordered strikes
and preceded by a dimensioning call to pre-reserve a bulk of storage
However, doing so directly would jeopardise the open and flexible nature
of the node builder API, thereby creating a dangerous coupling between
the implementation levels of the node graph and of prospective library
wrapper plug-ins in charge of controlling details of the graph layout.
The solution devised here entails a functional helper data structure
created temporarily within the builder API stack frames; the detailed
and local type information provided from within the library plug-in
can thereby be embedded into opaque builder functors, allowing to
delay the actual data generation up until the final builder step,
at which point the complete number and size requirements of
connectivity data is known and can be used for dimensioning.
2024-10-20 21:51:34 +02:00
# include "lib/util.hpp"
2024-07-23 03:52:44 +02:00
//#include <utility>
2024-07-24 20:29:37 +02:00
# include <functional>
2024-07-23 03:52:44 +02:00
//#include <array>
2024-07-24 20:29:37 +02:00
# include <vector>
2024-11-03 22:55:06 +01:00
# include <string>
2024-07-23 03:52:44 +02:00
namespace steam {
namespace engine {
2024-10-26 03:03:11 +02:00
namespace err = lumiera : : error ;
2024-07-23 03:52:44 +02:00
2024-11-03 22:55:06 +01:00
using StrView = std : : string_view ;
2024-07-23 03:52:44 +02:00
using std : : forward ;
2024-10-31 23:50:59 +01:00
using lib : : Literal ;
2024-07-23 03:52:44 +02:00
using lib : : Several ;
2024-10-12 04:17:39 +02:00
using lib : : Depend ;
2024-10-26 03:03:11 +02:00
using util : : _Fmt ;
Invocation: work out solution for a precisely fitting allocation
Conduct in-depth analysis to handle a secondary, implementation-related
(and frankly quite challenging) concern regarding the placement of node
and port connectivity data in memory. The intention is for the low-level
model to use a custom data structure based on `lib::Several`, allowing for
flexible and compact arrangement of the connectivity descriptors within
tiled memory blocks, which can then later be discarded in bulk, whenever
a segment of the render graph is superseded. Yet since the generated
descriptors are heterogeneous and, due to virtual functions, can not be
trivially copied, the corresponding placement invocations on the
data builder API must not be mixed, but rather given in ordered strikes
and preceded by a dimensioning call to pre-reserve a bulk of storage
However, doing so directly would jeopardise the open and flexible nature
of the node builder API, thereby creating a dangerous coupling between
the implementation levels of the node graph and of prospective library
wrapper plug-ins in charge of controlling details of the graph layout.
The solution devised here entails a functional helper data structure
created temporarily within the builder API stack frames; the detailed
and local type information provided from within the library plug-in
can thereby be embedded into opaque builder functors, allowing to
delay the actual data generation up until the final builder step,
at which point the complete number and size requirements of
connectivity data is known and can be used for dimensioning.
2024-10-20 21:51:34 +02:00
using util : : max ;
2024-07-23 03:52:44 +02:00
2024-10-11 03:33:05 +02:00
namespace { // Introspection helpers....
using lib : : meta : : _Fun ;
using lib : : meta : : is_BinaryFun ;
using std : : remove_reference_t ;
/** Helper to pick up the parameter dimensions from the processing function
* @ remark this is the rather simple yet common case that media processing
* is done by a function , which takes an array of input and output
* buffer pointers with a common type ; this simple case is used
* 7 / 2024 for prototyping and validate the design .
* @ tparam FUN a _function - like_ object , expected to accept two arguments ,
* which both are arrays of buffer pointers ( input , output ) .
*/
template < class FUN >
struct _ProcFun
{
static_assert ( _Fun < FUN > ( ) , " something funktion-like required " ) ;
static_assert ( is_BinaryFun < FUN > ( ) , " function with two arguments expected " ) ;
using ArgI = remove_reference_t < typename _Fun < FUN > : : Args : : List : : Head > ;
using ArgO = remove_reference_t < typename _Fun < FUN > : : Args : : List : : Tail : : Head > ;
template < class ARG >
struct MatchBuffArray
{
static_assert ( not sizeof ( ARG ) , " processing function expected to take array-of-buffer-pointers " ) ;
} ;
template < class BUF , size_t N >
struct MatchBuffArray < std : : array < BUF * , N > >
{
using Buff = BUF ;
enum { SIZ = N } ;
} ;
using BuffI = typename MatchBuffArray < ArgI > : : Buff ;
using BuffO = typename MatchBuffArray < ArgO > : : Buff ;
enum { FAN_I = MatchBuffArray < ArgI > : : SIZ
, FAN_O = MatchBuffArray < ArgO > : : SIZ
} ;
} ;
/**
* Pick a suitable size for the FeedManifold to accommodate the given function .
* @ remark only returning one of a small selection of sizes , to avoid
* excessive generation of template instances .
* @ todo 10 / 24 this is a premature safety guard ;
* need to assess if there is actually a problem
* ( chances are that the optimiser absorbs most of the combinatoric complexity ,
* or that , to the contrary , other proliferation mechanisms cause more harm )
*/
template < class FUN >
inline constexpr uint
manifoldSiz ( )
{
using _F = _ProcFun < FUN > ;
2024-10-13 03:49:01 +02:00
auto constexpr bound = std : : max ( _F : : FAN_I , _F : : FAN_O ) ;
2024-10-11 03:33:05 +02:00
static_assert ( bound < = 10 ,
" Limitation of template instances exceeded " ) ;
return bound < 3 ? bound
: bound < 6 ? 5
: 10 ;
}
} //(End)Introspection helpers.
/**
* Adapter to handle a simple yet common setup for media processing
* - somehow we can invoke processing as a simple function
* - this function takes two arrays : the input - and output buffers
* @ remark this setup is useful for testing , and as documentation example ;
* actually the FeedManifold is mixed in as baseclass , and the
* buffer pointers are retrieved from the BuffHandles .
* @ tparam MAN a FeedManifold , providing arrays of BuffHandles
* @ tparam FUN the processing function
*/
template < class MAN , class FUN >
struct SimpleFunctionInvocationAdapter
: MAN
{
using BuffI = typename _ProcFun < FUN > : : BuffI ;
using BuffO = typename _ProcFun < FUN > : : BuffO ;
2024-10-14 04:07:47 +02:00
enum { N = MAN : : STORAGE_SIZ
2024-10-11 03:33:05 +02:00
, FAN_I = _ProcFun < FUN > : : FAN_I
, FAN_O = _ProcFun < FUN > : : FAN_O
} ;
static_assert ( FAN_I < = N and FAN_O < = N ) ;
using ArrayI = std : : array < BuffI * , FAN_I > ;
using ArrayO = std : : array < BuffO * , FAN_O > ;
FUN process ;
ArrayI inParam ;
ArrayO outParam ;
template < typename . . . INIT >
SimpleFunctionInvocationAdapter ( INIT & & . . . funSetup )
2024-10-14 04:07:47 +02:00
: process { forward < INIT > ( funSetup ) . . . }
2024-10-11 03:33:05 +02:00
{ }
void
connect ( uint fanIn , uint fanOut )
{
REQUIRE ( fanIn > = FAN_I and fanOut > = FAN_O ) ;
for ( uint i = 0 ; i < FAN_I ; + + i )
inParam [ i ] = & MAN : : inBuff [ i ] . template accessAs < BuffI > ( ) ;
for ( uint i = 0 ; i < FAN_O ; + + i )
outParam [ i ] = & MAN : : outBuff [ i ] . template accessAs < BuffO > ( ) ;
}
void
invoke ( )
{
process ( inParam , outParam ) ;
}
} ;
/**
2024-10-22 05:59:00 +02:00
* Typical base configuration for a Weaving - Pattern chain :
2024-10-11 03:33:05 +02:00
* - use a simple processing function
* - pass an input / output buffer array to this function
* - map all » slots « directly without any re - ordering
* - use a sufficiently sized FeedManifold as storage scheme
2024-10-22 05:59:00 +02:00
* @ remark actual media handling plug - ins may choose to
* employ more elaborate _invocation adapters_
* specifically tailored to the library ' s needs .
2024-10-11 03:33:05 +02:00
*/
template < uint N , class FUN >
Invocation: switch `WeavingBuilder` to produce the result via λ
This change allows to disentangle the usages of `lib::SeveralBuilder`,
so that at any time during the build process only a single instance is
actively populated, all in one row — and thus the required storage can
either be pre-allocated, or dynamically extended and shrinked (when
filling elements into the last `SeveralBuilder` currently activated)
By packaging into a λ-closure, the building of the actual `Port`
implementation objects (≙ `Turnout` instances) is delayed until the
very end of the build process, and then unloaded into yet another
`lib::Several` in one strike. Temporarily, those building functor
objects are „hidden“ in the current stack frame, as a new `NodeBuilder`
instance is dropped off with an adapted type parameter (embedding the
λ-type produced by the last nested `PortBuilder` invocation, while
inheriting from previous ones.
However, defining a special constructor to cause this »chaining«
poses some challenge (regarding overload resolution). Moreover,
since the actual processing function shall be embedded directly
(as opposed to wrapping it into a `std::function`), further problems
can arise when this function is given as a ''function reference''
2024-10-22 03:20:50 +02:00
struct DirectFunctionInvocation
2024-10-11 03:33:05 +02:00
: util : : MoveOnly
{
using Manifold = FeedManifold < N > ;
using Feed = SimpleFunctionInvocationAdapter < Manifold , FUN > ;
enum { MAX_SIZ = N } ;
std : : function < Feed ( ) > buildFeed ;
2024-10-14 04:07:47 +02:00
2024-10-22 05:59:00 +02:00
/** when building the Turnout, prepare the _invocation adapter_
* @ note processing function \ a fun is bound by value into the closure ,
* so that each invocation will create a copy of that function ,
* embedded ( and typically inlined ) into the invocation adapter .
*/
Invocation: switch `WeavingBuilder` to produce the result via λ
This change allows to disentangle the usages of `lib::SeveralBuilder`,
so that at any time during the build process only a single instance is
actively populated, all in one row — and thus the required storage can
either be pre-allocated, or dynamically extended and shrinked (when
filling elements into the last `SeveralBuilder` currently activated)
By packaging into a λ-closure, the building of the actual `Port`
implementation objects (≙ `Turnout` instances) is delayed until the
very end of the build process, and then unloaded into yet another
`lib::Several` in one strike. Temporarily, those building functor
objects are „hidden“ in the current stack frame, as a new `NodeBuilder`
instance is dropped off with an adapted type parameter (embedding the
λ-type produced by the last nested `PortBuilder` invocation, while
inheriting from previous ones.
However, defining a special constructor to cause this »chaining«
poses some challenge (regarding overload resolution). Moreover,
since the actual processing function shall be embedded directly
(as opposed to wrapping it into a `std::function`), further problems
can arise when this function is given as a ''function reference''
2024-10-22 03:20:50 +02:00
DirectFunctionInvocation ( FUN fun )
2024-10-22 05:59:00 +02:00
: buildFeed { [ = ] { return Feed { fun } ; } }
2024-10-14 04:07:47 +02:00
{ }
2024-10-11 03:33:05 +02:00
} ;
2024-07-23 03:52:44 +02:00
template < class POL , class I , class E = I >
using DataBuilder = lib : : SeveralBuilder < I , E , POL : : template Policy > ;
Invocation: switch `WeavingBuilder` to produce the result via λ
This change allows to disentangle the usages of `lib::SeveralBuilder`,
so that at any time during the build process only a single instance is
actively populated, all in one row — and thus the required storage can
either be pre-allocated, or dynamically extended and shrinked (when
filling elements into the last `SeveralBuilder` currently activated)
By packaging into a λ-closure, the building of the actual `Port`
implementation objects (≙ `Turnout` instances) is delayed until the
very end of the build process, and then unloaded into yet another
`lib::Several` in one strike. Temporarily, those building functor
objects are „hidden“ in the current stack frame, as a new `NodeBuilder`
instance is dropped off with an adapted type parameter (embedding the
λ-type produced by the last nested `PortBuilder` invocation, while
inheriting from previous ones.
However, defining a special constructor to cause this »chaining«
poses some challenge (regarding overload resolution). Moreover,
since the actual processing function shall be embedded directly
(as opposed to wrapping it into a `std::function`), further problems
can arise when this function is given as a ''function reference''
2024-10-22 03:20:50 +02:00
template < uint siz >
using SizMark = std : : integral_constant < uint , siz > ;
2024-07-23 03:52:44 +02:00
Invocation: switch `WeavingBuilder` to produce the result via λ
This change allows to disentangle the usages of `lib::SeveralBuilder`,
so that at any time during the build process only a single instance is
actively populated, all in one row — and thus the required storage can
either be pre-allocated, or dynamically extended and shrinked (when
filling elements into the last `SeveralBuilder` currently activated)
By packaging into a λ-closure, the building of the actual `Port`
implementation objects (≙ `Turnout` instances) is delayed until the
very end of the build process, and then unloaded into yet another
`lib::Several` in one strike. Temporarily, those building functor
objects are „hidden“ in the current stack frame, as a new `NodeBuilder`
instance is dropped off with an adapted type parameter (embedding the
λ-type produced by the last nested `PortBuilder` invocation, while
inheriting from previous ones.
However, defining a special constructor to cause this »chaining«
poses some challenge (regarding overload resolution). Moreover,
since the actual processing function shall be embedded directly
(as opposed to wrapping it into a `std::function`), further problems
can arise when this function is given as a ''function reference''
2024-10-22 03:20:50 +02:00
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1371 : Prototyping: how to assemble a Turnout
Invocation: work out solution for a precisely fitting allocation
Conduct in-depth analysis to handle a secondary, implementation-related
(and frankly quite challenging) concern regarding the placement of node
and port connectivity data in memory. The intention is for the low-level
model to use a custom data structure based on `lib::Several`, allowing for
flexible and compact arrangement of the connectivity descriptors within
tiled memory blocks, which can then later be discarded in bulk, whenever
a segment of the render graph is superseded. Yet since the generated
descriptors are heterogeneous and, due to virtual functions, can not be
trivially copied, the corresponding placement invocations on the
data builder API must not be mixed, but rather given in ordered strikes
and preceded by a dimensioning call to pre-reserve a bulk of storage
However, doing so directly would jeopardise the open and flexible nature
of the node builder API, thereby creating a dangerous coupling between
the implementation levels of the node graph and of prospective library
wrapper plug-ins in charge of controlling details of the graph layout.
The solution devised here entails a functional helper data structure
created temporarily within the builder API stack frames; the detailed
and local type information provided from within the library plug-in
can thereby be embedded into opaque builder functors, allowing to
delay the actual data generation up until the final builder step,
at which point the complete number and size requirements of
connectivity data is known and can be used for dimensioning.
2024-10-20 21:51:34 +02:00
/**
* Recursive functional data structure to collect weaving pattern data
* and finally to emplace a Turnout instance into the data storage
* for each port , as specified by preceding builder - API invocations .
* @ tparam PAR recursive layering for preceding entries
* @ tparam BUILD a builder functor to emplace one Turnout instance ,
* opaquely embedding all specific data typing .
* @ tparam siz storage in bytes to hold data produced by \ a BUILD
*/
template < class PAR , class BUILD , uint siz >
struct PatternData
: PAR
{
BUILD buildEntry ;
2024-10-25 18:13:55 +02:00
uint size ( ) { return 1 + PAR : : size ( ) ; }
Invocation: work out solution for a precisely fitting allocation
Conduct in-depth analysis to handle a secondary, implementation-related
(and frankly quite challenging) concern regarding the placement of node
and port connectivity data in memory. The intention is for the low-level
model to use a custom data structure based on `lib::Several`, allowing for
flexible and compact arrangement of the connectivity descriptors within
tiled memory blocks, which can then later be discarded in bulk, whenever
a segment of the render graph is superseded. Yet since the generated
descriptors are heterogeneous and, due to virtual functions, can not be
trivially copied, the corresponding placement invocations on the
data builder API must not be mixed, but rather given in ordered strikes
and preceded by a dimensioning call to pre-reserve a bulk of storage
However, doing so directly would jeopardise the open and flexible nature
of the node builder API, thereby creating a dangerous coupling between
the implementation levels of the node graph and of prospective library
wrapper plug-ins in charge of controlling details of the graph layout.
The solution devised here entails a functional helper data structure
created temporarily within the builder API stack frames; the detailed
and local type information provided from within the library plug-in
can thereby be embedded into opaque builder functors, allowing to
delay the actual data generation up until the final builder step,
at which point the complete number and size requirements of
connectivity data is known and can be used for dimensioning.
2024-10-20 21:51:34 +02:00
template < class DAB >
void
collectEntries ( DAB & dataBuilder , uint cntElm = 0 , uint maxSiz = 0 )
{
PAR : : collectEntries ( dataBuilder , cntElm + 1 , max ( siz , maxSiz ) ) ;
buildEntry ( dataBuilder ) ;
}
2024-10-21 03:40:19 +02:00
PatternData ( PAR & & predecessor , BUILD & & entryBuilder )
: PAR { move ( predecessor ) }
, buildEntry { move ( entryBuilder ) }
{ }
Invocation: work out solution for a precisely fitting allocation
Conduct in-depth analysis to handle a secondary, implementation-related
(and frankly quite challenging) concern regarding the placement of node
and port connectivity data in memory. The intention is for the low-level
model to use a custom data structure based on `lib::Several`, allowing for
flexible and compact arrangement of the connectivity descriptors within
tiled memory blocks, which can then later be discarded in bulk, whenever
a segment of the render graph is superseded. Yet since the generated
descriptors are heterogeneous and, due to virtual functions, can not be
trivially copied, the corresponding placement invocations on the
data builder API must not be mixed, but rather given in ordered strikes
and preceded by a dimensioning call to pre-reserve a bulk of storage
However, doing so directly would jeopardise the open and flexible nature
of the node builder API, thereby creating a dangerous coupling between
the implementation levels of the node graph and of prospective library
wrapper plug-ins in charge of controlling details of the graph layout.
The solution devised here entails a functional helper data structure
created temporarily within the builder API stack frames; the detailed
and local type information provided from within the library plug-in
can thereby be embedded into opaque builder functors, allowing to
delay the actual data generation up until the final builder step,
at which point the complete number and size requirements of
connectivity data is known and can be used for dimensioning.
2024-10-20 21:51:34 +02:00
} ;
/**
* Data recursion end : prime the port data storage
* by reserving appropriate storage to hold all known Turnout elements .
*/
2024-10-21 03:40:19 +02:00
struct PatternDataAnchor
Invocation: work out solution for a precisely fitting allocation
Conduct in-depth analysis to handle a secondary, implementation-related
(and frankly quite challenging) concern regarding the placement of node
and port connectivity data in memory. The intention is for the low-level
model to use a custom data structure based on `lib::Several`, allowing for
flexible and compact arrangement of the connectivity descriptors within
tiled memory blocks, which can then later be discarded in bulk, whenever
a segment of the render graph is superseded. Yet since the generated
descriptors are heterogeneous and, due to virtual functions, can not be
trivially copied, the corresponding placement invocations on the
data builder API must not be mixed, but rather given in ordered strikes
and preceded by a dimensioning call to pre-reserve a bulk of storage
However, doing so directly would jeopardise the open and flexible nature
of the node builder API, thereby creating a dangerous coupling between
the implementation levels of the node graph and of prospective library
wrapper plug-ins in charge of controlling details of the graph layout.
The solution devised here entails a functional helper data structure
created temporarily within the builder API stack frames; the detailed
and local type information provided from within the library plug-in
can thereby be embedded into opaque builder functors, allowing to
delay the actual data generation up until the final builder step,
at which point the complete number and size requirements of
connectivity data is known and can be used for dimensioning.
2024-10-20 21:51:34 +02:00
{
2024-10-25 18:13:55 +02:00
uint size ( ) { return 0 ; }
Invocation: work out solution for a precisely fitting allocation
Conduct in-depth analysis to handle a secondary, implementation-related
(and frankly quite challenging) concern regarding the placement of node
and port connectivity data in memory. The intention is for the low-level
model to use a custom data structure based on `lib::Several`, allowing for
flexible and compact arrangement of the connectivity descriptors within
tiled memory blocks, which can then later be discarded in bulk, whenever
a segment of the render graph is superseded. Yet since the generated
descriptors are heterogeneous and, due to virtual functions, can not be
trivially copied, the corresponding placement invocations on the
data builder API must not be mixed, but rather given in ordered strikes
and preceded by a dimensioning call to pre-reserve a bulk of storage
However, doing so directly would jeopardise the open and flexible nature
of the node builder API, thereby creating a dangerous coupling between
the implementation levels of the node graph and of prospective library
wrapper plug-ins in charge of controlling details of the graph layout.
The solution devised here entails a functional helper data structure
created temporarily within the builder API stack frames; the detailed
and local type information provided from within the library plug-in
can thereby be embedded into opaque builder functors, allowing to
delay the actual data generation up until the final builder step,
at which point the complete number and size requirements of
connectivity data is known and can be used for dimensioning.
2024-10-20 21:51:34 +02:00
template < class DAB >
void
collectEntries ( DAB & dataBuilder , uint cntElm , uint maxSiz )
{
dataBuilder . reserve ( cntElm , maxSiz ) ;
}
} ;
2024-10-21 03:40:19 +02:00
2024-10-22 05:59:00 +02:00
2024-07-23 03:52:44 +02:00
template < uint N , class FUN >
2024-12-14 03:50:10 +01:00
using SimpleDirectInvoke = MediaWeavingPattern < DirectFunctionInvocation < N , FUN > > ;
2024-07-23 03:52:44 +02:00
2024-10-22 05:59:00 +02:00
2024-12-05 23:58:22 +01:00
/**
* A low - level Builder to prepare and adapt for a specific node invocation .
* In this context , » weaving « refers to the way parameters and results of an
* processing function are provided , combined and forwarded within the setup
* for an actual Render Node invocation . When the invocation happens , a kind
* of preconfigured _blue print_ or invocation plan is executed ; the purpose
* of the build at » Level - 2 « ( ≙ the purpose of this code ) is to preconfigure
* this invocation scheme . Using a _low level builder_ as controlled by the
* actual NodeBuilder and PortBuilder allows to introduce extension points
* and helps to abstract away internal technical details of the invocation .
* @ tparam POL allocation and context configuration policy
* @ tparam N maximum number of input and output slots
* @ tparam FUN function or invocation adapter to invoke
*/
2024-07-23 03:52:44 +02:00
template < class POL , uint N , class FUN >
2024-10-11 02:45:51 +02:00
struct WeavingBuilder
2024-07-23 03:52:44 +02:00
: util : : MoveOnly
{
2024-10-24 23:51:45 +02:00
using FunSpec = _ProcFun < FUN > ;
Invocation: switch `WeavingBuilder` to produce the result via λ
This change allows to disentangle the usages of `lib::SeveralBuilder`,
so that at any time during the build process only a single instance is
actively populated, all in one row — and thus the required storage can
either be pre-allocated, or dynamically extended and shrinked (when
filling elements into the last `SeveralBuilder` currently activated)
By packaging into a λ-closure, the building of the actual `Port`
implementation objects (≙ `Turnout` instances) is delayed until the
very end of the build process, and then unloaded into yet another
`lib::Several` in one strike. Temporarily, those building functor
objects are „hidden“ in the current stack frame, as a new `NodeBuilder`
instance is dropped off with an adapted type parameter (embedding the
λ-type produced by the last nested `PortBuilder` invocation, while
inheriting from previous ones.
However, defining a special constructor to cause this »chaining«
poses some challenge (regarding overload resolution). Moreover,
since the actual processing function shall be embedded directly
(as opposed to wrapping it into a `std::function`), further problems
can arise when this function is given as a ''function reference''
2024-10-22 03:20:50 +02:00
using TurnoutWeaving = Turnout < SimpleDirectInvoke < N , FUN > > ;
static constexpr SizMark < sizeof ( TurnoutWeaving ) > sizMark { } ;
2024-10-26 03:03:11 +02:00
static constexpr uint FAN_I = FunSpec : : FAN_I ;
static constexpr uint FAN_O = FunSpec : : FAN_O ;
2024-07-23 03:52:44 +02:00
2024-07-24 20:29:37 +02:00
using TypeMarker = std : : function < BuffDescr ( BufferProvider & ) > ;
using ProviderRef = std : : reference_wrapper < BufferProvider > ;
2024-10-23 16:27:09 +02:00
DataBuilder < POL , PortRef > leadPorts ;
std : : vector < TypeMarker > buffTypes ;
std : : vector < ProviderRef > providers ;
2024-07-24 20:29:37 +02:00
2024-07-30 23:44:55 +02:00
uint resultSlot { 0 } ;
2024-10-12 04:17:39 +02:00
Depend < EngineCtx > ctx ;
2024-07-24 20:29:37 +02:00
2024-11-03 22:55:06 +01:00
StrView nodeSymb_ ;
StrView portSpec_ ;
2024-10-11 03:33:05 +02:00
FUN fun_ ;
2024-10-23 16:27:09 +02:00
template < typename . . . INIT >
2024-11-03 22:55:06 +01:00
WeavingBuilder ( FUN & & init , StrView nodeSymb , StrView portSpec , INIT & & . . . alloInit )
2024-10-23 16:27:09 +02:00
: leadPorts { forward < INIT > ( alloInit ) . . . }
2024-11-03 22:55:06 +01:00
, nodeSymb_ { nodeSymb }
, portSpec_ { portSpec }
2024-10-23 16:27:09 +02:00
, fun_ { move ( init ) }
2024-10-11 03:33:05 +02:00
{ }
2024-10-25 18:13:55 +02:00
WeavingBuilder & &
attachToLeadPort ( ProcNode & lead , uint portNr )
2024-07-23 03:52:44 +02:00
{
2024-10-26 03:03:11 +02:00
if ( leadPorts . size ( ) > = FAN_I )
throw err : : Logic { _Fmt { " Builder: attempt to add further input, "
" but all %d »input slots« of the processing function are already connected. " }
% FAN_I
} ;
2024-10-23 16:27:09 +02:00
PortRef portRef { lead . getPort ( portNr ) } ;
leadPorts . append ( portRef ) ;
2024-07-23 03:52:44 +02:00
return move ( * this ) ;
}
2024-07-24 20:29:37 +02:00
template < class BU >
2024-10-25 18:13:55 +02:00
WeavingBuilder & &
appendBufferTypes ( uint cnt )
2024-07-23 03:52:44 +02:00
{
2024-10-26 03:03:11 +02:00
if ( buffTypes . size ( ) + cnt > FAN_O )
throw err : : Logic { _Fmt { " Builder: attempt add %d further output buffers, "
" while %d of %d possible outputs are already connected. " }
% cnt % buffTypes . size ( ) % FAN_O
} ;
2024-07-24 20:29:37 +02:00
while ( cnt - - )
buffTypes . emplace_back ( [ ] ( BufferProvider & provider )
{ return provider . getDescriptor < BU > ( ) ; } ) ;
2024-10-22 05:59:00 +02:00
ENSURE ( buffTypes . size ( ) < = N ) ;
2024-07-23 03:52:44 +02:00
return move ( * this ) ;
}
2024-10-25 18:13:55 +02:00
WeavingBuilder & &
2024-10-14 04:07:47 +02:00
fillRemainingBufferTypes ( )
{
using BuffO = typename FunSpec : : BuffO ;
uint cnt = FAN_O - buffTypes . size ( ) ;
return appendBufferTypes < BuffO > ( cnt ) ;
}
2024-10-25 18:13:55 +02:00
WeavingBuilder & &
connectRemainingInputs ( DataBuilder < POL , ProcNodeRef > & knownLeads , uint defaultPort )
{
REQUIRE ( leadPorts . size ( ) < = FAN_I ) ;
uint cnt = FAN_I - leadPorts . size ( ) ;
2024-10-26 03:03:11 +02:00
if ( FAN_I > knownLeads . size ( ) )
throw err : : Logic { _Fmt { " Builder: attempt to auto-connect %d further »input slots«, "
" but this ProcNode has only %d predecessor nodes, while the "
" given processing function expects %d inputs. " }
% cnt % knownLeads . size ( ) % FAN_I
} ;
2024-10-25 18:13:55 +02:00
while ( cnt - - )
attachToLeadPort ( knownLeads [ leadPorts . size ( ) ] , defaultPort ) ;
return move ( * this ) ;
}
WeavingBuilder & &
selectResultSlot ( uint idx )
2024-07-24 20:29:37 +02:00
{
2024-07-30 23:44:55 +02:00
this - > resultSlot = idx ;
return move ( * this ) ;
}
2024-07-24 20:29:37 +02:00
2024-07-23 03:52:44 +02:00
auto
build ( )
{
Invocation: switch `WeavingBuilder` to produce the result via λ
This change allows to disentangle the usages of `lib::SeveralBuilder`,
so that at any time during the build process only a single instance is
actively populated, all in one row — and thus the required storage can
either be pre-allocated, or dynamically extended and shrinked (when
filling elements into the last `SeveralBuilder` currently activated)
By packaging into a λ-closure, the building of the actual `Port`
implementation objects (≙ `Turnout` instances) is delayed until the
very end of the build process, and then unloaded into yet another
`lib::Several` in one strike. Temporarily, those building functor
objects are „hidden“ in the current stack frame, as a new `NodeBuilder`
instance is dropped off with an adapted type parameter (embedding the
λ-type produced by the last nested `PortBuilder` invocation, while
inheriting from previous ones.
However, defining a special constructor to cause this »chaining«
poses some challenge (regarding overload resolution). Moreover,
since the actual processing function shall be embedded directly
(as opposed to wrapping it into a `std::function`), further problems
can arise when this function is given as a ''function reference''
2024-10-22 03:20:50 +02:00
// discard excess storage prior to allocating the output types sequence
2024-10-23 16:27:09 +02:00
leadPorts . shrinkFit ( ) ;
Invocation: switch `WeavingBuilder` to produce the result via λ
This change allows to disentangle the usages of `lib::SeveralBuilder`,
so that at any time during the build process only a single instance is
actively populated, all in one row — and thus the required storage can
either be pre-allocated, or dynamically extended and shrinked (when
filling elements into the last `SeveralBuilder` currently activated)
By packaging into a λ-closure, the building of the actual `Port`
implementation objects (≙ `Turnout` instances) is delayed until the
very end of the build process, and then unloaded into yet another
`lib::Several` in one strike. Temporarily, those building functor
objects are „hidden“ in the current stack frame, as a new `NodeBuilder`
instance is dropped off with an adapted type parameter (embedding the
λ-type produced by the last nested `PortBuilder` invocation, while
inheriting from previous ones.
However, defining a special constructor to cause this »chaining«
poses some challenge (regarding overload resolution). Moreover,
since the actual processing function shall be embedded directly
(as opposed to wrapping it into a `std::function`), further problems
can arise when this function is given as a ''function reference''
2024-10-22 03:20:50 +02:00
2024-07-24 20:29:37 +02:00
maybeFillDefaultProviders ( buffTypes . size ( ) ) ;
2024-10-14 04:07:47 +02:00
REQUIRE ( providers . size ( ) = = buffTypes . size ( ) ) ;
2024-10-23 16:27:09 +02:00
auto outTypes = DataBuilder < POL , BuffDescr > { leadPorts . policyConnect ( ) }
Invocation: switch `WeavingBuilder` to produce the result via λ
This change allows to disentangle the usages of `lib::SeveralBuilder`,
so that at any time during the build process only a single instance is
actively populated, all in one row — and thus the required storage can
either be pre-allocated, or dynamically extended and shrinked (when
filling elements into the last `SeveralBuilder` currently activated)
By packaging into a λ-closure, the building of the actual `Port`
implementation objects (≙ `Turnout` instances) is delayed until the
very end of the build process, and then unloaded into yet another
`lib::Several` in one strike. Temporarily, those building functor
objects are „hidden“ in the current stack frame, as a new `NodeBuilder`
instance is dropped off with an adapted type parameter (embedding the
λ-type produced by the last nested `PortBuilder` invocation, while
inheriting from previous ones.
However, defining a special constructor to cause this »chaining«
poses some challenge (regarding overload resolution). Moreover,
since the actual processing function shall be embedded directly
(as opposed to wrapping it into a `std::function`), further problems
can arise when this function is given as a ''function reference''
2024-10-22 03:20:50 +02:00
. reserve ( buffTypes . size ( ) ) ;
2024-07-24 20:29:37 +02:00
uint i = 0 ;
2024-07-30 23:44:55 +02:00
for ( auto & typeConstructor : buffTypes )
2024-10-14 04:07:47 +02:00
outTypes . append (
typeConstructor ( providers [ i + + ] ) ) ;
2024-07-24 20:29:37 +02:00
2024-10-23 16:27:09 +02:00
ENSURE ( leadPorts . size ( ) < = N ) ;
2024-10-24 23:51:45 +02:00
ENSURE ( leadPorts . size ( ) = = FunSpec : : FAN_I ) ;
2024-10-23 16:27:09 +02:00
ENSURE ( outTypes . size ( ) < = N ) ;
2024-10-24 23:51:45 +02:00
ENSURE ( outTypes . size ( ) = = FunSpec : : FAN_O ) ;
2024-07-24 20:29:37 +02:00
Invocation: switch `WeavingBuilder` to produce the result via λ
This change allows to disentangle the usages of `lib::SeveralBuilder`,
so that at any time during the build process only a single instance is
actively populated, all in one row — and thus the required storage can
either be pre-allocated, or dynamically extended and shrinked (when
filling elements into the last `SeveralBuilder` currently activated)
By packaging into a λ-closure, the building of the actual `Port`
implementation objects (≙ `Turnout` instances) is delayed until the
very end of the build process, and then unloaded into yet another
`lib::Several` in one strike. Temporarily, those building functor
objects are „hidden“ in the current stack frame, as a new `NodeBuilder`
instance is dropped off with an adapted type parameter (embedding the
λ-type produced by the last nested `PortBuilder` invocation, while
inheriting from previous ones.
However, defining a special constructor to cause this »chaining«
poses some challenge (regarding overload resolution). Moreover,
since the actual processing function shall be embedded directly
(as opposed to wrapping it into a `std::function`), further problems
can arise when this function is given as a ''function reference''
2024-10-22 03:20:50 +02:00
using PortDataBuilder = DataBuilder < POL , Port > ;
// provide a free-standing functor to build a suitable Port impl (≙Turnout)
2024-10-23 16:27:09 +02:00
return [ leads = move ( leadPorts . build ( ) )
Invocation: switch `WeavingBuilder` to produce the result via λ
This change allows to disentangle the usages of `lib::SeveralBuilder`,
so that at any time during the build process only a single instance is
actively populated, all in one row — and thus the required storage can
either be pre-allocated, or dynamically extended and shrinked (when
filling elements into the last `SeveralBuilder` currently activated)
By packaging into a λ-closure, the building of the actual `Port`
implementation objects (≙ `Turnout` instances) is delayed until the
very end of the build process, and then unloaded into yet another
`lib::Several` in one strike. Temporarily, those building functor
objects are „hidden“ in the current stack frame, as a new `NodeBuilder`
instance is dropped off with an adapted type parameter (embedding the
λ-type produced by the last nested `PortBuilder` invocation, while
inheriting from previous ones.
However, defining a special constructor to cause this »chaining«
poses some challenge (regarding overload resolution). Moreover,
since the actual processing function shall be embedded directly
(as opposed to wrapping it into a `std::function`), further problems
can arise when this function is given as a ''function reference''
2024-10-22 03:20:50 +02:00
, types = move ( outTypes . build ( ) )
, procFun = move ( fun_ )
, resultIdx = resultSlot
2024-11-03 22:55:06 +01:00
, procID = ProcID : : describe ( nodeSymb_ , portSpec_ )
Invocation: switch `WeavingBuilder` to produce the result via λ
This change allows to disentangle the usages of `lib::SeveralBuilder`,
so that at any time during the build process only a single instance is
actively populated, all in one row — and thus the required storage can
either be pre-allocated, or dynamically extended and shrinked (when
filling elements into the last `SeveralBuilder` currently activated)
By packaging into a λ-closure, the building of the actual `Port`
implementation objects (≙ `Turnout` instances) is delayed until the
very end of the build process, and then unloaded into yet another
`lib::Several` in one strike. Temporarily, those building functor
objects are „hidden“ in the current stack frame, as a new `NodeBuilder`
instance is dropped off with an adapted type parameter (embedding the
λ-type produced by the last nested `PortBuilder` invocation, while
inheriting from previous ones.
However, defining a special constructor to cause this »chaining«
poses some challenge (regarding overload resolution). Moreover,
since the actual processing function shall be embedded directly
(as opposed to wrapping it into a `std::function`), further problems
can arise when this function is given as a ''function reference''
2024-10-22 03:20:50 +02:00
]
( PortDataBuilder & portData ) mutable - > void
{
2024-11-03 22:55:06 +01:00
portData . template emplace < TurnoutWeaving > ( procID
2024-11-02 23:54:41 +01:00
, move ( leads )
Invocation: switch `WeavingBuilder` to produce the result via λ
This change allows to disentangle the usages of `lib::SeveralBuilder`,
so that at any time during the build process only a single instance is
actively populated, all in one row — and thus the required storage can
either be pre-allocated, or dynamically extended and shrinked (when
filling elements into the last `SeveralBuilder` currently activated)
By packaging into a λ-closure, the building of the actual `Port`
implementation objects (≙ `Turnout` instances) is delayed until the
very end of the build process, and then unloaded into yet another
`lib::Several` in one strike. Temporarily, those building functor
objects are „hidden“ in the current stack frame, as a new `NodeBuilder`
instance is dropped off with an adapted type parameter (embedding the
λ-type produced by the last nested `PortBuilder` invocation, while
inheriting from previous ones.
However, defining a special constructor to cause this »chaining«
poses some challenge (regarding overload resolution). Moreover,
since the actual processing function shall be embedded directly
(as opposed to wrapping it into a `std::function`), further problems
can arise when this function is given as a ''function reference''
2024-10-22 03:20:50 +02:00
, move ( types )
, resultIdx
, move ( procFun )
) ;
} ;
2024-07-23 03:52:44 +02:00
}
2024-07-24 20:29:37 +02:00
private :
void
maybeFillDefaultProviders ( size_t maxSlots )
{
for ( uint i = providers . size ( ) ; i < maxSlots ; + + i )
2024-10-12 04:17:39 +02:00
providers . emplace_back ( ctx ( ) . mem ) ;
2024-07-24 20:29:37 +02:00
}
2024-07-23 03:52:44 +02:00
} ;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1367 : (End)Prototyping: how to assemble a Turnout
} } // namespace steam::engine
# endif /*STEAM_ENGINE_WEAVING_PATTERN_BUILDER_H*/