Compare commits
63 commits
integratio
...
prg@ichthy
| Author | SHA1 | Date | |
|---|---|---|---|
| fc8dd9408a | |||
| da9b40df03 | |||
| fd5548b61b | |||
| 00b0aa0c5b | |||
| 206f9635c9 | |||
| 92b7b05ea2 | |||
| c0cff8abe6 | |||
| 684bb2ca50 | |||
| a1b80c22df | |||
| 0cbe9d7dd6 | |||
| 9221fbeef1 | |||
| 549216dba3 | |||
| 6eb6ec48da | |||
| 303edcb6a1 | |||
| d0e6998b9c | |||
| ce980d64bf | |||
| 29a39a0f7f | |||
| 77257c5653 | |||
| 426d7c610e | |||
| c3c103e1a9 | |||
| 17c95a9b18 | |||
| 59fbb8e6b0 | |||
| 5fbe08ebe0 | |||
| a4ae568601 | |||
| f8c1222197 | |||
| 41adaa18ef | |||
| d32e4bbc06 | |||
| 535e372827 | |||
| e0fa980f4c | |||
| f6cc5fe1d9 | |||
| 7af51ae92e | |||
| 5c7a65bc18 | |||
| 9f19f5d895 | |||
| fca0c08b22 | |||
| 9665c5d320 | |||
| 3bd64935f3 | |||
| 1f29d202b4 | |||
| d582b95e7c | |||
| f0cdb2f5d2 | |||
| 0661b27c74 | |||
| 109d682408 | |||
| 595bb538be | |||
| 7c35af3717 | |||
| 4cdf01bc72 | |||
| c60e7d35c6 | |||
| a37e99a643 | |||
| e48c03e35d | |||
| e3b6b87e93 | |||
| 11bb3bce73 | |||
| 7d35790e11 | |||
| 82b7acac57 | |||
| 532408a305 | |||
| 5df0252cc1 | |||
| 36684ff092 | |||
| 793c9950d4 | |||
| ce2c3d2c06 | |||
| 8db1813831 | |||
| 64416e3ba8 | |||
| 3bffc00dbc | |||
| 437ef3a4a5 | |||
| 2ddb921e6e | |||
| 70bef00324 | |||
| f3dcbfe8b9 |
86 changed files with 13374 additions and 884 deletions
10
.clangd
Normal file
10
.clangd
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# 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
|
||||
|
||||
|
|
@ -29,6 +29,7 @@ sys.path.append('./admin/scons')
|
|||
import Setup
|
||||
import Options
|
||||
import Platform
|
||||
import CompileDB
|
||||
|
||||
from Buildhelper import *
|
||||
from LumieraEnvironment import *
|
||||
|
|
@ -39,6 +40,7 @@ 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)
|
||||
|
||||
|
||||
|
||||
|
|
@ -61,7 +63,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)
|
||||
build = env.Alias('build', lumiera + plugins + tools + gui + compileDB)
|
||||
# this defines the SCons **default target**
|
||||
env.Default('build')
|
||||
|
||||
|
|
|
|||
|
|
@ -140,39 +140,7 @@ def createPlugins(env, directory, **kw):
|
|||
|
||||
|
||||
|
||||
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
|
||||
# removed 10/2026 : checkCommandOption() "evaluate and verify an option, which may point at a command"
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
59
admin/scons/CompileDB.py
Normal file
59
admin/scons/CompileDB.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# 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
|
||||
|
|
@ -44,8 +44,6 @@ 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)
|
||||
|
|
|
|||
|
|
@ -29,13 +29,12 @@ 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)
|
||||
|
|
@ -60,6 +59,7 @@ 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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
# 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')
|
||||
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
# 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')
|
||||
|
||||
|
|
@ -238,6 +238,7 @@ 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
|
||||
|
||||
|
|
@ -265,8 +266,34 @@ 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 (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.
|
||||
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.
|
||||
--
|
||||
|
||||
Configuration options
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
@ -355,3 +382,62 @@ 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)
|
||||
|
|
|
|||
518
research/try.cpp
518
research/try.cpp
|
|
@ -1,6 +1,7 @@
|
|||
/* 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
|
||||
|
|
@ -11,425 +12,146 @@
|
|||
|
||||
|
||||
/** @file try.cpp
|
||||
* 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
|
||||
* 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
|
||||
*/
|
||||
|
||||
|
||||
#include "lib/format-cout.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-quant.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
using std::string;
|
||||
|
||||
|
||||
template<typename X>
|
||||
#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
|
||||
show()
|
||||
{
|
||||
SHOW_TYPE(X)
|
||||
}
|
||||
showCase (N val, N minVal, N maxVal)
|
||||
{
|
||||
cout << "val:"<<val<<" (min:"<<minVal<<" , max:"<<maxVal<<") ->"<< util::cyclicWrap (val,minVal,maxVal)<<endl;
|
||||
}
|
||||
|
||||
template<lib::meta::tuple_like X>
|
||||
template<typename N>
|
||||
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;
|
||||
});
|
||||
}
|
||||
showFloorDiv (N num, N den)
|
||||
{
|
||||
cout << "FloorDiv:"<<num<<" / "<<den<<" ->"<< util::floordiv(num,den)<<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 { };
|
||||
template<typename N>
|
||||
void
|
||||
showFloorWrap (N num, N den)
|
||||
{
|
||||
auto fwrap = util::floorwrap (num,den);
|
||||
cout << "FloorWrap:"<<num<<" / "<<den<<" -> q="<<fwrap.quot<<" r="<<fwrap.rem<<endl;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main (int, char**)
|
||||
{
|
||||
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;
|
||||
const int PER = 3;
|
||||
int i = -5;
|
||||
int d = i / PER;
|
||||
cout << "d="<<d<<endl;
|
||||
cout << "fd="<<util::floordiv(i,PER)<<endl;
|
||||
|
||||
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> ))
|
||||
uint u = _MAX<uint>;
|
||||
|
||||
show<Tup>();
|
||||
show<Arr>();
|
||||
show<Hetero>();
|
||||
show<int>();
|
||||
cout << u << " fd="<<util::floordiv(u,uint(PER))<<" ...re="<<PER*util::floordiv(u,uint(PER))<<endl;
|
||||
|
||||
using TupStringy = FunTrampoline<Stringify, ElmTypes<Tup>::Seq>;
|
||||
SHOW_EXPR(TupStringy::size())
|
||||
showFloorDiv (7,5);
|
||||
showFloorDiv (-7,5);
|
||||
showFloorDiv (7,-5);
|
||||
showFloorDiv (-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 (7,5);
|
||||
showFloorWrap (-7,5);
|
||||
showFloorWrap (7,-5);
|
||||
showFloorWrap (-7,-5);
|
||||
|
||||
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)
|
||||
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);
|
||||
|
||||
|
||||
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&> ))
|
||||
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);
|
||||
|
||||
|
||||
cout << "\n.gulp." <<endl;
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -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 in the last Extent is exhausted. In theory, it would be possible
|
||||
** if storage space if 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 a allocator template).
|
||||
** which is a lib::LinkedElements and could be parametrised with an 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,7 +361,13 @@ namespace lib {
|
|||
|
||||
|
||||
Policy (AllocationCluster& clu)
|
||||
: AllocationPolicy<I,E,Adapter> (clu.getAllocator<std::byte>())
|
||||
: Base{clu.getAllocator<std::byte>()}
|
||||
{ }
|
||||
|
||||
/** @note allow cross-construction from any AllocationCluster based allocator */
|
||||
template<class U, class V>
|
||||
Policy (AllocationPolicy<U,V,Adapter>& relatedPolicy)
|
||||
: Base(relatedPolicy)
|
||||
{ }
|
||||
|
||||
bool
|
||||
|
|
|
|||
|
|
@ -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 (f128 val) noexcept { return showFloatingPoint (val, PRECISION_DECIMAL<f128>); }
|
||||
string showDecimal (lflp val) noexcept { return showFloatingPoint (val, PRECISION_DECIMAL<lflp>); }
|
||||
|
||||
string showComplete (double val) noexcept { return showFloatingPoint (val, PRECISION_COMPLETE<double>); }
|
||||
string showComplete (float val) noexcept { return showFloatingPoint (val, PRECISION_COMPLETE<float>); }
|
||||
string showComplete (f128 val) noexcept { return showFloatingPoint (val, PRECISION_COMPLETE<f128>); }
|
||||
string showComplete (lflp val) noexcept { return showFloatingPoint (val, PRECISION_COMPLETE<lflp>); }
|
||||
|
||||
|
||||
string
|
||||
|
|
|
|||
|
|
@ -93,10 +93,10 @@ namespace util {
|
|||
|
||||
std::string showDecimal (double) noexcept;
|
||||
std::string showDecimal (float) noexcept;
|
||||
std::string showDecimal (f128) noexcept;
|
||||
std::string showDecimal (lflp) noexcept;
|
||||
std::string showComplete (double)noexcept;
|
||||
std::string showComplete (float) noexcept;
|
||||
std::string showComplete (f128) noexcept;
|
||||
std::string showComplete (lflp) noexcept;
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
/** @file format-util.hpp
|
||||
** Collection of small helpers and convenience shortcuts for diagnostics & formatting.
|
||||
** - util::str() performs a failsafe to-String conversion, thereby preferring a
|
||||
** - util::toString() 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
|
||||
|
|
|
|||
|
|
@ -30,8 +30,11 @@
|
|||
#ifndef LIB_HASH_VALUE_H
|
||||
#define LIB_HASH_VALUE_H
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <cstdlib>
|
||||
#else
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* storage for a Lumiera unique ID,
|
||||
|
|
@ -44,8 +47,6 @@ typedef lumiera_uid* LumieraUid;
|
|||
|
||||
#ifdef __cplusplus /* =========== C++ definitions ====================== */
|
||||
|
||||
#include <climits>
|
||||
|
||||
namespace lib {
|
||||
|
||||
/** a STL compatible hash value */
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ namespace lib {
|
|||
{
|
||||
using Tuple = std::tuple<DATA...>;
|
||||
|
||||
using Tuple::tuple;
|
||||
using Tuple::Tuple;
|
||||
|
||||
template<typename SPEC>
|
||||
void linkInto (HeteroData<SPEC>&);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
#ifndef LIB_INTEGRAL_H
|
||||
#define LIB_INTEGRAL_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
|
|
@ -33,10 +32,10 @@ using llong = long long int;
|
|||
using ullong = unsigned long long int;
|
||||
using ushort = unsigned short int;
|
||||
|
||||
using f128 = long double;
|
||||
static_assert(10 <= sizeof(f128));
|
||||
using lflp = long double;
|
||||
static_assert(10 <= sizeof(lflp));
|
||||
|
||||
const uint LIFE_AND_UNIVERSE_4EVER = 42;
|
||||
|
||||
|
||||
|
||||
#endif /*LIB_INTEGRAL_H*/
|
||||
|
|
|
|||
|
|
@ -138,10 +138,10 @@ namespace lib {
|
|||
struct iterator
|
||||
: IterAdapter<Pos, DataHandle>
|
||||
{ ////////////////////////////////////////////////////TICKET #1125 : should be build on top of IterStateWrapper rather than IterAdapter!
|
||||
using _I = IterAdapter<Pos, DataHandle>;
|
||||
using _I::IterAdapter;
|
||||
using Adapter = IterAdapter<Pos, DataHandle>;
|
||||
using Adapter::Adapter; // expose inherited ctors
|
||||
|
||||
operator string() const {return _I::source()? string(*_I::source()) : "⟂"; }
|
||||
operator string() const {return Adapter::source()? string(*Adapter::source()) : "⟂"; }
|
||||
};
|
||||
|
||||
/** build an iterator frontend for the given source,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@
|
|||
|
||||
#include "lib/hash-value.h" /* Types lumiera_uid and LumieraUid */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ namespace meta{
|
|||
* In the typical usage situation, the delegate type \a FUNC is implemented
|
||||
* mostly with inline functions, and thus the bulk of the actual implementation
|
||||
* code will be emitted into this static delegation function. The template
|
||||
* \ref InvokerBuilder provides the blueprint for this code generation.
|
||||
* \ref FunInline::activate<FUNC>() provides the blueprint for this code generation.
|
||||
*/
|
||||
template<class FUN>
|
||||
struct Invoker
|
||||
|
|
|
|||
|
|
@ -115,6 +115,26 @@ 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.
|
||||
|
|
|
|||
|
|
@ -456,12 +456,12 @@ namespace util {
|
|||
/** show maximum reproducible decimal representation */
|
||||
std::string showDecimal (double) noexcept;
|
||||
std::string showDecimal (float) noexcept;
|
||||
std::string showDecimal (f128) noexcept;
|
||||
std::string showDecimal (lflp) noexcept;
|
||||
|
||||
/** show enough decimal digits to represent every distinct value */
|
||||
std::string showComplete (double) noexcept;
|
||||
std::string showComplete (float) noexcept;
|
||||
std::string showComplete (f128) noexcept;
|
||||
std::string showComplete (lflp) noexcept;
|
||||
|
||||
std::string showSize (size_t) noexcept;
|
||||
|
||||
|
|
|
|||
|
|
@ -810,7 +810,7 @@ namespace lib {
|
|||
using Holder = InPlaceBuffer<BA, sizeof(SUB), DEFAULT>;
|
||||
Holder& holder = *static_cast<Holder*> (buffer_);
|
||||
|
||||
return holder.template emplace (std::forward<SUB> (implementation));
|
||||
return holder.emplace (std::forward<SUB> (implementation));
|
||||
}
|
||||
|
||||
/** Abbreviation for placement new of a subclass SUB into the opaque buffer*/
|
||||
|
|
|
|||
174
src/lib/par/disposition.hpp
Normal file
174
src/lib/par/disposition.hpp
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
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*/
|
||||
99
src/lib/par/domain.hpp
Normal file
99
src/lib/par/domain.hpp
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
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*/
|
||||
53
src/lib/par/nom-set.hpp
Normal file
53
src/lib/par/nom-set.hpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
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*/
|
||||
37
src/lib/par/param-type.cpp
Normal file
37
src/lib/par/param-type.cpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
ParamType - parameter type record including domain and scale
|
||||
|
||||
Copyright (C)
|
||||
2026 Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
**Lumiera** is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation; either version 2 of the License, or (at your
|
||||
option) any later version. See the file COPYING for further details.
|
||||
|
||||
* *****************************************************************/
|
||||
|
||||
|
||||
/** @file param-type.cpp
|
||||
** Implementation of parameter type registration.
|
||||
**
|
||||
** @todo WIP-WIP-WIP can be considered an initial draft and placeholder, as of 2026
|
||||
*/
|
||||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/par/parameter.hpp"
|
||||
#include "lib/par/provision.hpp"
|
||||
#include "lib/par/param-type.hpp"
|
||||
|
||||
namespace lib {
|
||||
namespace par {
|
||||
|
||||
|
||||
/* emit VTables in this translation unit... */
|
||||
Provision::~Provision() { }
|
||||
Domain::~Domain() { }
|
||||
|
||||
|
||||
|
||||
}} // namespace lib::par
|
||||
144
src/lib/par/param-type.hpp
Normal file
144
src/lib/par/param-type.hpp
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
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*/
|
||||
33
src/lib/par/parameter.cpp
Normal file
33
src/lib/par/parameter.cpp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
Parameter - generic parameter values
|
||||
|
||||
Copyright (C)
|
||||
2008,2026 Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
**Lumiera** is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation; either version 2 of the License, or (at your
|
||||
option) any later version. See the file COPYING for further details.
|
||||
|
||||
* *****************************************************************/
|
||||
|
||||
|
||||
/** @file parameter.cpp
|
||||
** Implementation functions of the Parameter abstraction.
|
||||
**
|
||||
** @todo this is a draft and placeholder, as of 2026
|
||||
*/
|
||||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/par/parameter.hpp"
|
||||
|
||||
namespace lib {
|
||||
namespace par {
|
||||
|
||||
|
||||
/** */
|
||||
|
||||
|
||||
|
||||
}} // namespace lib::par
|
||||
174
src/lib/par/parameter.hpp
Normal file
174
src/lib/par/parameter.hpp
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
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*/
|
||||
113
src/lib/par/provision.hpp
Normal file
113
src/lib/par/provision.hpp
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
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*/
|
||||
343
src/lib/par/scale.hpp
Normal file
343
src/lib/par/scale.hpp
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
/*
|
||||
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*/
|
||||
95
src/lib/par/spec.hpp
Normal file
95
src/lib/par/spec.hpp
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
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*/
|
||||
226
src/lib/par/type-handler.hpp
Normal file
226
src/lib/par/type-handler.hpp
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
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*/
|
||||
101
src/lib/par/type-registry.hpp
Normal file
101
src/lib/par/type-registry.hpp
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
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*/
|
||||
|
|
@ -516,7 +516,7 @@ namespace lib {
|
|||
static PolymorphicValue
|
||||
build (ARGS&&... args)
|
||||
{
|
||||
Adapter<IMP>* type_to_build_in_buffer(0);
|
||||
IMP* type_to_build_in_buffer{};
|
||||
return PolymorphicValue (type_to_build_in_buffer, std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
f128 const ROUND_ULP = 1 + 1/(f128(std::numeric_limits<int64_t>::max()) * 2);
|
||||
lflp const ROUND_ULP = 1 + 1/(lflp(std::numeric_limits<int64_t>::max()) * 2);
|
||||
|
||||
// construct approximation quantised to 1/u
|
||||
f128 frac = f128(r) / den;
|
||||
lflp frac = lflp(r) / den;
|
||||
int64_t res = d*u + int64_t(frac*u * ROUND_ULP);
|
||||
ENSURE (abs (f128(res)/u - rational_cast<f128>(Rat{num,den})) <= 1.0/abs(u)
|
||||
ENSURE (abs (lflp(res)/u - rational_cast<lflp>(Rat{num,den})) <= 1.0/abs(u)
|
||||
,"Requantisation error exceeded num=%li / den=%li -> res=%li / quant=%li"
|
||||
, num, den, res, u);
|
||||
return res;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,19 @@
|
|||
** - 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/
|
||||
*/
|
||||
|
||||
|
||||
|
|
@ -70,17 +82,6 @@ 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
|
||||
|
|
|
|||
|
|
@ -542,8 +542,8 @@ namespace lib {
|
|||
addCode(COND);
|
||||
break;
|
||||
case TagSyntax::END_IF:
|
||||
addLead();
|
||||
__checkBalanced(IF);
|
||||
addLead();
|
||||
if (hasElse())
|
||||
linkJumpToNext();
|
||||
else
|
||||
|
|
@ -557,8 +557,8 @@ namespace lib {
|
|||
addCode(ITER);
|
||||
break;
|
||||
case TagSyntax::END_FOR:
|
||||
addLead();
|
||||
__checkBalanced(FOR);
|
||||
addLead();
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -22,12 +22,15 @@
|
|||
|
||||
#include "lib/integral.hpp"
|
||||
|
||||
#include <type_traits>
|
||||
#include <concepts>
|
||||
#include <cstdlib>
|
||||
#include <climits>
|
||||
#include <cfloat>
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
|
||||
#include <nobug.h>
|
||||
|
||||
|
||||
|
||||
namespace util {
|
||||
|
|
@ -136,7 +139,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>
|
||||
|
|
@ -154,6 +157,104 @@ 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,
|
||||
|
|
@ -170,12 +271,13 @@ 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 (double d1, double d2, unsigned int ulp =2) noexcept
|
||||
almostEqual (F d1, F d2, uint ulp =2) noexcept
|
||||
{
|
||||
using std::fabs;
|
||||
return fabs (d1-d2) < DBL_EPSILON * fabs (d1+d2) * ulp
|
||||
or fabs (d1-d2) < DBL_MIN; // special treatment for subnormal results
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -224,6 +326,47 @@ 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*/
|
||||
|
|
|
|||
|
|
@ -60,7 +60,9 @@ namespace util {
|
|||
inline constexpr int
|
||||
sgn (NUM n)
|
||||
{
|
||||
return (n==0)? 0 :((n<0)? -1:+1 );
|
||||
return n== NUM{0}? 0
|
||||
: n < NUM{0}? -1
|
||||
: +1;
|
||||
}
|
||||
|
||||
template <class N1, class N2>
|
||||
|
|
@ -77,12 +79,20 @@ namespace util {
|
|||
return n1 < n2? N1(n2) : n1;
|
||||
}
|
||||
|
||||
/** @remark safe to use also on unsigned */
|
||||
template<typename NUM>
|
||||
inline constexpr bool
|
||||
isNeg (NUM const& num)
|
||||
{
|
||||
return num < NUM{0};
|
||||
};
|
||||
|
||||
/** cut a numeric value to be >=0 */
|
||||
template <typename NUM>
|
||||
inline constexpr NUM
|
||||
noneg (NUM val)
|
||||
{
|
||||
return (0<val? val : 0);
|
||||
return max (val, NUM(0));
|
||||
}
|
||||
|
||||
/** force a numeric to be within bounds, inclusively */
|
||||
|
|
|
|||
|
|
@ -668,8 +668,8 @@ namespace model {
|
|||
n2 = d2==u? n2 : reQuant (n2,d2, u);
|
||||
FSecs res{s1*n1 + s2*n2, u};
|
||||
|
||||
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
|
||||
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
|
||||
return detox (res);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,13 +175,13 @@ namespace control {
|
|||
Symbol instanceID{prototypeID, invocationID};
|
||||
Command& instance = table_[instanceID];
|
||||
if (instance)
|
||||
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)
|
||||
);
|
||||
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)
|
||||
);
|
||||
// create new clone from the prototype
|
||||
table_[instanceID] = move (Command::get(prototypeID).newInstance());
|
||||
ENSURE (instance, "cloning of command prototype failed");
|
||||
|
|
|
|||
|
|
@ -63,9 +63,7 @@ namespace engine {
|
|||
|
||||
static lib::Depend<EngineCtx> access;
|
||||
|
||||
private:
|
||||
EngineCtx();
|
||||
friend class lib::DependencyFactory<EngineCtx>;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -117,14 +117,6 @@ namespace engine {
|
|||
}
|
||||
|
||||
|
||||
namespace { ///////////////////////////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs
|
||||
inline bool
|
||||
operator== (InvocationInstanceID const& l, InvocationInstanceID const& r)
|
||||
{
|
||||
return lumiera_invokey_eq (unConst(&l), unConst(&r));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for tests: verify the given invocation parameters match this JobTicket.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -138,8 +138,8 @@ using lib::LUID;
|
|||
{
|
||||
if (empty()) return false;
|
||||
|
||||
InvocationInstanceID empty; /////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs
|
||||
return not lumiera_invokey_eq (&util::unConst(provision_).invocationSeed, &empty)
|
||||
InvocationInstanceID empty; //////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs
|
||||
return provision_.invocationSeed != empty
|
||||
and provision_.exitNode.isValid()
|
||||
and util::and_all (provision_.prerequisites
|
||||
,[](auto& pq){ return pq.prereqTicket.isValid(); });
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ namespace engine {
|
|||
/**
|
||||
* Standard implementation for a _Weaving Pattern_ to connect
|
||||
* the input and output data feeds (buffers) into a processing function.
|
||||
* @tparam INVO a configuration / policy base class to _adapt for invocation_
|
||||
* @tparam INVO a configuration class / prototype to _adapt for invocation_
|
||||
* @note assumptions made regarding the overall structure
|
||||
* - `INVO::Feed` defines an _invocation adapter_ for the processing function
|
||||
* - `INVO::buildFeed()` is a functor to (repeatedly) build `Feed` instances
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
** specialised parametrisation for some media handling library, and a lot of cross-linking pointers,
|
||||
** it is important to care for efficient usage of memory with good locality. Furthermore, the higher
|
||||
** levels of the build process will generate additional temporary data structures, refined gradually
|
||||
** until the actual render node network can be emitted. Each builder level can thus be outfitted
|
||||
** until the actual render node network can be emitted. Each builder level can thus be outfitted
|
||||
** with a custom allocator — typically an instance of lib::AllocationCluster. Notably the higher
|
||||
** levels can be attached to a separate AllocationCluster instance, which will be discarded once
|
||||
** the build process is complete, while Level-2 (and below) uses the allocator for the actual
|
||||
|
|
@ -155,16 +155,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.
|
||||
*/
|
||||
template<class POL, class I, class E=I>
|
||||
using DataBuilder = lib::SeveralBuilder<I,E, POL::template Policy>;
|
||||
|
||||
|
||||
|
||||
template<class POL, class DAT>
|
||||
class NodeBuilder;
|
||||
|
||||
|
|
@ -257,7 +247,7 @@ namespace engine {
|
|||
Connectivity
|
||||
build()
|
||||
{
|
||||
PortData ports;
|
||||
PortData ports{leads_.policyConnect()}; // use same allocator...
|
||||
patternData_.collectEntries(ports);
|
||||
return Connectivity{ports.build()
|
||||
,leads_.build()
|
||||
|
|
@ -506,8 +496,8 @@ namespace engine {
|
|||
closeParamFront (PAR v1, PARS ...vs)
|
||||
{
|
||||
return adaptParam(
|
||||
WAB::ParamClosure::template closeFront (forward<PAR> (v1)
|
||||
,forward<PARS>(vs)...));
|
||||
WAB::ParamClosure::closeFront (forward<PAR> (v1)
|
||||
,forward<PARS>(vs)...));
|
||||
}
|
||||
|
||||
/** immediately close the rightmost parameter positions,
|
||||
|
|
@ -517,8 +507,8 @@ namespace engine {
|
|||
closeParamBack (PAR v1, PARS ...vs)
|
||||
{
|
||||
return adaptParam(
|
||||
WAB::ParamClosure::template closeBack (forward<PAR> (v1)
|
||||
,forward<PARS>(vs)...));
|
||||
WAB::ParamClosure::closeBack (forward<PAR> (v1)
|
||||
,forward<PARS>(vs)...));
|
||||
}
|
||||
|
||||
/** immediately close a single parameter at designated position
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ namespace engine{
|
|||
BufferProvider&
|
||||
EngineFacilities::setupCacheProvider() ////////////////////////////////////////////////////////////////TICKET #1223 : in what ways does client code need to interact with the Frame Cache / Management?
|
||||
{
|
||||
return getNaiveBufferManager();
|
||||
return setupBufferProvider();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
64
src/steam/engine/render-output-ctx.hpp
Normal file
64
src/steam/engine/render-output-ctx.hpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
RENDER-OUTPUT-CTX.hpp - output control parameters for rendering
|
||||
|
||||
Copyright (C)
|
||||
2026, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
**Lumiera** is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation; either version 2 of the License, or (at your
|
||||
option) any later version. See the file COPYING for further details.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/** @file render-output-ctx.hpp
|
||||
** A configuration context with output control parameters for render processing.
|
||||
** This context parameter store is associated with the [calculation streams](\ref CalcStream)
|
||||
** and accessed from the [render node invocation](\ref RenderInvocation) to retrieve additional
|
||||
** and context dependent settings for aspects beyond the automated parameters and the actual
|
||||
** render function. Notably the output DataSink is abstracted and the corresponding target
|
||||
** data buffer is exposed through this context.
|
||||
** @todo »Playback Vertical Slice« -- effort towards first integration of render process ///////////////////TICKET #1221
|
||||
** @todo 6/2026 still not entirely decided regarding the organisation of Render Job parameters;
|
||||
** introducing such a context seems a good way to retain some limited flexibility while
|
||||
** keeping matters focused.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef STEAM_ENGINE_RENDER_OUTPUT_CTX_H
|
||||
#define STEAM_ENGINE_RENDER_OUTPUT_CTX_H
|
||||
|
||||
#include "steam/common.hpp"
|
||||
#include "vault/mem/buffhandle.hpp"
|
||||
#include "vault/out/output-slot.hpp"
|
||||
#include "lib/time/timevalue.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
|
||||
|
||||
namespace steam {
|
||||
namespace engine{
|
||||
|
||||
using lib::time::Time;
|
||||
using vault::mem::BuffHandle;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Render output control parameters and configuration details.
|
||||
*/
|
||||
class RenderOutputCtx
|
||||
: util::MoveOnly
|
||||
{
|
||||
using Output = vault::out::OutputSlot::DataSink;
|
||||
Output sink_;
|
||||
|
||||
public:
|
||||
BuffHandle getOutputBuffer (Time absoluteNominalTime);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
}} // namespace steam::engine
|
||||
#endif /*STEAM_ENGINE_RENDER_OUTPUT_CTX_H*/
|
||||
|
|
@ -163,6 +163,17 @@ 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>;
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,12 @@ namespace fixture {
|
|||
* must remain fixed in memory as long as any derived render jobs are alive.
|
||||
*
|
||||
* @todo WIP-WIP as of 4/2023 -- starting with a fake implementation /////////////////////TICKET #1306 : create an actual link to the low-level Model
|
||||
* @deprecated 2025 now it looks like this entity was just a step stone towards the actual design;
|
||||
* during the first prototypical implementation it became clear that the concept of an
|
||||
* »ExitNode« needs to be repurposed (and shifted by one level in the architecture).
|
||||
* However -- it is not clear yet in which way to address/designate the actual node
|
||||
* that shall be pulled from a given render job; there might be some mapping based
|
||||
* on a _feed ID_ (or a process-ID).
|
||||
*/
|
||||
class NodeGraphAttachment
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
** Core abstraction: automation parameter interpolator.
|
||||
** Each interpolator implementation has the ability to resolve intermediary
|
||||
** values and to provide a parameter value for _every arbitrary point in time_.
|
||||
** @deprecated 2026 the basic idea is valid — yet the final design will certainly be different;
|
||||
** the only usage of this code is Fixture::getAutomation() ⟼ `Auto<double>*`
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
** Implementation functions of the Parameter abstraction.
|
||||
**
|
||||
** @todo as of 2016, we still need to work out our parameter/automation concept.
|
||||
** @deprecated 2026 the basic idea is valid — yet the final design will certainly be different;
|
||||
** the only usage of this code is Fixture::getAutomation() ⟼ `Auto<double>*`
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
** Core abstraction: parameter to be controlled and possibly automated.
|
||||
**
|
||||
** @todo as of 2016, we still need to work out our parameter/automation concept.
|
||||
** @deprecated 2026 the basic idea is valid — yet the final design will certainly be different;
|
||||
** the only usage of this code is Fixture::getAutomation() ⟼ `Auto<double>*`
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
** Implementation functions of the Parameter abstraction.
|
||||
**
|
||||
** @todo as of 2016, we still need to work out our parameter/automation concept.
|
||||
** @deprecated 2026 the basic idea is valid — yet the final design will certainly be different;
|
||||
** the only usage of this code is Fixture::getAutomation() ⟼ `Auto<double>*`
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
** Core abstraction: an entity to expose a single parameter
|
||||
**
|
||||
** @todo as of 2016, we still need to work out our parameter/automation concept.
|
||||
** @deprecated 2026 the basic idea is valid — yet the final design will certainly be different;
|
||||
** the only usage of this code is Fixture::getAutomation() ⟼ `Auto<double>*`
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -33,12 +33,14 @@
|
|||
|
||||
#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>
|
||||
|
|
@ -61,14 +63,15 @@ namespace test{
|
|||
using util::showType;
|
||||
using util::showTypes;
|
||||
|
||||
using std::floating_point;
|
||||
|
||||
constexpr auto ROUGH_PRECISION = pow (10, -3);
|
||||
constexpr auto ROUGH_PRECISION = util::ipow (10.0, -3);
|
||||
constexpr auto EPSILON_ULP = 5;
|
||||
|
||||
|
||||
template<typename F, typename N>
|
||||
constexpr inline enable_if< std::is_floating_point<F>,
|
||||
bool >
|
||||
template<floating_point F, typename N>
|
||||
constexpr inline
|
||||
bool
|
||||
roughEQ (F val, N target, F limit =ROUGH_PRECISION)
|
||||
{
|
||||
REQUIRE (0 < limit);
|
||||
|
|
@ -76,9 +79,9 @@ namespace test{
|
|||
} //////////////////////////////////////////////////////////////////////////TICKET #1360 looks like this problem was solved several times
|
||||
|
||||
|
||||
template<typename F>
|
||||
constexpr inline enable_if< std::is_floating_point<F>,
|
||||
F >
|
||||
template<floating_point F>
|
||||
constexpr inline
|
||||
F
|
||||
ulp (F val)
|
||||
{
|
||||
val = fabs (val);
|
||||
|
|
@ -90,9 +93,9 @@ namespace test{
|
|||
return scaledUlp;
|
||||
}
|
||||
|
||||
template<typename F, typename N>
|
||||
constexpr inline enable_if< std::is_floating_point<F>,
|
||||
bool >
|
||||
template<floating_point F, typename N>
|
||||
constexpr inline
|
||||
bool
|
||||
epsEQ (F val, N target, uint ulps =EPSILON_ULP)
|
||||
{
|
||||
return abs (val - target) < ulps * ulp<F> (target);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,19 @@
|
|||
#include <typeinfo>
|
||||
|
||||
|
||||
/** @deprecated 6/2026 very likely the InvocationInstanceID will be obsoleted,
|
||||
* and instead just the two parameter values need to be compared.
|
||||
* As an interim solution, comparing the two 64bit data words
|
||||
*/
|
||||
bool
|
||||
operator== (InvocationInstanceID const& l, InvocationInstanceID const& r)
|
||||
{
|
||||
return l.code.w1 == r.code.w1
|
||||
and l.code.w2 == r.code.w2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
namespace vault{
|
||||
namespace gear {
|
||||
|
||||
|
|
@ -115,44 +128,4 @@ namespace gear {
|
|||
return hash;
|
||||
}
|
||||
|
||||
|
||||
}} // namespace vault::gear
|
||||
|
||||
namespace {
|
||||
using vault::gear::Job;
|
||||
|
||||
inline Job&
|
||||
forwardInvocation (lumiera_jobDefinition& jobDef)
|
||||
{
|
||||
Job& job = static_cast<Job&> (jobDef);
|
||||
return job;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
extern "C" { /* ==== implementation C interface for job invocation ======= */
|
||||
|
||||
void
|
||||
lumiera_job_invoke (LumieraJobDefinition jobDef)
|
||||
{
|
||||
REQUIRE (jobDef);
|
||||
forwardInvocation(*jobDef).triggerJob();
|
||||
}
|
||||
|
||||
size_t
|
||||
lumiera_job_get_hash (LumieraJobDefinition jobDef)
|
||||
{
|
||||
REQUIRE (jobDef);
|
||||
return hash_value (forwardInvocation (*jobDef));
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs
|
||||
#include "lib/luid.h"
|
||||
|
||||
int
|
||||
lumiera_invokey_eq (void* l, void* r)
|
||||
{
|
||||
return lumiera_uid_eq ((LumieraUid)l, (LumieraUid)r);
|
||||
}
|
||||
}/* extern "C" */
|
||||
|
|
|
|||
|
|
@ -32,6 +32,13 @@
|
|||
** the job over to the scheduler.
|
||||
**
|
||||
** @warning as of 4/2023 the Job datastructure will be remoulded ///////////////////////////////////////////TICKET #1280
|
||||
** @deprecated in 2026 it does not seem like a good idea anymore to use "Render Jobs"
|
||||
** to act like an _interface_ between Steam and Vault layer. Meanwhile, while
|
||||
** the concept of **Jobs** (coroutines) is still upheld, the C datastructure job,
|
||||
** as defined in this header, seems to mix up the traits of an interface and
|
||||
** aspects of implementation. It is largely superseded by the _Activity Language_
|
||||
** of the scheduler, and will likely be remoulded into a technical artifact of
|
||||
** preparing job definitions in the Dispatcher.
|
||||
**
|
||||
** @see SchedulerFrontend
|
||||
** @see JobTicket
|
||||
|
|
@ -100,6 +107,11 @@ typedef struct lumiera_jobClosure* LumieraJobClosure;
|
|||
* hash key for each invocation, which can be used for caching; obviously
|
||||
* this hash need to be built from the JobTicket, based on ProcNode structure
|
||||
* and the nominal Time. ///////////////////////////////////TICKET #1293
|
||||
* @deprecated 6/2026 now able to conduct a full analysis of required information,
|
||||
* and it seems the job definition does not need any kind of identity marker;
|
||||
* caching will rely on chained specification hash keys rather.
|
||||
* The existing tests will have to switch over using the two free parameter
|
||||
* values, now present in each LumieraJobParameter...
|
||||
*/
|
||||
union InvocationInstanceID
|
||||
{
|
||||
|
|
@ -128,8 +140,9 @@ struct lumiera_jobParameter_struct
|
|||
raw_time_64 nominalTime; /////////////////////////////////////////////////////////////////////////TICKET #1295 job invocation parameter: framework to interpret this time
|
||||
InvocationInstanceID invoKey;
|
||||
//////////////////////////////////////////////////////////////TODO: place an additional parameter value here, or make the instanceID globally unique?
|
||||
////////////////////////////////////////////////////////////////////////////////////TICKET #1293 job invocation identity
|
||||
////////////////////////////////////////////////////////////////////////////////////TICKET #1295 : rework Job parameters to accommodate input / output info required for rendering
|
||||
////////////////////////////////////////////////////////////////////////////////////TICKET #1293 job invocation identity (possibly not needed anymore, can be derived from the data...)
|
||||
size_t srcParam;
|
||||
size_t sinkParam;
|
||||
};
|
||||
typedef struct lumiera_jobParameter_struct lumiera_jobParameter;
|
||||
typedef lumiera_jobParameter* LumieraJobParameter;
|
||||
|
|
@ -194,6 +207,9 @@ typedef lumiera_jobDescriptor* LumieraJobDescriptor;
|
|||
#include <string>
|
||||
|
||||
|
||||
/** @warning performs a comparison on the entire packed content */
|
||||
bool operator== (InvocationInstanceID const&, InvocationInstanceID const&);
|
||||
|
||||
|
||||
namespace vault{
|
||||
namespace gear {
|
||||
|
|
@ -284,6 +300,8 @@ namespace gear {
|
|||
this->jobClosure = &specificJobDefinition;
|
||||
this->parameter.nominalTime = _raw(nominalFrameTime);
|
||||
this->parameter.invoKey = invoKey;
|
||||
this->parameter.srcParam = 0;
|
||||
this->parameter.sinkParam = 0;
|
||||
}
|
||||
|
||||
// using standard copy operations
|
||||
|
|
@ -326,28 +344,6 @@ namespace gear {
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
}} // namespace vault::gear
|
||||
|
||||
|
||||
|
||||
|
||||
extern "C" {
|
||||
#endif /* =========================== C Interface ===================== */
|
||||
|
||||
|
||||
/** trigger execution of a specific job,
|
||||
* assuming availability of all prerequisites */
|
||||
void lumiera_job_invoke (LumieraJobDefinition);
|
||||
|
||||
/** calculate a hash value based on the Job's \em identity. */
|
||||
size_t lumiera_job_get_hash (LumieraJobDefinition);
|
||||
|
||||
int lumiera_invokey_eq (void* l, void* r);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /*VAULT_GEAR_JOB_H*/
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ namespace out {
|
|||
* handle is retained.
|
||||
* @tparam isTest use a simple BufferMetadata table in OutputBufferProxy.
|
||||
*/
|
||||
template<class CON, bool isTest=false>
|
||||
template<class CON, bool isTest> // ◁───── default defined at declaration (output-slot.hpp)
|
||||
class OutputSlot::AllocState
|
||||
: public OutputSlot::Allocation
|
||||
{
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
namespace lib {
|
||||
namespace time{
|
||||
class Quantiser;
|
||||
typedef std::shared_ptr<const Quantiser> PQuant;
|
||||
using PQuant = std::shared_ptr<const Quantiser>;
|
||||
}}
|
||||
|
||||
namespace vault {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,11 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
TEST "Cyclic wrapping number domain" CyclicWrap_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
TEST "Deleter function collection" DelStash_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
|
|
|||
32
tests/16parameter.tests
Normal file
32
tests/16parameter.tests
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
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
|
||||
|
|
@ -26,7 +26,7 @@ TEST "Proc Node metadata key" NodeMeta_test <<END
|
|||
END
|
||||
|
||||
|
||||
PLANNED "Proc Node operation modes" NodeOpera_test <<END
|
||||
TEST "Proc Node operation modes" NodeOpera_test <<END
|
||||
END
|
||||
|
||||
|
||||
|
|
|
|||
196
tests/lib/cyclic-wrap-test.cpp
Normal file
196
tests/lib/cyclic-wrap-test.cpp
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
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
|
||||
87
tests/lib/par/nom-set-test.cpp
Normal file
87
tests/lib/par/nom-set-test.cpp
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
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
|
||||
293
tests/lib/par/parameter-domain-test.cpp
Normal file
293
tests/lib/par/parameter-domain-test.cpp
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
/*
|
||||
ParameterDomain(Test) - document the handling of parameter type domain
|
||||
|
||||
Copyright (C)
|
||||
2026, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
**Lumiera** is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation; either version 2 of the License, or (at your
|
||||
option) any later version. See the file COPYING for further details.
|
||||
|
||||
* *****************************************************************/
|
||||
|
||||
/** @file parameter-domain-test.cpp
|
||||
** unit test \ref ParameterDomain_test
|
||||
*/
|
||||
|
||||
|
||||
#include "test/run.hpp"
|
||||
//#include "test/test-helper.hpp"
|
||||
#include "lib/par/domain.hpp"
|
||||
#include "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
|
||||
91
tests/lib/par/parameter-provision-test.cpp
Normal file
91
tests/lib/par/parameter-provision-test.cpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
ParameterProvision(Test) - verify parameter value provision and automation
|
||||
|
||||
Copyright (C)
|
||||
2026, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
**Lumiera** is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation; either version 2 of the License, or (at your
|
||||
option) any later version. See the file COPYING for further details.
|
||||
|
||||
* *****************************************************************/
|
||||
|
||||
/** @file parameter-provision-test.cpp
|
||||
** unit test \ref ParameterProvision_test
|
||||
*/
|
||||
|
||||
|
||||
#include "test/run.hpp"
|
||||
//#include "test/test-helper.hpp"
|
||||
#include "lib/par/parameter.hpp"
|
||||
//#include "lib/format-cout.hpp"
|
||||
|
||||
//#include <utility>
|
||||
//#include <string>
|
||||
//#include <vector>
|
||||
|
||||
//using std::string;
|
||||
//using std::vector;
|
||||
//using std::swap;
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace par {
|
||||
namespace test{
|
||||
|
||||
// using lumiera::error::LUMIERA_ERROR_LOGIC;
|
||||
|
||||
namespace {//Test fixture....
|
||||
|
||||
|
||||
}//(End)Test fixture
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**************************************************************************//**
|
||||
* @test document behaviour of parameter value provision and automation.
|
||||
*/
|
||||
class ParameterProvision_test : public Test
|
||||
{
|
||||
|
||||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
simpleUsage();
|
||||
verify_Value();
|
||||
verify_Function();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
simpleUsage()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
verify_Value()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
verify_Function()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** Register this test class... */
|
||||
LAUNCHER (ParameterProvision_test, "unit lib");
|
||||
|
||||
|
||||
|
||||
}}} // namespace lib::par::test
|
||||
365
tests/lib/par/parameter-scale-test.cpp
Normal file
365
tests/lib/par/parameter-scale-test.cpp
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
/*
|
||||
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
|
||||
137
tests/lib/par/parameter-test.cpp
Normal file
137
tests/lib/par/parameter-test.cpp
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
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
|
||||
117
tests/lib/par/type-registry-test.cpp
Normal file
117
tests/lib/par/type-registry-test.cpp
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
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
|
||||
|
|
@ -171,7 +171,7 @@ namespace test{
|
|||
}
|
||||
|
||||
|
||||
/** @test various ways to build an populate the container
|
||||
/** @test various ways to build and populate the container
|
||||
* - with a defined interface type \a I, instances of arbitrary subclasses
|
||||
* can be added, assuming there is sufficient pre-allocated buffer space;
|
||||
* all these subclass instances are accessed through the common interface.
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ namespace test{
|
|||
|
||||
CHECK (format4Csv(double(1) / 3) == "0.333333333333333"_expect );
|
||||
CHECK (format4Csv(float(1) / 3) == "0.333333"_expect );
|
||||
CHECK (format4Csv(f128(1) / 3) == "0.333333333333333333"_expect);
|
||||
CHECK (format4Csv(lflp(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 ;-)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
using ::Test;
|
||||
using util::isnil;
|
||||
using util::_Fmt;
|
||||
|
||||
using std::integral;
|
||||
|
||||
namespace util {
|
||||
namespace test {
|
||||
|
|
@ -42,6 +42,13 @@ 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
|
||||
|
|
@ -108,7 +115,7 @@ namespace test {
|
|||
{
|
||||
seedRand();
|
||||
|
||||
verifyBehaviour ();
|
||||
verifyBehaviour();
|
||||
|
||||
verifyIntegerTypes<int>();
|
||||
verifyIntegerTypes<long>();
|
||||
|
|
@ -116,6 +123,8 @@ namespace test {
|
|||
verifyIntegerTypes<int64_t>();
|
||||
verifyIntegerTypes<llong>();
|
||||
|
||||
verifyBounds();
|
||||
|
||||
if (not isnil (arg))
|
||||
runPerformanceTest();
|
||||
}
|
||||
|
|
@ -154,7 +163,7 @@ namespace test {
|
|||
|
||||
template<typename I>
|
||||
void
|
||||
verifyIntegerTypes ()
|
||||
verifyIntegerTypes()
|
||||
{
|
||||
I n,d,expectedRes;
|
||||
|
||||
|
|
@ -168,11 +177,51 @@ 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:
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
#include "lib/format-string.hpp"
|
||||
#include "lib/format-cout.hpp"
|
||||
#include "lib/time/timevalue.hpp"
|
||||
#include "lib/luid.h"
|
||||
#include "lib/hash-indexed.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ namespace test {
|
|||
{
|
||||
InvocationInstanceID empty; ///////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs
|
||||
JobClosure& jobFunctor = static_cast<JobClosure&> (*job.jobClosure); //////////////////////////TICKET #1287 : fix actual interface down to JobFunctor (after removing C structs)
|
||||
return lumiera_invokey_eq (&util::unConst(job).parameter.invoKey, &empty)
|
||||
return job.parameter.invoKey == empty
|
||||
and util::isSameObject (jobFunctor, nopFunctor());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ namespace test {
|
|||
CHECK (INSTANCEOF (vault::gear::NopJobFunctor, static_cast<JobClosure*> (nopJob.jobClosure))); //////////TICKET #1287 : fix actual interface down to JobFunctor (after removing C structs)
|
||||
CHECK (nopJob.parameter.nominalTime == frameTime);
|
||||
InvocationInstanceID empty; ///////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs
|
||||
CHECK (lumiera_invokey_eq (&nopJob.parameter.invoKey, &empty));
|
||||
CHECK (nopJob.parameter.invoKey == empty);
|
||||
CHECK (MockJob::isNopJob(nopJob)); // this diagnostic helper checks the same conditions as done here explicitly
|
||||
|
||||
MockJobTicket mockTicket;
|
||||
|
|
@ -211,7 +211,7 @@ namespace test {
|
|||
// create another job from the (empty) seg1
|
||||
job = seg1.jobTicket(0).createJobFor (someTime);
|
||||
InvocationInstanceID empty; /////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs
|
||||
CHECK (lumiera_invokey_eq (&job.parameter.invoKey, &empty)); // indicates that it's just a placeholder to mark a "NOP"-Job
|
||||
CHECK (job.parameter.invoKey == empty); // indicates that it's just a placeholder to mark a "NOP"-Job
|
||||
CHECK (seg1.jobTicket(0).empty());
|
||||
CHECK (seg1.empty());
|
||||
CHECK (not seg2.empty());
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include "steam/engine/engine-ctx.hpp"
|
||||
#include "steam/engine/node-builder.hpp"
|
||||
#include "steam/asset/meta/time-grid.hpp"
|
||||
#include "lib/allocation-cluster.hpp"
|
||||
#include "lib/time/timequant.hpp"
|
||||
#include "lib/time/timecode.hpp"
|
||||
#include "lib/iter-explorer.hpp"
|
||||
|
|
@ -66,11 +67,12 @@ namespace test {
|
|||
build_Node_closedParam();
|
||||
build_connectedNodes();
|
||||
build_ParamNode();
|
||||
|
||||
useAllocationCluster();
|
||||
}
|
||||
|
||||
|
||||
/** @test build a simple output-only Render Node
|
||||
* @todo 12/24 ✔ define ⟶ ✔ implement
|
||||
*/
|
||||
void
|
||||
build_simpleNode()
|
||||
|
|
@ -97,11 +99,10 @@ namespace test {
|
|||
* - expect the buffer to hold a single `uint` value after invocation
|
||||
*/
|
||||
uint
|
||||
invokeRenderNode (ProcNode& theNode, Time nomTime =Time::ZERO)
|
||||
invokeRenderNode (ProcNode& theNode, Time nomTime =Time::ZERO, uint port =0)
|
||||
{
|
||||
BuffHandle buff = EngineCtx::access().mem.lockBufferFor<long> (-55);
|
||||
ProcessKey key{0};
|
||||
uint port{0};
|
||||
|
||||
CHECK (-55 == buff.accessAs<long>());
|
||||
|
||||
|
|
@ -115,7 +116,6 @@ namespace test {
|
|||
|
||||
|
||||
/** @test build a Node with a fixed invocation parameter
|
||||
* @todo 12/24 ✔ define ⟶ ✔ implement
|
||||
*/
|
||||
void
|
||||
build_Node_fixedParam()
|
||||
|
|
@ -139,7 +139,6 @@ namespace test {
|
|||
* the time into an implicitly defined grid
|
||||
* - install both into a render node
|
||||
* - set a random _nominal time_ for invocation
|
||||
* @todo 12/24 ✔ define ⟶ ✔ implement
|
||||
*/
|
||||
void
|
||||
build_Node_dynamicParam()
|
||||
|
|
@ -171,7 +170,6 @@ namespace test {
|
|||
* - again use a processing function which takes a parameter
|
||||
* - but then _decorate_ this functor, so that it takes different arguments
|
||||
* - attach parameter handling to supply these adapted arguments
|
||||
* @todo 2/25 ✔ define ⟶ ✔ implement
|
||||
*/
|
||||
void
|
||||
build_Node_adaptedParam()
|
||||
|
|
@ -239,7 +237,6 @@ namespace test {
|
|||
* - use the »simplified 1:1 wiring«, which connects consecutively
|
||||
* each input slot to the next given node on the same port number;
|
||||
* here we only use port#0 on all three nodes.
|
||||
* @todo 12/24 ✔ define ⟶ ✔ implement
|
||||
*/
|
||||
void
|
||||
build_connectedNodes()
|
||||
|
|
@ -287,7 +284,6 @@ namespace test {
|
|||
* - perform effectively the same computation as the preceding test
|
||||
* - but use two new custom parameters in the Param Agent Node
|
||||
* - pick them up from the nested source nodes by accessor-functors
|
||||
* @todo 12/24 ✔ define ⟶ ✔ implement
|
||||
*/
|
||||
void
|
||||
build_ParamNode()
|
||||
|
|
@ -343,6 +339,49 @@ namespace test {
|
|||
CHECK (res == peek+1 + LIFE_AND_UNIVERSE_4EVER+1 );
|
||||
CHECK (peek != -1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** @test demonstrate use of a custom allocator
|
||||
* - use local AllocationCluster instance for this test
|
||||
* - place and manage the resulting ProcNode in this cluster
|
||||
* - furthermore, allocate administrative internals also there
|
||||
* - build a Node with two ports, each with a parameter and output buffer
|
||||
* - demonstrate the created Node works as expected
|
||||
* - verify that allocations happened
|
||||
*/
|
||||
void
|
||||
useAllocationCluster()
|
||||
{
|
||||
auto procFun = [](ushort param, uint* buff){ *buff = param; };
|
||||
|
||||
lib::AllocationCluster alloc;
|
||||
CHECK (0 == alloc.numBytes());
|
||||
|
||||
auto& node = alloc.create<ProcNode>( // ◁───────────────────────────────────┨ place resulting ProcNode into the cluster
|
||||
prepareNode("Test")
|
||||
.withAllocator(alloc) // ◁───────────────────────────────────┨ policy to use this cluster for internal allocations
|
||||
.preparePort()
|
||||
.invoke ("funA()", procFun)
|
||||
.setParam (LIFE_AND_UNIVERSE_4EVER)
|
||||
.completePort()
|
||||
.preparePort()
|
||||
.invoke ("funB()", procFun)
|
||||
.setParam (LIFE_AND_UNIVERSE_4EVER + 13)
|
||||
.completePort()
|
||||
.build()
|
||||
);
|
||||
|
||||
CHECK (LIFE_AND_UNIVERSE_4EVER == invokeRenderNode (node, Time::ZERO, 0));
|
||||
CHECK (LIFE_AND_UNIVERSE_4EVER+13 == invokeRenderNode (node, Time::ZERO, 1));
|
||||
|
||||
// minimum bounds of memory allocations...
|
||||
const auto MIN_EXPECT = sizeof(ProcNode)
|
||||
+ 2 * (sizeof(Port) + sizeof(BuffDescr) // defined two ports, each with an output type...
|
||||
+ 2*(sizeof(void*) + sizeof(std::function<void()>))); // and parameter + buffer + proc-fun and param-fun
|
||||
|
||||
CHECK (MIN_EXPECT < alloc.numBytes());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ using lib::Symbol;
|
|||
using util::isnil;
|
||||
using util::isSameObject;
|
||||
using test::TestFrame;
|
||||
using test::testRand;
|
||||
using test::testRand;
|
||||
namespace ont = test::ont;
|
||||
|
||||
namespace steam {
|
||||
|
|
@ -362,7 +362,7 @@ namespace test {
|
|||
|
||||
|
||||
// Prepare a precomputed parameter for the complete tree
|
||||
auto selectFrameNo = [&](TurnoutSystem& tuSys){ return quantSecs(tuSys.getNomTime()); };
|
||||
auto selectFrameNo = [&](TurnoutSystem& turSys){ return quantSecs(turSys.getNomTime()); };
|
||||
auto paramSpec = buildParamSpec()
|
||||
.addSlot (selectFrameNo);
|
||||
|
||||
|
|
@ -373,8 +373,8 @@ namespace test {
|
|||
auto stepMixer = [] (FrameCnt id)-> ont::Factr { return util::limited (0, + id, 50) / 50.0; };
|
||||
|
||||
// note: binds the accessor for the precomputed FrameNo-parameter
|
||||
auto autoFilter = [=](TurnoutSystem& tuSys){ return stepFilter (tuSys.get (accFrameNo)); };
|
||||
auto autoMixer = [=](TurnoutSystem& tuSys){ return stepMixer (tuSys.get (accFrameNo)); };
|
||||
auto autoFilter = [=](TurnoutSystem& turSys){ return stepFilter (turSys.get (accFrameNo)); };
|
||||
auto autoMixer = [=](TurnoutSystem& turSys){ return stepMixer (turSys.get (accFrameNo)); };
|
||||
|
||||
|
||||
// A Node with two (source) ports
|
||||
|
|
|
|||
|
|
@ -17,28 +17,375 @@
|
|||
|
||||
|
||||
#include "test/run.hpp"
|
||||
//#include "lib/util.hpp"
|
||||
#include "lib/allocation-cluster.hpp"
|
||||
#include "test/test-rand-ontology.hpp"
|
||||
#include "vault/out/diagnostic-output-slot.hpp"
|
||||
#include "vault/mem/buffer-provider-setup.hpp"
|
||||
#include "vault/mem/engine-buffer-metadata.hpp"
|
||||
#include "vault/mem/engine-buffer-manager.hpp"
|
||||
#include "vault/mem/local-buffer-stage.hpp"
|
||||
#include "vault/mem/local-buffer-store.hpp"
|
||||
#include "steam/engine/engine-facilities.hpp"
|
||||
#include "steam/engine/node-builder.hpp"
|
||||
#include "steam/asset/meta/time-grid.hpp"
|
||||
#include "lib/time/timequant.hpp"
|
||||
#include "lib/time/timecode.hpp"
|
||||
#include "lib/depend-inject.hpp"
|
||||
#include "lib/thread.hpp"
|
||||
//#include "lib/symbol.hpp"
|
||||
#include "lib/util.hpp"
|
||||
#include "test/diagnostic-output.hpp"///////////////TODO
|
||||
|
||||
|
||||
//using std::string;
|
||||
using lib::Thread;
|
||||
using std::this_thread::yield;
|
||||
using std::this_thread::sleep_for;
|
||||
using std::chrono_literals::operator ""us;
|
||||
|
||||
using lib::time::Time;
|
||||
using lib::time::QuTime;
|
||||
using lib::time::FrameNr;
|
||||
using lib::time::FrameCnt;
|
||||
using lib::time::FrameRate;
|
||||
using lib::time::PGrid;
|
||||
using steam::asset::meta::TimeGrid;
|
||||
using vault::out::DiagnosticOutputSlot;
|
||||
//using lib::Symbol;
|
||||
//using util::isnil;
|
||||
//using util::isSameObject;
|
||||
using util::isLimited;
|
||||
using test::TestFrame;
|
||||
using test::testRand;
|
||||
namespace ont = test::ont;
|
||||
|
||||
namespace steam {
|
||||
namespace engine{
|
||||
namespace test {
|
||||
|
||||
namespace { // ========== Test Setup ==========
|
||||
|
||||
using vault::mem::BufferProviderSetup;
|
||||
using vault::mem::LocalBufferStage;
|
||||
using vault::mem::LocalBufferStore;
|
||||
|
||||
using vault::mem::EngineBufferMetadata;
|
||||
using vault::mem::EngineBufferManager;
|
||||
|
||||
class TestEngineBufferSetup
|
||||
: public BufferProviderSetup
|
||||
, public EngineFacilities
|
||||
{
|
||||
public:
|
||||
TestEngineBufferSetup()
|
||||
: BufferProviderSetup{*this}
|
||||
{ }
|
||||
|
||||
auto buildStage() { return std::make_unique<LocalBufferStage>("NodeOpera_test"); }
|
||||
auto buildStore() { return std::make_unique<LocalBufferStore>(); }
|
||||
|
||||
BufferProvider& setupBufferProvider() override { return *this; }
|
||||
};
|
||||
|
||||
struct TestEngineCtx
|
||||
{
|
||||
// install mock instances for the relevant services
|
||||
lib::DependInject<EngineBufferMetadata>::Local<> metaHub;
|
||||
lib::DependInject<EngineBufferManager>::Local<> globalPool;
|
||||
|
||||
// install a rigged access front-end for the buffer management
|
||||
lib::DependInject<EngineFacilities>::Local<TestEngineBufferSetup> bufferSetup;
|
||||
lib::DependInject<EngineCtx>::Local<EngineCtx> frontEndCtx;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/***************************************************************//**
|
||||
* @test check render node operation modes and collaboration.
|
||||
* @todo 6/2026 this test shell demonstrate all relevant operation modes
|
||||
* of render nodes, at the level of an component-integration-test.
|
||||
* It is not clear yet what further operation modes this entails,
|
||||
* beyond (obviously) invoking a render job... ////////////////////////////////////////////////TICKET #1367 : build a node invocation
|
||||
* @remark the purpose of this test is to accomplish component integration
|
||||
* regarding relevant usages of the render node network, seen from the
|
||||
* perspective of an (exit) render node that is used / invoked / pulled.
|
||||
*/
|
||||
class NodeOpera_test : public Test
|
||||
{
|
||||
virtual void run(Arg)
|
||||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
UNIMPLEMENTED ("operate some render nodes as linked together");
|
||||
seedRand();
|
||||
|
||||
invokeSimpleNode();
|
||||
invokeNodeTree();
|
||||
}
|
||||
|
||||
|
||||
/** @test directly invoke a simple source node.
|
||||
* - use the node builder to create a simple node
|
||||
* immediately into the current stack frame
|
||||
* - the processing function feeds a fixed random number
|
||||
* into the given output buffer
|
||||
* - then launch a worker thread to pull this node
|
||||
* - create an output buffer directly from the worker thread
|
||||
* - invoke the single port of the node, which is visible
|
||||
* since it was created before starting the local worker thread
|
||||
* - after the invocation, the fixed random number should have been
|
||||
* placed into the output buffer
|
||||
* - after the worker thread has finished, a single buffer allocation
|
||||
* should have happened; this allocation was sent back automatically
|
||||
* and can be claimed and released by the global buffer manager.
|
||||
*/
|
||||
void
|
||||
invokeSimpleNode()
|
||||
{
|
||||
int randNum{rani()};
|
||||
auto fun = [&](int* buff){ *buff = randNum; };
|
||||
|
||||
TestEngineCtx mockEngine;
|
||||
|
||||
ProcNode node{prepareNode("Test")
|
||||
.preparePort()
|
||||
.invoke("fun()", fun)
|
||||
.completePort()
|
||||
.build()};
|
||||
|
||||
Thread testWorker{[&] /* === directly perform a node invocation === */
|
||||
{
|
||||
BuffHandle buff = EngineCtx::access().mem.lockBufferFor<int> (-55);
|
||||
Time nomTime{Time::ZERO};
|
||||
ProcessKey key{0};
|
||||
uint port{0};
|
||||
|
||||
CHECK (-55 == buff.accessAs<int>());
|
||||
|
||||
// Trigger Node invocation...
|
||||
buff = node.pull (port, buff, nomTime, key);
|
||||
|
||||
CHECK (randNum == buff.accessAs<int>());
|
||||
buff.release();
|
||||
}};
|
||||
|
||||
while (testWorker)
|
||||
yield(); // wait for worker to finish
|
||||
|
||||
// the central services were indeed requested / created
|
||||
CHECK (mockEngine.bufferSetup);
|
||||
CHECK (mockEngine.globalPool);
|
||||
CHECK (mockEngine.metaHub);
|
||||
|
||||
// at least two buffer types registered (one generic for int)
|
||||
CHECK (2 <= mockEngine.metaHub->cntEntries());
|
||||
|
||||
// one buffer was requested...
|
||||
CHECK (1 == watch(*mockEngine.globalPool).numAllocs());
|
||||
|
||||
sleep_for (500us); // additional delay for the local pool's destructor
|
||||
// (which is invoked after the thread has terminated)
|
||||
|
||||
// process and close remaining allocations
|
||||
// sent back by the local pool's destructor...
|
||||
mockEngine.globalPool->processPendingRequests();
|
||||
// now there should be no memory leaks.......
|
||||
CHECK (0 == watch(*mockEngine.globalPool).bytesLeased());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** @test perform a RenderInvocation to pull from a complex node tree into an output sink.
|
||||
* - use a test helper class to build a node topology similar to NodeLink_test
|
||||
* - the ProcNodes and all backing structures are managed by an AllocationCluster
|
||||
* - use a DiagnosticOutputSlot (by default configured for buffers sized for TestFrame)
|
||||
*/
|
||||
void
|
||||
invokeNodeTree()
|
||||
{
|
||||
TestEngineCtx mockEngine;
|
||||
RenderPipeline pipeline;
|
||||
|
||||
DiagnosticOutputSlot oSlot;
|
||||
auto sink0 = * oSlot.getOpenedSinks();
|
||||
|
||||
const uint OUTPUT_FRAME{123};
|
||||
|
||||
Thread testWorker{[&] /* === perform a render invocation === */
|
||||
{
|
||||
Time nomTime{rani(60'000),0}; // drive test with a random »nominal Time« <60s with ms granularity
|
||||
ProcessKey key{0};
|
||||
uint port{0};
|
||||
|
||||
BuffHandle buff = sink0.lockFrame (OUTPUT_FRAME); // claim output buffer
|
||||
CHECK (not buff.accessAs<TestFrame>().isValid()); // underlying buffer is zero initialised, thus no valid TestFrame yet
|
||||
|
||||
// Trigger Node invocation...
|
||||
buff = pipeline.exitNode.pull (port, buff, nomTime, key);
|
||||
buff.release();
|
||||
|
||||
// DiagnosticOutputSlot allows to investigate the data sent into the DataSink...
|
||||
TestFrame const& result = watch(oSlot).feed(0).frame(OUTPUT_FRAME).accessAs<TestFrame>();
|
||||
CHECK (result.isValid()); // data matches checksum
|
||||
CHECK (not result.isPristine()); // is not a source frame (but processed)
|
||||
CHECK (result.getChecksum() == pipeline.expectedChecksum (nomTime));
|
||||
}};
|
||||
|
||||
while (testWorker)
|
||||
yield(); // wait for worker to finish
|
||||
sleep_for (500us);
|
||||
|
||||
mockEngine.globalPool->processPendingRequests();
|
||||
// now there should be no memory leaks.......
|
||||
CHECK (0 == watch(*mockEngine.globalPool).bytesLeased());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper context to build and manage a processing chain of Render Nodes.
|
||||
* In the real Render Engine, these processing chains are generated by the Builder,
|
||||
* and organised into »Segments« of the timeline, where each segment features a
|
||||
* distinct node connectivity (i.e. typically for one clip).
|
||||
* For this test, a single top-level _exit node_ is exposed.
|
||||
* @note this helper context is constructed in _RAII style_,
|
||||
* and the embedded AllocationCluster unwinds automatically.
|
||||
* @see \ref NodeLink_test for a detailed breakdown of a similar topology
|
||||
*/
|
||||
class RenderPipeline
|
||||
: util::NonCopyable
|
||||
{
|
||||
static constexpr ont::Flavr SRC_A = 10; ///< »chain-A« arbitrary source frame marker
|
||||
static constexpr ont::Flavr SRC_B = 20; ///< similar for »chain-B«
|
||||
|
||||
PGrid secondsGrid_{TimeGrid::build (FrameRate{1})};
|
||||
FrameCnt quantSecs (Time time) { return secondsGrid_->gridPoint (time); };
|
||||
|
||||
/** custom allocator for this node tree */
|
||||
lib::AllocationCluster alloc_;
|
||||
|
||||
public:
|
||||
ProcNode& exitNode{buildTree()};
|
||||
|
||||
HashVal
|
||||
expectedChecksum (Time nomTime)
|
||||
{
|
||||
ont::FraNo fraNo = quantSecs(nomTime);
|
||||
ont::Param param = stepFilter(fraNo);
|
||||
ont::Factr mix = stepMixer (fraNo);
|
||||
|
||||
TestFrame f1{uint(fraNo),SRC_A};
|
||||
TestFrame f2{uint(fraNo),SRC_B};
|
||||
|
||||
ont::manipulateFrame (&f1, &f1, param);
|
||||
ont::combineFrames (&f1, &f1, &f2, mix);
|
||||
return f1.getChecksum();
|
||||
}
|
||||
|
||||
private:
|
||||
ont::Param stepFilter(FrameCnt id) { return util::limited (10, -10 + id, 50); };
|
||||
ont::Factr stepMixer (FrameCnt id) { return util::limited (0, + id, 50) / 50.0; };
|
||||
|
||||
/** @internal wire the processing node topology.
|
||||
* This fake "render pipeline" is based on hash chaining computations,
|
||||
* operating on TestFrame (with reproducible random data content).
|
||||
* Two processing chains are built, each producing test frames;
|
||||
* the first chain even adds a "filter". Some "parameters" are added
|
||||
* to change the random data in a deterministic way. Finally, both chains
|
||||
* are "mixed" together into a single result TestFrame. Furthermore, the
|
||||
* parameter automation is based on a precomputed _frame number_ parameter,
|
||||
* that is derived from the _absolute nominal time_ by quantisation into a
|
||||
* time grid with a stepping of 1 second (-> `FrameRate{1}`). This precomputed
|
||||
* frame number parameter is stored in a parameter data block on the call stack,
|
||||
* prior to recursively evaluating the node chain attached below.
|
||||
* @note the generated Render Nodes are placed into the AllocationCluster,
|
||||
* together with all the further storage used by those nodes internally.
|
||||
* The real Render Engine uses an AllocationCluster per Segment of the timeline.
|
||||
*/
|
||||
ProcNode&
|
||||
buildTree()
|
||||
{
|
||||
// Prepare a precomputed parameter for the complete tree
|
||||
auto selectFrameNo = [&](TurnoutSystem& turSys){ return quantSecs (turSys.getNomTime()); };
|
||||
auto paramSpec = buildParamSpec()
|
||||
.addSlot (selectFrameNo);
|
||||
auto accFrameNo = paramSpec.makeAccessor<0>();
|
||||
|
||||
// Automation functions using the accessor for the precomputed FrameNo-parameter
|
||||
auto autoFilter = [&](TurnoutSystem& turSys){ return stepFilter (turSys.get (accFrameNo)); };
|
||||
auto autoMixer = [&](TurnoutSystem& turSys){ return stepMixer (turSys.get (accFrameNo)); };
|
||||
|
||||
// use processing functions from the »TestRand Ontology«
|
||||
auto testGen = testRand().setupGenerator();
|
||||
auto testMan = testRand().setupManipulator();
|
||||
auto testMix = testRand().setupCombinator();
|
||||
|
||||
// A source node to produce random test frames
|
||||
auto& n1s = alloc_.create<ProcNode>(
|
||||
prepareNode("srcA")
|
||||
.withAllocator(alloc_)
|
||||
.preparePort()
|
||||
.invoke(testGen.procID(), testGen.makeFun()) // params(frameNo, flavour)
|
||||
.closeParam<1>(SRC_A) // --> fix the flavour ≔ SRC_A
|
||||
.retrieveParam(accFrameNo)
|
||||
.completePort()
|
||||
.build());
|
||||
|
||||
// A node to »filter« the data in chain-A
|
||||
auto& n1f = alloc_.create<ProcNode>(
|
||||
prepareNode("filterA")
|
||||
.withAllocator(alloc_)
|
||||
.preparePort()
|
||||
.invoke(testMan.procID(), testMan.makeFun())
|
||||
.attachParamFun(autoFilter) // filter-param <-- autoFilter(frameNo)
|
||||
.connectLead(n1s)
|
||||
.completePort()
|
||||
.build());
|
||||
|
||||
|
||||
// A secondary source Node for the »chain-B«
|
||||
auto& n2s = alloc_.create<ProcNode>(
|
||||
prepareNode("srcB")
|
||||
.withAllocator(alloc_)
|
||||
.preparePort()
|
||||
.invoke(testGen.procID(), testGen.makeFun()) // params(frameNo, flavour)
|
||||
.closeParam<1>(SRC_B) // --> fix the flavour ≔ SRC_B
|
||||
.retrieveParam(accFrameNo)
|
||||
.completePort()
|
||||
.build());
|
||||
|
||||
|
||||
// A mixing Node to combine both chains
|
||||
auto& mix = alloc_.create<ProcNode>(
|
||||
prepareNode("mix")
|
||||
.withAllocator(alloc_)
|
||||
.preparePort()
|
||||
.invoke(testMix.procID(), testMix.makeFun())
|
||||
.attachParamFun(autoMixer) // mixer-param <-- autoMixer(frameNo)
|
||||
.connectLead(n1f)
|
||||
.connectLead(n2s)
|
||||
.completePort()
|
||||
.build());
|
||||
|
||||
|
||||
// Place a »Param-Agent«-Node on top to pre-compute the FrameNo
|
||||
auto& parNode = alloc_.create<ProcNode>(
|
||||
prepareNode("Param")
|
||||
.withAllocator(alloc_)
|
||||
.preparePort()
|
||||
.computeParam(paramSpec)
|
||||
.delegateLead(mix)
|
||||
.completePort()
|
||||
.build());
|
||||
|
||||
// the AllocationCluster managed some storage...
|
||||
CHECK (isLimited (1_KiB, alloc_.numBytes(), 5_KiB));
|
||||
|
||||
// here the »Param-Agent« also acts as the Exit-Node
|
||||
return parNode;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ namespace test {
|
|||
* - the prime goal is to demonstrate correctness, balancing memory demand
|
||||
* mostly through lock-free queues, without loosing any allocation.
|
||||
*/
|
||||
class BufferProviderLoad_test : public Test
|
||||
class BufferProviderLoad_test : public Test
|
||||
{
|
||||
|
||||
virtual void
|
||||
|
|
|
|||
|
|
@ -92,9 +92,9 @@ namespace test {
|
|||
CHECK (a2.siz > 555);
|
||||
|
||||
// can use that memory without blowing up....
|
||||
auto doooh = new(a1.mem) f128{std::numeric_limits<f128>::max()};
|
||||
auto doooh = new(a1.mem) lflp{std::numeric_limits<lflp>::max()};
|
||||
*doooh /= 2;
|
||||
CHECK (2 == std::numeric_limits<f128>::max() / *doooh);
|
||||
CHECK (2 == std::numeric_limits<lflp>::max() / *doooh);
|
||||
|
||||
allo.deallocate (a1);
|
||||
allo.deallocate (a2);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue