Compare commits

..

23 commits

Author SHA1 Message Date
764185eb85 Parameters: look for a suitable arrangement of interfaces
While the role of all the relevant interfaces has been determined,
it still remains difficult to establish a sensible relationship.
Which one is the most fundamental interface?

After a lot of mulling, it seems that the `Provision` interface
should be used as foundation, since it describes the very core
of the parameter concept developed here -- even while the actual
form of the operations must be ''somewhat cunningly reversed'',
so that the knowledge regarding the underlying type can be
kept an opaque implementation detail.

The `Domain` interface would then be layered on top, and describes
the capability to transfer values between buffers marked with different
domains (which effectively requires a double-dispatch in the general case).
2026-09-01 01:53:43 +02:00
3764479ebd Parameters: detailed the prototype design sketch (see #1431)
Last week, I resumed the work that had been suspended
due to my focus on this year’s Froscon talk (on `io_uring`).
I have reconsidered my previous design ideas and sought to
arrive at a coherent design. Nevertheless, these are preliminary,
exploratory design studies that should be regarded as a basis
for future work rather....
2026-08-31 00:24:37 +02:00
7c35af3717 Workflow: integrate some work notes in my mindmap
I took some work notes during that ongoing discussion in a separate ''scratchpad document.''
As part of the effort to create an overview digest, I also revisited these work notes
and extracted a complete listing of all mails, each marked with some key topics.

Furthermore I took some notes while interacting with claude and revising the generated text.
2026-07-29 22:15:31 +02:00
4cdf01bc72 Parameters: better API for opaque transfer
The type-independent data transfer is based on a double-dispatch,
which is implemented as visitor pattern; so the operation must start
from the source parameter's VTable and be ''deflected back'' to the receiver

This completes the base operations required to establish a »Domain« (data type)
Adding more test coverage to demonstrate usage patterns...
2026-07-25 21:52:51 +02:00
c60e7d35c6 Parameters: reconsider where to use the visitor-pattern
...turns out as better to code two distinct low-level operations,
albeit implemented by delegation to a single generic assignment implementation.

The visitor patter thus becomes relocated to the high-level interface,
allowing to assign one opaque parameter to another one
2026-07-24 19:44:20 +02:00
a37e99a643 Parameters: attempt to base all access on a single implementation
However then the problem is that we know ''our domain'' but not the ''other domain''...

... and thus this forces one direction to pass through a double dispatch
2026-07-23 01:46:27 +02:00
e48c03e35d Parameters: verify the clamping operation
- systematic testing of boundary values for various numeric types
 - verify even clamping of an uint-128 when targeting float
 - verify signed / unsigned of different rank
 - demonstrate the clamping now integrated into `extractAs()`

As an aside, move the `isNeg()` function (safe test for negative numbers)
into util.hpp and fix related bugs with unsigned there.
2026-07-22 23:10:02 +02:00
e3b6b87e93 Parameters: look for ways to properly clamp signed / unsigned
Signed and unsigned integral types have a partial overlap of definition domains,
yet due to the automatic promotion rules it is impossible to write
a domain clamping function directly, since `std::numeric_limits` returns
the domain boundaries in the given type, and `int(-1) < uint(0)`
evaluates to false, due to int being is promoted to uint.

The attempt to circumvent this problem here relies on a check if the
comparison crosses the zero point, and otherwise relies explicitly
on the promotion to the number with the larger domain.
2026-07-22 01:17:13 +02:00
11bb3bce73 Parameters: draft a family of pre-conditioning operations
- we need a generic operation to clamp to the smaller value domain
 - furthermore we need a virtual function to apply a type specific limiter

__TODO__: this draft does not handle properly signed vs. unsigned types
2026-07-21 01:34:37 +02:00
7d35790e11 Parameters: implement simple value extraction for base types
...so this is not yet a visitor, rather the building blocks required
to be able to extract values and to assign and conform values

And my intention is to build something akin to a set of conversion rules
2026-07-20 01:10:19 +02:00
82b7acac57 Parameters: pick up work on type handling experiments
...after an extended break where I focused on learning IO_URING,
which is the topic of my upcoming talk at Froscon-2026;
(I built a prototype to read and display video with async IO)

Today ''I have re-read my design notes'' and the analysis seems to hold up well;
furthermore I solved the coding problem how to generate type handlers for a list of types.
2026-07-19 22:48:50 +02:00
532408a305 Parameters: design sketch for base-domain and conversion
While it is unfortunate that I seem to get deeper and deeper into that topic...
 - it should be noted that data size matters most (pervasive use expected)
 - the design is leaning towards multiple functional closures
 - both together suggest to use a classical virtual interface, without much ado
 - yet parameters must be copyable values, to allow for evaluation graphs and snapshots
 - so this turns out as yet another use case for ''polymorphic values''
 - furthermore, fluid usage requires flexible conversions between result values
 - given the situation with numeric value types and strings, this implies ''to build a visitor''

🠲 ''putting that work aside for the moment, since I need to focus on my IO_URING research (for FrOSCon 26)''
2026-07-06 01:17:31 +02:00
5df0252cc1 Parameters: preliminary requirement analysis
In the current stage, a design-draft is required that is sufficiently adequate
to be used as code placeholder and to evolve into the full specification eventually.

 - we need a single parameter container type with value semantics
 - parameters must support a selection of basic value types, with conversions
 - the value range can be limited, and a parameter can be associated
   with a ''Scale'' (stepped, continuous, logarithmic)
 - a parameter can be linked to a functional source
 - parameters are (virtually) copyable
2026-07-03 19:55:55 +02:00
36684ff092 Parameters: start a design draft
Parameters and Automation will certainly play a crucial role eventually.
They will form a network of relationships to cover all further concerns that can not directly be conceptualised as arrangement or processing.

A shift in the setup of the Job-Functor arguments necessitates to introduce a `Parameter` type — ''albeit just as placeholder''
2026-07-01 01:50:26 +02:00
793c9950d4 Invocation: evaluate ways to represent control parameters
As a follow-up to the decisions regarding Render Job arguments,
it should be noted that the `ProcessKey` in the `TurnoutSystem` becomes redundant;
we'd need some access to ''generic Parameters'' rather
2026-06-30 16:30:41 +02:00
ce2c3d2c06 Invocation: draft a generalised output context
...the plan is to wrap the `DataSink` and also to expose possible further control parameters here...

Rather difficult to get started, since mostly related to ''planned features''
2026-06-30 01:35:53 +02:00
8db1813831 Invocation: adapt job parameters to requirements
This is the result of an requirement analysis,
based on the current (better) understanding of ''render job invocation''

 * the `InvocationInstanceID` is @deprecated, because rather a data based hash is required
 * instead, two free parameters are introduced, with a meaning defined by the specific `JobFunctor`
 * notably the functor for rendering, the `RenderInvocation´ can use these for the exit node and an output context

**Note**: the C-structs for the `job` are no longer considered an interface (and will be remoulded into an internal detail of the dispatcher)
2026-06-29 23:19:19 +02:00
64416e3ba8 Invocation: considerations regarding the DataSink to use
While the setup here in the test seems trivial,
this touches on some challenging questions of design:
Presumably the invocation of a Render Job will need some kind of
''Render Context'' — since the relation of the absolute nominal time
to some output frame number is strictly ''a concern of the render process''.

However, it seems prudent to keep that relation separate from the `RenderInvocation`,
so that access to all invocation parameters has to be supplied by the
Render Job Functor.

This change in the test setup now uses a `DiagnosticOutputSlot`
instead of directly allocating some buffer for output; the data
written into the `DataSink` can be investigated after-the-fact.
2026-06-28 01:41:59 +02:00
3bffc00dbc Invocation: verify (and fix) usage of the AllocationCluster
turns out that was not (yet) tested and did not even compile,
due to a missing cross-constructor to get from one data allocation
policy to another one backed by the same cluster.

- added test case to `NodeBuilder_test` to demonstrate basic usage
- use `AllocationCluster` in NodeOpera_test and verify that allocations took place
2026-06-26 19:25:25 +02:00
437ef3a4a5 Invocation: verify correct chained hash computation
Works as expected, since it was directly adapted from NodeLink_test

The preceding changeset however caused a SEGFAULT,
since the initialisation order in the helper class `RenderPipeline`
was not correct, so that the node builder used a not-yet initialised
`AllocationCluster`....
2026-06-26 01:44:53 +02:00
2ddb921e6e Invocation: create a test setup with a complex Render Node tree
In NodeLink_test, a processing pipeline was established that
works on `TestFrame` and is quite complex; here I use a slightly
simplified variant (featuring only a single port) to perform
the same kind of computations.

However, node memory allocations are managed by an `AllocationCluster`
2026-06-25 23:34:52 +02:00
70bef00324 Invocation: provide a fake engine context setup
The purpose of this test is to invoke a Render Node graph
using the full service environment as present in the Render Engine.
However, for sake of clarity, I do not want to pull up the Engine subsystem,
but rather use a local / mocked setup of all the services involved.

The node invocation is performed from within a worker thread,
to demonstrate integration with the thread-local memory pools.

The first simple test case builds and then pulls a source node.
2026-06-25 01:54:21 +02:00
f3dcbfe8b9 pick up work after the LAC-26 Maynooth
...after having settled the problems related to buffer management
and the interplay between buffers and output sinks, the ''next goal''
is to drive ahead the integration between job planning and invocation...
2026-06-24 18:27:12 +02:00
40 changed files with 7336 additions and 173 deletions

View file

@ -361,7 +361,13 @@ namespace lib {
Policy (AllocationCluster& clu)
: AllocationPolicy<I,E,Adapter> (clu.getAllocator<std::byte>())
: Base{clu.getAllocator<std::byte>()}
{ }
/** @note allow cross-construction from any AllocationCluster based allocator */
template<class U, class V>
Policy (AllocationPolicy<U,V,Adapter>& relatedPolicy)
: Base(relatedPolicy)
{ }
bool

View file

@ -114,7 +114,7 @@ namespace meta{
* In the typical usage situation, the delegate type \a FUNC is implemented
* mostly with inline functions, and thus the bulk of the actual implementation
* code will be emitted into this static delegation function. The template
* \ref InvokerBuilder provides the blueprint for this code generation.
* \ref FunInline::activate<FUNC>() provides the blueprint for this code generation.
*/
template<class FUN>
struct Invoker

View file

@ -0,0 +1,52 @@
/*
DISPOSITION.hpp - actual constitution of a parameter descriptor
Copyright (C)
2026 Hermann Vosseler <Ichthyostega@web.de>
  **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.
*/
/** @file disposition.hpp
** Concrete configuration of a parameter descriptor with type and value provision.
** This is the full definition, with all details spelled out, and what is actually
** stored within the _outer shell_ of a Parameter. The publicly visible interface
** of the latter is implemented by delegating to the constituents of the disposition.
**
** @todo WIP-WIP-WIP can be considered an initial draft and placeholder, as of 2026
*/
#ifndef LIB_PAR_DISPOSITION_H
#define LIB_PAR_DISPOSITION_H
#include "lib/par/provision.hpp"
#include "lib/par/param-type.hpp"
namespace lib {
namespace par {
/**
* A complete configuration and setup of a parameter, including a type specification
* with domain and scale, and a value provision. Can be generated through a builder
* notation and stored within the Parameter object (as a container).
*/
class Disposition
: public ParamType
{
public:
};
}} // namespace lib::par
#endif /*LIB_PAR_DISPOSITION_H*/

313
src/lib/par/domain.hpp Normal file
View file

@ -0,0 +1,313 @@
/*
DOMAIN.hpp - represent the value domain of parameters
Copyright (C)
2026 Hermann Vosseler <Ichthyostega@web.de>
  **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.
*/
/** @file domain.hpp
** Specification of a base type and a value domain for parameters.
** A Domain descriptor implicitly also encodes the concrete value type
** and exposes a set of virtual functions to handle all supported value conversions.
** Implementing this interface creates a conversion matrix and allows to implement
** generic conversions by double-dispatch. Since virtual interfaces must be complete
** at compile time however, such an approach requires to define a finite set of
** supported parameter value types, which seems adequate, since the universe
** of possible values is limited anyway: elementary values in parameters
** must be primitive base types that can be created and destroyed
** without side effect. Relying on the further features of
** the parameter type system, compound types (vectors)
** and limited sub-scales can be defined.
**
** @todo WIP-WIP-WIP this is a draft and placeholder, as of 2026
*/
#ifndef LIB_PAR_DOMAIN_H
#define LIB_PAR_DOMAIN_H
#include "lib/meta/typelist.hpp"
//#include "lib/meta/typelist-util.hpp"
#include "lib/meta/generator.hpp"
#include "lib/par/provision.hpp"
#include "lib/meta/trait.hpp"
#include "lib/util.hpp"
#include <concepts>
#include <limits>
namespace lib {
namespace par {
using util::isNeg;
using std::same_as;
using std::floating_point;
using std::numeric_limits;
template<typename X>
constexpr inline ValBuff&
asValBuff (X& something)
{
void* rawMem{& something};
return * static_cast<ValBuff*> (rawMem);
}
template<typename X>
constexpr inline ValBuff const&
asValBuff (X const& something)
{
void const * rawMem{& something};
return * static_cast<ValBuff const *> (rawMem);
}
template<typename X>
constexpr inline X&
asValue (ValBuff& storage)
{
void* rawMem{& storage};
return * static_cast<X*> (rawMem);
}
template<typename X>
constexpr inline X const&
asValue (ValBuff const& storage)
{
void const * rawMem{& storage};
return * static_cast<X const *> (rawMem);
}
template<typename X>
class TypeHandler
{
public:
virtual void extractAs (X& targetVal, ValBuff const& valBuff) =0;
virtual void conform (ValBuff& targetBuff, X const& srcVal) =0;
};
// using lib::meta::typeseq;
using BaseTypes = meta::Types<int
,int64_t
,uint
,uint64_t
,double
,float
,bool
>;
/** build a generic visitor interface for all types in list */
using TypeHandlerInterface = meta::InstantiateForEach<BaseTypes::List, TypeHandler, Provision>;
/**
* Interface: mark the allowed value domain of a parameter value.
* The identity of the actual implementation can be used as type descriptor,
* since all concrete full base domains are registered at compile time; the
* virtual Domain interface exposes the possible conversions.
* @todo 7/2026 not clear if we can support all conversions unconditionally,
* or if some notion of an _impossible conversion_ is required. It is
* clear however, that the access to an actual parameter **must fail**
* at compile time, if a sensible conversion can not be provided.
*/
class Domain
: public TypeHandlerInterface
{
public:
virtual ~Domain(); ///< this is an interface
virtual void applyLimit (ValBuff&) =0;
virtual void transferTo (ValBuff const& source, Domain& targetDomain, ValBuff& target) =0;
protected:
};
// GCC > 13 warns at class definition when a new overload shadows an inherited virtual function.
// While theoretically correct, in practice any call will be dispatched through the base interface,
// which in turn is also generated by metaprogramming from a type list.
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109740
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Woverloaded-virtual\"")
template<typename V>
struct DomainSetup
{
template<typename X, class BAS>
class TypeHandlerImpl
: public BAS
{
void extractAs (X& targetVal, ValBuff const& valBuff) override;
void conform (ValBuff& targetBuff, X const& srcVal) override;
};
using TypeHandlerChain = meta::InstantiateChained<BaseTypes::List, TypeHandlerImpl, Domain>;
};
template<typename V>
class BaseDomain
: public DomainSetup<V>::TypeHandlerChain
{
public:
void applyLimit (ValBuff&) override;
void transferTo (ValBuff const& source, Domain& targetDomain, ValBuff& target) override;
};
/* ===== type conversion details ===== */
/** Constraint: type \a SUB can not represent the full domain of type \a BAS
* @todo implicitly also implements a limitation to »numeric« types,
* due to the use of std::numeric_limits
*/
template<typename SUB, typename BAS>
concept sub_domain = std::signed_integral<SUB> != std::signed_integral<BAS>
or std::numeric_limits<SUB>::max() < std::numeric_limits<BAS>::max()
or std::numeric_limits<SUB>::lowest() > std::numeric_limits<BAS>::lowest()
;
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wsign-compare\"")
// the following functions are specifically crafted to work around
// the ambiguities caused by automatic signed-to-unsigned promotion;
// furthermore, the automatic promotion allows to perform the comparison.
template<typename X, typename Y>
inline constexpr bool
isLower_safe (X const& x, Y const& y)
{
return isNeg(x) == isNeg(y)? x < y
: isNeg(x);
}
template<typename X, typename Y>
inline constexpr bool
isLarger_safe (X const& x, Y const& y)
{
return isNeg(x) == isNeg(y)? x > y
: isNeg(y);
}
_Pragma("GCC diagnostic pop")
/** generic helper to conform into a common value domain
* @tparam SUB the implied target value domain
* @tparam BAS source data type
* @return a value in source data type,
* yet conformed into the target data range
*/
template<typename SUB, typename BAS>
inline constexpr BAS
preClamp (BAS const& value)
{
return value;
}
/** special case: clamp the source value into the target value domain.
* @remark tricky due to automatic conversions between signed/unsigned */
template<typename SUB, typename BAS> requires (sub_domain<SUB,BAS>
and not same_as<SUB,bool>)
inline constexpr BAS
preClamp (BAS const& rawVal)
{
static_assert (std::is_constructible_v<BAS,SUB const&>
,"Value domains must overlap at least partially "
"and values must be cross-constructible within overlap");
auto upperBound = numeric_limits<SUB>::max();
auto lowerBound = numeric_limits<SUB>::lowest();
return isLarger_safe(rawVal, upperBound)? BAS(upperBound)
: isLower_safe (rawVal, lowerBound)? BAS(lowerBound)
: rawVal;
}
/** special case: trigger a bool at the 0.5 level */
template<same_as<bool> B, floating_point F>
inline constexpr F
preClamp (F const& rawVal)
{
return 0.5 < rawVal? F{1} : F{0};
}
template<typename V, typename X>
inline void
assignConverted (V& targetVal, X const& srcVal)
{
if constexpr (std::is_assignable_v<V&, X const&>)
{
targetVal = preClamp<V> (srcVal);
}
else
if constexpr (std::is_constructible_v<V, X const&>)
{
targetVal.~U();
new(&targetVal) V (preClamp<V> (srcVal));
}
else
static_assert (!sizeof(X), "this type conversion is not supported");
}
template<typename V>
template<typename X, class BAS>
inline void
DomainSetup<V>::TypeHandlerImpl<X,BAS>::extractAs (X& targetVal, ValBuff const& valBuff)
{
V const& srcVal = asValue<V> (valBuff);
assignConverted (targetVal, srcVal);
}
template<typename V>
template<typename X, class BAS>
inline void
DomainSetup<V>::TypeHandlerImpl<X,BAS>::conform (ValBuff& targetBuff, X const& srcVal)
{
V& targetVal = asValue<V> (targetBuff);
assignConverted (targetVal, srcVal);
}
template<typename V>
inline void
BaseDomain<V>::applyLimit (ValBuff& valBuff)
{
V& val{asValue<V> (valBuff)};
val = preClamp<V> (val);
}
template<typename V>
inline void
BaseDomain<V>::transferTo (ValBuff const& source, Domain& targetDomain, ValBuff& target)
{
TypeHandler<V>& typeHandler{targetDomain};
typeHandler.conform (target, asValue<V> (source));
targetDomain.applyLimit (target);
}
}} // namespace lib::par
#endif /*LIB_PAR_DOMAIN_H*/

View file

@ -0,0 +1,37 @@
/*
ParamType - parameter type record including domain and scale
Copyright (C)
2026 Hermann Vosseler <Ichthyostega@web.de>
  **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.
* *****************************************************************/
/** @file param-type.cpp
** Implementation of parameter type registration.
**
** @todo WIP-WIP-WIP can be considered an initial draft and placeholder, as of 2026
*/
#include "lib/error.hpp"
#include "lib/par/parameter.hpp"
#include "lib/par/provision.hpp"
#include "lib/par/param-type.hpp"
namespace lib {
namespace par {
/* emit VTables in this translation unit... */
Provision::~Provision() { }
Domain::~Domain() { }
}} // namespace lib::par

View file

@ -0,0 +1,53 @@
/*
PARAM-TYPE.hpp - parameter type record including domain and scale
Copyright (C)
2026 Hermann Vosseler <Ichthyostega@web.de>
  **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.
*/
/** @file param-type.hpp
** Specification of the value domain and scale of a parameter.
** A ParamType descriptor implicitly also encodes the concrete value type, the
** allowed value range, clipping behaviour and specifics of the scale (discrete,
** stepping, continuous, logarithmic...).
**
** @todo WIP-WIP-WIP can be considered an initial draft and placeholder, as of 2026
*/
#ifndef LIB_PAR_PARAM_TYPE_H
#define LIB_PAR_PARAM_TYPE_H
#include "lib/par/domain.hpp"
namespace lib {
namespace par {
/**
* Interface: represent a specific configuration of parameter value type,
* range and scale. Every ParamType descriptor is also a _prototype_ and has
* an unique identity, ensured by registration, so that it acts as a type identifier.
* @todo RLY? registration??
* Furthermore, the interface exposes allowed conversion and conforming operations.
*/
class ParamType
: public Domain
{
public:
};
}} // namespace lib::par
#endif /*LIB_PAR_PARAM_TYPE_H*/

33
src/lib/par/parameter.cpp Normal file
View file

@ -0,0 +1,33 @@
/*
Parameter - generic parameter values
Copyright (C)
2008,2026 Hermann Vosseler <Ichthyostega@web.de>
  **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.
* *****************************************************************/
/** @file parameter.cpp
** Implementation functions of the Parameter abstraction.
**
** @todo this is a draft and placeholder, as of 2026
*/
#include "lib/error.hpp"
#include "lib/par/parameter.hpp"
namespace lib {
namespace par {
/** */
}} // namespace lib::par

100
src/lib/par/parameter.hpp Normal file
View file

@ -0,0 +1,100 @@
/*
PARAMETER.hpp - generic parameter values
Copyright (C)
2008,2026 Hermann Vosseler <Ichthyostega@web.de>
  **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.
*/
/** @file parameter.hpp
** A generic typed value that can be discovered and automated.
** The actual data type of the parameter is an opaque implementation detail,
** yet value ranges and conversions are handled automatically. It is thus
** both a descriptor and an opaque container. Parameter elements can be copied,
** yet the presence of some specific parameter at a given point in the model
** constitutes an identity -- and the parameter descriptor can be tagged
** with that identity.
**
** @todo this is a draft and placeholder, as of 2026
*/
#ifndef LIB_PAR_PARAMETER_H
#define LIB_PAR_PARAMETER_H
namespace lib {
namespace par {
/**
* Descriptor and container to hold a generic parameter.
* The value can be fixed or supplied by an automation function.
*/
class Parameter
{
public:
class Builder;
template<typename VAL>
VAL getVal();
template<typename VAL>
void setVal (VAL&&);
/* === Builder API === */
template<typename VAL>
static Builder forType();
protected:
};
class Parameter::Builder
{
public:
Parameter
build()
{
UNIMPLEMENTED ("terminal builder operation: package a disposition into the Parameter");
}
};
template<typename VAL>
Parameter::Builder
Parameter::forType()
{
UNIMPLEMENTED ("how to setup the base type configuration? do we need a type parameter in the Builder class?");
}
template<typename VAL>
VAL
Parameter::getVal()
{
UNIMPLEMENTED ("extract the »value«, possibly applying suitable type conversions");
}
template<typename VAL>
void
Parameter::setVal (VAL&& changedVal)
{
UNIMPLEMENTED ("apply suitable type conversions and clamping, then transfer the new value into the Provision interface");
}
}} // namespace lib::par
#endif /*LIB_PAR_PARAMETER_H*/

67
src/lib/par/provision.hpp Normal file
View file

@ -0,0 +1,67 @@
/*
PROVISION.hpp - determine how a parameter's value is given
Copyright (C)
2026 Hermann Vosseler <Ichthyostega@web.de>
  **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.
*/
/** @file provision.hpp
** Provide access to a parameter's value, either directly or by automation.
** Each parameter has a _base value_ and a time-based current value. The latter
** is retrieved by evaluating an automation function.
**
** @todo WIP-WIP-WIP can be considered an initial draft and placeholder, as of 2026
*/
#ifndef LIB_PAR_PROVISION_H
#define LIB_PAR_PROVISION_H
#include "lib/time/timevalue.hpp"
namespace lib {
namespace par {
using time::Time;
struct ValBuff { /*placeholder*/ };
/**
* Interface: access to a parameter's value, to adjust or evaluate automation.
* Conceptually, the value is decomposed into a _base value_ and possibly a
* time-controlled adjustment (automation). The base value can be retrieved
* and assigned, while the current time-based value is the result of evaluation.
* Any parameter value is associated to a ParamType that defines the underlying
* value domain and possibly a scale with range limits and metric constraints.
* @remark the value access and manipulation works _backwards_ from an implicit
* value that is conceptually located _within_ this provision towards
* an ValBuff for external access. The reason for this indirect
* formulation is that the »value« is assumed to comply to some data type,
* which however remains an opaque internal detail and is not disclosed.
*/
class Provision
{
public:
virtual ~Provision(); ///< this is an interface
virtual void retrieveInto (ValBuff&) const { /*NOP*/ }
virtual void pullAt (Time, ValBuff&) const { /*NOP*/ }
virtual void setValFrom (ValBuff const&) { /*NOP*/ }
virtual void connect (Provision&) { /*NOP*/ }
virtual void disconnnect() { /*NOP*/ }
};
}} // namespace lib::par
#endif /*LIB_PAR_PROVISION_H*/

54
src/lib/par/scale.hpp Normal file
View file

@ -0,0 +1,54 @@
/*
PARAM-TYPE.hpp - value scale to use for parameters
Copyright (C)
2026 Hermann Vosseler <Ichthyostega@web.de>
  **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.
*/
/** @file scale.hpp
** Specification of the allowed value scale of a parameter, including range and metric.
** The scale descriptor is used as delegate from the ParamType descriptor and maintained
** within a global registry for sake of deduplication. It is expected that a lot of parameters
** will share some common scales, like e.g. percentage values or decibels. The setup of a scale
** may limit the value range, provide hints for the UI representation -- yet may possibly also
** encompass nominal scales that comprise a selection of ID values.
**
** @todo WIP-WIP-WIP can be considered an initial draft and placeholder, as of 2026
*/
#ifndef LIB_PAR_SCALE_H
#define LIB_PAR_SCALE_H
#include "lib/par/domain.hpp"
namespace lib {
namespace par {
/**
* Interface: describe the properties of a value scale to use for parameters,
* including range limits and metric (linear, logarithmic). The core operation
* is to conform a value to this specific scale.
* @todo not clear if we want a virtual interface here....?
*/
class Scale
{
public:
virtual ~Scale(); ///< this is an interface
};
}} // namespace lib::par
#endif /*LIB_PAR_SCALE_H*/

View file

@ -60,7 +60,9 @@ namespace util {
inline constexpr int
sgn (NUM n)
{
return (n==0)? 0 :((n<0)? -1:+1 );
return n== NUM{0}? 0
: n < NUM{0}? -1
: +1;
}
template <class N1, class N2>
@ -77,12 +79,20 @@ namespace util {
return n1 < n2? N1(n2) : n1;
}
/** @remark safe to use also on unsigned */
template<typename NUM>
inline constexpr bool
isNeg (NUM const& num)
{
return num < NUM{0};
};
/** cut a numeric value to be >=0 */
template <typename NUM>
inline constexpr NUM
noneg (NUM val)
{
return (0<val? val : 0);
return max (val, NUM(0));
}
/** force a numeric to be within bounds, inclusively */

View file

@ -63,9 +63,7 @@ namespace engine {
static lib::Depend<EngineCtx> access;
private:
EngineCtx();
friend class lib::DependencyFactory<EngineCtx>;
};

View file

@ -117,14 +117,6 @@ namespace engine {
}
namespace { ///////////////////////////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs
inline bool
operator== (InvocationInstanceID const& l, InvocationInstanceID const& r)
{
return lumiera_invokey_eq (unConst(&l), unConst(&r));
}
}
/**
* Helper for tests: verify the given invocation parameters match this JobTicket.
*/

View file

@ -138,8 +138,8 @@ using lib::LUID;
{
if (empty()) return false;
InvocationInstanceID empty; /////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs
return not lumiera_invokey_eq (&util::unConst(provision_).invocationSeed, &empty)
InvocationInstanceID empty; //////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs
return provision_.invocationSeed != empty
and provision_.exitNode.isValid()
and util::and_all (provision_.prerequisites
,[](auto& pq){ return pq.prereqTicket.isValid(); });

View file

@ -152,7 +152,7 @@ namespace engine {
/**
* Standard implementation for a _Weaving Pattern_ to connect
* the input and output data feeds (buffers) into a processing function.
* @tparam INVO a configuration / policy base class to _adapt for invocation_
* @tparam INVO a configuration class / prototype to _adapt for invocation_
* @note assumptions made regarding the overall structure
* - `INVO::Feed` defines an _invocation adapter_ for the processing function
* - `INVO::buildFeed()` is a functor to (repeatedly) build `Feed` instances

View file

@ -58,7 +58,7 @@
** specialised parametrisation for some media handling library, and a lot of cross-linking pointers,
** it is important to care for efficient usage of memory with good locality. Furthermore, the higher
** levels of the build process will generate additional temporary data structures, refined gradually
** until the actual render node network can be emitted. Each builder level can thus be outfitted
** until the actual render node network can be emitted. Each builder level can thus be outfitted
** with a custom allocator — typically an instance of lib::AllocationCluster. Notably the higher
** levels can be attached to a separate AllocationCluster instance, which will be discarded once
** the build process is complete, while Level-2 (and below) uses the allocator for the actual
@ -257,7 +257,7 @@ namespace engine {
Connectivity
build()
{
PortData ports;
PortData ports{leads_.policyConnect()}; // use same allocator...
patternData_.collectEntries(ports);
return Connectivity{ports.build()
,leads_.build()

View file

@ -68,7 +68,7 @@ namespace engine{
BufferProvider&
EngineFacilities::setupCacheProvider() ////////////////////////////////////////////////////////////////TICKET #1223 : in what ways does client code need to interact with the Frame Cache / Management?
{
return getNaiveBufferManager();
return setupBufferProvider();
}

View file

@ -0,0 +1,64 @@
/*
RENDER-OUTPUT-CTX.hpp - output control parameters for rendering
Copyright (C)
2026, Hermann Vosseler <Ichthyostega@web.de>
  **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.
*/
/** @file render-output-ctx.hpp
** A configuration context with output control parameters for render processing.
** This context parameter store is associated with the [calculation streams](\ref CalcStream)
** and accessed from the [render node invocation](\ref RenderInvocation) to retrieve additional
** and context dependent settings for aspects beyond the automated parameters and the actual
** render function. Notably the output DataSink is abstracted and the corresponding target
** data buffer is exposed through this context.
** @todo »Playback Vertical Slice« -- effort towards first integration of render process ///////////////////TICKET #1221
** @todo 6/2026 still not entirely decided regarding the organisation of Render Job parameters;
** introducing such a context seems a good way to retain some limited flexibility while
** keeping matters focused.
*/
#ifndef STEAM_ENGINE_RENDER_OUTPUT_CTX_H
#define STEAM_ENGINE_RENDER_OUTPUT_CTX_H
#include "steam/common.hpp"
#include "vault/mem/buffhandle.hpp"
#include "vault/out/output-slot.hpp"
#include "lib/time/timevalue.hpp"
#include "lib/nocopy.hpp"
namespace steam {
namespace engine{
using lib::time::Time;
using vault::mem::BuffHandle;
/**
* Render output control parameters and configuration details.
*/
class RenderOutputCtx
: util::MoveOnly
{
using Output = vault::out::OutputSlot::DataSink;
Output sink_;
public:
BuffHandle getOutputBuffer (Time absoluteNominalTime);
};
}} // namespace steam::engine
#endif /*STEAM_ENGINE_RENDER_OUTPUT_CTX_H*/

View file

@ -50,6 +50,12 @@ namespace fixture {
* must remain fixed in memory as long as any derived render jobs are alive.
*
* @todo WIP-WIP as of 4/2023 -- starting with a fake implementation /////////////////////TICKET #1306 : create an actual link to the low-level Model
* @deprecated 2025 now it looks like this entity was just a step stone towards the actual design;
* during the first prototypical implementation it became clear that the concept of an
* »ExitNode« needs to be repurposed (and shifted by one level in the architecture).
* However -- it is not clear yet in which way to address/designate the actual node
* that shall be pulled from a given render job; there might be some mapping based
* on a _feed ID_ (or a process-ID).
*/
class NodeGraphAttachment
{

View file

@ -16,6 +16,8 @@
** Core abstraction: automation parameter interpolator.
** Each interpolator implementation has the ability to resolve intermediary
** values and to provide a parameter value for _every arbitrary point in time_.
** @deprecated 2026 the basic idea is valid — yet the final design will certainly be different;
** the only usage of this code is Fixture::getAutomation() ⟼ `Auto<double>*`
*/

View file

@ -16,6 +16,8 @@
** Implementation functions of the Parameter abstraction.
**
** @todo as of 2016, we still need to work out our parameter/automation concept.
** @deprecated 2026 the basic idea is valid — yet the final design will certainly be different;
** the only usage of this code is Fixture::getAutomation() ⟼ `Auto<double>*`
*/

View file

@ -16,6 +16,8 @@
** Core abstraction: parameter to be controlled and possibly automated.
**
** @todo as of 2016, we still need to work out our parameter/automation concept.
** @deprecated 2026 the basic idea is valid — yet the final design will certainly be different;
** the only usage of this code is Fixture::getAutomation() ⟼ `Auto<double>*`
*/

View file

@ -16,6 +16,8 @@
** Implementation functions of the Parameter abstraction.
**
** @todo as of 2016, we still need to work out our parameter/automation concept.
** @deprecated 2026 the basic idea is valid — yet the final design will certainly be different;
** the only usage of this code is Fixture::getAutomation() ⟼ `Auto<double>*`
*/

View file

@ -16,6 +16,8 @@
** Core abstraction: an entity to expose a single parameter
**
** @todo as of 2016, we still need to work out our parameter/automation concept.
** @deprecated 2026 the basic idea is valid — yet the final design will certainly be different;
** the only usage of this code is Fixture::getAutomation() ⟼ `Auto<double>*`
*/

View file

@ -32,6 +32,19 @@
#include <typeinfo>
/** @deprecated 6/2026 very likely the InvocationInstanceID will be obsoleted,
* and instead just the two parameter values need to be compared.
* As an interim solution, comparing the two 64bit data words
*/
bool
operator== (InvocationInstanceID const& l, InvocationInstanceID const& r)
{
return l.code.w1 == r.code.w1
and l.code.w2 == r.code.w2;
}
namespace vault{
namespace gear {
@ -115,44 +128,4 @@ namespace gear {
return hash;
}
}} // namespace vault::gear
namespace {
using vault::gear::Job;
inline Job&
forwardInvocation (lumiera_jobDefinition& jobDef)
{
Job& job = static_cast<Job&> (jobDef);
return job;
}
}
extern "C" { /* ==== implementation C interface for job invocation ======= */
void
lumiera_job_invoke (LumieraJobDefinition jobDef)
{
REQUIRE (jobDef);
forwardInvocation(*jobDef).triggerJob();
}
size_t
lumiera_job_get_hash (LumieraJobDefinition jobDef)
{
REQUIRE (jobDef);
return hash_value (forwardInvocation (*jobDef));
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs
#include "lib/luid.h"
int
lumiera_invokey_eq (void* l, void* r)
{
return lumiera_uid_eq ((LumieraUid)l, (LumieraUid)r);
}
}/* extern "C" */

View file

@ -32,6 +32,13 @@
** the job over to the scheduler.
**
** @warning as of 4/2023 the Job datastructure will be remoulded ///////////////////////////////////////////TICKET #1280
** @deprecated in 2026 it does not seem like a good idea anymore to use "Render Jobs"
** to act like an _interface_ between Steam and Vault layer. Meanwhile, while
** the concept of **Jobs** (coroutines) is still upheld, the C datastructure job,
** as defined in this header, seems to mix up the traits of an interface and
** aspects of implementation. It is largely superseded by the _Activity Language_
** of the scheduler, and will likely be remoulded into a technical artifact of
** preparing job definitions in the Dispatcher.
**
** @see SchedulerFrontend
** @see JobTicket
@ -100,6 +107,11 @@ typedef struct lumiera_jobClosure* LumieraJobClosure;
* hash key for each invocation, which can be used for caching; obviously
* this hash need to be built from the JobTicket, based on ProcNode structure
* and the nominal Time. ///////////////////////////////////TICKET #1293
* @deprecated 6/2026 now able to conduct a full analysis of required information,
* and it seems the job definition does not need any kind of identity marker;
* caching will rely on chained specification hash keys rather.
* The existing tests will have to switch over using the two free parameter
* values, now present in each LumieraJobParameter...
*/
union InvocationInstanceID
{
@ -128,8 +140,9 @@ struct lumiera_jobParameter_struct
raw_time_64 nominalTime; /////////////////////////////////////////////////////////////////////////TICKET #1295 job invocation parameter: framework to interpret this time
InvocationInstanceID invoKey;
//////////////////////////////////////////////////////////////TODO: place an additional parameter value here, or make the instanceID globally unique?
////////////////////////////////////////////////////////////////////////////////////TICKET #1293 job invocation identity
////////////////////////////////////////////////////////////////////////////////////TICKET #1295 : rework Job parameters to accommodate input / output info required for rendering
////////////////////////////////////////////////////////////////////////////////////TICKET #1293 job invocation identity (possibly not needed anymore, can be derived from the data...)
size_t srcParam;
size_t sinkParam;
};
typedef struct lumiera_jobParameter_struct lumiera_jobParameter;
typedef lumiera_jobParameter* LumieraJobParameter;
@ -194,6 +207,9 @@ typedef lumiera_jobDescriptor* LumieraJobDescriptor;
#include <string>
/** @warning performs a comparison on the entire packed content */
bool operator== (InvocationInstanceID const&, InvocationInstanceID const&);
namespace vault{
namespace gear {
@ -284,6 +300,8 @@ namespace gear {
this->jobClosure = &specificJobDefinition;
this->parameter.nominalTime = _raw(nominalFrameTime);
this->parameter.invoKey = invoKey;
this->parameter.srcParam = 0;
this->parameter.sinkParam = 0;
}
// using standard copy operations
@ -326,28 +344,6 @@ namespace gear {
}} // namespace vault::gear
extern "C" {
#endif /* =========================== C Interface ===================== */
/** trigger execution of a specific job,
* assuming availability of all prerequisites */
void lumiera_job_invoke (LumieraJobDefinition);
/** calculate a hash value based on the Job's \em identity. */
size_t lumiera_job_get_hash (LumieraJobDefinition);
int lumiera_invokey_eq (void* l, void* r);
#ifdef __cplusplus
}
#endif
#endif /*VAULT_GEAR_JOB_H*/

View file

@ -52,7 +52,7 @@
namespace lib {
namespace time{
class Quantiser;
typedef std::shared_ptr<const Quantiser> PQuant;
using PQuant = std::shared_ptr<const Quantiser>;
}}
namespace vault {

17
tests/16parameter.tests Normal file
View file

@ -0,0 +1,17 @@
TESTING "Library Test Suite: parameters" ./test-suite --group=param
PLANNED "Parameter Values" Parameter_test <<END
return: 0
END
TEST "Parameter Domain Type" ParameterDomain_test << END
return: 0
END
PLANNED "Parameter Provision" ParameterProvision_test << END
return: 0
END

View file

@ -26,7 +26,7 @@ TEST "Proc Node metadata key" NodeMeta_test <<END
END
PLANNED "Proc Node operation modes" NodeOpera_test <<END
TEST "Proc Node operation modes" NodeOpera_test <<END
END

View file

@ -0,0 +1,293 @@
/*
ParameterDomain(Test) - document the handling of parameter type domain
Copyright (C)
2026, Hermann Vosseler <Ichthyostega@web.de>
  **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.
* *****************************************************************/
/** @file parameter-domain-test.cpp
** unit test \ref ParameterDomain_test
*/
#include "test/run.hpp"
//#include "test/test-helper.hpp"
#include "lib/par/domain.hpp"
#include "test/diagnostic-output.hpp"////////////TODO
//#include <utility>
//#include <string>
//#include <vector>
#include <concepts>
//using std::string;
//using std::vector;
//using std::swap;
namespace lib {
namespace par {
namespace test{
// using lumiera::error::LUMIERA_ERROR_LOGIC;
using std::floating_point;
namespace {//Test fixture....
template<typename NUM>
constexpr NUM _MAX = std::numeric_limits<NUM>::max();
template<typename NUM>
constexpr NUM _MIN = std::numeric_limits<NUM>::lowest();
template<floating_point FLO>
constexpr FLO _EPSILON = std::numeric_limits<FLO>::epsilon();
}//(End)Test fixture
/**************************************************************************//**
* @test cover properties of generic parameter containers.
*/
class ParameterDomain_test : public Test
{
virtual void
run (Arg)
{
seedRand();
basics();
verify_valueLimits();
verify_valueAccess();
verify_valueSetting();
verify_opaqueAssign();
}
void
basics()
{
}
/** @test base operation to bracket a given value
* so that it fits within another target type's value domain.
* @note properly handles the signed vs. unsigned mismatch, so that
* conversion from signed to unsigned can not wrap.
*/
void
verify_valueLimits()
{
// Generic check if the first (target) type can hold
// only a limited part of the second (source) type's domain
CHECK ((sub_domain<uint,int> == true));
CHECK ((sub_domain<int,uint> == true));
CHECK ((sub_domain<short,long> == true));
CHECK ((sub_domain<long,short> == false));
CHECK ((sub_domain<float,double> == true));
CHECK ((sub_domain<double,float> == false));
// Limit a value to within a target type's domain
// (without actually converting into the target type)
CHECK (preClamp<int> (23) == 23);
CHECK (preClamp<int> (-55) == -55);
CHECK (preClamp<uint> (23) == 23);
CHECK (preClamp<uint>(-55) == 0);
CHECK (preClamp<short> (_MAX<int64_t>) == 32767);
CHECK (preClamp<short> (_MIN<int64_t>) == -32768);
CHECK (preClamp<ushort> (_MAX<float>) == 65535);
CHECK (preClamp<ushort> (_MIN<float>) == 0 );
// the return value has the same type as the argument...
CHECK (preClamp<float> (_MAX<uint64_t>) == _MAX<uint64_t>);
CHECK (preClamp<float> (_MIN<double>) == double(_MIN<float>) );
// Warning: the following is not standard C++, yet supported on GCC / CLang / libstdc++
static_assert (_MAX<float> < _MAX<unsigned __int128>);
CHECK (float(_MAX<unsigned __int128>) == "inf"_expect );
CHECK ((unsigned __int128)_MAX<float> == "340282346638528859811704183484516925440"_expect );
CHECK (preClamp<float> (_MAX<unsigned __int128>) == (unsigned __int128)_MAX<float>);
// Note: a trigger at 0.5+ε is built into the clamp towards bool
CHECK (preClamp<bool> (0.5) == 0 );
CHECK (preClamp<bool> (0.5+_EPSILON<double>) == 1 );
CHECK (preClamp<bool> (_MIN<double>) == 0 );
// Systematic coverage of signed ⟷ unsigned
CHECK (preClamp<int8_t> (_MAX<int16_t>) == 127);
CHECK (preClamp<int8_t> ( int16_t{0}) == 0);
CHECK (preClamp<int8_t> (_MIN<int16_t>) == -128);
CHECK (preClamp<int8_t> (_MAX<uint16_t>) == 127);
CHECK (preClamp<int8_t> (_MIN<uint16_t>) == 0);
CHECK (preClamp<uint8_t> (_MAX<int16_t>) == 255);
CHECK (preClamp<uint8_t> ( int16_t{0}) == 0);
CHECK (preClamp<uint8_t> (_MIN<int16_t>) == 0);
CHECK (preClamp<int16_t> (_MAX<int8_t>) == 127);
CHECK (preClamp<int16_t> ( int8_t{0}) == 0);
CHECK (preClamp<int16_t> (_MIN<int8_t>) == -128);
CHECK (preClamp<int16_t> (_MAX<uint8_t>) == 255);
CHECK (preClamp<int16_t> (_MIN<uint8_t>) == 0);
CHECK (preClamp<uint16_t> (_MAX<int8_t>) == 127);
CHECK (preClamp<uint16_t> ( int8_t{0}) == 0);
CHECK (preClamp<uint16_t> (_MIN<int8_t>) == 0);
CHECK (preClamp<int8_t> (_MAX<int8_t>) == 127);
CHECK (preClamp<int8_t> ( int8_t{0}) == 0);
CHECK (preClamp<int8_t> (_MIN<int8_t>) == -128);
CHECK (preClamp<int8_t> (_MAX<uint8_t>) == 127);
CHECK (preClamp<int8_t> (_MIN<uint8_t>) == 0);
CHECK (preClamp<uint8_t> (_MAX<int8_t>) == 127);
CHECK (preClamp<uint8_t> ( int8_t{0}) == 0);
CHECK (preClamp<uint8_t> (_MIN<int8_t>) == 0);
}
/** @test base function to access the contents of an opaque buffer
* with known type, and convert into another target type.
* - the base interface #Domain represents an opaque type
* - it exposes #TypeHandler interfaces for a fixed selection
* of common elementary #BaseTypes
* - here we use a `BaseDomain<int>` as actual implementation,
* yet knowledge regarding the type `int` is erased and any
* access is based solely on a `Domain&`
* - an opaque source ValBuf is passed explicitly
* @remark this covers the value access capability of a
* type handler interface, working on external storage.
*/
void
verify_valueAccess()
{
BaseDomain<int> domInt;
SHOW_EXPR(sizeof(BaseDomain<int>)) ///////////////////////////////////////TODO this should be only one »slot«, but is actually quite large, due to using multiple inheritance in the Domain interface. /////OOO fix that!
Domain& dom{domInt};
int val = 1 + rani (1000);
ValBuff& src = asValBuff (val);
uint target1{0};
CHECK (not target1);
TypeHandler<uint>& hu{dom};
hu.extractAs (target1, src);
CHECK (target1 == uint(val));
float target2{0};
TypeHandler<float>& hf{dom};
hf.extractAs (target2, src);
CHECK (target2 == float(val));
// value clamped to target domain...
val = _MIN<int>;
uint64_t target3{55};
TypeHandler<uint64_t>& hu64{dom};
hu64.extractAs (target3, src);
CHECK (target3 == _MIN<uint64_t>);
}
/** @test base function to assign a directly given data value
* into an opaque buffer with known (yet different) type.
* @remark since the type handler interface covers both reading
* and writing, here the roles are reversed here, so that the
* target of the assignment is the opaque buffer, whose actual
* type remains hidden behind the #Domain interface.
*/
void
verify_valueSetting()
{
BaseDomain<int> domInt;
Domain& dom{domInt};
int val_in_buff = 0;
ValBuff& target = asValBuff (val_in_buff);
uint src1 = 1 + rani (1000);
TypeHandler<uint>& hu{dom};
hu.conform (target, src1);
CHECK (val_in_buff == int(src1));
double src2 = ranRange (1,100);
CHECK (1 <= src2 and src2 < 100);
TypeHandler<double>& hd{dom};
hd.conform (target, src2);
CHECK (val_in_buff == int(floor (src2)));
// value clamped to target domain...
auto mini = _MIN<int64_t>;
TypeHandler<int64_t>& h64{dom};
h64.conform (target, mini);
CHECK (val_in_buff == _MIN<int>);
}
/** @test value transfer between opaque buffers with double-dispatch.
* - use several typed values, yet access these only marked as `ValBuff`
* - the call must be passed through _two virtual functions_, in order
* to re-gain the fully typed context; this is the »visitor pattern«
* @remark the API might seem bewildering; this is due to passing both source
* and target as reference to _some value buffer_, since this API describes
* only a data type disposition, while the actual data storage is defined
* as derived parameter data type.
*/
void
verify_opaqueAssign()
{
double double_val = ranRange (2,100);
int64_t i64_val{0};
bool boo{false};
BaseDomain<double> ddom;
BaseDomain<int64_t> idom;
BaseDomain<bool> bdom;
Domain& d1{ddom};
Domain& d3{bdom};
CHECK (i64_val == 0);
d1.transferTo (asValBuff(double_val), idom, asValBuff(i64_val));
CHECK (i64_val == int64_t(floor (double_val)));
CHECK (not boo);
d1.transferTo (asValBuff(double_val), bdom, asValBuff(boo));
CHECK (boo);
CHECK (double_val != 1.0);
d3.transferTo (asValBuff(boo), ddom, asValBuff(double_val));
CHECK (double_val == 1.0);
}
};
/** Register this test class... */
LAUNCHER (ParameterDomain_test, "unit lib");
}}} // namespace lib::par::test

View file

@ -0,0 +1,91 @@
/*
ParameterProvision(Test) - verify parameter value provision and automation
Copyright (C)
2026, Hermann Vosseler <Ichthyostega@web.de>
  **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.
* *****************************************************************/
/** @file parameter-provision-test.cpp
** unit test \ref ParameterProvision_test
*/
#include "test/run.hpp"
//#include "test/test-helper.hpp"
#include "lib/par/parameter.hpp"
//#include "lib/format-cout.hpp"
//#include <utility>
//#include <string>
//#include <vector>
//using std::string;
//using std::vector;
//using std::swap;
namespace lib {
namespace par {
namespace test{
// using lumiera::error::LUMIERA_ERROR_LOGIC;
namespace {//Test fixture....
}//(End)Test fixture
/**************************************************************************//**
* @test document behaviour of parameter value provision and automation.
*/
class ParameterProvision_test : public Test
{
virtual void
run (Arg)
{
simpleUsage();
verify_Value();
verify_Function();
}
void
simpleUsage()
{
}
void
verify_Value()
{
}
void
verify_Function()
{
}
};
/** Register this test class... */
LAUNCHER (ParameterProvision_test, "unit lib");
}}} // namespace lib::par::test

View file

@ -0,0 +1,102 @@
/*
Parameter(Test) - verify generic parameter values
Copyright (C)
2026, Hermann Vosseler <Ichthyostega@web.de>
  **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.
* *****************************************************************/
/** @file parameter-test.cpp
** unit test \ref Parameter_test
*/
#include "test/run.hpp"
//#include "test/test-helper.hpp"
#include "lib/par/parameter.hpp"
//#include "lib/format-cout.hpp"
//#include <utility>
//#include <string>
//#include <vector>
//using std::string;
//using std::vector;
//using std::swap;
namespace lib {
namespace par {
namespace test{
// using lumiera::error::LUMIERA_ERROR_LOGIC;
namespace {//Test fixture....
class Thing
{ };
template<typename X>
struct Some
{
X x;
};
typedef Some<Thing> SomeThing;
}//(End)Test fixture
/**************************************************************************//**
* @test cover properties of generic parameter containers.
*/
class Parameter_test : public Test
{
virtual void
run (Arg)
{
simpleUsage();
verify_typeMarker();
}
void
simpleUsage()
{
Parameter par = Parameter::forType<int>().build();
CHECK (0 == par.getVal<int>());
par.setVal (12.3);
CHECK (12 == par.getVal<int>());
CHECK (12u == par.getVal<uint64_t>());
CHECK (12.0 == par.getVal<double>());
CHECK (true == par.getVal<bool>());
}
void
verify_typeMarker()
{
}
};
/** Register this test class... */
LAUNCHER (Parameter_test, "unit lib");
}}} // namespace lib::par::test

View file

@ -171,7 +171,7 @@ namespace test{
}
/** @test various ways to build an populate the container
/** @test various ways to build and populate the container
* - with a defined interface type \a I, instances of arbitrary subclasses
* can be added, assuming there is sufficient pre-allocated buffer space;
* all these subclass instances are accessed through the common interface.

View file

@ -250,7 +250,7 @@ namespace test {
{
InvocationInstanceID empty; ///////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs
JobClosure& jobFunctor = static_cast<JobClosure&> (*job.jobClosure); //////////////////////////TICKET #1287 : fix actual interface down to JobFunctor (after removing C structs)
return lumiera_invokey_eq (&util::unConst(job).parameter.invoKey, &empty)
return job.parameter.invoKey == empty
and util::isSameObject (jobFunctor, nopFunctor());
}

View file

@ -127,7 +127,7 @@ namespace test {
CHECK (INSTANCEOF (vault::gear::NopJobFunctor, static_cast<JobClosure*> (nopJob.jobClosure))); //////////TICKET #1287 : fix actual interface down to JobFunctor (after removing C structs)
CHECK (nopJob.parameter.nominalTime == frameTime);
InvocationInstanceID empty; ///////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs
CHECK (lumiera_invokey_eq (&nopJob.parameter.invoKey, &empty));
CHECK (nopJob.parameter.invoKey == empty);
CHECK (MockJob::isNopJob(nopJob)); // this diagnostic helper checks the same conditions as done here explicitly
MockJobTicket mockTicket;
@ -211,7 +211,7 @@ namespace test {
// create another job from the (empty) seg1
job = seg1.jobTicket(0).createJobFor (someTime);
InvocationInstanceID empty; /////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs
CHECK (lumiera_invokey_eq (&job.parameter.invoKey, &empty)); // indicates that it's just a placeholder to mark a "NOP"-Job
CHECK (job.parameter.invoKey == empty); // indicates that it's just a placeholder to mark a "NOP"-Job
CHECK (seg1.jobTicket(0).empty());
CHECK (seg1.empty());
CHECK (not seg2.empty());

View file

@ -20,6 +20,7 @@
#include "steam/engine/engine-ctx.hpp"
#include "steam/engine/node-builder.hpp"
#include "steam/asset/meta/time-grid.hpp"
#include "lib/allocation-cluster.hpp"
#include "lib/time/timequant.hpp"
#include "lib/time/timecode.hpp"
#include "lib/iter-explorer.hpp"
@ -66,11 +67,12 @@ namespace test {
build_Node_closedParam();
build_connectedNodes();
build_ParamNode();
useAllocationCluster();
}
/** @test build a simple output-only Render Node
* @todo 12/24 ✔ define ⟶ ✔ implement
*/
void
build_simpleNode()
@ -97,11 +99,10 @@ namespace test {
* - expect the buffer to hold a single `uint` value after invocation
*/
uint
invokeRenderNode (ProcNode& theNode, Time nomTime =Time::ZERO)
invokeRenderNode (ProcNode& theNode, Time nomTime =Time::ZERO, uint port =0)
{
BuffHandle buff = EngineCtx::access().mem.lockBufferFor<long> (-55);
ProcessKey key{0};
uint port{0};
CHECK (-55 == buff.accessAs<long>());
@ -115,7 +116,6 @@ namespace test {
/** @test build a Node with a fixed invocation parameter
* @todo 12/24 ✔ define ⟶ ✔ implement
*/
void
build_Node_fixedParam()
@ -139,7 +139,6 @@ namespace test {
* the time into an implicitly defined grid
* - install both into a render node
* - set a random _nominal time_ for invocation
* @todo 12/24 ✔ define ⟶ ✔ implement
*/
void
build_Node_dynamicParam()
@ -171,7 +170,6 @@ namespace test {
* - again use a processing function which takes a parameter
* - but then _decorate_ this functor, so that it takes different arguments
* - attach parameter handling to supply these adapted arguments
* @todo 2/25 ✔ define ⟶ ✔ implement
*/
void
build_Node_adaptedParam()
@ -239,7 +237,6 @@ namespace test {
* - use the »simplified 1:1 wiring«, which connects consecutively
* each input slot to the next given node on the same port number;
* here we only use port#0 on all three nodes.
* @todo 12/24 ✔ define ⟶ ✔ implement
*/
void
build_connectedNodes()
@ -287,7 +284,6 @@ namespace test {
* - perform effectively the same computation as the preceding test
* - but use two new custom parameters in the Param Agent Node
* - pick them up from the nested source nodes by accessor-functors
* @todo 12/24 ✔ define ⟶ ✔ implement
*/
void
build_ParamNode()
@ -343,6 +339,49 @@ namespace test {
CHECK (res == peek+1 + LIFE_AND_UNIVERSE_4EVER+1 );
CHECK (peek != -1);
}
/** @test demonstrate use of a custom allocator
* - use local AllocationCluster instance for this test
* - place and manage the resulting ProcNode in this cluster
* - furthermore, allocate administrative internals also there
* - build a Node with two ports, each with a parameter and output buffer
* - demonstrate the created Node works as expected
* - verify that allocations happened
*/
void
useAllocationCluster()
{
auto procFun = [](ushort param, uint* buff){ *buff = param; };
lib::AllocationCluster alloc;
CHECK (0 == alloc.numBytes());
auto& node = alloc.create<ProcNode>( // ◁───────────────────────────────────┨ place resulting ProcNode into the cluster
prepareNode("Test")
.withAllocator(alloc) // ◁───────────────────────────────────┨ policy to use this cluster for internal allocations
.preparePort()
.invoke ("funA()", procFun)
.setParam (LIFE_AND_UNIVERSE_4EVER)
.completePort()
.preparePort()
.invoke ("funB()", procFun)
.setParam (LIFE_AND_UNIVERSE_4EVER + 13)
.completePort()
.build()
);
CHECK (LIFE_AND_UNIVERSE_4EVER == invokeRenderNode (node, Time::ZERO, 0));
CHECK (LIFE_AND_UNIVERSE_4EVER+13 == invokeRenderNode (node, Time::ZERO, 1));
// minimum bounds of memory allocations...
const auto MIN_EXPECT = sizeof(ProcNode)
+ 2 * (sizeof(Port) + sizeof(BuffDescr) // defined two ports, each with an output type...
+ 2*(sizeof(void*) + sizeof(std::function<void()>))); // and parameter + buffer + proc-fun and param-fun
CHECK (MIN_EXPECT < alloc.numBytes());
}
};

View file

@ -39,7 +39,7 @@ using lib::Symbol;
using util::isnil;
using util::isSameObject;
using test::TestFrame;
using test::testRand;
using test::testRand;
namespace ont = test::ont;
namespace steam {
@ -362,7 +362,7 @@ namespace test {
// Prepare a precomputed parameter for the complete tree
auto selectFrameNo = [&](TurnoutSystem& tuSys){ return quantSecs(tuSys.getNomTime()); };
auto selectFrameNo = [&](TurnoutSystem& turSys){ return quantSecs(turSys.getNomTime()); };
auto paramSpec = buildParamSpec()
.addSlot (selectFrameNo);
@ -373,8 +373,8 @@ namespace test {
auto stepMixer = [] (FrameCnt id)-> ont::Factr { return util::limited (0, + id, 50) / 50.0; };
// note: binds the accessor for the precomputed FrameNo-parameter
auto autoFilter = [=](TurnoutSystem& tuSys){ return stepFilter (tuSys.get (accFrameNo)); };
auto autoMixer = [=](TurnoutSystem& tuSys){ return stepMixer (tuSys.get (accFrameNo)); };
auto autoFilter = [=](TurnoutSystem& turSys){ return stepFilter (turSys.get (accFrameNo)); };
auto autoMixer = [=](TurnoutSystem& turSys){ return stepMixer (turSys.get (accFrameNo)); };
// A Node with two (source) ports

View file

@ -17,28 +17,375 @@
#include "test/run.hpp"
//#include "lib/util.hpp"
#include "lib/allocation-cluster.hpp"
#include "test/test-rand-ontology.hpp"
#include "vault/out/diagnostic-output-slot.hpp"
#include "vault/mem/buffer-provider-setup.hpp"
#include "vault/mem/engine-buffer-metadata.hpp"
#include "vault/mem/engine-buffer-manager.hpp"
#include "vault/mem/local-buffer-stage.hpp"
#include "vault/mem/local-buffer-store.hpp"
#include "steam/engine/engine-facilities.hpp"
#include "steam/engine/node-builder.hpp"
#include "steam/asset/meta/time-grid.hpp"
#include "lib/time/timequant.hpp"
#include "lib/time/timecode.hpp"
#include "lib/depend-inject.hpp"
#include "lib/thread.hpp"
//#include "lib/symbol.hpp"
#include "lib/util.hpp"
#include "test/diagnostic-output.hpp"///////////////TODO
//using std::string;
using lib::Thread;
using std::this_thread::yield;
using std::this_thread::sleep_for;
using std::chrono_literals::operator ""us;
using lib::time::Time;
using lib::time::QuTime;
using lib::time::FrameNr;
using lib::time::FrameCnt;
using lib::time::FrameRate;
using lib::time::PGrid;
using steam::asset::meta::TimeGrid;
using vault::out::DiagnosticOutputSlot;
//using lib::Symbol;
//using util::isnil;
//using util::isSameObject;
using util::isLimited;
using test::TestFrame;
using test::testRand;
namespace ont = test::ont;
namespace steam {
namespace engine{
namespace test {
namespace { // ========== Test Setup ==========
using vault::mem::BufferProviderSetup;
using vault::mem::LocalBufferStage;
using vault::mem::LocalBufferStore;
using vault::mem::EngineBufferMetadata;
using vault::mem::EngineBufferManager;
class TestEngineBufferSetup
: public BufferProviderSetup
, public EngineFacilities
{
public:
TestEngineBufferSetup()
: BufferProviderSetup{*this}
{ }
auto buildStage() { return std::make_unique<LocalBufferStage>("NodeOpera_test"); }
auto buildStore() { return std::make_unique<LocalBufferStore>(); }
BufferProvider& setupBufferProvider() override { return *this; }
};
struct TestEngineCtx
{
// install mock instances for the relevant services
lib::DependInject<EngineBufferMetadata>::Local<> metaHub;
lib::DependInject<EngineBufferManager>::Local<> globalPool;
// install a rigged access front-end for the buffer management
lib::DependInject<EngineFacilities>::Local<TestEngineBufferSetup> bufferSetup;
lib::DependInject<EngineCtx>::Local<EngineCtx> frontEndCtx;
};
}
/***************************************************************//**
* @test check render node operation modes and collaboration.
* @todo 6/2026 this test shell demonstrate all relevant operation modes
* of render nodes, at the level of an component-integration-test.
* It is not clear yet what further operation modes this entails,
* beyond (obviously) invoking a render job... ////////////////////////////////////////////////TICKET #1367 : build a node invocation
* @remark the purpose of this test is to accomplish component integration
* regarding relevant usages of the render node network, seen from the
* perspective of an (exit) render node that is used / invoked / pulled.
*/
class NodeOpera_test : public Test
{
virtual void run(Arg)
virtual void
run (Arg)
{
UNIMPLEMENTED ("operate some render nodes as linked together");
seedRand();
invokeSimpleNode();
invokeNodeTree();
}
/** @test directly invoke a simple source node.
* - use the node builder to create a simple node
* immediately into the current stack frame
* - the processing function feeds a fixed random number
* into the given output buffer
* - then launch a worker thread to pull this node
* - create an output buffer directly from the worker thread
* - invoke the single port of the node, which is visible
* since it was created before starting the local worker thread
* - after the invocation, the fixed random number should have been
* placed into the output buffer
* - after the worker thread has finished, a single buffer allocation
* should have happened; this allocation was sent back automatically
* and can be claimed and released by the global buffer manager.
*/
void
invokeSimpleNode()
{
int randNum{rani()};
auto fun = [&](int* buff){ *buff = randNum; };
TestEngineCtx mockEngine;
ProcNode node{prepareNode("Test")
.preparePort()
.invoke("fun()", fun)
.completePort()
.build()};
Thread testWorker{[&] /* === directly perform a node invocation === */
{
BuffHandle buff = EngineCtx::access().mem.lockBufferFor<int> (-55);
Time nomTime{Time::ZERO};
ProcessKey key{0};
uint port{0};
CHECK (-55 == buff.accessAs<int>());
// Trigger Node invocation...
buff = node.pull (port, buff, nomTime, key);
CHECK (randNum == buff.accessAs<int>());
buff.release();
}};
while (testWorker)
yield(); // wait for worker to finish
// the central services were indeed requested / created
CHECK (mockEngine.bufferSetup);
CHECK (mockEngine.globalPool);
CHECK (mockEngine.metaHub);
// at least two buffer types registered (one generic for int)
CHECK (2 <= mockEngine.metaHub->cntEntries());
// one buffer was requested...
CHECK (1 == watch(*mockEngine.globalPool).numAllocs());
sleep_for (500us); // additional delay for the local pool's destructor
// (which is invoked after the thread has terminated)
// process and close remaining allocations
// sent back by the local pool's destructor...
mockEngine.globalPool->processPendingRequests();
// now there should be no memory leaks.......
CHECK (0 == watch(*mockEngine.globalPool).bytesLeased());
}
/** @test perform a RenderInvocation to pull from a complex node tree into an output sink.
* - use a test helper class to build a node topology similar to NodeLink_test
* - the ProcNodes and all backing structures are managed by an AllocationCluster
* - use a DiagnosticOutputSlot (by default configured for buffers sized for TestFrame)
*/
void
invokeNodeTree()
{
TestEngineCtx mockEngine;
RenderPipeline pipeline;
DiagnosticOutputSlot oSlot;
auto sink0 = * oSlot.getOpenedSinks();
const uint OUTPUT_FRAME{123};
Thread testWorker{[&] /* === perform a render invocation === */
{
Time nomTime{rani(60'000),0}; // drive test with a random »nominal Time« <60s with ms granularity
ProcessKey key{0};
uint port{0};
BuffHandle buff = sink0.lockFrame (OUTPUT_FRAME); // claim output buffer
CHECK (not buff.accessAs<TestFrame>().isValid()); // underlying buffer is zero initialised, thus no valid TestFrame yet
// Trigger Node invocation...
buff = pipeline.exitNode.pull (port, buff, nomTime, key);
buff.release();
// DiagnosticOutputSlot allows to investigate the data sent into the DataSink...
TestFrame const& result = watch(oSlot).feed(0).frame(OUTPUT_FRAME).accessAs<TestFrame>();
CHECK (result.isValid()); // data matches checksum
CHECK (not result.isPristine()); // is not a source frame (but processed)
CHECK (result.getChecksum() == pipeline.expectedChecksum (nomTime));
}};
while (testWorker)
yield(); // wait for worker to finish
sleep_for (500us);
mockEngine.globalPool->processPendingRequests();
// now there should be no memory leaks.......
CHECK (0 == watch(*mockEngine.globalPool).bytesLeased());
}
/**
* Helper context to build and manage a processing chain of Render Nodes.
* In the real Render Engine, these processing chains are generated by the Builder,
* and organised into »Segments« of the timeline, where each segment features a
* distinct node connectivity (i.e. typically for one clip).
* For this test, a single top-level _exit node_ is exposed.
* @note this helper context is constructed in _RAII style_,
* and the embedded AllocationCluster unwinds automatically.
* @see \ref NodeLink_test for a detailed breakdown of a similar topology
*/
class RenderPipeline
: util::NonCopyable
{
static constexpr ont::Flavr SRC_A = 10; ///< »chain-A« arbitrary source frame marker
static constexpr ont::Flavr SRC_B = 20; ///< similar for »chain-B«
PGrid secondsGrid_{TimeGrid::build (FrameRate{1})};
FrameCnt quantSecs (Time time) { return secondsGrid_->gridPoint (time); };
/** custom allocator for this node tree */
lib::AllocationCluster alloc_;
public:
ProcNode& exitNode{buildTree()};
HashVal
expectedChecksum (Time nomTime)
{
ont::FraNo fraNo = quantSecs(nomTime);
ont::Param param = stepFilter(fraNo);
ont::Factr mix = stepMixer (fraNo);
TestFrame f1{uint(fraNo),SRC_A};
TestFrame f2{uint(fraNo),SRC_B};
ont::manipulateFrame (&f1, &f1, param);
ont::combineFrames (&f1, &f1, &f2, mix);
return f1.getChecksum();
}
private:
ont::Param stepFilter(FrameCnt id) { return util::limited (10, -10 + id, 50); };
ont::Factr stepMixer (FrameCnt id) { return util::limited (0, + id, 50) / 50.0; };
/** @internal wire the processing node topology.
* This fake "render pipeline" is based on hash chaining computations,
* operating on TestFrame (with reproducible random data content).
* Two processing chains are built, each producing test frames;
* the first chain even adds a "filter". Some "parameters" are added
* to change the random data in a deterministic way. Finally, both chains
* are "mixed" together into a single result TestFrame. Furthermore, the
* parameter automation is based on a precomputed _frame number_ parameter,
* that is derived from the _absolute nominal time_ by quantisation into a
* time grid with a stepping of 1 second (-> `FrameRate{1}`). This precomputed
* frame number parameter is stored in a parameter data block on the call stack,
* prior to recursively evaluating the node chain attached below.
* @note the generated Render Nodes are placed into the AllocationCluster,
* together with all the further storage used by those nodes internally.
* The real Render Engine uses an AllocationCluster per Segment of the timeline.
*/
ProcNode&
buildTree()
{
// Prepare a precomputed parameter for the complete tree
auto selectFrameNo = [&](TurnoutSystem& turSys){ return quantSecs (turSys.getNomTime()); };
auto paramSpec = buildParamSpec()
.addSlot (selectFrameNo);
auto accFrameNo = paramSpec.makeAccessor<0>();
// Automation functions using the accessor for the precomputed FrameNo-parameter
auto autoFilter = [&](TurnoutSystem& turSys){ return stepFilter (turSys.get (accFrameNo)); };
auto autoMixer = [&](TurnoutSystem& turSys){ return stepMixer (turSys.get (accFrameNo)); };
// use processing functions from the »TestRand Ontology«
auto testGen = testRand().setupGenerator();
auto testMan = testRand().setupManipulator();
auto testMix = testRand().setupCombinator();
// A source node to produce random test frames
auto& n1s = alloc_.create<ProcNode>(
prepareNode("srcA")
.withAllocator(alloc_)
.preparePort()
.invoke(testGen.procID(), testGen.makeFun()) // params(frameNo, flavour)
.closeParam<1>(SRC_A) // --> fix the flavour ≔ SRC_A
.retrieveParam(accFrameNo)
.completePort()
.build());
// A node to »filter« the data in chain-A
auto& n1f = alloc_.create<ProcNode>(
prepareNode("filterA")
.withAllocator(alloc_)
.preparePort()
.invoke(testMan.procID(), testMan.makeFun())
.attachParamFun(autoFilter) // filter-param <-- autoFilter(frameNo)
.connectLead(n1s)
.completePort()
.build());
// A secondary source Node for the »chain-B«
auto& n2s = alloc_.create<ProcNode>(
prepareNode("srcB")
.withAllocator(alloc_)
.preparePort()
.invoke(testGen.procID(), testGen.makeFun()) // params(frameNo, flavour)
.closeParam<1>(SRC_B) // --> fix the flavour ≔ SRC_B
.retrieveParam(accFrameNo)
.completePort()
.build());
// A mixing Node to combine both chains
auto& mix = alloc_.create<ProcNode>(
prepareNode("mix")
.withAllocator(alloc_)
.preparePort()
.invoke(testMix.procID(), testMix.makeFun())
.attachParamFun(autoMixer) // mixer-param <-- autoMixer(frameNo)
.connectLead(n1f)
.connectLead(n2s)
.completePort()
.build());
// Place a »Param-Agent«-Node on top to pre-compute the FrameNo
auto& parNode = alloc_.create<ProcNode>(
prepareNode("Param")
.withAllocator(alloc_)
.preparePort()
.computeParam(paramSpec)
.delegateLead(mix)
.completePort()
.build());
// the AllocationCluster managed some storage...
CHECK (isLimited (1_KiB, alloc_.numBytes(), 5_KiB));
// here the »Param-Agent« also acts as the Exit-Node
return parNode;
}
};
};

View file

@ -303,7 +303,7 @@ namespace test {
* - the prime goal is to demonstrate correctness, balancing memory demand
* mostly through lock-free queues, without loosing any allocation.
*/
class BufferProviderLoad_test : public Test
class BufferProviderLoad_test : public Test
{
virtual void

File diff suppressed because it is too large Load diff