Compare commits

..

No commits in common. "prg@ichthyostega.de/dev/play_signature" and "integration" have entirely different histories.

86 changed files with 885 additions and 13375 deletions

10
.clangd
View file

@ -1,10 +0,0 @@
# Configuration for the `clangd` language server -- https://clangd.llvm.org/config
#
# The compilation database is generated by the SCons build system into the target tree:
# scons COMPILEDB=yes (sticky: recorded in ./optcache)
# scons compiledb (one-off, compiles nothing)
# clangd does not search 'target/' on its own, hence this pointer.
CompileFlags:
CompilationDatabase: target

View file

@ -29,7 +29,6 @@ sys.path.append('./admin/scons')
import Setup
import Options
import Platform
import CompileDB
from Buildhelper import *
from LumieraEnvironment import *
@ -40,7 +39,6 @@ from LumieraEnvironment import *
env = Setup.defineBuildEnvironment() # dirs & compiler flags
env = Platform.configure(env) # library dependencies
compileDB = CompileDB.setup(env) # code intelligence (opt-in: COMPILEDB=yes)
@ -63,7 +61,7 @@ env.Clean ('build', [ 'scache.conf', '.sconf_temp', '.sconsign.dblite', 'config.
#Import('lumiera plugins tools gui testsuite doxydoc')
Import('lumiera plugins tools gui testsuite')
build = env.Alias('build', lumiera + plugins + tools + gui + compileDB)
build = env.Alias('build', lumiera + plugins + tools + gui)
# this defines the SCons **default target**
env.Default('build')

View file

@ -140,7 +140,39 @@ def createPlugins(env, directory, **kw):
# removed 10/2026 : checkCommandOption() "evaluate and verify an option, which may point at a command"
def checkCommandOption(env, optID, val=None, cmdName=None):
""" evaluate and verify an option, which may point at a command.
besides specifying a path, the option may read True, yes or 1,
denoting that the system default for this command should be used.
@return: True, if the key has been expanded and validated,
False, if this failed and the key was removed
"""
if not val:
if not env.get(optID): return False
else:
val = env.get(optID)
if val=='True' or val=='true' or val=='yes' or val=='1' or val == 1 :
if not cmdName:
print("WARNING: no default for %s, please specify a full path." % optID)
del env[optID]
return False
else:
val = env.WhereIs(cmdName)
if not val:
print("WARNING: %s not found, please specify a full path" % cmdName)
del env[optID]
return False
if not os.path.isfile(val):
val = env.WhereIs(val)
if val and os.path.isfile(val):
env[optID] = val
return True
else:
del env[optID]
return False

View file

@ -1,59 +0,0 @@
# coding: utf-8
##
## CompileDB.py - SCons build: generate a compilation database for clangd
##
# 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.
#####################################################################
from SCons.Script import COMMAND_LINE_TARGETS
def setup(env):
""" Emit a *compilation database*, describing every compiler invocation of the
complete build. This JSON file is the interface used by `clangd` and the other
tools built on the Clang front-end, which need to reproduce the exact flags
of a translation unit in order to parse it the way the build does.
The actual work is performed by the `CompilationDatabase` builder, which SCons
provides since 4.0: it installs an additional *emitter* on the object builders,
capturing each command line while the dependency graph is constructed. Thus no
compilation is involved -- `scons compiledb` takes about two seconds -- and the
database always covers the *complete declared build*, not just the requested
target, since the SConscript definitions are processed into a dependency tree
on every invocation anyway.
@return a dependency node, to be added to the `build` alias, or an empty list
when the feature is switched off (in which case nothing at all is
installed into the build environment).
@note **must be invoked after** `Platform.configure(env)`. The emitter would
otherwise also capture the `conftest_*` compilations performed by the
configure checks, which then show up as bogus entries below '.sconf_temp'.
This ordering constraint is why this module is a plain helper invoked
from the 'SConstruct' -- like `Setup`, `Options` and `Platform` -- and
not a SCons tool loaded from the `LumieraEnvironment` constructor.
@remark the database is written into the (relocatable) target tree, while the
'.clangd' configuration in the project root directs the language server
there; consumers do not search 'target/' on their own.
"""
if not isActivated(env): return []
env.Tool('compilation_db') # the builder provided by SCons
env['COMPILATIONDB_USE_ABSPATH'] = True # make recorded content unambiguous
compileDB = env.CompilationDatabase('$TARGDIR/compile_commands.json')
env.Alias('compiledb', compileDB)
return compileDB
def isActivated(env):
""" generation is opt-in through the (sticky) build option,
but can also be requested for a single invocation by naming the target.
"""
return env['COMPILEDB'] or 'compiledb' in COMMAND_LINE_TARGETS

View file

@ -44,6 +44,8 @@ class LumieraEnvironment(Environment):
self.path = Record (extract_localPathDefs(buildSetup)) # ◁───── e.g. buildExe -> env.path.buildExe
self.libInfo = {}
self.Tool("BuilderDoxygen")
self.Tool("ToolDistCC")
self.Tool("ToolCCache")
register_LumieraIconBuilder(self)
register_LumieraResourceBuilders(self)
register_LumieraCustomBuilders(self)

View file

@ -29,12 +29,13 @@ def defineCmdlineVariables(buildVars):
('ARCHFLAGS', 'Set architecture-specific compilation flags (passed literally to gcc)','')
,('CC', 'Set the C compiler to use.', 'gcc')
,('CXX', 'Set the C++ compiler to use.', 'g++')
,PathVariable('CCACHE', 'Integrate with CCache', '', PathVariable.PathAccept)
,PathVariable('DISTCC', 'Invoke C/C++ compiler commands through DistCC', '', PathVariable.PathAccept)
,EnumVariable('BUILDLEVEL', 'NoBug build level for debugging', 'ALPHA', allowed_values=('ALPHA', 'BETA', 'RELEASE'))
,BoolVariable('DEBUG', 'Build with debugging information and no optimisations', False)
,BoolVariable('OPTIMIZE', 'Build with strong optimisation (-O3)', False)
,BoolVariable('VALGRIND', 'Run Testsuite under valgrind control', True)
,BoolVariable('VERBOSE', 'Print full build commands', False)
,BoolVariable('COMPILEDB','Generate target/compile_commands.json for clangd', False)
,('TESTSUITES', 'Run only test suites matching the given pattern', '')
,('TESTMODE', 'test suite error mode for test.sh', '')
# ,BoolVariable('OPENGL', 'Include support for OpenGL preview rendering', False)
@ -59,7 +60,6 @@ Special Targets:
testcode: additionally compile the Testsuite
check : build and run the Testsuite
doc : generate documentation (Doxygen)
compiledb: just (re)generate target/compile_commands.json for clangd
all : build and testcode and doc
install : install created artifacts at PREFIX

67
admin/scons/ToolCCache.py Normal file
View file

@ -0,0 +1,67 @@
# coding: utf-8
##
## ToolCCache.py - SCons tool for integrating with CCache compiler cache
##
# Copyright (C) Lumiera.org and FreeOrion.org
# 2008, 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.
#####################################################################
# This SCons builder was extracted from http://www.freeorion.org/
# FreeOrion is an open-source platform-independent galactic conquest game
#
# history: 12/2008 adapted for Lumiera build system
import os
from Buildhelper import *
def generate(env):
""" Modify the environment such as to redirect any
C/C++ compiler invocations through CCache, while using
CCache config variables found in the os.environment.
"""
if not exists(env): return
assert env['CCACHE']
if not env['CCACHE'] in env['CC']:
env['CC'] = env.subst('$CCACHE $CC')
if not env['CCACHE'] in env['CXX']:
env['CXX'] = env.subst('$CCACHE $CXX')
print(env.subst("* Build using $CCACHE"))
for i in ['HOME'
,'CCACHE_DIR'
,'CCACHE_TEMPDIR'
,'CCACHE_LOGFILE'
,'CCACHE_PATH'
,'CCACHE_CC'
,'CCACHE_CPP2'
,'CCACHE_PREFIX'
,'CCACHE_DISABLE'
,'CCACHE_READONLY'
,'CCACHE_NOSTATS'
,'CCACHE_NLEVELS'
,'CCACHE_HARDLINK'
,'CCACHE_RECACHE'
,'CCACHE_UMASK'
,'CCACHE_HASHDIR'
,'CCACHE_UNIFY'
,'CCACHE_EXTENSION'
]:
if i in os.environ and i not in env:
env['ENV'][i] = os.environ[i]
def exists(env):
""" Ensure CCache is available.
"""
return checkCommandOption(env, 'CCACHE', cmdName='ccache')

56
admin/scons/ToolDistCC.py Normal file
View file

@ -0,0 +1,56 @@
# coding: utf-8
##
## ToolDistCC.py - SCons tool for distributed compilation using DistCC
##
# Copyright (C) Lumiera.org and FreeOrion.org
# 2008, 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.
#####################################################################
# This SCons builder was extracted from http://www.freeorion.org/
# FreeOrion is an open-source platform-independent galactic conquest game
#
# history: 12/2008 adapted for Lumiera build system
import os
from Buildhelper import *
def generate(env):
""" Modify the environment such as to redirect any
C/C++ compiler invocations through DistCC. Additionally
pull in the environment config variables used by DistCC
"""
if not exists(env): return
assert env['DISTCC']
if not env['DISTCC'] in env['CC']:
env['CC'] = env.subst('$DISTCC $CC')
if not env['DISTCC'] in env['CXX']:
env['CXX'] = env.subst('$DISTCC $CXX')
print(env.subst("* Build using $DISTCC"))
for i in ['HOME'
,'DISTCC_HOSTS'
,'DISTCC_VERBOSE'
,'DISTCC_FALLBACK'
,'DISTCC_LOG'
,'DISTCC_MMAP'
,'DISTCC_SAVE_TEMPS'
,'DISTCC_TCP_CORK'
,'DISTCC_SSH'
]:
if i in os.environ and i not in env:
env['ENV'][i] = os.environ[i]
def exists(env):
""" Ensure DistCC exists.
"""
return checkCommandOption(env, 'DISTCC', cmdName='distcc')

View file

@ -238,7 +238,6 @@ Targets
- *check* builds test code and runs our test-suites
- *research* builds just the research tree
- *doc* builds documentation (currently just Doxygen)
- *compiledb* (re)generates the xref:CompilationDB[compilation database] -- compiles nothing
- *all* builds the Application, test-suites and documentation
- *install* builds and installs the Lumiera Application
@ -266,34 +265,8 @@ of the application to be re-built. And of course it means, that you only ever co
necessary.
With SCons, there is no need for the usual ``cleaning paranoia''. Similarly, there is no need
for CCache, since the content signatures already prevent any redundant compilation within a
working tree. Unfortunately, calculating those MD5 sums requires some time on each build,
even if the net result is that nothing will happen at all.
.CCache and DistCC (removed 10/2026)
[NOTE]
--
Both were supported by the build system from 12/2008 until 10/2026, as options `CCACHE`
and `DISTCC`, each redirecting the compiler invocation through a wrapper command.
Neither was ever used in practice, and both are considered to be of marginal relevance.
- *CCache* caches compilation results across working trees and over time. SCons provides a
superior solution with content MD5, taking the defined dependency graph into account.
CCache would only provide a _secondary, opaque notion_ of ``this is the same compilation'' --
and when such a judgement is wrong, the resulting confusion is expensive to diagnose.
Furthermore, conditional compilation is also based on the MD5 change detection, and
turns out to be highly effective -- to the degree that full rebuilds are rare.
- *DistCC* distributes compilation over a network of build hosts. It was added when one
contributor had a private build network available; the project has had no such setup for
many years. Note also that DistCC needs to ship _preprocessed_ translation units over
the wire -- for a template-heavy C++ codebase the payload grows with exactly those
units that are slowest, so the benefit is smallest where it would be most welcome.
Beyond being unused, both carried a concrete liability: each rewrote the `$CXX` construction
variable into `wrapper g++` -- a mutation that silently changes what any tool reading the
build commands will conclude about the compiler in use, the
xref:CompilationDB[compilation database] among them.
--
for CCache (but using DistCC rocks !). Unfortunately, calculating those MD5 sums requires some
time on each build, even if the net result is that nothing will happen at all.
Configuration options
^^^^^^^^^^^^^^^^^^^^^
@ -382,62 +355,3 @@ to see examples of that technique -- which we also use to define that
global compound target variables like `core`, `vessel_lib`, `core_lib`,
`vault_lib`, and `support_lib`. These in turn are essential for
building the layered dependency hierarchy in our code.
[[CompilationDB]]
Compilation database
^^^^^^^^^^^^^^^^^^^^
The tooling built on the Clang front-end -- the https://clangd.llvm.org/[clangd] language
server, `clang-tidy`, `clang-format`'s include ordering, and various code browsers -- must
be able to parse a translation unit *exactly the way the build parses it*: same include
paths, same preprocessor definitions, same language standard. Since the build system is the
only place where that knowledge exists, the tools expect it to be handed over, in the form
of a JSON file listing one entry per compiler invocation. The format is specified by LLVM as
the https://clang.llvm.org/docs/JSONCompilationDatabase.html[JSON Compilation Database];
clangd's own https://clangd.llvm.org/design/compile-commands[notes on compile commands]
explain how a language server consumes it, and which heuristic it applies for header files,
which never appear in the database themselves.
Generation is *opt-in*, through the sticky option `COMPILEDB=yes`, or for a single
invocation by naming the `compiledb` target. Both write 'target/compile_commands.json'.
When the option is set, the database is a dependency of the `build` alias and is thus
refreshed by every ordinary build.
.How it works
SCons has provided a `CompilationDatabase` builder since version 4.0. Rather than
scraping build output, it installs an additional *emitter* on the object builders: whenever
an object node is created while the SConscript definitions are read, the emitter captures
the unexpanded command line together with the environment in force for that node. This
setup has several consequences:
Nothing is compiled::
Generating the database takes about two seconds, because it only requires
the dependency graph to be constructed, while no build commands will be evaluated.
All targets are captured::
The generated database always describes the complete build, not the requested target.
A plain `scons compiledb` covers 'src/', 'tests/', 'research/' and 'src/tool/' alike,
including the per-subtree flag differences -- the GTK include paths appear on the
'src/stage/' entries and nowhere else, `-Itests` is given only below 'tests/'.
Note that the captured command lines are *not* affected by the `VERBOSE` option: the
emitter reads the underlying `$CXXCOM` construction variable directly, not the abbreviated
progress messages configured in 'Setup.py'.
.How the feature is attached
The integration lives in 'admin/scons/CompileDB.py' and is invoked from the 'SConstruct'
through `CompileDB.setup(env)`, which returns the generated node so that the `build` alias
stays defined in one single, prominent place. Unlike the other custom tools, this one is
deliberately *not* loaded from the `LumieraEnvironment`: the emitter must be installed only
*after* `Platform.configure(env)` has run, since it would otherwise also capture the `conftest_*`
compilations of the configure checks and litter the database with entries below '.sconf_temp'.
.Consuming the database
clangd searches for 'compile_commands.json' beside the source and in a 'build/' subdirectory,
but not in our 'target/'. The '.clangd' configuration file in the project root supplies that
pointer, and is the place for any further language-server settings; its syntax is documented
under https://clangd.llvm.org/config[clangd configuration]. Note that the compilation
database describes what *GCC* is invoked with, which might not in every case be what Clang accepts;
Yet the database is still correct and useful in those spots; the parse simply degrades locally.
TIP: Lumiera embraces the policy to be compilable both with GCC and Clang
(but Clang support has fallen back at times, temporarily)

View file

@ -1,7 +1,6 @@
/* try.cpp - to try out and experiment with new features....
* scons will create the binary bin/try
*/
// 09/26 - ways to build a cyclically wrapping number domain
// 03/26 - runtime index access and iteration over tuple-likes
// 06/25 - provide a concept to accept _tuple-like_ objects
// 06/25 - investigate function type detection of std::bind Binders
@ -12,146 +11,425 @@
/** @file try.cpp
* Investigate techniques for creating a cyclically wrapping numeric domain.
* Actually this could be achieved with my `floordiv()` utility, yet the ramifications
* were not clear -- so I settled to build a stand-alone solution, based on the naive modulus
* technique, attempting to fix the negative offsets. It became clear that this approach would
* have to consider the domain boundaries and identify cases that could not be handlet. At that
* point, I used Claude to scrutinise and improve my draft, leading to a generic tool that is
* now relocated into the \ref util-quant.hpp header
* @see lib::par::Scale
* Work out a construction scheme to build an efficient element-accessor to data
* in a tuple-like with run-time indexing. As an extension, run-time pulled iteration
* can be established, using the IterExplorer framework.
* @see lib::meta::FunTrampoline
* @see lib::meta::TupleIdxAdaptor
*/
#include "lib/format-cout.hpp"
#include "lib/util-quant.hpp"
#include "lib/format-util.hpp"
#include "test/test-helper.hpp"
#include "test/diagnostic-output.hpp"
#include "lib/meta/common-result.hpp"
#include "lib/meta/tuple-concept.hpp"
#include "lib/meta/tuple-helper.hpp"
#include "lib/meta/function.hpp"
#include "lib/iter-adapter.hpp"
#include "lib/hetero-data.hpp"
#include "lib/meta/trait.hpp"
#include "lib/util.hpp"
#include <cmath>
template<typename NUM>
constexpr NUM _MAX = std::numeric_limits<NUM>::max();
template<typename NUM>
constexpr NUM _MIN = std::numeric_limits<NUM>::lowest();
template<typename N>
void
showCase (N val, N minVal, N maxVal)
{
cout << "val:"<<val<<" (min:"<<minVal<<" , max:"<<maxVal<<") ->"<< util::cyclicWrap (val,minVal,maxVal)<<endl;
}
#include <array>
template<typename N>
void
showFloorDiv (N num, N den)
{
cout << "FloorDiv:"<<num<<" / "<<den<<" ->"<< util::floordiv(num,den)<<endl;
}
using std::string;
template<typename N>
template<typename X>
void
showFloorWrap (N num, N den)
{
auto fwrap = util::floorwrap (num,den);
cout << "FloorWrap:"<<num<<" / "<<den<<" -> q="<<fwrap.quot<<" r="<<fwrap.rem<<endl;
}
show()
{
SHOW_TYPE(X)
}
template<lib::meta::tuple_like X>
void
show()
{
cout << "Tulik!! "<< ::test::showType<X>() <<endl;
lib::meta::forEachIDX<X> ([](auto i)
{
using Elm = std::tuple_element_t<i, X>;
cout <<" "<<uint(i)<<": "<< ::test::showType<Elm>() <<endl;
});
}
namespace lib {
namespace meta{
#if false ///////////////////////////////////////////////////////////////////////////////////////////////////TODO : new generic library functions
/**
* Concept to require a generic type sequence `Types<TYPES...>`
*/
template<class TSEQ>
concept typeseq = requires (TSEQ seq)
{
// TSEQ can invoke a function with a Types<TYPES> subclass
[]<typename...TYPES>(Types<TYPES...>&){/*placeholder*/}(seq);
};
#endif ///////////////////////////////////////////////////////////////////////////////////////////////////(End) : new generic library functions
template<template<class> class BUILD, typeseq TSEQ>
class FunTrampoline
{
using AllElm = ElmTypes<TSEQ>;
using Indices = AllElm::Idx;
using Builder = BUILD<TSEQ>;
using Functor = Builder::Functor;
using Table = std::array<Functor, AllElm::SIZ>;
template<size_t...idx>
static constexpr Table
buildFunctors (std::index_sequence<idx...>)
{
return {Builder().template build<idx>()...};
}
static constexpr Table functors = buildFunctors (Indices{});
public:
static constexpr size_t size() { return functors.size(); }
static constexpr size_t memSiz() { return sizeof(functors); }
static Functor const&
dispatch (size_t idx)
{
ENSURE (idx < functors.size());
return functors[idx];
}
using ResType = _Fun<Functor>::Ret;
};
#if false ///////////////////////////////////////////////////////////////////////////////////////////////////TODO : new generic library functions
template<typename T>
using isConst = std::is_const<remove_reference_t<T>>;
template<typename T>
using isLRef = std::is_lvalue_reference<T>;
template<class SEQ>
using Any_Const = ElmTypes<SEQ>::template OrAll<isConst>;
template<class SEQ>
using All_LRef = ElmTypes<SEQ>::template AndAll<isLRef>;
template<class SEQ>
static constexpr bool Any_Const_v = Any_Const<SEQ>::value;
template<class SEQ>
static constexpr bool All_LRef_v = All_LRef<SEQ>::value;
template<typename X, typename U, typename...TYPES>
struct ProbeCommonType
{
template<typename T1, typename T2>
using Reconciled = decltype(true ? std::declval<T1>() : std::declval<T2>());
template<typename RES>
struct FoundResult
: std::true_type
{
using Type = RES;
};
struct NoResultFound
: std::false_type
{ };
template<class T1
,class T2
,typename = Reconciled<T1,T2>>
static FoundResult<Reconciled<T1,T2>>
check(int);
template<class,class>
static NoResultFound
check(...);
using FirstResult = decltype(check<X,U>(0));
template<bool success, typename...TS>
struct Join
{
using Result = FirstResult;
};
template<typename T1, typename...TS>
struct Join<true, T1,TS...>
{
//wenn RES erfolgreich
using CT = FirstResult::Type;
using Result = ProbeCommonType<CT,T1,TS...>::Result;
};
using Result = Join<FirstResult::value, TYPES...>::Result;
};
template<typename...TYPES>
struct CommonResult
: ProbeCommonType<TYPES...>::Result
{ };
template<typename T1>
struct CommonResult<T1>
: CommonResult<T1,T1>
{ };
template<>
struct CommonResult<>
: std::false_type
{ };
template<typename...TYPES>
struct CommonResult<Types<TYPES...>>
: CommonResult<TYPES...>
{ };
template<typename...TYPES>
using CommonResult_t = CommonResult<TYPES...>::Type;
template<typename...TYPES>
static constexpr bool has_CommonResult_v = CommonResult<TYPES...>::value;
template<typename...TYPES>
concept reconcilable = requires
{
typename CommonResult<TYPES...>::Type;
};
template<typename...TYPES>
concept reconcilable_ref = reconcilable<TYPES...>
and isLRef_v<CommonResult_t<TYPES...>>;
template<typename...TYPES>
struct CommonResultTraits
{
static_assert (reconcilable<TYPES...>, "unable to reconcile these types");
using CommonVal = std::common_type_t<TYPES...>;
using ResType = CommonResult_t<TYPES...>;
//NOTE: std::common_type decays (strips CV and ref)
static constexpr bool makeConst = isConst_v<ResType>;
static constexpr bool exposeRef = isLRef_v<ResType>;
};
#endif ///////////////////////////////////////////////////////////////////////////////////////////////////(End) : new generic library functions
template<class TUP>
class TupleIndexAdaptor
: public TUP
{
template<typeseq SEQ>
struct SubscriptBuilder
{
using Tuple = RefTraits<TUP>::value_type;
using AddRefs = ElmTypes<SEQ>::template Apply<std::add_lvalue_reference_t>;
using ResType = CommonResult_t<AddRefs>;
using Functor = ResType(*)(Tuple&);
template<size_t idx>
constexpr auto
build()
{
return [](Tuple& tuple) -> ResType
{
return getElm<idx> (tuple);
};
}
};
using TypeSeq = ElmTypes<TUP>::Seq;
using Accessor = FunTrampoline<SubscriptBuilder, TypeSeq>;
public:
using ResType = Accessor::ResType;
using reference = ResType;
using value_Type = RefTraits<ResType>::value_type;
template<typename T>
TupleIndexAdaptor(T&& dataSrc)
: TUP{forward<T> (dataSrc)}
{ }
using TUP::TUP;
static constexpr size_t size() { return Accessor::size(); }
ResType
operator[] (size_t idx)
{
return Accessor::dispatch(idx)(*this);
}
};
template<class TUP>
TupleIndexAdaptor(TUP) -> TupleIndexAdaptor<std::decay_t<TUP>>;
}}
using namespace lib::meta;
template<typeseq SEQ>
struct Stringify
{
using Tuple = ElmTypes<SEQ>::Tup;
using Functor = string(*)(Tuple const&);
template<size_t idx>
constexpr auto
build()
{
using ElmT = std::tuple_element_t<idx, Tuple>;
return [](Tuple const& tuple)
{
return util::toString<ElmT> (getElm<idx> (tuple));
};
}
};
struct Uber { };
struct Untr : Uber { };
struct Unta : Uber { };
int
main (int, char**)
{
const int PER = 3;
int i = -5;
int d = i / PER;
cout << "d="<<d<<endl;
cout << "fd="<<util::floordiv(i,PER)<<endl;
using Tup = std::tuple<long,short>;
using Arr = std::array<int,3>;
using Hetero = lib::HeteroData<int,string>::Chain<short>::ChainExtent<bool,lib::meta::Nil>::ChainType;
uint u = _MAX<uint>;
SHOW_EXPR((lib::meta::tuple_like<Tup> ))
SHOW_EXPR((lib::meta::tuple_like<Arr> ))
SHOW_EXPR((lib::meta::tuple_like<Hetero> ))
SHOW_EXPR((lib::meta::tuple_like<string> ))
SHOW_EXPR((lib::meta::tuple_like<int> ))
cout << u << " fd="<<util::floordiv(u,uint(PER))<<" ...re="<<PER*util::floordiv(u,uint(PER))<<endl;
show<Tup>();
show<Arr>();
show<Hetero>();
show<int>();
showFloorDiv (7,5);
showFloorDiv (-7,5);
showFloorDiv (7,-5);
showFloorDiv (-7,-5);
using TupStringy = FunTrampoline<Stringify, ElmTypes<Tup>::Seq>;
SHOW_EXPR(TupStringy::size())
showFloorWrap (7,5);
showFloorWrap (-7,5);
showFloorWrap (7,-5);
showFloorWrap (-7,-5);
Tup tup23{2,3};
Tup tup42{4,2};
SHOW_TYPE(decltype(std::get<1>(tup42)))
SHOW_EXPR(std::get<1>(tup42))
SHOW_EXPR(TupStringy::dispatch(0)(tup23))
SHOW_EXPR(TupStringy::dispatch(0)(tup42))
SHOW_EXPR(TupStringy::dispatch(1)(tup23))
SHOW_EXPR(TupStringy::dispatch(1)(tup42))
showFloorWrap (_MAX<int>,1);
showFloorWrap (_MAX<int>,2);
showFloorWrap (_MAX<int>,5);
showFloorWrap (_MIN<int>,1);
showFloorWrap (_MIN<int>,2);
showFloorWrap (_MIN<int>,5);
showFloorWrap (_MAX<int>,-1);
showFloorWrap (_MAX<int>,-2);
showFloorWrap (_MAX<int>,-5);
showFloorDiv (_MAX<int>,1);
showFloorDiv (_MAX<int>,2);
showFloorDiv (_MAX<int>,5);
showFloorDiv (_MIN<int>,1);
showFloorDiv (_MIN<int>,2);
showFloorDiv (_MIN<int>,5);
showFloorDiv (_MAX<int>,-1);
showFloorDiv (_MAX<int>,-2);
showFloorDiv (_MAX<int>,-5);
showFloorWrap (_MAX<uint>,1u);
showFloorWrap (_MAX<uint>,2u);
showFloorWrap (_MAX<uint>,5u);
showFloorDiv (_MAX<uint>,1u);
showFloorDiv (_MAX<uint>,2u);
showFloorDiv (_MAX<uint>,5u);
showCase<int>(-7,-5,5);
showCase<int>(-6,-5,5);
showCase<int>(-5,-5,5);
showCase<int>(-4,-5,5);
showCase<int>(+5,-5,5);
showCase<int>(+6,-5,5);
showCase<int>(+7,-5,5);
showCase<int>(+8,-5,5);
showCase<int>(+9,-5,5);
showCase<int>(+10,-5,5);
showCase<int>(+11,-5,5);
showCase<int>(+12,-5,5);
showCase<int>(+14,-5,5);
showCase<int>(+15,-5,5);
showCase<int>( -7,-10,-7);
showCase<int>( -8,-10,-7);
showCase<int>( -9,-10,-7);
showCase<int>(-10,-10,-7);
showCase<int>(-11,-10,-7);
showCase<int>(-12,-10,-7);
showCase<int>(-13,-10,-7);
showCase<int>(-14,-10,-7);
showCase<int>(-15,-10,-7);
showCase<uint>(5,1000,1003);
using T1 = Types<int const&,long&, char&>;
using T2 = Types<int*,void*>;
using T3 = Types<int,string>;
SHOW_EXPR((has_CommonResult_v<T1>))
SHOW_EXPR((has_CommonResult_v<T2>))
SHOW_EXPR((has_CommonResult_v<T3>))
SHOW_EXPR((has_CommonResult_v<>))
SHOW_EXPR((has_CommonResult_v<float*>))
SHOW_EXPR((has_CommonResult_v<float*,void* const>))
SHOW_TYPE(CommonResult_t<T1>)
SHOW_TYPE(CommonResult_t<T2>)
SHOW_TYPE(CommonResult_t<float*>)
using CoTx = CommonResult_t<float*,float const*>;
SHOW_TYPE(CoTx)
showCase<float>(0.2 ,-2,-1.8);
showCase<float>(0 ,-2,-1.8);
showCase<float>(-0.8,-2,-1.8);
showCase<float>(-1.79,-2,-1.8);
showCase<float>(-1.8,-2,-1.8);
showCase<float>(-1.81,-2,-1.8);
showCase<float>(-1.99,-2,-1.8);
showCase<float>(-2 ,-2,-1.8);
showCase<float>(-2.01,-2,-1.8);
TupleIndexAdaptor<Tup> idxAdapt{tup42};
SHOW_EXPR(sizeof(tup42))
SHOW_EXPR(sizeof(idxAdapt))
SHOW_TYPE(TupleIndexAdaptor<Tup>::ResType)
SHOW_EXPR(idxAdapt.size())
SHOW_EXPR(idxAdapt[0])
SHOW_EXPR(idxAdapt[1])
SHOW_EXPR(getElm<0>(static_cast<Tup&> (idxAdapt)))
SHOW_EXPR(getElm<1>(static_cast<Tup&> (idxAdapt)))
SHOW_TYPE(decltype(idxAdapt[1]))
using Tup2 = std::tuple<int,int&, int>;
int ii{23};
Tup2 tup21{-1,ii,55};
TupleIndexAdaptor idxApt2{tup21};
SHOW_TYPE(decltype(tup21))
using IdxApt2 = decltype(idxApt2);
SHOW_TYPE(IdxApt2)
SHOW_TYPE(IdxApt2::ResType)
SHOW_EXPR(idxApt2[0])
SHOW_EXPR(idxApt2[1])
SHOW_EXPR(idxApt2[2])
SHOW_EXPR(++idxApt2[0])
SHOW_EXPR(idxApt2[1]++)
SHOW_EXPR(++++idxApt2[2])
SHOW_EXPR(tup21)
SHOW_EXPR(Tup2(idxApt2))
SHOW_EXPR(ii)
using TupIt = lib::IterableDecorator<lib::IdxStoreCore<IdxApt2>>;
TupIt tupIt{move(idxApt2)};
SHOW_EXPR( bool(tupIt) )
SHOW_EXPR( *tupIt )
SHOW_TYPE(decltype(*tupIt))
++*tupIt;
--ii;
SHOW_EXPR( *tupIt )
SHOW_EXPR(util::join(tupIt, "▶▶"))
SHOW_EXPR(( reconcilable<long,string> ))
SHOW_EXPR(( reconcilable<long&,string&> ))
SHOW_EXPR(( reconcilable<long,short> ))
SHOW_EXPR(( reconcilable<long&,short&> ))
SHOW_EXPR(( reconcilable_ref<long,string> ))
SHOW_EXPR(( reconcilable_ref<long&,string&> ))
SHOW_EXPR(( reconcilable_ref<long,short> ))
SHOW_EXPR(( reconcilable_ref<long&,const long&> ))
using TX = CommonResult_t<long&,const long&>;
SHOW_TYPE(TX)
SHOW_EXPR(( reconcilable<float*,void*> ))
SHOW_EXPR(( reconcilable<void,void*> ))
SHOW_EXPR(( reconcilable<float*&,void*&> ))
using TX2 = CommonResult_t<float*&,float*&>;
SHOW_TYPE(TX2)
SHOW_EXPR(( reconcilable<Uber,Unta> ))
SHOW_EXPR(( reconcilable<Untr,Unta> ))
SHOW_EXPR(( reconcilable<Untr&,Unta&> ))
SHOW_EXPR(( reconcilable<Uber&,Unta&> ))
SHOW_EXPR(( reconcilable_ref<Uber&,Unta&> ))
SHOW_EXPR(( reconcilable_ref<Uber&,Unta> ))
SHOW_EXPR(( reconcilable<Uber&,Unta> ))
SHOW_EXPR(( reconcilable<Uber,Untr&> ))
using TX3 = CommonResult_t<Uber&,Unta>;
SHOW_TYPE(TX3)
using TX4 = CommonResult_t<Unta&, const Uber&>;
SHOW_TYPE(TX4)
SHOW_EXPR(( reconcilable<Uber&,Unta&,Untr&> ))
using TX5 = CommonResult_t<const Uber&,Unta&,Untr&>;
SHOW_TYPE(TX5)
using TX6 = CommonResult_t<const int&,uint16_t,int64_t>;
SHOW_TYPE(TX6)
SHOW_EXPR(( CommonResult<int,string>::value ))
SHOW_EXPR(( CommonResult<int,string,long>::value ))
SHOW_EXPR(( CommonResult<const int,int&>::value ))
SHOW_EXPR(( reconcilable<const int&,uint16_t,int64_t> ))
SHOW_EXPR(( reconcilable<const Uber&,Unta&,Untr&> ))
SHOW_EXPR(( reconcilable<Uber&,const Unta&,Untr&> ))
cout << "\n.gulp." <<endl;
return 0;

View file

@ -27,9 +27,9 @@
** mostly at start and then hold and use the memory, the allocation is never
** actually _closed_ — implying that further allocations can be added during
** the whole life time, which may possibly even trigger a further base allocation
** if storage space if the last Extent is exhausted. In theory, it would be possible
** if storage space in the last Extent is exhausted. In theory, it would be possible
** to use a custom allocation (in the AllocationCluster::StorageManager::Extents,
** which is a lib::LinkedElements and could be parametrised with an allocator template).
** which is a lib::LinkedElements and could be parametrised with a allocator template).
** Allocations are never discarded, and thus any alloted memory will be kept until
** the whole AllocationCluster is destroyed as a compound.
** \par using as STL allocator
@ -361,13 +361,7 @@ namespace lib {
Policy (AllocationCluster& clu)
: 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)
: AllocationPolicy<I,E,Adapter> (clu.getAllocator<std::byte>())
{ }
bool

View file

@ -363,11 +363,11 @@ namespace util {
string showDecimal (double val) noexcept { return showFloatingPoint (val, PRECISION_DECIMAL<double>); }
string showDecimal (float val) noexcept { return showFloatingPoint (val, PRECISION_DECIMAL<float>); }
string showDecimal (lflp val) noexcept { return showFloatingPoint (val, PRECISION_DECIMAL<lflp>); }
string showDecimal (f128 val) noexcept { return showFloatingPoint (val, PRECISION_DECIMAL<f128>); }
string showComplete (double val) noexcept { return showFloatingPoint (val, PRECISION_COMPLETE<double>); }
string showComplete (float val) noexcept { return showFloatingPoint (val, PRECISION_COMPLETE<float>); }
string showComplete (lflp val) noexcept { return showFloatingPoint (val, PRECISION_COMPLETE<lflp>); }
string showComplete (f128 val) noexcept { return showFloatingPoint (val, PRECISION_COMPLETE<f128>); }
string

View file

@ -93,10 +93,10 @@ namespace util {
std::string showDecimal (double) noexcept;
std::string showDecimal (float) noexcept;
std::string showDecimal (lflp) noexcept;
std::string showDecimal (f128) noexcept;
std::string showComplete (double)noexcept;
std::string showComplete (float) noexcept;
std::string showComplete (lflp) noexcept;
std::string showComplete (f128) noexcept;

View file

@ -14,7 +14,7 @@
/** @file format-util.hpp
** Collection of small helpers and convenience shortcuts for diagnostics & formatting.
** - util::toString() performs a failsafe to-String conversion, thereby preferring a
** - util::str() performs a failsafe to-String conversion, thereby preferring a
** built-in conversion operator, falling back to just a mangled type string.
** - util::join() generates an enumerating string from elements
** of an arbitrary sequence or iterable. Elements will be passed

View file

@ -30,11 +30,8 @@
#ifndef LIB_HASH_VALUE_H
#define LIB_HASH_VALUE_H
#ifdef __cplusplus
#include <cstdlib>
#else
#include <stdlib.h>
#endif
#include <stdlib.h>
/**
* storage for a Lumiera unique ID,
@ -47,6 +44,8 @@ typedef lumiera_uid* LumieraUid;
#ifdef __cplusplus /* =========== C++ definitions ====================== */
#include <climits>
namespace lib {
/** a STL compatible hash value */

View file

@ -108,7 +108,7 @@ namespace lib {
{
using Tuple = std::tuple<DATA...>;
using Tuple::Tuple;
using Tuple::tuple;
template<typename SPEC>
void linkInto (HeteroData<SPEC>&);

View file

@ -21,6 +21,7 @@
#ifndef LIB_INTEGRAL_H
#define LIB_INTEGRAL_H
#include <stdlib.h>
#include <cstddef>
#include <cstdint>
@ -32,10 +33,10 @@ using llong = long long int;
using ullong = unsigned long long int;
using ushort = unsigned short int;
using lflp = long double;
static_assert(10 <= sizeof(lflp));
using f128 = long double;
static_assert(10 <= sizeof(f128));
const uint LIFE_AND_UNIVERSE_4EVER = 42;
#endif /*LIB_INTEGRAL_H*/

View file

@ -138,10 +138,10 @@ namespace lib {
struct iterator
: IterAdapter<Pos, DataHandle>
{ ////////////////////////////////////////////////////TICKET #1125 : should be build on top of IterStateWrapper rather than IterAdapter!
using Adapter = IterAdapter<Pos, DataHandle>;
using Adapter::Adapter; // expose inherited ctors
using _I = IterAdapter<Pos, DataHandle>;
using _I::IterAdapter;
operator string() const {return Adapter::source()? string(*Adapter::source()) : "⟂"; }
operator string() const {return _I::source()? string(*_I::source()) : "⟂"; }
};
/** build an iterator frontend for the given source,

View file

@ -26,7 +26,6 @@
#include <unistd.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>

View file

@ -46,6 +46,7 @@
#include "lib/hash-value.h" /* Types lumiera_uid and LumieraUid */
#include <stdlib.h>

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 FunInline::activate<FUNC>() provides the blueprint for this code generation.
* \ref InvokerBuilder provides the blueprint for this code generation.
*/
template<class FUN>
struct Invoker

View file

@ -115,26 +115,6 @@ namespace meta {
{ };
/**
* Metafunction: build an ID-Type based on the position in the list
* @remark when the type is not in the list, TypeIdxID ≔ 0
*/
template<typename TY, typename TYPES, uint idx=1>
struct TypeIdxID
: std::integral_constant<uint, 0>
{ };
template<typename TY, typename TYPES, uint idx>
struct TypeIdxID<TY, Node<TY,TYPES>, idx> ///< @return 1-based position in case of match
: std::integral_constant<uint, idx>
{ };
template<typename X, typename TY, typename TYPES, uint idx>
struct TypeIdxID<X, Node<TY,TYPES>, idx> ///< else recurse to check next element
: TypeIdxID<X, TYPES, idx+1>
{ };
/**
* Build a list of const types from a given typelist.

View file

@ -456,12 +456,12 @@ namespace util {
/** show maximum reproducible decimal representation */
std::string showDecimal (double) noexcept;
std::string showDecimal (float) noexcept;
std::string showDecimal (lflp) noexcept;
std::string showDecimal (f128) noexcept;
/** show enough decimal digits to represent every distinct value */
std::string showComplete (double) noexcept;
std::string showComplete (float) noexcept;
std::string showComplete (lflp) noexcept;
std::string showComplete (f128) noexcept;
std::string showSize (size_t) noexcept;

View file

@ -810,7 +810,7 @@ namespace lib {
using Holder = InPlaceBuffer<BA, sizeof(SUB), DEFAULT>;
Holder& holder = *static_cast<Holder*> (buffer_);
return holder.emplace (std::forward<SUB> (implementation));
return holder.template emplace (std::forward<SUB> (implementation));
}
/** Abbreviation for placement new of a subclass SUB into the opaque buffer*/

View file

@ -1,174 +0,0 @@
/*
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/spec.hpp"
#include "lib/par/provision.hpp"
#include "lib/par/param-type.hpp"
namespace lib {
namespace par {
using ValStorage = ValBuffStorage<BASETYPE_MAX_SIZ>;
/**
* Implementation-Service: complete Parameter functionality.
* Includes the ability to access / set a »value«, attach automation,
* identify and convert underlying value data types and handle
* trait- and identity tags and the specifics of the value scale.
*/
class Disposition
: public Domain
{
IDRecord idr_;
public:
template<typename X>
X extract() const;
template<typename X>
void setVal (X);
protected:
template<typename X>
bool
isBaseDomainMatch() const
{
return idr_.baseTypeID == BaseTypeID<X>();
}
template<typename X>
void
markBaseDomain()
{
idr_.baseTypeID = BaseTypeID<X>();
}
};
/**
* 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).
*/
template<typename VAL, class IFA =Disposition> /////////////////////////////////////////////////////TICKET #1197 : the second template argument is quite confusing and became necessary due to the limitations of PolymorphicValue
class ParamData
: public TypedEntity<VAL, IFA>
{
VAL val_;
Provision* src_;
public:
ParamData (VAL initVal)
: val_{std::move (initVal)}
, src_{nullptr}
{
Disposition::markBaseDomain<VAL>();
}
/* === Provision Interface === */
void retrieveInto (ValBuff&) const override final; ///< @note is sometimes invoked non-virtually for sake of optimisation
void setValFrom (ValBuff const&) override final;
};
template<typename X>
using FullDomain = BaseDomain<X, Disposition>;
template<typename X>
using SimpleData = ParamData<X, Disposition>;
template<typename VAL, class IFA>
void
ParamData<VAL, IFA>::retrieveInto (ValBuff& target) const
{
asValue<VAL>(target) = val_;
}
template<typename VAL, class IFA>
void
ParamData<VAL, IFA>::setValFrom (ValBuff const& newVal)
{
val_ = asValue<VAL>(newVal);
}
/**
* @warning optimisation to a direct access will be applied
* when the baseTypeID in the IDRecord matches the type \a X.
* It is assumed in this case that the actual implementation of
* the Disposition interface is a ParamData<X>, allowing to side-step
* any virtual dispatch; since the implementation of #retrieveInto and
* #setValFrom is defined in the same source file, the optimiser will
* inline the call, resulting in a direct value access.
*/
template<typename X>
X
Disposition::extract() const
{
X result; // RVO
if (isBaseDomainMatch<X>())
{ // short-circuit by direct force-cast, bypassing virtual dispatch
auto& paramData = * static_cast<SimpleData<X> const *> (this);
paramData.SimpleData<X>::retrieveInto (asValBuff (result));
}
else
{ // run full type-conversion dispatch
ValStorage valueBuffer;
this->retrieveInto (valueBuffer);
this->extractAs (result, valueBuffer);
}
return result;
}
template<typename X>
void
Disposition::setVal (X changedVal)
{
if (isBaseDomainMatch<X>())
{ // short-circuit by direct force-cast, bypassing virtual dispatch
auto& paramData = * static_cast<SimpleData<X>*> (this);
paramData.SimpleData<X>::setValFrom (asValBuff (changedVal));
}
else
{ // run full type-conversion dispatch
ValStorage valueBuffer;
this->conform (valueBuffer, changedVal);
this->setValFrom (valueBuffer);
}
}
}} // namespace lib::par
#endif /*LIB_PAR_DISPOSITION_H*/

View file

@ -1,99 +0,0 @@
/*
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/par/spec.hpp"
#include "lib/par/provision.hpp"
#include "lib/meta/generator.hpp"
#include "lib/meta/trait.hpp"
namespace lib {
namespace par {
/* ===== Type Handler Interface for generic parameter value access ===== */
class HandlerBase
: public Provision
{
public:
void extractAs();
void conform();
};
template<typename X, class PAR>
class TypeHandler
: public PAR
{
public:
virtual void extractAs (X& targetVal, ValBuff const& valBuff) const =0;
virtual void conform (ValBuff& targetBuff, X const& srcVal) =0;
using PAR::extractAs;
using PAR::conform;
};
/** build a generic visitor interface for all types in list */
using TypeHandlerInterface = meta::InstantiateChained<BaseTypes::List, TypeHandler, HandlerBase>;
/**
* 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) const =0;
protected:
};
}} // namespace lib::par
#endif /*LIB_PAR_DOMAIN_H*/

View file

@ -1,53 +0,0 @@
/*
NOM-SET.hpp - nominal scale 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 nom-set.hpp
** Collection of identifiers to constitute the domain for a nominal or ordinal scale.
** 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_NOM_SET_H
#define LIB_PAR_NOM_SET_H
#include "lib/par/spec.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 NomSet
{
public:
};
}} // namespace lib::par
#endif /*LIB_PAR_NOM_SET_H*/

View file

@ -1,37 +0,0 @@
/*
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

@ -1,144 +0,0 @@
/*
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.
** The implementation of parameters is built up in layers, starting from the generic
** interfaces \ref Provision, which describes the fundamental capabilities of a
** parameter as conceptualised here) and \ref Domain to support type conversions.
**
** All extensions beyond this point can be considered implementation-level services.
** The BaseDomain layer provides the actual type conversion services, based on internal
** knowledge of the concrete data type handled by this parameter.
**
** A \ref TypedEntity descriptor adds storage to capture type properties and allow
** for an ID registration. In addition, the concrete value type is tagged, marking
** 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"
#include "lib/par/type-handler.hpp"
#include "lib/meta/generator.hpp"
namespace lib {
namespace par {
// 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 VAL, class IFA>
struct DomainSetup
{
template<typename X, class PAR>
class TypeHandlerImpl
: public PAR
{
void extractAs (X& targetVal, ValBuff const& valBuff) const override;
void conform (ValBuff& targetBuff, X const& srcVal) override;
};
using TypeHandlerChain = meta::InstantiateChained<BaseTypes::List, TypeHandlerImpl, IFA>;
};
template<typename VAL, class IFA>
class BaseDomain
: public DomainSetup<VAL, IFA>::TypeHandlerChain
{
public:
void applyLimit (ValBuff&) override;
void transferTo (ValBuff const& source, Domain& targetDomain, ValBuff& target) const override;
};
/* ===== type conversion virtual dispatch ===== */
template<typename VAL, class IFA>
template<typename X, class PAR>
inline void
DomainSetup<VAL,IFA>::TypeHandlerImpl<X,PAR>::extractAs (X& targetVal, ValBuff const& valBuff) const
{
VAL const& srcVal = asValue<VAL> (valBuff);
assignConverted (targetVal, srcVal);
}
template<typename VAL, class IFA>
template<typename X, class PAR>
inline void
DomainSetup<VAL,IFA>::TypeHandlerImpl<X,PAR>::conform (ValBuff& targetBuff, X const& srcVal)
{
VAL& targetVal = asValue<VAL> (targetBuff);
assignConverted (targetVal, srcVal);
}
template<typename VAL, class IFA>
inline void
BaseDomain<VAL,IFA>::applyLimit (ValBuff& valBuff)
{
VAL& val{asValue<VAL> (valBuff)};
val = preClamp<VAL> (val);
}
template<typename VAL, class IFA>
inline void
BaseDomain<VAL,IFA>::transferTo (ValBuff const& source, Domain& targetDomain, ValBuff& target) const
{
targetDomain.conform (target, asValue<VAL> (source));
targetDomain.applyLimit (target);
}
/**
* Service-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.
*/
template<typename VAL, class IFA>
class TypedEntity
: public BaseDomain<VAL,IFA>
{
public:
};
}} // namespace lib::par
#endif /*LIB_PAR_PARAM_TYPE_H*/

View file

@ -1,33 +0,0 @@
/*
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

View file

@ -1,174 +0,0 @@
/*
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
#include "lib/par/disposition.hpp"
#include "lib/polymorphic-value.hpp"
#include "lib/util.hpp"
#include <utility>
namespace lib {
namespace par {
namespace {
static constexpr size_t PAYLOAD_SIZ = sizeof(ParamData<double>) - sizeof(void*); ////////////////////////TICKET #1197 : this is a mess! PolymorphicValue adds the ADMIN_OVERHEAD (but our payload has already a VTable)
using ParameterImplAPI = Disposition;
using CopySupportMarker = polyvalue::CopySupport<ParameterImplAPI>;
using ParamContainer = PolymorphicValue<ParameterImplAPI, PAYLOAD_SIZ, CopySupportMarker>;
}
using util::unConst;
/**
* Descriptor and container to hold a generic parameter.
* The value can be fixed or supplied by an automation function.
*/
class Parameter
: protected ParamContainer
{
public:
template<typename VAL>
VAL getVal() const;
template<typename VAL>
void setVal (VAL&&);
/* === Builder API === */
template<typename VAL>
class Builder;
template<typename VAL>
static Builder<VAL>
forType() { return {}; }
template<typename VAL>
static Builder<std::decay_t<VAL>>
forValue (VAL&& initVal)
{
using PayloadType = std::decay_t<VAL>;
return forType<PayloadType>()
.withValue (std::forward<VAL> (initVal));
}
class ProtoBuilder;
static ProtoBuilder from (Parameter const& prototype);
protected:
template<class IMP, typename...ARGS>
Parameter (IMP* typeTag, ARGS&&... args)
: PolymorphicValue (typeTag, std::forward<ARGS>(args)...)
{ }
};
template<typename VAL> requires par_basetype<VAL>
class Parameter::Builder
{
VAL initVal_{};
public:
Parameter
build()
{
using ImplPackage = ParamData<VAL, CopySupportMarker>; //////////////////////////////////////////TICKET #1197 : remove the necessity to inject the CopySupportMarker
ImplPackage* typeTag{nullptr};
Parameter resParam{typeTag, std::move (initVal_)};
return resParam;
}
template<typename X>
Builder&&
withValue (X&& initVal)
{
initVal_ = std::forward<X> (initVal);
return std::move(*this);
}
};
class Parameter::ProtoBuilder
{
Parameter ref_;
public:
ProtoBuilder (Parameter const& prototype)
: ref_{prototype}
{ }
Parameter
build()
{
return ref_;
}
template<typename X>
ProtoBuilder&&
maxVal (X&& upperBound)
{
/////////////////////////////////////////////OOO how to remould a scale?
return std::move(*this);
}
};
inline Parameter::ProtoBuilder
Parameter::from (Parameter const& prototype)
{
return ProtoBuilder{prototype};
}
template<typename VAL>
inline VAL
Parameter::getVal() const
{
static_assert (isBaseType<VAL>, "only preconfigured parameter BaseTypes are supported");
return unConst(this)->getPayload().extract<VAL>();
}
template<typename VAL>
inline void
Parameter::setVal (VAL&& changedVal)
{
static_assert (isBaseType<VAL>, "only preconfigured parameter BaseTypes are supported");
getPayload().setVal (changedVal);
}
}} // namespace lib::par
#endif /*LIB_PAR_PARAMETER_H*/

View file

@ -1,113 +0,0 @@
/*
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;
/** Marker for an opaque value buffer with
* undisclosed size and content type */
struct ValBuff { /*placeholder*/ };
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<size_t bytes>
struct ValBuffStorage
: ValBuff
{
std::byte data[bytes];
};
/**
* 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 implicitly associated with a Type that defines the
* underlying [value domain](\ref Domain) and possibly a \ref Scale that
* defines 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*/

View file

@ -1,343 +0,0 @@
/*
SCALE.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/error.hpp"
#include "lib/par/domain.hpp"
#include "lib/par/type-handler.hpp"
#include "lib/util-quant.hpp"
#include "lib/format-string.hpp"
//#include "lib/util.hpp"
#include <optional>
namespace lib {
namespace par {
namespace err = lumiera::error;
using std::abs;
using util::_Fmt;
using std::optional;
enum Metric {LIN ///< linear ordinary numeric scale
,BIN ///< binary logarithm
,NAT ///< natural logarithm
,DEC ///< decibels (1/10 of 10-based log)
,NOM ///< value encodes a nominal or ordinal scale
};
/**
* 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....?
*/
template<typename VAL>
class Scale
{
public:
Metric metric{LIN};
/////////////////////////////OOO Quantiser?
optional<VAL> minVal{};
optional<VAL> maxVal{};
optional<VAL> minUse{};
optional<VAL> maxUse{};
optional<VAL> neutral{};
optional<VAL> defVal{};
/////////////////////////////OOO »Sentinels« and nominal scales?
VAL cyclicLim =0;
/* ===== information functions ===== */
bool isValid() const;
bool isFactor() const;
bool isCyclic() const;
bool isLimited() const;
bool isLogarithmic() const;
/* ===== conforming operations ===== */
VAL getDefault() const;
VAL conform (VAL const&) const;
template<typename SRC>
VAL join (VAL const&, SRC const&, Scale<SRC> const&) const;
VAL join (VAL const&, VAL const&) const;
};
/* ===== information functions ===== */
/** self consistency check */
template<typename VAL>
inline bool
Scale<VAL>::isValid() const
{
return ((not minVal or not maxVal)
or *minVal <= *maxVal
)
and (not minUse
or (not minVal or *minVal <= *minUse)
)
and (not maxUse
or (not maxVal or *maxUse <= *maxVal)
)
and ((not minUse or not maxUse)
or *minUse <= *maxUse
)
and (not neutral
or ( (not maxVal or *neutral <= *maxVal)
and (not minVal or *minVal <= *neutral)
)
)
and (not defVal
or ( (not maxVal or *defVal <= *maxVal)
and (not minVal or *minVal <= *defVal)
)
)
and (not isCyclic()
or (minVal and maxVal
and *minVal < *maxVal
and VAL(0) < cyclicLim
and abs (*maxVal) <= cyclicLim)
)
;
}
template<typename VAL>
inline bool
Scale<VAL>::isFactor() const
{
return metric == LIN
and neutral
and *neutral == VAL(1);
}
template<typename VAL>
inline bool
Scale<VAL>::isCyclic() const
{
return VAL(0) != cyclicLim;
}
template<typename VAL>
inline bool
Scale<VAL>::isLimited() const
{
return (maxVal or minVal)
and not isCyclic();
}
template<typename VAL>
inline bool
Scale<VAL>::isLogarithmic() const
{
return metric == DEC
or metric == NAT
or metric == BIN;
}
/* ===== conforming operations ===== */
/** Build initial value that fits into the scale. */
template<typename VAL>
inline VAL
Scale<VAL>::getDefault() const
{
return defVal? *defVal
: neutral? *neutral
: conform (VAL{});
}
/**
* Accommodate a raw value from the underlying domain,
* so that it conforms with this Scale definition.
*/
template<typename VAL>
inline VAL
Scale<VAL>::conform (VAL const& rawVal) const
{
if (isCyclic())
{
REQUIRE (minVal and maxVal);
REQUIRE (*minVal < *maxVal);
if constexpr (std::is_floating_point_v<VAL>)
if (not (std::abs (rawVal) < cyclicLim))
throw err::Invalid {_Fmt{"Parameter value %4.2g beyond supported numeric precision "
"for cyclic wrapping into [%1.2g...%1.2g[ "}
% rawVal % *minVal % *maxVal
};
return util::cyclicWrap (rawVal, *minVal, *maxVal);
}
if (minVal and rawVal < *minVal)
return *minVal;
if (maxVal and rawVal > *maxVal)
return *maxVal;
// no further constraint to enforce...
return rawVal;
}
namespace { // logarithmic scale conversions...
/** transform a logarithmic value into an exponential factor */
template<typename TAR, typename VAL>
inline auto
expFactor (Metric metric, VAL const& value)
{
using WorkType = CommonComputeType<VAL,TAR>;
switch (metric)
{
case NAT: return std::exp (WorkType(value));
case BIN: return std::exp2 (WorkType(value));
case DEC: return std::pow (WorkType(10), WorkType(value/10.0));
default:
throw err::Logic{_Fmt{"Attempt to compute exponential "
"from a non-logarithmic value with metric %d"}
% metric};
break;
}
}
/** transform this value into a suitable logarithm for the target scale */
template<typename TAR, typename VAL>
inline auto
asLogarithm (Metric targetMetric, VAL const& value)
{
if (isNeg (value))
throw err::Invalid{_Fmt{"Attempt to take logarithm from %f < 0"}
% value};
using WorkType = CommonComputeType<VAL,TAR>;
switch (targetMetric)
{
case NAT: return std::log (WorkType(value));
case BIN: return std::log2 (WorkType(value));
case DEC: return std::log10 (WorkType(value)) * 10;
default:
throw err::Logic{_Fmt{"Attempt to compute logarithm "
"for a non-logarithmic metric %d"}
% targetMetric};
break;
}
}
/** provide the adaptation factor from this logarithmic scale
* to the given other logarithmic scale and target type \a VAL
* @remarks
* - use the logarithm conversion formula logb(x) ≡ logb(k)·logk(x)
* - if target is in decibel, have to apply factor 10; asLogarithm() does that already
* - if we are in decibel, log10 = dB /10; fold that factor in the logarithm of the base,
* by using log(b)·x ≡ log(b^x) and rewrite that as b^x = exp(ln(b)·x)
*/
template<typename TAR, typename VAL>
inline auto
baseScale (Metric targetMetric, Metric srcMetric)
{
using WorkType = CommonComputeType<VAL,TAR>;
if (srcMetric == targetMetric)
return WorkType(1);
auto srcBase = [&]{switch (srcMetric)
{
case BIN: return WorkType(2);
case NAT: return std::exp (WorkType(1));
case DEC: return std::exp (std::log (WorkType(10)) / 10);
default:
throw err::Logic{"Attempt to treat non-logarithmic scales as logarithms"};
break;
}};
return asLogarithm<WorkType> (targetMetric, srcBase());
}
}//(End) log helpers
/**
* Join and combine an additional feed value with an anchor value.
* @note actual joining operation is picked based on both scales and types involved.
* @remark notably multiplicative contributions and logarithmic scales to be considered.
*/
template<typename VAL>
template<typename SRC>
inline VAL
Scale<VAL>::join (VAL const& value, SRC const& srcFeed, Scale<SRC> const& feedScale) const
{
VAL res{};
using JoinT = NumberJoinType<VAL,SRC>;
if (NOM == metric)
UNIMPLEMENTED ("nominal and ordinal scales");
if (LIN == metric)
{
if (feedScale.isLogarithmic())
assignConverted (res, value * expFactor<VAL> (feedScale.metric, srcFeed));
else
if (feedScale.isFactor())
assignConverted (res, JoinT(value) * JoinT(srcFeed));
else
assignConverted (res, JoinT(value) + JoinT(srcFeed));
}
else
if (this->isLogarithmic())
{
if (feedScale.isLogarithmic())
assignConverted (res, value + baseScale<VAL,SRC> (metric,feedScale.metric) * srcFeed);
else
if (feedScale.isFactor())
assignConverted (res, value + asLogarithm<VAL> (metric, srcFeed));
else
throw err::Invalid {"adding a non-logarithmic data feed on top of a logarithmic base value is pointless."};
}
else
NOTREACHED ("Unexpected metric conversion case");
return conform (res);
} // last step: re-establish target scale rules
/**
* Simplified join() variant, assuming the feed is based on the same scale.
*/
template<typename VAL>
inline VAL
Scale<VAL>::join (VAL const& value, VAL const& feed) const
{
return conform (isFactor()? value * feed
: value + feed
);
}
}} // namespace lib::par
#endif /*LIB_PAR_SCALE_H*/

View file

@ -1,95 +0,0 @@
/*
SPEC.hpp - parameter trait specification
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 spec.hpp
** Common definition and specification record for parameters.
** While most functionality of parameters is indirected in some way, allowing
** for future extension, some basic traits are marked as flag, so that frequently
** used code-paths might take a shortcut. Notably the disposition interface, which
** describes the full implementation service of a parameter container, explicitly
** reserves one «slot» of storage for flags, type markers and an ID record.
**
** @todo WIP-WIP-WIP can be considered an initial draft and placeholder, as of 2026
*/
#ifndef LIB_PAR_SPEC_H
#define LIB_PAR_SPEC_H
#include "lib/symbol.hpp"
#include "lib/meta/typelist.hpp"
#include "lib/meta/typelist-util.hpp"
namespace lib {
namespace par {
/**
* Fixed collection of elementary types supported in parameters
*/
using BaseTypes = meta::Types<int
,int64_t
,uint
,uint64_t
,double
,float
,bool
>;
static constexpr uint BASETYPE_CNT = meta::count<BaseTypes::List>();
static constexpr uint BASETYPE_MAX_SIZ = meta::maxSize<BaseTypes::List>();
template<typename T>
static constexpr bool isBaseType = meta::isInList<T, BaseTypes::List>::value;
/** @note Only some fixed base data types are supported as Parameter value type. */
template<typename T>
concept par_basetype = isBaseType<T>;
template<typename T>
using BaseTypeID = meta::TypeIdxID<T, BaseTypes::List>;
/* ===== setup of ID space ===== */
namespace {
auto _bit_fit = [](uint bits){ return (1u << bits) - 1; };
}
static constexpr uint _BASE_TYPE_BITS = 4;
static constexpr uint _SCALES_BITS = 10;
static constexpr uint _PARAM_ID_BITS = 18;
static constexpr uint MAX_BASETYPES = _bit_fit(_BASE_TYPE_BITS); ///< max number of parameter base types that can be configured
static constexpr uint MAX_SCALES = _bit_fit(_SCALES_BITS); ///< max number of distinct scales that can be defined at runtime
static constexpr uint MAX_PARAM_ID = _bit_fit(_PARAM_ID_BITS); ///< max number of distinct param identities that can be created
static_assert (BASETYPE_CNT <= MAX_BASETYPES);
struct IDRecord
{
uint baseTypeID :_BASE_TYPE_BITS = 0;
uint scaleID :_SCALES_BITS = 0;
uint paramIDID :_PARAM_ID_BITS = 0;
};
}} // namespace lib::par
#endif /*LIB_PAR_SPEC_H*/

View file

@ -1,226 +0,0 @@
/*
TYPE-HANDLER.hpp - generic type conversion handling support
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 type-handler.hpp
** Generic building blocks to support parameter value conversions and adaptation.
** The implementation of parameters is built such as to conceal the actual type of
** the parameter values -- which requires a virtual dispatch at some point. Yet the
** underlying type conversion functionality can be defined in a generic way, based
** on the actual types involved. Simple value-like parameters are based on a fixed
** collection of number types, both integral and floating-point. Thus the actual
** conversion is handled by the promotion- and coercion rules of C++ in most cases.
**
** @todo WIP-WIP-WIP can be considered an initial draft and placeholder, as of 2026
*/
#ifndef LIB_PAR_TYPE_HANDLER_H
#define LIB_PAR_TYPE_HANDLER_H
#include "lib/par/spec.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::integral;
using std::signed_integral;
using std::floating_point;
using std::numeric_limits;
using std::common_type_t;
using std::is_signed_v;
using std::is_unsigned_v;
using std::is_integral_v;
using std::is_floating_point_v;
/* ===== type conversion implementation 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 = signed_integral<SUB> != signed_integral<BAS>
or numeric_limits<SUB>::max() < numeric_limits<BAS>::max()
or numeric_limits<SUB>::lowest() > 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.~V();
new(&targetVal) V (preClamp<V> (srcVal));
}
else
static_assert (!sizeof(X), "this type conversion is not supported");
}
template<typename T>
concept number = integral<T> or floating_point<T>;
namespace {
static_assert (64 <= std::numeric_limits<lflp>::digits
,"long double type can not represent uint64_t exactly");
/** @internal decision logic for combining numeric parameters */
template<typename V, typename W>
auto
selectNumberJoinType()
{
if constexpr (is_floating_point_v<V> or is_floating_point_v<W>)
return std::type_identity<common_type_t<V,W>>{};
else
if constexpr (is_signed_v<V> == is_signed_v<W>)
return std::type_identity<common_type_t<V,W>>{};
else
{ // mixed signed/unsigned integral: need a type wide enough to accommodate.
using U = std::conditional_t<is_signed_v<V>, W,V>;
if constexpr (8 > sizeof(U))
return std::type_identity<int64_t>{};
else
return std::type_identity<lflp>{};
// no standard integral type can hold uint64_t's full range signed.
// Expand to long double, which can hold 64bit without precision loss
}
}
/** @internal logic to pick a suitable floating-point for intermediary computations */
template<typename V, typename W>
auto
selectCommonComputeType()
{
if constexpr (is_integral_v<V> and is_integral_v<W>)
{ // ensure integral precision is retained to the degree possible...
if constexpr (8 > sizeof(V) and 8 > sizeof(W))
return std::type_identity<double>{};
else
return std::type_identity<lflp>{};
}
else
return std::type_identity<common_type_t<V,W>>{};
}
}
/**
* @return A common type to perform simple numeric operations safely.
* - when one of the type parameters is floating-point, computations switch over there
* - on integral types with same signedness, std::common_type handles a sane common ground
* - when mixed computations can be managed within signed 64bit, switch up to that
* - in the remaining problematic case(s), long double is used, as it can accommodate
* the full 64bit value range without loss on platforms relevant for Lumiera.
*/
template<number V, number W>
using NumberJoinType = typename decltype(selectNumberJoinType<V,W>())::type;
/**
* best-precision floating point type to carry out
* a numeric operation to transform and adapt value scales.
* @note the result type is always some floating point,
* since the goal is to use the exponential / logarithm family.
*/
template<number V, number W>
using CommonComputeType = typename decltype(selectCommonComputeType<V,W>())::type;
}} // namespace lib::par
#endif /*LIB_PAR_TYPE_HANDLER_H*/

View file

@ -1,101 +0,0 @@
/*
TYPE-REGISTRY.hpp - generic registry for type or policy descriptors
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 type-registry.hpp
** Generic registration table and access mechanism to type descriptors keyed by ID.
** The implementation of parameters is based on layered virtual interfaces and implementations,
** packaged into a fixed storage buffer that is transported with value semantics. This overall shape
** implies a strict storage size regime and thus forces to _fold away_ the more flexible parts of
** the behaviour variations into a second layer, retaining only an ID in the core implementation.
**
** The type registry for parameters provides the generic mechanism underlying this setup scheme:
** - it is parametrised by an implementation type (of the descriptor) with a fixed size
** - the storage is organised by a fixed-sized index table and heap allocated storage extents.
** - entries are never discarded, and thus an atomic lock suffices _for read access_
** @remark while this scheme is defined in a generic way, it aims at one specific
** usage pattern and should not be stretched beyond that.
**
** @todo WIP-WIP-WIP can be considered an initial draft and placeholder, as of 2026
*/
#ifndef LIB_PAR_TYPE_REGISTRY_H
#define LIB_PAR_TYPE_REGISTRY_H
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
//#include "lib/meta/trait.hpp"
//#include "lib/util.hpp"
//#include <concepts>
//#include <limits>
namespace lib {
namespace par {
/**
* Management of descriptor entries keyed by numeric ID.
* Based on a fixed index table and extents added dynamically.
* Slots can be claimed, creating an entry with stable address.
* Creation and access are thread safe, Entries are never discarded.
*/
template<class ELM>
class TypeRegistry
: util::NonCopyable
{
public:
class ID;
template<typename...INIT>
ID makeSlot (INIT&&...);
ELM& operator[] (ID) const;
};
/** type tagged numeric registry entry ID */
template<class ELM>
class TypeRegistry<ELM>::ID
{
uint idx_;
public:
ID (uint ix =0) : idx_{ix} { }
operator uint() const { return idx_; }
// default copy operations
};
template<class ELM>
template<typename...INIT>
inline TypeRegistry<ELM>::ID
TypeRegistry<ELM>::makeSlot (INIT&&... initArg)
{
UNIMPLEMENTED ("allocate new slot, possibly grow extents, under mutex lock");
}
template<class ELM>
inline ELM&
TypeRegistry<ELM>::operator[] (ID slot) const
{
UNIMPLEMENTED ("access slot by ID, protected by atomic acquire");
}
}} // namespace lib::par
#endif /*LIB_PAR_TYPE_REGISTRY_H*/

View file

@ -516,7 +516,7 @@ namespace lib {
static PolymorphicValue
build (ARGS&&... args)
{
IMP* type_to_build_in_buffer{};
Adapter<IMP>* type_to_build_in_buffer(0);
return PolymorphicValue (type_to_build_in_buffer, std::forward<ARGS>(args)...);
}

View file

@ -122,12 +122,12 @@ namespace util {
u = 0!=u? u:1;
auto [d,r] = util::iDiv (num, den);
// round to smallest integer fraction, to shake off "number dust"
lflp const ROUND_ULP = 1 + 1/(lflp(std::numeric_limits<int64_t>::max()) * 2);
f128 const ROUND_ULP = 1 + 1/(f128(std::numeric_limits<int64_t>::max()) * 2);
// construct approximation quantised to 1/u
lflp frac = lflp(r) / den;
f128 frac = f128(r) / den;
int64_t res = d*u + int64_t(frac*u * ROUND_ULP);
ENSURE (abs (lflp(res)/u - rational_cast<lflp>(Rat{num,den})) <= 1.0/abs(u)
ENSURE (abs (f128(res)/u - rational_cast<f128>(Rat{num,den})) <= 1.0/abs(u)
,"Requantisation error exceeded num=%li / den=%li -> res=%li / quant=%li"
, num, den, res, u);
return res;

View file

@ -17,19 +17,7 @@
** - average over the N last elements in a data sequence
** - simple linear regression with weights (single predictor variable)
** - also over a time series with zero-based indices
**
** @remark this header was imported from the [yoshimi-test] acceptance suite, where it
** is maintained as `src/util/statistic.hpp` within namespace `util` -- so the
** two copies are kept deliberately similar, to allow carrying fixes either way.
** Note in particular that a helper `round<places>(double)` for decimal rounding
** was **removed here** (10/2026): it was never instantiated within Lumiera, while
** its `constexpr double shift{pow(10.0,places)}` is not a constant expression by
** the standard (while GCC accepts it, relying on a _builtin_). It remains in use
** upstream, in `suite/step/TimingObservation.cpp`. Should decimal rounding be
** wanted here, settle it together with the other half-solutions collected in #1360
** -- and note util::ipow() now provides the constexpr exponentiation.
**
** [yoshimi-test]: https://github.com/Ichthyostega/yoshimi-test/
**
*/
@ -82,6 +70,17 @@ namespace stat{
return std::apply (makeArray, forward<TUP> (tuple));
}
template<size_t places>
inline double
round (double val)
{
constexpr double shift{pow(10.0, places)};
return std::round(val*shift) / shift;
}
/**
* Read-only view into a segment within a sequence of data
* @tparam D value type of the data series

View file

@ -542,8 +542,8 @@ namespace lib {
addCode(COND);
break;
case TagSyntax::END_IF:
__checkBalanced(IF);
addLead();
__checkBalanced(IF);
if (hasElse())
linkJumpToNext();
else
@ -557,8 +557,8 @@ namespace lib {
addCode(ITER);
break;
case TagSyntax::END_FOR:
__checkBalanced(FOR);
addLead();
__checkBalanced(FOR);
if (hasElse())
linkJumpToNext();
else
@ -570,9 +570,9 @@ namespace lib {
closeScope();
break;
case TagSyntax::ELSE:
addLead();
__checkInScope();
__checkNoDup();
addLead();
if (IF == scope_.top().clause)
{
markJumpInScope();

View file

@ -22,15 +22,12 @@
#include "lib/integral.hpp"
#include <type_traits>
#include <concepts>
#include <cstdlib>
#include <climits>
#include <limits>
#include <cfloat>
#include <cmath>
#include <nobug.h>
namespace util {
@ -139,7 +136,7 @@ namespace util {
}
/** quantise towards ceiling.
/** quantise towards ceiling.
* This function models to fit something into a tiled structure.
*/
template<typename U> requires std::unsigned_integral<U>
@ -157,104 +154,6 @@ namespace util {
/**
* Transform into a cyclically wrapping domain.
* @tparam N any integral type, signed or unsigned
* @return mapping into the range [minVal...maxVal(
* @remark This function exploits the equivalence between signed and corresponding unsigned type
* to perform the modulus computation with positive numbers, because the modulus operator of C++
* works flipped for negative numbers, and oriented towards zero, while we want here always
* the same cyclic wrap-around, irrespective of the position relative to zero.
* - mathematically we want: `minVal + (rawVal-minVal) modulus PERIOD`
* - the unsigned representation of a negative number wraps cleanly, and thus maxVal - minVal,
* when converted into unsigned, is off by multiples of the whole number domain length, which
* is again absorbed by wrapping. Thus surprisingly `PERIOD = U(U(maxVal) - U(minVal))` is exact
* and can always be represented in the unsigned type.
* - for rawVal < minVal a flipped definition is used that can also be represented as unsigned:
* ** define dist ≔ minVal - rawval, then dist ≥ 1 for rawVal < minVal
* ** mathematically we need offset ≡ (-dist) modulus PERIOD
* ** we could compute PERIOD - (dist % PERIOD) -- but that would flip for dist ≡ PERIOD
* ** thus both arguments are shifted by -1, which keeps them in-range and computable as unsigned
* @note the repeated casts `U(signed)` prevent C++ integer promotion to kick in (esp. by short and char)
* @remark Claude was used (9/26) to improve and then formally verify this solution.
*/
template<std::integral N>
inline constexpr N
cyclicWrap (N rawVal, N minVal, N maxVal)
{
REQUIRE (minVal < maxVal);
using U = std::make_unsigned_t<N>;
const U PERIOD = U(U(maxVal) - U(minVal)); // always representable
U offset;
if (rawVal >= minVal)
offset = U(U(rawVal) - U(minVal)) % PERIOD;
else
{
const U dist = U(U(minVal) - U(rawVal)); // dist ≥ 1
offset = PERIOD-1 - (dist-1) % PERIOD; // ≡ (-dist) modulus PERIOD
}
return N(U(U(minVal) + offset));
}
/**
* Transform into a cyclically wrapping domain: floating point implementation.
* @return mapping into the range [minVal...maxVal(
* @warning this implementation can not cover extreme numeric values beyond #limit_cyclicWrap()
* @remark within the usable numeric range, the flipped modulus for values below minVal can simply
* be fixed by shifting once by PERIOD. But there is a caveat: extremely small negative offsets
* could be absorbed, so that the result lands on +PERIOD
*/
template<std::floating_point F>
inline constexpr F
cyclicWrap (F rawVal, F minVal, F maxVal)
{
REQUIRE (minVal < maxVal);
const F PERIOD = maxVal - minVal;
F offset = std::fmod (rawVal - minVal, PERIOD);
if (offset < 0)
offset += PERIOD;
if (not (offset < PERIOD)) // can happen due to ε or non-finite values
offset = 0;
F res = minVal + offset; // could be rounded to maxVal (or become infinite)
return res < maxVal? res : minVal;
}
/**
* Validation helper for the floating-point implementation of #cyclicWrap().
* Values beyond the computed limit can not be handled by the implementation,
* due to the limited internal resolution of floating-point computations.
* @param period length of the cyclic wrap period (≔ maxVal - minVal)
* @return a limit for each parameter values, taken absolutely
* @remark at the core of the #cyclicWrap() computation sits a numeric difference,
* taken modulo the period length. Each floating point number has a minimum resolution ("ULP").
* When the ULP of the difference computation surpasses the periodic cycle length, the cyclicWrap
* becomes meaningless (albeit computed correct without glitch). And since that offset is then
* added on top of meanValue, the same argument holds, so `std::abs (minVal) <= limit` is required,
* and by transitive argument also for `maxVal` and `rawVal`. Furthermore, the difference and the
* period length must also be finite numbers by themselves.
* @note for performance reasons, that limit should be computed once, checked against `minVal` and `maxVal`,
* and then before each computation of #cyclicWrap(), `std::abs (rawVal) <= limit` should be checked.
* It is recommended to fail with an hard error, since resolving theses settings typically requires
* an intervention by the user (parameters need to be adjusted).
* @remark Claude was used (9/26) to analyse the computation and derive a boundary criterion.
*/
template<std::floating_point F>
inline constexpr F
limit_cyclicWrap (F period)
{
REQUIRE (std::isnormal (period));
REQUIRE (F(0) < period);
F limit = period / std::numeric_limits<F>::epsilon();
if (not std::isfinite (limit))
limit = std::numeric_limits<F>::max();
return limit / 2; // exact
}
/**
* epsilon comparison of doubles.
* @remarks Floating point calculations are only accurate up to a certain degree,
@ -271,13 +170,12 @@ namespace util {
* @see https://en.wikipedia.org/wiki/Unit_in_the_last_place
* @todo 3/2024 seems we have solved this problem several times meanwhile /////////////////////////////////TICKET #1360 sort out floating-point rounding and precision
*/
template<std::floating_point F>
inline constexpr bool
almostEqual (F d1, F d2, uint ulp =2) noexcept
almostEqual (double d1, double d2, unsigned int ulp =2) noexcept
{
using std::fabs;
return fabs (d1-d2) < std::numeric_limits<F>::epsilon() * fabs (d1+d2) * ulp
or fabs (d1-d2) < std::numeric_limits<F>::min(); // special treatment for subnormal results
return fabs (d1-d2) < DBL_EPSILON * fabs (d1+d2) * ulp
or fabs (d1-d2) < DBL_MIN; // special treatment for subnormal results
}
@ -326,47 +224,6 @@ namespace util {
return logB;
}
static_assert (-1 == ilog2(-5));
static_assert ( 0 == ilog2( 1));
static_assert ( 5 == ilog2(42));
/**
* Exponentiation with integral exponent (counterpart to #ilog2).
* @param base the value to be raised
* @param exponent integral exponent; a negative exponent yields the reciprocal
* @remark `std::pow` can **not** be used in a constant expression: the standard does
* not mark it `constexpr` and only GCC accepts it there, through a _builtin_.
* Besides, for a small integral exponent, repeated multiplication is exact
* whenever base and result are representable -- unlike the generic
* implementation by `exp(ln(base)·exponent)`.
* @note the implementation uses [square-and-multiply], so it performs `ilog2(exponent)` squarings;
* the reciprocal is formed only once, on the final result, and is thus correctly rounded
* @see ilog2()
* [square-and-multiply]: https://en.wikipedia.org/wiki/Exponentiation_by_squaring
*/
template<std::floating_point F>
inline constexpr F
ipow (F base, int exponent) noexcept
{
bool reciprocal = exponent < 0;
uint64_t exp = reciprocal? -int64_t(exponent) // (widen first, to cover INT_MIN)
: int64_t(exponent);
F res{1};
for ( ; exp; exp >>= 1, base *= base)
if (exp & 1)
res *= base;
return reciprocal? F(1)/res
: res;
}
static_assert (1.0 == ipow (10.0, 0));
static_assert (1e3 == ipow (10.0, 3));
static_assert (1e-3 == ipow (10.0,-3)); // ◁───── exactly the nearest double to 0.001
static_assert (0.25 == ipow ( 2.0,-2));
static_assert (3125 == ipow ( 5.0, 5));
} // namespace util
#endif /*UTIL_QUANT_H*/

View file

@ -60,9 +60,7 @@ namespace util {
inline constexpr int
sgn (NUM n)
{
return n== NUM{0}? 0
: n < NUM{0}? -1
: +1;
return (n==0)? 0 :((n<0)? -1:+1 );
}
template <class N1, class N2>
@ -79,20 +77,12 @@ 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 max (val, NUM(0));
return (0<val? val : 0);
}
/** force a numeric to be within bounds, inclusively */

View file

@ -668,8 +668,8 @@ namespace model {
n2 = d2==u? n2 : reQuant (n2,d2, u);
FSecs res{s1*n1 + s2*n2, u};
auto lflp = [](Rat n){ return rational_cast<long double>(n); }; // can't use the guess from above,
ENSURE (abs (lflp(res) - (lflp(t1)+lflp(t2))) < 1.0/u); // double precision is not sufficient
auto f128 = [](Rat n){ return rational_cast<long double>(n); }; // can't use the guess from above,
ENSURE (abs (f128(res) - (f128(t1)+f128(t2))) < 1.0/u); // double precision is not sufficient
return detox (res);
}
}

View file

@ -175,13 +175,13 @@ namespace control {
Symbol instanceID{prototypeID, invocationID};
Command& instance = table_[instanceID];
if (instance)
throw error::Logic (_Fmt{"Attempt to create a new Command instance '%s', "
"while an instance for this invocationID %s "
"is currently open for parametrisation and "
"not yet dispatched for execution."}
% instanceID % invocationID
, LERR_(DUPLICATE_COMMAND)
);
throw new error::Logic (_Fmt{"Attempt to create a new Command instance '%s', "
"while an instance for this invocationID %s "
"is currently open for parametrisation and "
"not yet dispatched for execution."}
% instanceID % invocationID
, LERR_(DUPLICATE_COMMAND)
);
// create new clone from the prototype
table_[instanceID] = move (Command::get(prototypeID).newInstance());
ENSURE (instance, "cloning of command prototype failed");

View file

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

View file

@ -117,6 +117,14 @@ 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 provision_.invocationSeed != empty
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)
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 class / prototype to _adapt for invocation_
* @tparam INVO a configuration / policy base class 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
@ -155,6 +155,16 @@ namespace engine {
/**
* A builder to collect working data.
* Implemented through a suitable configuration of lib::SeveralBuilder,
* with a policy configuration parameter to define the allocator to use.
*/
template<class POL, class I, class E=I>
using DataBuilder = lib::SeveralBuilder<I,E, POL::template Policy>;
template<class POL, class DAT>
class NodeBuilder;
@ -247,7 +257,7 @@ namespace engine {
Connectivity
build()
{
PortData ports{leads_.policyConnect()}; // use same allocator...
PortData ports;
patternData_.collectEntries(ports);
return Connectivity{ports.build()
,leads_.build()
@ -496,8 +506,8 @@ namespace engine {
closeParamFront (PAR v1, PARS ...vs)
{
return adaptParam(
WAB::ParamClosure::closeFront (forward<PAR> (v1)
,forward<PARS>(vs)...));
WAB::ParamClosure::template closeFront (forward<PAR> (v1)
,forward<PARS>(vs)...));
}
/** immediately close the rightmost parameter positions,
@ -507,8 +517,8 @@ namespace engine {
closeParamBack (PAR v1, PARS ...vs)
{
return adaptParam(
WAB::ParamClosure::closeBack (forward<PAR> (v1)
,forward<PARS>(vs)...));
WAB::ParamClosure::template closeBack (forward<PAR> (v1)
,forward<PARS>(vs)...));
}
/** immediately close a single parameter at designated position

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 setupBufferProvider();
return getNaiveBufferManager();
}

View file

@ -1,64 +0,0 @@
/*
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

@ -163,17 +163,6 @@ namespace engine {
/**
* A builder to collect working data.
* Implemented through a suitable configuration of lib::SeveralBuilder,
* with a policy configuration parameter to define the allocator to use.
* @todo 10/2026 extract the builder-DSL definitions? `DataBuilder` and `SizMark`
* are vocabulary of the node-builder language and are used from
* 'node-builder.hpp' as well -- they reside here merely because this is
* the upstream header in the include order, not because the concepts
* belong to the weaving pattern. The same holds for `UseHeapAlloc`,
* which sits in an anonymous namespace over there.
*/
template<class POL, class I, class E=I>
using DataBuilder = lib::SeveralBuilder<I,E, POL::template Policy>;

View file

@ -50,12 +50,6 @@ 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,8 +16,6 @@
** 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,8 +16,6 @@
** 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,8 +16,6 @@
** 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,8 +16,6 @@
** 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,8 +16,6 @@
** 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

@ -33,14 +33,12 @@
#include "lib/symbol.hpp"
#include "lib/meta/trait.hpp"
#include "lib/util-quant.hpp"
#include "lib/time/timevalue.hpp"
#include "test/transiently.hpp"
#include "lib/format-obj.hpp"
#include "lib/random.hpp"
#include <boost/lexical_cast.hpp>
#include <concepts>
#include <typeinfo>
#include <cstdlib>
#include <utility>
@ -63,15 +61,14 @@ namespace test{
using util::showType;
using util::showTypes;
using std::floating_point;
constexpr auto ROUGH_PRECISION = util::ipow (10.0, -3);
constexpr auto ROUGH_PRECISION = pow (10, -3);
constexpr auto EPSILON_ULP = 5;
template<floating_point F, typename N>
constexpr inline
bool
template<typename F, typename N>
constexpr inline enable_if< std::is_floating_point<F>,
bool >
roughEQ (F val, N target, F limit =ROUGH_PRECISION)
{
REQUIRE (0 < limit);
@ -79,9 +76,9 @@ namespace test{
} //////////////////////////////////////////////////////////////////////////TICKET #1360 looks like this problem was solved several times
template<floating_point F>
constexpr inline
F
template<typename F>
constexpr inline enable_if< std::is_floating_point<F>,
F >
ulp (F val)
{
val = fabs (val);
@ -93,9 +90,9 @@ namespace test{
return scaledUlp;
}
template<floating_point F, typename N>
constexpr inline
bool
template<typename F, typename N>
constexpr inline enable_if< std::is_floating_point<F>,
bool >
epsEQ (F val, N target, uint ulps =EPSILON_ULP)
{
return abs (val - target) < ulps * ulp<F> (target);

View file

@ -32,7 +32,6 @@
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

View file

@ -32,19 +32,6 @@
#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 {
@ -128,4 +115,44 @@ 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,13 +32,6 @@
** 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
@ -107,11 +100,6 @@ 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
{
@ -140,9 +128,8 @@ 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 (possibly not needed anymore, can be derived from the data...)
size_t srcParam;
size_t sinkParam;
////////////////////////////////////////////////////////////////////////////////////TICKET #1293 job invocation identity
////////////////////////////////////////////////////////////////////////////////////TICKET #1295 : rework Job parameters to accommodate input / output info required for rendering
};
typedef struct lumiera_jobParameter_struct lumiera_jobParameter;
typedef lumiera_jobParameter* LumieraJobParameter;
@ -207,9 +194,6 @@ 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 {
@ -300,8 +284,6 @@ 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
@ -344,6 +326,28 @@ 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

@ -167,7 +167,7 @@ namespace out {
* handle is retained.
* @tparam isTest use a simple BufferMetadata table in OutputBufferProxy.
*/
template<class CON, bool isTest> // ◁───── default defined at declaration (output-slot.hpp)
template<class CON, bool isTest=false>
class OutputSlot::AllocState
: public OutputSlot::Allocation
{

View file

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

View file

@ -53,11 +53,6 @@ return: 0
END
TEST "Cyclic wrapping number domain" CyclicWrap_test <<END
return: 0
END
TEST "Deleter function collection" DelStash_test <<END
return: 0
END

View file

@ -1,32 +0,0 @@
TESTING "Library Test Suite: parameters" ./test-suite --group=param
TEST "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
TEST "Parameter value scales" ParameterScale_test << END
return: 0
END
PLANNED "Enumeration domains" NomSet_test << END
return: 0
END
PLANNED "Parameter type registry" TypeRegistry_test << END
return: 0
END

View file

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

View file

@ -1,196 +0,0 @@
/*
CyclicWrap(Test) - verify cyclically wrapping number 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 util-floordiv-test.cpp
** unit test \ref CyclicWrap_test
*/
#include "test/run.hpp"
#include "lib/util-quant.hpp"
#include "lib/util.hpp"
using util::isLimited;
using std::floating_point;
namespace util {
namespace test {
namespace{ // Test helpers...
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 _EPS = std::numeric_limits<FLO>::epsilon();
} // (End) Test helpers
/******************************************************************//**
* @test Demonstrate and verify a cyclic wrapping numeric domain.
* - show the wrapping behaviour with simple numbers
* - verify the behaviour pattern for all integer types (signed / unsigned)
* - verify the computation works at the very bounds of the domain
* - verify the most difficult path with a negative minVal,
* and the smallest number in the domain
* - show a similar simple wrapping behaviour with all float types
* - demonstrate the meaning of the precision limit
* - demonstrate aliasing and sanitised rounding errors
* @see util::cyclicWrap
* @see parameter-scale-test.cpp
*/
class CyclicWrap_test : public Test
{
virtual void
run (Arg)
{
simpleDemonstration();
verifyIntegerTypes<int>();
verifyIntegerTypes<uint>();
verifyIntegerTypes<int8_t>();
verifyIntegerTypes<uint8_t>();
verifyIntegerTypes<int16_t> ();
verifyIntegerTypes<uint16_t>();
verifyIntegerTypes<int32_t> ();
verifyIntegerTypes<uint32_t>();
verifyIntegerTypes<int64_t> ();
verifyIntegerTypes<uint64_t>();
verifyFloatTypes<float>();
verifyFloatTypes<double>();
verifyFloatTypes<lflp>();
}
void
simpleDemonstration ()
{
CHECK (10 == cyclicWrap (10,10,15));
CHECK (11 == cyclicWrap (11,10,15));
CHECK (12 == cyclicWrap (12,10,15));
CHECK (13 == cyclicWrap (13,10,15));
CHECK (14 == cyclicWrap (14,10,15));
CHECK (10 == cyclicWrap (15,10,15));
CHECK (11 == cyclicWrap (16,10,15));
CHECK (12 == cyclicWrap (17,10,15));
CHECK (13 == cyclicWrap (18,10,15));
CHECK (14 == cyclicWrap (19,10,15));
CHECK (10 == cyclicWrap (20,10,15));
CHECK (10 == cyclicWrap ( 0,10,15));
CHECK (14 == cyclicWrap (-1,10,15));
}
template<typename I>
void
verifyIntegerTypes ()
{
const I LOWER(-100); // note: expected to wrap for unsigned types
const I UPPER(LOWER+5);
auto cyc = [&](I val){ return cyclicWrap (val, LOWER,UPPER); };
CHECK (LOWER == cyc(LOWER+0) );
CHECK (LOWER+1 == cyc(LOWER+1) );
CHECK (LOWER+2 == cyc(LOWER+2) );
CHECK (LOWER+3 == cyc(LOWER+3) );
CHECK (LOWER+4 == cyc(LOWER+4) );
CHECK (LOWER == cyc(LOWER+5) );
CHECK (LOWER == cyc(LOWER-5) );
CHECK (LOWER+1 == cyc(LOWER-4) );
CHECK (LOWER+2 == cyc(LOWER-3) );
CHECK (LOWER+3 == cyc(LOWER-2) );
CHECK (LOWER+4 == cyc(LOWER-1) );
CHECK (isLimited (LOWER, cyc(_MAX<I>), UPPER));
CHECK (isLimited (LOWER, cyc(_MIN<I>), UPPER));
CHECK (isLimited (LOWER, cyc(_MAX<I>-1), UPPER));
CHECK (isLimited (LOWER, cyc(_MIN<I>+1), UPPER));
//------ Demonstrate behaviour at lower domain bound
//
I dist = LOWER - _MIN<I>; // guaranteed to be positive and representable in-domain
I step = (dist / 5) * 5; // the next applicable grid point
I excess = dist - step;
I offset = 5 - excess; // flip orientation (since the input val lies below LOWER)
CHECK (LOWER + offset == cyc(_MIN<I>) );
}
template<typename F>
void
verifyFloatTypes()
{
auto cyc = [&](F val){ return cyclicWrap (val, F(-10),F(-5)); };
CHECK (almostEqual (F(-10), cyc(0)) );
CHECK (almostEqual (F( -9), cyc(1)) );
CHECK (almostEqual (F( -8), cyc(2)) );
CHECK (almostEqual (F( -7), cyc(3)) );
CHECK (almostEqual (F( -6), cyc(4)) );
CHECK (almostEqual (F(-10), cyc(5)) );
CHECK (almostEqual (F( -9), cyc(6)) );
CHECK (almostEqual (F( -8), cyc(7)) );
CHECK (almostEqual (F( -7), cyc(8)) );
CHECK (almostEqual (F( -6), cyc(9)) );
CHECK (almostEqual (F(-10), cyc(105)));
// For each period-length, a precision limit can be computed;
// whenever bounds or inputs approach that limit, the rounding errors
// approach the order of magnitude of the period-length, and thus
// computing of wrapped values becomes pointless...
F period5 = F(5);
F limit5 = limit_cyclicWrap<F> (period5);
// one ULP-Step at the limit equals already half the period-length:
CHECK (almostEqual (period5/2, limit5 * _EPS<F>));
// so small steps will alias...
CHECK (limit5 + 1 == limit5);
// HOWEWER, very large periods can be handled (within numeric precision)
CHECK (1 == cyclicWrap<F>( 1, 1,_MAX<F>) );
CHECK (2 == cyclicWrap<F>( 2, 1,_MAX<F>) ); // note: these values are *exact* -- due to std::fmod()
CHECK (3 == cyclicWrap<F>( 3, 1,_MAX<F>) );
CHECK (4 == cyclicWrap<F>( 4, 1,_MAX<F>) );
CHECK (5 == cyclicWrap<F>( 5, 1,_MAX<F>) );
CHECK (1 == cyclicWrap<F>(-1, 1,_MAX<F>) ); // yet this one gets aliased with the minVal
F epsStep = _MAX<F>*_EPS<F>;
// yet the following input is clearly below minVal and will wrap upwards...
F wrapped = cyclicWrap (-epsStep, F(1),_MAX<F>);
CHECK ( 1 < wrapped);
CHECK (wrapped < _MAX<F>);
}
};
LAUNCHER (CyclicWrap_test, "unit common");
}} // namespace util::test

View file

@ -1,87 +0,0 @@
/*
NomSet(Test) - verify nominal and ordinal scale enumerations
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 nom-set-test.cpp
** unit test \ref NomSet_test
*/
#include "test/run.hpp"
//#include "test/test-helper.hpp"
#include "lib/par/nom-set.hpp"
#include "test/diagnostic-output.hpp"/////////////TODO
//#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 the definition of domain enumerations for nominal / ordinal scales.
*/
class NomSet_test : public Test
{
virtual void
run (Arg)
{
simpleUsage();
verify_typeMarker();
}
void
simpleUsage()
{
UNIMPLEMENTED ("nebbich");
}
/** @test
* @todo WIP 9/26 🔁 define ⟶ implement
*/
void
verify_typeMarker()
{
}
};
/** Register this test class... */
LAUNCHER (NomSet_test, "unit lib");
}}} // namespace lib::par::test

View file

@ -1,293 +0,0 @@
/*
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 "lib/par/param-type.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 helpers....
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 helpers
/**************************************************************************//**
* @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);
}
template<typename X>
using FullDomain = BaseDomain<X, Domain>;
/** @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()
{
FullDomain<int> domInt;
CHECK (sizeof(FullDomain<int>) <= sizeof(void*)); // only the VTable
// use the generic interface for access...
Domain& dom{domInt};
int val = 1 + rani (1000);
ValBuff& src = asValBuff (val);
uint target1{0};
CHECK (not target1);
dom.extractAs (target1, src);
CHECK (target1 == uint(val));
float target2{0};
dom.extractAs (target2, src);
CHECK (target2 == float(val));
// value clamped to target domain...
val = _MIN<int>;
uint64_t target3{55};
dom.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()
{
FullDomain<int> domInt;
Domain& dom{domInt};
int val_in_buff = 0;
ValBuff& target = asValBuff (val_in_buff);
uint src1 = 1 + rani (1000);
dom.conform (target, src1);
CHECK (val_in_buff == int(src1));
double src2 = ranRange (1,100);
CHECK (1 <= src2 and src2 < 100);
dom.conform (target, src2);
CHECK (val_in_buff == int(floor (src2)));
// value clamped to target domain...
auto mini = _MIN<int64_t>;
dom.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};
FullDomain<double> ddom;
FullDomain<int64_t> idom;
FullDomain<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

@ -1,91 +0,0 @@
/*
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

@ -1,365 +0,0 @@
/*
ParameterScale(Test) - verify parameter value scales
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 ParameterScale_test
*/
#include "test/run.hpp"
#include "test/test-helper.hpp"
#include "lib/par/scale.hpp"
#include "test/diagnostic-output.hpp"/////////////TODO
using test::roughEQ;
namespace lib {
namespace par {
namespace test{
namespace {//Test helpers....
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 helpers
/**************************************************************************//**
* @test demonstrate features and verify behaviour of value Scale definitions.
* - a par::Scale is a descriptor record with some feature configuration settings
* - some combination of setting marks specific scale features to be present
* - values can be _conformed_ to comply with the scale constraints
* - a Scale can be configured to maintain cyclic wrapping values
* - values defined on different scales can be _joined_ (numerically combined)
* - scales can be defined to be logarithmic, and work together with linear scales.
*/
class ParameterScale_test : public Test
{
virtual void
run (Arg)
{
simpleUsage();
verify_Predicate();
verify_Conforming();
verify_cyclicScale();
verify_valueJoining();
}
void
simpleUsage()
{
Scale<int> scale = {.minVal = -5, .maxVal = 23};
CHECK (scale.isValid());
CHECK (-5 == scale.conform (-55));
CHECK (23 == scale.conform (+55));
CHECK ( 5 == scale.join (2,3) );
CHECK (-5 == scale.join (23,-55));
}
/** @test verify the feature detection and descriptor predicates */
void
verify_Predicate()
{
Scale<double> scale;
CHECK (scale.isValid());
CHECK (not scale.isCyclic());
CHECK (not scale.isLimited());
CHECK (not scale.isLogarithmic());
scale.minVal = -5;
CHECK (scale.isValid());
CHECK (not scale.isCyclic());
CHECK ( scale.isLimited());
scale.cyclicLim = 10;
CHECK (not scale.isValid());
CHECK ( scale.isCyclic());
CHECK (not scale.isLimited());
scale.maxVal = +5;
CHECK ( scale.isValid());
CHECK ( scale.isCyclic());
CHECK (not scale.isLimited());
CHECK (not scale.isFactor());
scale.neutral = 2;
CHECK (not scale.isFactor());
scale.neutral = 1;
CHECK ( scale.isFactor());
CHECK (scale.metric == LIN);
CHECK (not scale.isLogarithmic());
scale.metric = NOM;
CHECK (not scale.isLogarithmic());
scale.metric = DEC;
CHECK ( scale.isLogarithmic());
scale.metric = BIN;
CHECK ( scale.isLogarithmic());
scale.metric = NAT;
CHECK ( scale.isLogarithmic());
}
/** @test accommodate values to comply with the scale
* - optionally a maximum and minimum can be enforced
* - optionally the value can be cyclically wrapped
* @todo WIP 9/26 ✔ define ⟶ ✔ implement
*/
void
verify_Conforming()
{
Scale<float> scale;
CHECK (1e5f == scale.conform (1e5f));
scale.maxVal = -5.0f;
CHECK (-5.0f == scale.conform (1e5f));
CHECK (scale.isValid());
scale.minVal = 5.0f;
CHECK (not scale.isValid());
scale.maxVal = 8.0f;
CHECK (scale.isValid());
CHECK (5.0f == scale.conform (-5.0f));
CHECK (not scale.isCyclic());
scale.cyclicLim = 100;
CHECK (scale.isCyclic());
CHECK (7.0f == scale.conform (-5.0f));
CHECK (5.0f == scale.conform (-4.0f));
CHECK (6.0f == scale.conform (-3.0f));
CHECK (7.0f == scale.conform (-2.0f));
VERIFY_FAIL ("beyond supported numeric precision"
, scale.conform (-1e5f));
}
/** @test Scales can be configured to wrap cyclically, e.g. for angles.
* - demonstrate an unsigned angle scale with short precision
* - demonstrate a high-resolution scale symmetric to zero
* @todo WIP 9/26 ✔define ⟶ ✔ implement
*/
void
verify_cyclicScale()
{
Scale<ushort> angleScale = {.minVal=0, .maxVal=360, .cyclicLim=0xFFFF};
CHECK (angleScale.isValid());
CHECK (angleScale.isCyclic());
CHECK ( 0 == angleScale.conform(0));
CHECK (180 == angleScale.conform(180));
CHECK (240 == angleScale.conform(240));
CHECK ( 0 == angleScale.conform(360));
CHECK (359 == angleScale.conform(719));
CHECK ( 0 == angleScale.conform(65520));
CHECK ( 15 == angleScale.conform(65535));
Scale<lflp> tinyScale = {.minVal=-lflp(1)/100, .maxVal=lflp(1)/100};
tinyScale.cyclicLim = util::limit_cyclicWrap(*tinyScale.maxVal - *tinyScale.minVal);
CHECK (1e16 < tinyScale.cyclicLim);
CHECK (tinyScale.isValid());
CHECK (tinyScale.isCyclic());
CHECK ( tinyScale.conform(0 ) == 0 );
CHECK (roughEQ (tinyScale.conform(0.001) , 0.001));
CHECK (roughEQ (tinyScale.conform(0.005) , 0.005));
CHECK (roughEQ (tinyScale.conform(0.00999), 0.00999));
CHECK (roughEQ (tinyScale.conform(0.01 ),-0.0099999)); // surprise: 1/100 not exactly representable in floating-point
// since the domain is symmetric and 2/100 long, the ten-folds will fall into the middle,
// but they typically do not land precisely on zero due to inherent "number dust"
CHECK (1e15 > fabs(tinyScale.conform(1)));
CHECK (0 != tinyScale.conform(1) );
auto beyond_precision = -10 * tinyScale.cyclicLim;
VERIFY_FAIL ("beyond supported numeric precision"
, tinyScale.conform (beyond_precision));
}
/** @test Base value and data feeds from differing Scales can be joined together
* - when the target scale defines limits (or is cyclic),
* this conforming is applied after all conversions
* - a feed can be marked as "factor", in which case
* the feed is multiplied to the target rather than added
* - even scales with integral type can carry log meaning
* - suitable intermediary types used for good precision,
* and especially to avoid wrapping signed into unsigned
* - the supported logarithmic flavours are accommodated,
* possibly converting the base of the logarithm accordingly
* - the factor 10 inherent to decibels is applied / removed
* before combining decibel values with other logarithms
* - applying a linear factor to a logarithmic scale is
* transformed into an addition in the logarithmic domain
* - adding a linear offset to a logarithmic scale is rejected
* - extended precision scales handled using `lflp` (≙`long double`)
* - this allows to retain full precision of 64bit integrals
* @todo WIP 9/26 ✔ define ⟶ ✔ implement
*/
void
verify_valueJoining()
{
Scale<ushort> scale = {.minVal = 2, .maxVal = 10};
Scale<int64_t> lscale;
CHECK ( 5 == scale.join (2, int64_t(3), lscale)); // 2+3 ≡ 5 (all computations done as double)
CHECK (10 == scale.join (5, int64_t(8), lscale)); // 5+8 ≡ 13 ⟼ capped to 10
lscale.neutral = 1;
CHECK (lscale.isFactor());
CHECK ( 6 == scale.join (2, int64_t(3), lscale)); // 2*3 ≡ 6
CHECK ( 2 == scale.join (2, int64_t(-3),lscale)); // 2*-3 ≡ -6 ⟼ first conditioned to ushort domain, then capped to 2
lscale.metric = DEC; // in dB
CHECK ( 7 == scale.join (4, int64_t(3), lscale)); // 4 + 3dB ⟼ 4 * 1.995 ≡ 7.98 ⟼ truncated to 7
Scale<short> sscale;
Scale<uint64_t> uscale = {.neutral = 1};
// Note: number promotion rules of C++ would backfire nastily here...
CHECK (short(-3) * uint64_t(2) > numeric_limits<int64_t>::max());
CHECK (short(-6) == sscale.join (-3, uint64_t(2), uscale));
//---floating-point logarithmic scale conversions---
//
Scale<float> fscale = {.metric = NAT};
Scale<float> feed_dB = {.metric = DEC};
CHECK (roughEQ (fscale.join (0, float(20), feed_dB) , log(100.0f) )); // dB ⟼ natural logarithm; note 20dB ≙ factor 10 * lg(100); input 20 divided by 10
fscale.metric = BIN;
CHECK (roughEQ (fscale.join (0, float(20), feed_dB) , log2(100.0f))); // dB ⟼ binary logarithm; (factor 1/10 on input is folded into the log factor)
fscale.metric = DEC;
CHECK (roughEQ (fscale.join (0, float(20), feed_dB) , 20.0f )); // dB ⟼ dB just passed-through and then added to value ≔ 0.0
fscale.metric = DEC;
lscale.metric = BIN;
CHECK (roughEQ (fscale.join (0, int64_t(-3), lscale), log10(1.0f/8) *10)); // binary log (given as int value) ⟼ dB
lscale.metric = NAT;
CHECK (roughEQ (fscale.join (0, int64_t(-3), lscale), log10(exp(-3))*10)); // natural log (input as int) ⟼ dB
lscale.metric = DEC;
CHECK (roughEQ (fscale.join (0, int64_t(-3), lscale), -3 )); // dB ⟼ dB just passed-through
fscale.metric = BIN;
lscale.metric = BIN;
CHECK (roughEQ (fscale.join (0, int64_t(-3), lscale), -3 )); // lb ⟼ lb just passed-through
lscale.metric = NAT;
CHECK (roughEQ (fscale.join (0, int64_t(-3), lscale), log2 (exp(-3)) )); // natural log ⟼ binary log
lscale.metric = DEC;
CHECK (roughEQ (fscale.join (0, int64_t(-30),lscale), log2 (1.0/1000) )); // conversion factor: dB/10 ⟼ binary log
fscale.metric = LIN;
lscale.metric = BIN;
CHECK (roughEQ (fscale.join (1, int64_t(-3), lscale), 1.0f/8 )); // binary log applied as factor to a linear scale
lscale.metric = NAT;
CHECK (roughEQ (fscale.join (1, int64_t(-1), lscale), 1.0f/exp(1))); // natural log applied as factor 1/e
lscale.metric = DEC;
CHECK (roughEQ (fscale.join (1, int64_t(-30),lscale), 1.0f/1000 )); // decibels applied as factor : -30 dB ≙ 10^-3
fscale.metric = BIN;
lscale.metric = LIN;
lscale.neutral = 1;
CHECK (lscale.isFactor());
CHECK (fscale.isLogarithmic());
CHECK ( fscale.join (1, int64_t(8), lscale) == 1 + 3); // feed factor 8 ⟼ to binary logarithmic fscale (8 ≡ 2^3) and added there (exact)
fscale.metric = NAT;
CHECK (roughEQ (fscale.join (1, int64_t(8), lscale), 1 + logf(8))); // feed factor 8 ⟼ now applied to a natural log scale (and added to one, in float)
fscale.metric = DEC;
CHECK (roughEQ (fscale.join (1, int64_t(100),lscale), 1 + 20)); // feed factor 8 ⟼ now applied as offset in decibels
lscale.neutral = 0;
CHECK (not lscale.isFactor());
VERIFY_FAIL ("adding a non-logarithmic data feed on top of a logarithmic base value"
, fscale.join (1, int64_t(8), lscale));
//---verify a long-double target scale
//
Scale<lflp> megaScale = {.minVal = -10, .maxVal = +10}; // setup a cyclic scale using the long double domain
megaScale.cyclicLim = util::limit_cyclicWrap<lflp>(20); // (cyclic wrap is handled by conform(); just another little twist)
CHECK (megaScale.isCyclic());
megaScale.metric = DEC;
fscale.metric = LIN;
fscale.neutral = 1;
CHECK (fscale.isFactor());
CHECK ( 5 == megaScale.join ( 5, 100.0f, fscale)); // input ⟼ decibels, then added to the base value, and finally wrapped ∈ [-10 ... 10[
CHECK (-5 == megaScale.join (-5, 100.0f, fscale));
CHECK ( 5 == megaScale.join (-5, 1000.0f, fscale)); // ...so here we get -5dB + 30dB
// now push that to the limits....
CHECK (logb (1e18) < logb (_MAX<int64_t>)); // use the largest power of 10 representable in int64_t
CHECK (logb (_MAX<int64_t>) < logb (megaScale.cyclicLim)); // ...which the (cyclic) target scale can still handle precisely!
lflp res1 = megaScale.join (-5, 1e18f, fscale); // log10 ⟼ 18, so we add -5dB + 180dB;
CHECK (roughEQ (res1, -5)); // the cyclicWrap then computes 175 - (-10) ≡ 185 % 20 (≙5) + (minVal ≙ -10)
// but the computation is not exact...
CHECK (0 != std::fmod (res1, 1));
// yet the cause lies in the input already:
lflp srcErr = lflp(1e18f) - pow (lflp(10), 18);
CHECK (1e10 < fabs (srcErr)); // ... so the float input incurs a gigantic error margin
// however: use an exact input, and the result will be exact
lscale.neutral = 1;
CHECK (lscale.isFactor());
CHECK (-5 == megaScale.join (-5, int64_t(1e18), lscale));
lflp res2 = megaScale.join (-5, int64_t(1e18)+1, lscale);
CHECK (-5 != res2); // even a tiny offset makes it through the computation
CHECK (-16 > log10 (res2 -(-5))); // error is below the 16. decimal place (plausible, since log10 is computed)
// compare to target scale with lesser precision...
Scale<double> doubleTargetScale = {.metric = DEC};
Scale<uint64_t> longTargetScale = {.metric = DEC};
CHECK (180 == doubleTargetScale.join (0, int64_t(1e18), lscale)); // uses double for internal log10 computation
CHECK (180 == doubleTargetScale.join (0, int64_t(1e18)-10, lscale)); // offset chosen large enough to prevail in int64_t but not in double
CHECK (180 == longTargetScale.join (0, int64_t(1e18), lscale)); // 64bit integral scales use long-double for log10 computation internally
CHECK (179 == longTargetScale.join (0, int64_t(1e18)-10, lscale)); // ...and thus the offset survives the log10 and is truncated down on conversion
}
/** @test
* @todo WIP 9/26 🔁 define ⟶ implement
*/
void
verify_NominalScale()
{
}
};
/** Register this test class... */
LAUNCHER (ParameterScale_test, "unit lib");
}}} // namespace lib::par::test

View file

@ -1,137 +0,0 @@
/*
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 "test/diagnostic-output.hpp"/////////////TODO
//#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 cover properties of generic parameter containers.
*/
class Parameter_test : public Test
{
virtual void
run (Arg)
{
simpleUsage();
verify_Builder();
verify_Identity();
verify_ParamType();
verify_Automation();
}
void
simpleUsage()
{
CHECK (sizeof(Parameter) <= 4 * sizeof(void*));
CHECK (BASETYPE_MAX_SIZ <= sizeof(ValStorage));
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>());
}
/** @test explore ways to define a parameter...
* @todo WIP 9/26 🔁 define ⟶ implement
*/
void
verify_Builder()
{
Parameter p1 = Parameter::forValue(123.45f).build();
CHECK (123.45f == p1.getVal<float>());
p1.setVal (12u);
CHECK (12.0 == p1.getVal<double>());
Parameter p2 = Parameter::from(p1)
.maxVal (sqrt(2))
.build();
SHOW_EXPR(p2.getVal<float>());
}
/** @test
* @todo WIP 9/26 🔁 define ⟶ implement
*/
void
verify_Identity()
{
}
/** @test
* @todo WIP 9/26 🔁 define ⟶ implement
*/
void
verify_ParamType()
{
}
/** @test
* @todo WIP 9/26 🔁 define ⟶ implement
*/
void
verify_Automation()
{
}
};
/** Register this test class... */
LAUNCHER (Parameter_test, "unit lib");
}}} // namespace lib::par::test

View file

@ -1,117 +0,0 @@
/*
TypeRegistry(Test) - verify nominal and ordinal scale enumerations
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 type-registry.cpp
** unit test \ref TypeRegistry_test
*/
#include "test/run.hpp"
//#include "test/test-helper.hpp"
#include "lib/par/type-registry.hpp"
#include "test/tracking-dummy.hpp"
#include "lib/util.hpp"
#include "test/diagnostic-output.hpp"/////////////TODO
//#include <utility>
//#include <string>
//#include <vector>
//using std::string;
//using std::vector;
//using std::swap;
using util::isSameObject;
namespace lib {
namespace par {
namespace test{
// using lumiera::error::LUMIERA_ERROR_LOGIC;
namespace {//Test fixture....
}//(End)Test fixture
using ::test::Dummy;
using Registry = TypeRegistry<Dummy>;
using RegID = Registry::ID;
/**************************************************************************//**
* @test verify a index/extent based registration table for type descriptors
* - new slots can be claimed and then re-accessed by ID
* - storage is stable even while new extents are added, and not leaked
* - read access can happen concurrently with allocating new slots.
*/
class TypeRegistry_test : public Test
{
virtual void
run (Arg)
{
seedRand();
simpleUsage();
verify_storageSanity();
verify_concurrentAccess();
}
void
simpleUsage()
{
const int MARK = 1 + rani (1000);
Registry registry;
RegID id = registry.makeSlot (MARK);
Dummy& entry = registry[id];
CHECK (MARK == entry.getID());
CHECK (isSameObject (entry, registry[id]));
VERIFY_FAIL ("not yet allocated", registry[12345]);
}
/** @test
* @todo WIP 9/26 🔁 define ⟶ implement
*/
void
verify_storageSanity()
{
UNIMPLEMENTED ("verify coherent storage handling and proper clean-up");
}
/** @test
* @todo WIP 9/26 🔁 define ⟶ implement
*/
void
verify_concurrentAccess()
{
UNIMPLEMENTED ("press slot allocation and data access concurrently");
}
};
/** Register this test class... */
LAUNCHER (TypeRegistry_test, "unit lib");
}}} // namespace lib::par::test

View file

@ -171,7 +171,7 @@ namespace test{
}
/** @test various ways to build and populate the container
/** @test various ways to build an 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

@ -224,7 +224,7 @@ namespace test{
CHECK (format4Csv(double(1) / 3) == "0.333333333333333"_expect );
CHECK (format4Csv(float(1) / 3) == "0.333333"_expect );
CHECK (format4Csv(lflp(1) / 3) == "0.333333333333333333"_expect);
CHECK (format4Csv(f128(1) / 3) == "0.333333333333333333"_expect);
CHECK (format4Csv(bool(1)) == "true"_expect );
CHECK (format4Csv(bool(0)) == "false"_expect);
CHECK (format4Csv("Starship-3") == "\"Starship-3\""_expect ); // 3rd test today ;-)

View file

@ -30,7 +30,7 @@
using ::Test;
using util::isnil;
using util::_Fmt;
using std::integral;
namespace util {
namespace test {
@ -42,13 +42,6 @@ namespace test {
const uint NUM_ELMS_PERFORMANCE_TEST = 50000000;
const uint NUMBER_LIMIT = 1 << 30;
template<typename NUM>
constexpr NUM _MAX = std::numeric_limits<NUM>::max();
template<typename NUM>
constexpr NUM _MIN = std::numeric_limits<NUM>::lowest();
typedef std::vector<int> VecI;
VecI
@ -115,7 +108,7 @@ namespace test {
{
seedRand();
verifyBehaviour();
verifyBehaviour ();
verifyIntegerTypes<int>();
verifyIntegerTypes<long>();
@ -123,8 +116,6 @@ namespace test {
verifyIntegerTypes<int64_t>();
verifyIntegerTypes<llong>();
verifyBounds();
if (not isnil (arg))
runPerformanceTest();
}
@ -163,7 +154,7 @@ namespace test {
template<typename I>
void
verifyIntegerTypes()
verifyIntegerTypes ()
{
I n,d,expectedRes;
@ -177,51 +168,11 @@ namespace test {
}
void
verifyBounds()
{
auto verify = []<integral I>(I num, I den, I expect_quot, I expect_rem)
{
auto res = floorwrap (num,den);
CHECK (res.quot == expect_quot, "quotient mismatch");
CHECK (res.rem == expect_rem , "remainder mismatch");
CHECK (res.quot == floordiv (num,den), "floordiv diverges");
};
//----- num | den | quotient | remainder
verify (_MAX<int>, 1, _MAX<int> , 0);
verify (_MAX<int>, 2, _MAX<int>/2 , 1);
verify (_MAX<int>, 5, _MAX<int>/5 , 2);
verify (_MIN<int>, 1, _MIN<int> , 0);
verify (_MIN<int>, 2, _MIN<int>/2 , 0);
verify (_MIN<int>, 5, _MIN<int>/5 -1, 2); // _MIN<int> ≡ -2147483648 /5 ≡ -429496729 -1 step ≡ -429496730 · 5 ≡ -2147483650 ⟹ Δ = +2
verify (_MAX<int>,-1, -_MAX<int> , 0);
verify (_MAX<int>,-2, -_MAX<int>/2 -1,-1);
verify (_MAX<int>,-5, -_MAX<int>/5 -1,-3); // Δ ≡ -3 here since -_MAX<int> ≡ _MIN<int> + 1
verify (_MAX<uint>, 1u, _MAX<uint> ,0u);
verify (_MAX<uint>, 2u, _MAX<uint>/2 ,1u);
verify (_MAX<uint>, 5u, _MAX<uint>/5 ,0u);
verify (_MIN<int64_t>, int64_t(1), _MIN<int64_t> , int64_t(0));
verify (_MIN<int64_t>, int64_t(2), _MIN<int64_t>/2 , int64_t(0));
verify (_MIN<int64_t>, int64_t(5), _MIN<int64_t>/5 -1, int64_t(2)); // 2^63 / 5 ≡ 1844674407370955161.6 ; 1844674407370955162 · 5 ⟹ Δ = +2
verify (_MIN<int8_t>, int8_t(1), int8_t(-128) , int8_t(0));
verify (_MIN<int8_t>, int8_t(2), int8_t(-64 ) , int8_t(0));
verify (_MIN<int8_t>, int8_t(5), int8_t(-26 ) , int8_t(2)); // 2^7 / 5 ≡ 25.6 ; 26 · 5 ≡ 130 ⟹ Δ = +2
verify (_MAX<int8_t>, int8_t(5), int8_t( 25 ) , int8_t(2)); // (2^7-1) / 5 ≡ 25.4 ; 25 · 5 ≡ 125 ⟹ Δ = +2
}
/** @test timing measurements to compare implementation details.
* This test uses a sequence of random integers, where the values
* used as denominator are ensured not to be zero.
*
*
* \par measurement results
* My experiments (AMD Athlon-64 4200 X2) gave me
* the following timing measurements in nanoseconds:

View file

@ -36,7 +36,7 @@
#include "lib/format-string.hpp"
#include "lib/format-cout.hpp"
#include "lib/time/timevalue.hpp"
#include "lib/hash-indexed.hpp"
#include "lib/luid.h"
#include "lib/util.hpp"
#include <boost/lexical_cast.hpp>

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 job.parameter.invoKey == empty
return lumiera_invokey_eq (&util::unConst(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 (nopJob.parameter.invoKey == empty);
CHECK (lumiera_invokey_eq (&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 (job.parameter.invoKey == empty); // indicates that it's just a placeholder to mark a "NOP"-Job
CHECK (lumiera_invokey_eq (&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,7 +20,6 @@
#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"
@ -67,12 +66,11 @@ 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()
@ -99,10 +97,11 @@ namespace test {
* - expect the buffer to hold a single `uint` value after invocation
*/
uint
invokeRenderNode (ProcNode& theNode, Time nomTime =Time::ZERO, uint port =0)
invokeRenderNode (ProcNode& theNode, Time nomTime =Time::ZERO)
{
BuffHandle buff = EngineCtx::access().mem.lockBufferFor<long> (-55);
ProcessKey key{0};
uint port{0};
CHECK (-55 == buff.accessAs<long>());
@ -116,6 +115,7 @@ namespace test {
/** @test build a Node with a fixed invocation parameter
* @todo 12/24 ✔ define ⟶ ✔ implement
*/
void
build_Node_fixedParam()
@ -139,6 +139,7 @@ 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()
@ -170,6 +171,7 @@ 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()
@ -237,6 +239,7 @@ 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()
@ -284,6 +287,7 @@ 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()
@ -339,49 +343,6 @@ 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& turSys){ return quantSecs(turSys.getNomTime()); };
auto selectFrameNo = [&](TurnoutSystem& tuSys){ return quantSecs(tuSys.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& turSys){ return stepFilter (turSys.get (accFrameNo)); };
auto autoMixer = [=](TurnoutSystem& turSys){ return stepMixer (turSys.get (accFrameNo)); };
auto autoFilter = [=](TurnoutSystem& tuSys){ return stepFilter (tuSys.get (accFrameNo)); };
auto autoMixer = [=](TurnoutSystem& tuSys){ return stepMixer (tuSys.get (accFrameNo)); };
// A Node with two (source) ports

View file

@ -17,375 +17,28 @@
#include "test/run.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
//#include "lib/util.hpp"
//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)
{
seedRand();
invokeSimpleNode();
invokeNodeTree();
UNIMPLEMENTED ("operate some render nodes as linked together");
}
/** @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

View file

@ -92,9 +92,9 @@ namespace test {
CHECK (a2.siz > 555);
// can use that memory without blowing up....
auto doooh = new(a1.mem) lflp{std::numeric_limits<lflp>::max()};
auto doooh = new(a1.mem) f128{std::numeric_limits<f128>::max()};
*doooh /= 2;
CHECK (2 == std::numeric_limits<lflp>::max() / *doooh);
CHECK (2 == std::numeric_limits<f128>::max() / *doooh);
allo.deallocate (a1);
allo.deallocate (a2);

File diff suppressed because it is too large Load diff