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:
parent
30636b3253
commit
6de07e9a36
6 changed files with 324 additions and 17 deletions
|
|
@ -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 ===== */
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
244
src/lib/par/type-handler.hpp
Normal file
244
src/lib/par/type-handler.hpp
Normal 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*/
|
||||
|
|
@ -163,7 +163,16 @@ namespace test{
|
|||
verify_valueJoining()
|
||||
{
|
||||
Scale<float> scale;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @test
|
||||
* @todo WIP 9/26 🔁 define ⟶ implement
|
||||
*/
|
||||
void
|
||||
verify_NominalScale()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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ängen">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
<node CREATED="1789164561486" ID="ID_376197998" MODIFIED="1789164593909" TEXT="bedeutet: die darauf aufbauende Provision muß ggfs. einen double-Dispatch machen"/>
|
||||
<node CREATED="1789165221192" ID="ID_388299163" MODIFIED="1789165234448" TEXT="die Basis-Implementierung muß dann generisch sein und von Scale bereitgestellt werden"/>
|
||||
<node CREATED="1789165187065" ID="ID_1109273270" MODIFIED="1789165213989" TEXT="Konsequenz ⟹ die Scale muß hier zwei Typ-Parameter nehmen"/>
|
||||
<node CREATED="1789165221192" ID="ID_388299163" MODIFIED="1789687877259" TEXT="die Basis-Implementierung muß dann generisch sein und von Scale bereitgestellt werden">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Zwar könnte man auch so argumentieren, daß zunächst der Quell-Feed so adaptiert werden sollte, daß der Basistyp gleich ist...
|
||||
</p>
|
||||
<p>
|
||||
Bei genauerer Betrachtung stellt sich jedoch heraus, daß dies keine gute Idee ist, denn...
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
zum Einen wäre das jeweils ein weiterer dynamisch hinzugefügter Dekorator (und damit zusätzlicher Overhead), obwohl doch die eigentliche Umwandlung auf Basis bekannter Typen bereits zur Compile-Zeit gemacht werden könnte
|
||||
</li>
|
||||
<li>
|
||||
und außerdem wirft man damit die detailierte Information über den Quell-Feed weg (oder reicht sie dann eben doch weiter, wodurch dieser Schritt dann redundant würde) obwohl eben diese zusätzliche Information durchaus in einen besser angepaßten Rechenweg umgesetzt werden könnte
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
<node CREATED="1789165187065" ID="ID_1109273270" MODIFIED="1789165213989" TEXT="Konsequenz ⟹ die Scale muß 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ür Parameter entsprechend eingeschränkt haben
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
<node CREATED="1789679178010" ID="ID_641923682" MODIFIED="1789679196411" TEXT="dazu genügt die preClamp-Funktion (generisch, mit Varianten)"/>
|
||||
<node CREATED="1789679234347" ID="ID_562560611" MODIFIED="1789679253968" TEXT="diese hä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ü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önnte einen anderen Datentyp testen (z.B. Float)"/>
|
||||
<node CREATED="1789607333006" ID="ID_1782430086" MODIFIED="1789607343123" TEXT="und zeigen daß 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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue