Commit graph

7265 commits

Author SHA1 Message Date
0217fc9dbe Parameters: demonstrate type conversions and opaque value assignment
Largely this test covers already established functionality.
It is ''somewhat difficult'' to prove that an opaque type is present,
beyond showing that values are truncated or accommodated.

At the beginning of the implementation-draft, I considered a double-dispatch,
but concluded that this would be too much overhead for the standard case;
the drafted visitor-style dispatch was "parked" as `Domain::transferTo`.
It turned out that this implementation is not directly usable, since it
assumes that »the value buffer« is accessible, albeit as opaque ValBuff&.

As the design matured, I decided that the presence of a »Buffer« should better
not be assumed, and thus flipped the high-level-APIs around. But it is
easily possible to implement this double-dispatch scheme anew on top of
the now settled implementation scheme:
 - `Disposition::assignValueTo (Disposition&)` invoked on the source side
 - implemented in `ParamData<VAL, IFA>::assignValueTo (Disposition&)`,
   and there delegating to the templated front-end setter
   `Disposition::setVal(X)` (which performs the second dispatch step)

It remains to be seen if such an opaque value assignement is required in practice
2026-10-19 00:05:15 +02:00
e406d73c13 Parameters: add further test coverage for builder + scale 2026-10-18 21:13:36 +02:00
6a60bf2caa Parameters: solve insidious problem related to PolymorphicValue
We are still using the first draft implementation of that concept.
At that time, I was not able to implement ''virtual copy support''
in a fully generic fashion -- rather I put up the requirement that
the payload type must expose a `CopySupportAPI`

Typically this does not fit well into a given domain-specific
interface- and implementation hierarchy; here I used a band-aid,
by injecting it via an additional template parameter -- and promptly
forgot using the same secondary parameter in some type aliases.

The result was that we got two disjoint instances of the ScaleRegistry,
leading to quite puzzling "ID not yet registered" errors.
2026-10-18 19:47:27 +02:00
27257a7737 Parameters: map the most relevant Scale operations into the Builder
- define lower bound, upper bound or both for a limited scale
 - setup a cyclic wrapping scale
 - mark the scale as logarithmic, binary log or decibels
 - mark the parameter as a factor value (important in interplay with log scales)
