Compare commits
23 commits
ce40b83bb6
...
764185eb85
| Author | SHA1 | Date | |
|---|---|---|---|
| 764185eb85 | |||
| 3764479ebd | |||
| 7c35af3717 | |||
| 4cdf01bc72 | |||
| c60e7d35c6 | |||
| a37e99a643 | |||
| e48c03e35d | |||
| e3b6b87e93 | |||
| 11bb3bce73 | |||
| 7d35790e11 | |||
| 82b7acac57 | |||
| 532408a305 | |||
| 5df0252cc1 | |||
| 36684ff092 | |||
| 793c9950d4 | |||
| ce2c3d2c06 | |||
| 8db1813831 | |||
| 64416e3ba8 | |||
| 3bffc00dbc | |||
| 437ef3a4a5 | |||
| 2ddb921e6e | |||
| 70bef00324 | |||
| f3dcbfe8b9 |
40 changed files with 7336 additions and 173 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
52
src/lib/par/disposition.hpp
Normal file
52
src/lib/par/disposition.hpp
Normal 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
313
src/lib/par/domain.hpp
Normal 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*/
|
||||
37
src/lib/par/param-type.cpp
Normal file
37
src/lib/par/param-type.cpp
Normal 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
|
||||
53
src/lib/par/param-type.hpp
Normal file
53
src/lib/par/param-type.hpp
Normal 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
33
src/lib/par/parameter.cpp
Normal 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
100
src/lib/par/parameter.hpp
Normal 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
67
src/lib/par/provision.hpp
Normal 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
54
src/lib/par/scale.hpp
Normal 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*/
|
||||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -63,9 +63,7 @@ namespace engine {
|
|||
|
||||
static lib::Depend<EngineCtx> access;
|
||||
|
||||
private:
|
||||
EngineCtx();
|
||||
friend class lib::DependencyFactory<EngineCtx>;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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(); });
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
64
src/steam/engine/render-output-ctx.hpp
Normal file
64
src/steam/engine/render-output-ctx.hpp
Normal 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*/
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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>*`
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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>*`
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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>*`
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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>*`
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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>*`
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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" */
|
||||
|
|
|
|||
|
|
@ -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*/
|
||||
|
|
|
|||
|
|
@ -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
17
tests/16parameter.tests
Normal 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
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
293
tests/lib/par/parameter-domain-test.cpp
Normal file
293
tests/lib/par/parameter-domain-test.cpp
Normal 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
|
||||
91
tests/lib/par/parameter-provision-test.cpp
Normal file
91
tests/lib/par/parameter-provision-test.cpp
Normal 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
|
||||
102
tests/lib/par/parameter-test.cpp
Normal file
102
tests/lib/par/parameter-test.cpp
Normal 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
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue