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.
This commit is contained in:
Fischlurch 2026-09-17 21:14:30 +02:00
parent 9221fbeef1
commit 0cbe9d7dd6
6 changed files with 324 additions and 17 deletions

View file

@ -41,18 +41,10 @@
#include "lib/meta/trait.hpp"
#include "lib/util.hpp"
#include <concepts>
#include <limits>
namespace lib {
namespace par {
using util::isNeg;
using std::same_as;
using std::floating_point;
using std::numeric_limits;
/* ===== Type Handler Interface for generic parameter value access ===== */

View file

@ -38,10 +38,18 @@
#include "lib/par/domain.hpp"
#include "lib/meta/generator.hpp"
#include <concepts>
#include <limits>
namespace lib {
namespace par {
using util::isNeg;
using std::same_as;
using std::floating_point;
using std::numeric_limits;
// 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,

View file

@ -30,6 +30,7 @@
#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"
@ -192,9 +193,10 @@ namespace par {
template<typename VAL>
template<typename SRC>
inline VAL
Scale<VAL>::join (VAL const& value, SRC const& feed, Scale<SRC> const& feedScale) const
Scale<VAL>::join (VAL const& value, SRC const& srcFeed, Scale<SRC> const& feedScale) const
{
UNIMPLEMENTED ("join a feed with a base value, observing Scale constraints");
VAL feed(preClamp<VAL> (srcFeed));
return join (value, feed);
}
/**

View file

@ -0,0 +1,244 @@
/*
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 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_TYPE_HANDLER_H
#define LIB_PAR_TYPE_HANDLER_H
#include "lib/par/spec.hpp"
#include "lib/meta/trait.hpp"
#include "lib/par/domain.hpp"
#include "lib/meta/generator.hpp"
namespace lib {
namespace par {
using util::isNeg;
using std::same_as;
using std::floating_point;
using std::numeric_limits;
// 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 details ===== */
/** Constraint: type \a SUB can not represent the full domain of type \a BAS
* @todo implicitly also implements a limitation to »numeric« types,
* due to the use of std::numeric_limits
*/
template<typename SUB, typename BAS>
concept sub_domain = std::signed_integral<SUB> != std::signed_integral<BAS>
or std::numeric_limits<SUB>::max() < std::numeric_limits<BAS>::max()
or std::numeric_limits<SUB>::lowest() > std::numeric_limits<BAS>::lowest()
;
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wsign-compare\"")
// the following functions are specifically crafted to work around
// the ambiguities caused by automatic signed-to-unsigned promotion;
// furthermore, the automatic promotion allows to perform the comparison.
template<typename X, typename Y>
inline constexpr bool
isLower_safe (X const& x, Y const& y)
{
return isNeg(x) == isNeg(y)? x < y
: isNeg(x);
}
template<typename X, typename Y>
inline constexpr bool
isLarger_safe (X const& x, Y const& y)
{
return isNeg(x) == isNeg(y)? x > y
: isNeg(y);
}
_Pragma("GCC diagnostic pop")
/** generic helper to conform into a common value domain
* @tparam SUB the implied target value domain
* @tparam BAS source data type
* @return a value in source data type,
* yet conformed into the target data range
*/
template<typename SUB, typename BAS>
inline constexpr BAS
preClamp (BAS const& value)
{
return value;
}
/** special case: clamp the source value into the target value domain.
* @remark tricky due to automatic conversions between signed/unsigned */
template<typename SUB, typename BAS> requires (sub_domain<SUB,BAS>
and not same_as<SUB,bool>)
inline constexpr BAS
preClamp (BAS const& rawVal)
{
static_assert (std::is_constructible_v<BAS,SUB const&>
,"Value domains must overlap at least partially "
"and values must be cross-constructible within overlap");
auto upperBound = numeric_limits<SUB>::max();
auto lowerBound = numeric_limits<SUB>::lowest();
return isLarger_safe(rawVal, upperBound)? BAS(upperBound)
: isLower_safe (rawVal, lowerBound)? BAS(lowerBound)
: rawVal;
}
/** special case: trigger a bool at the 0.5 level */
template<same_as<bool> B, floating_point F>
inline constexpr F
preClamp (F const& rawVal)
{
return 0.5 < rawVal? F{1} : F{0};
}
template<typename V, typename X>
inline void
assignConverted (V& targetVal, X const& srcVal)
{
if constexpr (std::is_assignable_v<V&, X const&>)
{
targetVal = preClamp<V> (srcVal);
}
else
if constexpr (std::is_constructible_v<V, X const&>)
{
targetVal.~U();
new(&targetVal) V (preClamp<V> (srcVal));
}
else
static_assert (!sizeof(X), "this type conversion is not supported");
}
template<typename 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_TYPE_HANDLER_H*/

View file

@ -163,7 +163,16 @@ namespace test{
verify_valueJoining()
{
Scale<float> scale;
}
}
/** @test
* @todo WIP 9/26 🔁 define ⟶ implement
*/
void
verify_NominalScale()
{
}
};

View file

@ -85572,8 +85572,52 @@
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1789164533222" ID="ID_1017590695" MODIFIED="1789164556832" TEXT="Problem: kann auch vom Typ des Beitrags (und des Basis-Werts) abh&#xe4;ngen">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1789164561486" ID="ID_376197998" MODIFIED="1789164593909" TEXT="bedeutet: die darauf aufbauende Provision mu&#xdf; ggfs. einen double-Dispatch machen"/>
<node CREATED="1789165221192" ID="ID_388299163" MODIFIED="1789165234448" TEXT="die Basis-Implementierung mu&#xdf; dann generisch sein und von Scale bereitgestellt werden"/>
<node CREATED="1789165187065" ID="ID_1109273270" MODIFIED="1789165213989" TEXT="Konsequenz &#x27f9; die Scale mu&#xdf; hier zwei Typ-Parameter nehmen"/>
<node CREATED="1789165221192" ID="ID_388299163" MODIFIED="1789687877259" TEXT="die Basis-Implementierung mu&#xdf; dann generisch sein und von Scale bereitgestellt werden">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Zwar k&#246;nnte man auch so argumentieren, da&#223; zun&#228;chst der Quell-Feed so adaptiert werden sollte, da&#223; der Basistyp gleich ist...
</p>
<p>
Bei genauerer Betrachtung stellt sich jedoch heraus, da&#223; dies keine gute Idee ist, denn...
</p>
<ul>
<li>
zum Einen w&#228;re das jeweils ein weiterer dynamisch hinzugef&#252;gter Dekorator (und damit zus&#228;tzlicher Overhead), obwohl doch die eigentliche Umwandlung auf Basis bekannter Typen bereits zur Compile-Zeit gemacht werden k&#246;nnte
</li>
<li>
und au&#223;erdem wirft man damit die detailierte Information &#252;ber den Quell-Feed weg (oder reicht sie dann eben doch weiter, wodurch dieser Schritt dann redundant w&#252;rde) obwohl eben diese zus&#228;tzliche Information durchaus in einen besser angepa&#223;ten Rechenweg umgesetzt werden k&#246;nnte
</li>
</ul>
</body>
</html>
</richcontent>
</node>
<node CREATED="1789165187065" ID="ID_1109273270" MODIFIED="1789165213989" TEXT="Konsequenz &#x27f9; die Scale mu&#xdf; hier zwei Typ-Parameter nehmen">
<node CREATED="1789679141799" ID="ID_907734481" MODIFIED="1789679174971" TEXT="die eigentliche Wert-Umwandlung ist hier vergleichsweise einfach">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
...weil wir die Basis-Typen f&#252;r Parameter entsprechend eingeschr&#228;nkt haben
</p>
</body>
</html>
</richcontent>
</node>
<node CREATED="1789679178010" ID="ID_641923682" MODIFIED="1789679196411" TEXT="dazu gen&#xfc;gt die preClamp-Funktion (generisch, mit Varianten)"/>
<node CREATED="1789679234347" ID="ID_562560611" MODIFIED="1789679253968" TEXT="diese h&#xe4;ngt aber (mit diversen weiteren Funktionen) in param-type.hpp"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1789679364793" ID="ID_1241002579" MODIFIED="1789681307994" TEXT="extrahieren in type-handler.hpp">
<arrowlink COLOR="#723f52" DESTINATION="ID_517472625" ENDARROW="Default" ENDINCLINATION="368;-30;" ID="Arrow_ID_914959509" STARTARROW="None" STARTINCLINATION="-203;15;"/>
<icon BUILTIN="yes"/>
<icon BUILTIN="flag-yellow"/>
</node>
</node>
<node CREATED="1789165444623" ID="ID_1649021234" MODIFIED="1789165459491" TEXT="vereinfachte Variante f&#xfc;r identische Scale"/>
</node>
</node>
@ -85596,6 +85640,12 @@
</html></richcontent>
<icon BUILTIN="yes"/>
</node>
<node CREATED="1789681257536" ID="ID_1700836715" MODIFIED="1789681261057" TEXT="Implementierung">
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1789681263702" ID="ID_517472625" MODIFIED="1789681294765" TEXT="type-handler.hpp verwenden">
<linktarget COLOR="#723f52" DESTINATION="ID_517472625" ENDARROW="Default" ENDINCLINATION="368;-30;" ID="Arrow_ID_914959509" SOURCE="ID_1241002579" STARTARROW="None" STARTINCLINATION="-203;15;"/>
<icon BUILTIN="flag-yellow"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1789002587495" ID="ID_509669988" MODIFIED="1789002595532" TEXT="Design nochmal reflektieren">
<icon BUILTIN="pencil"/>
@ -86059,8 +86109,8 @@ class ScaleRegistry
</node>
<node COLOR="#338800" CREATED="1789171875485" ID="ID_1356870727" MODIFIED="1789171905187" TEXT="conform">
<icon BUILTIN="button_ok"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1789171881280" ID="ID_339852589" MODIFIED="1789250353458" TEXT="zyklische Scale behandeln">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1789171881280" FOLDED="true" ID="ID_339852589" MODIFIED="1789676093985" TEXT="zyklische Scale behandeln">
<icon BUILTIN="button_ok"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1789250357099" ID="ID_684124457" MODIFIED="1789250365217" TEXT="Vorsicht Falle">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1789250367429" ID="ID_1987781341" MODIFIED="1789250382816" TEXT="modulus und unsigned"/>
@ -87272,8 +87322,7 @@ Prop.1: dist%3 1 2 0 1 2 0
...da ein Test eigentlich mehr machen sollte, als nur nochmal den Code zu wiederholen; und hier passiert nicht mehr, als ein cap an der boundrary, auf einem komplett offensichtlichen und einfachen Code-Pfad
</p>
</body>
</html>
</richcontent>
</html></richcontent>
</node>
<node CREATED="1789607312019" ID="ID_1154758301" MODIFIED="1789607325207" TEXT="aber man k&#xf6;nnte einen anderen Datentyp testen (z.B. Float)"/>
<node CREATED="1789607333006" ID="ID_1782430086" MODIFIED="1789607343123" TEXT="und zeigen da&#xdf; auch das cyclicWrap hier integriert ist"/>
@ -87300,6 +87349,9 @@ Prop.1: dist%3 1 2 0 1 2 0
<icon BUILTIN="yes"/>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1789675604211" ID="ID_201071963" MODIFIED="1789676070358" TEXT="verify_valueJoining">
<icon BUILTIN="pencil"/>
</node>
</node>
</node>
</node>