2026-10-18 18:57:17 +02:00
8130941cde Parameters: demonstrate pass-on of a scale by ID
BUT there is a problem: when the original parameter (used as prototype)
''does already define a scale'', then we need a way to get a clone
of this scale definition -- and this involves type conversion
2026-10-18 17:35:07 +02:00
af3d8f5b58 Parameters: tentatively require explicit typing on prototype-build
In-depth analysis indicated that an ''opaque solution'' would be possible,
yet complex to implement in detail
 - a Builder interface would be required
 - this interface needs to package all given param values as ValBuff
 - furthermore, each such value must be accompanied with a BaseTypeID
 - the actual builder would then become ''a PImpl''
 - it could be either implemented as heap-allocated with unique_ptr
 - or be a `PolymorphicValue` itself (in which case the builder front-end
   should be non-copyable and used by reference, inline in a builder expression
 - in any case, the front-End builder would proxy all calls to the actual builder
 - and the latter would return an opaque parameter at the end

This setup would even allow to use ''full type erasure'' on the Parameter front-end,
and might reduce the ''header inclusion tax''

BUT -- that being said, if we just require the user to specify the desired type explicitly,
not only when building a new parameter, but also when cloning a parameter from a prototype,
then the actual implementation could use one simple builder object and all the complexities
outlined above could be skipped.

So the latter is what we do for now -- since this whole effort is rather a
design draft and should enable to explore actual usages of the parameter framework.
At a later point, the elaborate version could still be implemented
2026-10-18 17:10:34 +02:00
a4a65f23dc Parameters: need to apply the scale when construct a Parameter
...since we do not check beforehand what value was specified;
it seems preferable to do this ''in the parameter implementation'',
as the builder can be expected to get a broad API, while the ParamData
is the natural place to enforce an invariant
2026-10-18 14:56:14 +02:00
c6978c3aa5 Parameters: successfully register and apply a Scale
This demonstrates how a Scale (descriptor) can be handled
in the simple case where the value type is known.
There is ''no deduplication'' -- every Scale definition
gets a new Slot.

The scale is then retrieved later and used to conform values,
as demonstrated with a simple `maxValue()`
2026-10-18 01:49:52 +02:00
99b9db1acb Parameters: find out how to integrate the Scale (prototyping)
Use the simplest possible example, a parameter with limited `maxVal()`.
Then find out where in the data patch the Scale-conforming operation
must be hooked in.

The decision was made to integrate **everything inline** for maximum performance.
An increased ''include tax'' is incurred, and thus the Parameter-Builder
should be split off into a separate header.

The static Scale-Registry is now part of `TypedEntity` (type-handler.hpp)
and will thus instantiated once per actual parameter value type.

⟹ the Scale-ID is a **runtime value** and **depends** on the BaseType-ID
2026-10-18 02:57:27 +02:00
8b3e05e99b Parameters: scrutinise behaviour under load
- Fix several subtle (and not so subtle) bugs.
 - Verify correct behaviour under concurrent pressure
 - leaving out the Mutex-lock leads to corrupted internal state
 - effect of the Atomics can not be demonstrated

Note: occasional failures because the threads sleep to allow
the other threads to complete their work. If a thread wakes up
too early, it might see stale state and access index-slots that
are not yet published. This confirms: you **must not** access
registration IDs unless you got them from somewhere, which
ensures they were registered and the change is visible.
2026-10-18 00:58:43 +02:00
f79c2e15e9 Parameters: devise a test to press the registry concurrently
This turns out to be tricky, since all registry operations are written
to be fast and should operate in the nanoseconds range; any scheduling
or thread sleeping/awaking activity would thus be slower by factor 1000.

It seems impossible to ''really press'' the visibility aspect,
but we can ensure the internal structures are built and extended
without corruption under concurrent pressure:
 - place numbers 1...N into the registry
 - and alternating read the numbers added by the other threads
At the end, each thread must see the same (predictable) sum, even while
the order or the numbers in the registry is slightly random
2026-10-17 19:11:25 +02:00
c5f53aed84 Parameters: verify sane storage handling
- as it turns out: we need `UninitialisedStorage` to avoid spurious ctor calls
 - and the extent-index is computed by div, not mod !!

With these fixes, I can now demonstrate that the allocation procees
exactly as expected, the registry can be filled completely, and no
memory is leaked or corrupted (the latter only verified with some probes)
2026-10-17 04:18:15 +02:00
1d0580cfb4 Parameters: care for sane exeption handling and clean-up
- extract an accessor for the Extent
 - increment the `size_` field only after successfully completing slot allocation
 - properly manage allocated memory without leak; we have a policy in Lumiera
   that prohibits to leak memory ''just because we know that it will not be de-allocated''

__NOTE__: implementation passes first unit test now
2026-10-17 02:32:22 +02:00
b8c1a55fd1 Parameters: implement slot allocation for the TypeRegistry 2026-10-17 01:28:51 +02:00
36a728c68c Parameters: define data layout for the TypeRegistry
- allocate storage from the heap in Extents
- each Extent is an array of entries
- the index table is an array of atomic<Extent*>
2026-10-17 01:10:35 +02:00
fc8dd9408a Parameters: establish requirements for a descriptor registry
Since the design established thus far calls for a fixed size container
and a size-constrained stack of virtual interfaces and implementation within,
it is clear that more diverse parts of the type and scale handling must be
forwarded into a second layer, maintained within a registry and keyed by ID.

Taking the prospective use into account, a two-level structure with a
fixed-size top-level index table and dynamically added extents seems to
cover all requirements, and allows for a lightweight, atomic-protected
read-access path.
2026-09-27 01:38:03 +02:00
da9b40df03 Parameters: resolve type precision problems with scale-joinig
The selection of the intermediary working type, used for exponential
and logarithmic functions when combining values from different scales,
was implemented too simplistic.

Following the example of the `NumberJoinType`, it seems preferrable
to spell out the decision logic explicitly. This allows to pick `long doubl`
as working type especially in cases where 64bit integral parameters are
involved, either as source or destination.

With these final tweaks, the `verify_valueJoining()` test is not complete,
presumably with path coverage of the conversion functions.

The Scale descriptor and operational logic is now defined to the point
that it can be integrated into the general Parameter implementation machinery.
2026-09-22 18:39:09 +02:00
fd5548b61b Parameters: verify use of a long-double target scale
Scrutinising the bounds of numeric precision.
As it turns out, most cases work as expected,
including the massive errors incurred by input-values given as float.

However: a logarithmic combination integral -> integral runs in loss of precision
So the selection logic for the intermediary working type is still overly simplistic!
2026-09-22 01:43:45 +02:00
00b0aa0c5b Parameters: expand logarithmic scale patch coverage
The control logic for the various combinations of linear and logarithmic scales is nasty,
and the added path coverage indeed unveiled several bugs.

One of these issues, an unjustified limitation on the input value type of `asLogarithm`,
convinced me to refactor the conversion functions into free functions,
since they effectively ''always span the relation of two scales'' --
writing them as member function introduces an unnecessary asymetry.
2026-09-21 22:10:18 +02:00
206f9635c9 Parameters: cover logarithmic scale conversions
this feature relies on the common logarithmic math,
yet with the additional twist that decibels need an additional factor of 10...
2026-09-21 05:42:33 +02:00
92b7b05ea2 Parameters: solve mixed signed / unsigned computation
There are some corner cases where the C++ number promotion rules can hurt us badly.
Devised a type selection logic to pick a suitable common numeric type explicitly,
using `std::common_type<T1,T2>` in most cases, yet handling explicitly the
case when the computation would wrap into unsigned; in such a mixed sign integral case,
we either use int64_t (when its precision suffices), or long double,
which can hold 64bit of mantissa without precision loss.
2026-09-21 01:05:53 +02:00
c0cff8abe6 Parameters: implementation for metric conversions
LIN -> LIN yet either additive or multiplicative
LOG -> LIN
LIN -> LOG
and different kinds of logarithms

all computations using a common intermediary numeric type,
preferably a suitably sized floating-point
2026-09-20 06:15:38 +02:00
684bb2ca50 Parameters: draft the cases of a generic feed-join
In the general case, a base value and a feed can also be combined
when the metric of both arguments differs; notably a linear multiplier
can be applied to an logarithmic base value by adding its logarithm,
and a logarithmic feed can be multiplied to a linear base value by
taking the exponential function.

Furthermore there are some cases involving nominal scales, which we can mark only, but leave unimplemented
2026-09-19 00:52:51 +02:00
a1b80c22df Parameters: reorganise type conversion dependencies
the generic cross-type assignment and -conversion will also be used by the `par::Scale` to join an automation evaluation result with a base value...
2026-09-17 23:38:02 +02:00
0cbe9d7dd6 Parameters: prepare for a generic Scale-join operation
The `join()`-operation allows to combine a value pulled from automation
to be combined with a local ''base value'' stored in the parameter.

When both the source and the target parameter use the same Scale,
this operation is simple; it becomes more tricky once they don't --
because then linear / logarithmic and type conversions must be handled.

I now decided to provide a generic implementation in the Scale (descriptor),
while the enclosing parameter shall handle the dispatch when connecting
to a opaque source with another Scale. And this generic implementation
relies on the same technique as was used for the parameter type conversions,
thus it should be factored out into a library header.
2026-09-17 23:38:02 +02:00
9221fbeef1 Parameters: document and verify integration into cyclic Scale
- verify using an unsigned integer, for a typical ''angle scale''
- also verify a high-res floating-point scale, and demonstrate rounding issues
2026-09-17 01:17:40 +02:00
549216dba3 Parameters: integrate the cyclic-wrap function
a much more complete and robust implementation,
but requires a precision limit to be pre-computed in the floating-point case
2026-09-16 19:42:55 +02:00
6eb6ec48da Library: better short-hand name for long double
As with all those C++ base types, unfortunately they are giving you
only rough size guarantees. For Lumiera, I defined short hand names
some time ago, notably because the explicit long X and short X qualifiers
do not work with ''constructor syntax'' for direct conversions.

But I choose the name `f128` at that time -- which might raise ill-guided
expectations, since the `long double` is not that long on relevant linux 64bit
platforms; it can just hold 64bit of mantissa at least however.

As replacement, I'll use `lflp` now, because it has also 4 letters,
looks somewhat weird (and thus catches attention) and points into the
right direction. I considered `lfloat` as alternative, but that looks
too much like `float`, visually...
2026-09-21 01:05:53 +02:00
303edcb6a1 Library: verification for all floating-point types
finally I was able to come up with a rather concise test
that works with float, double and long double; especially
the aliasing due to rounding could be demonstrated, and
the precision limit is made plausible.

Interestingly, `std::fabs()` really works precise enough
so that a very large positive domain can be implemented
and tested with exact match
2026-09-16 04:23:55 +02:00
d0e6998b9c Library: investigate what could be tested with floating-point
The floating-point variant turns out to be difficult to test,
since it is never ''actually exact'' -- due to the nature of floating-points.

We can not expect reasonable behaviour at the domain boundaries,
even while in some cases the computation works (and in others it
goes infinite and is sanitised to `minVal`).

The limit for rejection is itself rather a heuristic;
thus it is difficult to show "wrong" behaviour beyond the limit,
while the limit itself is typically already so large that it is
hard to handle in test code -- at least when expecting visible results.
2026-09-15 21:53:56 +02:00
ce980d64bf Library: verify the new cyclicWrap library function
The implementation should cover all integral data types,
both signed and signed, over the entire value domain.

Verify that by testing the bounds and the complex computation path
2026-09-15 01:37:38 +02:00
29a39a0f7f Library: extend text coverage for util::floorwrap
These two helpers ''just happen to work'' in the full domain, and also for unsigned types.
I can not recall that I actually was aware of that, but it's nice.

Thus add some test cases to cover the bounds of the domain
2026-09-14 16:34:15 +02:00
77257c5653 Parameters: numeric-types deep dive
Turns out that implementing a cyclically wrapping domain properly is much more tricky than initially anticipated.

After initial analysis I decided where to draw the line and what ''esoteric features'' aspects to leave out.
Then I used Knowledge-Technology (Claude) to scrutinise and improve my implementation,
and to learn a lot more about integral arithmetics in general.
2026-09-14 01:50:26 +02:00
426d7c610e Parameters: handle simple Scales
- build a default value
 - handle cyclic scales
 - implement clamping behaviour
 - identify a muliplicative factor
 - join a feed either multiplicative or additive
2026-09-12 01:16:16 +02:00
c3c103e1a9 Parameters: mark out the base operations of a Scale
Scales are defined as descriptor records and will be used as const.
Besides some information functions, a Scale
 * allows to conform a value to be compliant with the Scale's constraints
 * can ''join'' and thus apply a ''feed'' on top of a ''base value'';
   this is the foundation for applying an automation function, while
   handling multiplicative and logarithmic cases properly
2026-09-12 00:39:11 +02:00
17c95a9b18 Parameters: in-depth analysis regarding Scale parameters
Notably I considered how and to what extent we will need to represent ''nominal'' or ''ordinal scales''...
In fact we should represent them, yet as cascaded delegate, since that is a relevant topic,
yet not the primary use case -- and a topic with very deep complexity in itself.
2026-09-11 01:44:48 +02:00
59fbb8e6b0 Parameters: consider what to conceptualise as a Scale
It is clear by now that a `Scale` will act as a refinement of a full-domain value type.
However, in order to develop the definition and builder notation for parameters,
a much more detailed understanding of scales is necessary -- including the question
where to draw a line between the complex structured `ParamSet` and a simple
`Parameter` that is conceived to be number-value-like.
2026-08-10 01:28:41 +02:00
5fbe08ebe0 Parameters: sketch a prototype-builder API
...yet the actual problem remains that the value type of the
prototype remains a runtime-dynamic hidden implementation trait.

How can we remould the scale hidden within a parameter, while
in the regular setup, the scale is implemented as an immutable
entry placed into a registry?
2026-09-06 01:39:09 +02:00
a4ae568601 Parameters: mapping out the next steps to draft
...I do ''not intend to go into a parameter deep-dive'' yet,
but I want to set out the basic capabilities to be expected...
2026-09-06 00:50:53 +02:00
f8c1222197 Parameters: work out a solution for an optimised code-path
Overall, this is a questionable endeavour...
 - I want a framework that can be extended flexibly
 - I am concerned about the performance penalty of that flexibility

What makes this situation problematic is the fact that I am deep in
prototyping work here, and have not the slightest idea as to what would be
the actual usage context. So I am lacking a point of reference, yet still
I am engaged into a very tricky (premature) optimisation...

Anyway. I have now found a way to integrate an optimised access code-path
in a safe way, while retaining the general case with flexible type conversion:
 - whenever a BaseDomain<X> is used, a corresponding type-ID is marked into the ID-Record
 - this allows when it is possible to side-step any type conversion,
 - in which case we can also forcibly side-step the virtual dispatch,
   and invoke directly the value-accessors of the default implementation,
   reaching into the embedded value buffer.
2026-09-05 01:29:51 +02:00
41adaa18ef Parameters: streamline automatic type conversion
Can now express the automatic conversion between all allowed `BaseTypes`
by invoking the `TypeHandler` interface directly. The interfaces were streamlined
and storage size is managed automatically at compile time.
2026-09-03 01:42:18 +02:00
d32e4bbc06 Parameters: setup bit-layout for ID-Record 2026-09-03 00:18:46 +02:00
535e372827 Parameters: resolve PolymorpicValue setup
`PolymorphicValue` incurs some quite confusing requirements due to
the clumsy way how the instance handling and copy support must be injected.
As pointed out in #1197, with modern C++, this could be much simplified.

An overhaul of `PolymorphicValue` seems urgent thus -- in the meanwhile
we need to introduce more flexibiltiy with base-class template parameters,
so that we can inject the CopySupport interface at usage site of the
complete implementation class chain. Furthermore, the size of the VTable
is added automatically to the storage requirements in `PolymorphicValue`,
so we must subtract it when specifying the payload size by a compile-time
expression based on `sizeof(ParamData<double>)`

With these tweaks, we can now keep the limit of 4 «slots» (32 bytes)
and still reserve 64bit for ID and type flags.
2026-09-03 00:10:37 +02:00
e0fa980f4c Parameters: introduce ID-Record into the impl-interface
The solution from yesterday works, but is needlessly indirect.
However, if we want to circumvent ''any indirection'' in the
simple standard case, then it turns out to be necessary to
have some kind of ''flag store'' already in the `Disposition`
(the implementation service interface). Because otherwise
it would be impossible to trigger the short-circuit access
without going through some kind of indirection.

Need to define a generic ID-Record with some flexible extension space
that can be used by subclasses; and notably we need to generate some
base-type-IDs on a static level -- which should be possible, since all
allowed BaseTypes are defined as Type-Sequence.
2026-09-03 00:02:16 +02:00
f6cc5fe1d9 Parameters: simple testcase PASS (see #1431)
Add value access implementation of the Provision interface
by direct cast and access to the embedded value in `ParamData<VAL>`;
obviously this is an indirect dispatch, based on the actual type `VAL`

The test demonstrates the basic machinery built thus far
 - have an embedded container with inline storage
 - dispatch using type information encoded into the VTable
2026-09-02 01:48:51 +02:00
7af51ae92e Parameters: first rough-edged implementation of value access
...as expected, the first attempt to walk the full access path
unveils a host of problematic aspects in the design...
2026-09-02 01:27:27 +02:00
5c7a65bc18 Parameters: allow to pass an initial parameter value
...and in addition check that the defined value type
is consistent with the list of pre-configured `BaseTypes`

Signed-off-by: Ichthyostega <prg@ichthyostega.de>
2026-09-02 01:19:24 +02:00
9f19f5d895 Parameters: build container with in-place init
turns out to be quite tricky, due to the overly convoluted implementation
of PolymorphicValue (which looks quite dated, generally speaking,
and could use a complete overhaul).

- pass the basic value type as template parameter through the Builder
- allow for the ability to define an initial value for the parameter
- invoke the protected constructor of PolymorphicValue, directly passing
  a ''type tag'' to specify the implementation data package to be placed
  into the embedded data buffer
2026-09-02 01:19:24 +02:00
fca0c08b22 Parameters: now able to define the complete implementation layout
- `Disposition` is the service-API used by the front-End container
 - `ParamData<TY>` is a complete implementation with a inline-value buffer
 - The front-End is based on the `PolymorphicValue` container from our support-Lib
2026-09-01 23:15:09 +02:00
9665c5d320 Parameters: establish layered implementation order
Finally able to settle upon a layering for the implementation facilities

 Provision:: represents the core idea of a parameter
 Domain:: assumes the ability to handle typed values
 Disposition:: complete implementation capabilities

 BaseDomain:: establish type conversion functionality
 TypedEntity:: provide a type and ID descriptor and scale configuration
 ParamData:: full implementation based on an embedded value buffer
2026-09-01 22:55:56 +02:00