LUMIERA.clone/src/lib/par/parameter.hpp
Ichthyostega 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

280 lines
8.3 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
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/error.hpp"
#include "lib/par/disposition.hpp"
#include "lib/polymorphic-value.hpp"
#include "lib/format-string.hpp"
#include "lib/util.hpp"
#include <utility>
namespace lib {
namespace par {
namespace err = lumiera::error;
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::_Fmt;
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&&);
Parameter& assignValueFrom (Parameter const&);
/* === 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));
}
template<typename VAL>
static Builder<VAL>
from (Parameter const& prototype)
{
return {prototype.getVal<VAL>() // note: may convert value type
,unConst(prototype).getPayload().getScaleID()};
}
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_{};
uint scaleID_{0};
Scale<VAL> scale_{};
public:
Builder() = default;
Builder (VAL initVal, uint scaleID)
: initVal_{initVal}
, scaleID_{scaleID}
{ }
// default copy operations
Parameter
build()
{
using ImplPackage = ParamData<VAL, CopySupportMarker>; //////////////////////////////////////////TICKET #1197 : remove the necessity to inject the CopySupportMarker
ImplPackage* typeTag{nullptr};
return scaleID_? Parameter{typeTag, initVal_, scaleID_} // pass-through scale unmodified
: Parameter{typeTag, initVal_, std::move (scale_)};
}
template<typename X>
Builder&&
withValue (X&& initVal)
{
initVal_ = std::forward<X> (initVal);
return std::move(*this);
}
Builder&&
maxVal (VAL upperBound)
{
if (scale_.minVal and *scale_.minVal > upperBound)
throw err::Invalid{_Fmt{"invalid parameter build: maxVal(%s) must be above defined minVal(%s)"}
% upperBound % *scale_.minVal};
maybeCloneScale();
scale_.maxVal = upperBound;
return std::move(*this);
}
Builder&&
minVal (VAL lowerBound)
{
if (scale_.maxVal and *scale_.maxVal < lowerBound)
throw err::Invalid{_Fmt{"invalid parameter build: minVal(%s) must be below defined maxVal(%s)"}
% lowerBound % *scale_.maxVal};
maybeCloneScale();
scale_.minVal = lowerBound;
return std::move(*this);
}
Builder&&
limited (VAL lowerBound, VAL upperBound)
{
if (lowerBound > upperBound)
throw err::Invalid{_Fmt{"invalid parameter build: minVal(%s) <= maxVal(%s)"}
% lowerBound % upperBound};
maybeCloneScale();
scale_.maxVal = upperBound;
scale_.minVal = lowerBound;
return std::move(*this);
}
Builder&&
cyclicWrap (VAL lowerBound, VAL upperBound)
{
if constexpr (std::is_same_v<VAL,bool>)
throw err::Logic {"attempt to configure a bool-parameter for cyclicWrap"};
if (lowerBound >= upperBound)
throw err::Invalid{_Fmt{"invalid parameter build: cyclicWrap requires open interval minVal(%s) < maxVal(%s)"}
% lowerBound % upperBound};
maybeCloneScale();
scale_.maxVal = upperBound;
scale_.minVal = lowerBound;
if constexpr (std::is_floating_point_v<VAL>)
{ // ensure that the float-precision can actually handle the given interval
scale_.cyclicLim = util::limit_cyclicWrap<VAL> (upperBound - lowerBound);
if (abs(*scale_.maxVal > *scale_.cyclicLim) )
throw err::Invalid{_Fmt{"invalid parameter build: maxVal(%s) for cyclicWrap is too large to handle precisely (limit: %s)"}
% upperBound % *scale_.cyclicLim};
}
else
{ // limit must be set but actual value does not matter (ints are precise)
scale_.cyclicLim = std::numeric_limits<VAL>::max();
}
initVal_ = util::limited(lowerBound, initVal_, upperBound);
return std::move(*this);
}
Builder&&
useLogScale()
{
maybeCloneScale();
scale_.metric = NAT;
return std::move(*this);
}
Builder&&
useBinLogScale()
{
maybeCloneScale();
scale_.metric = BIN;
return std::move(*this);
}
Builder&&
useDecibelScale()
{
maybeCloneScale();
scale_.metric = DEC;
return std::move(*this);
}
Builder&&
markAsFactor()
{
maybeCloneScale();
scale_.neutral = VAL(1);
return std::move(*this);
}
private:
void
maybeCloneScale()
{
if (not scaleID_) return;
UNIMPLEMENTED ("extract and possibly type-convert the scale definition");
scaleID_ = 0; // use explicitly defined scale instead
}
};
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);
}
/** transfer value with type conversion between two opaque parameters.
* @remark this requires a double dispatch, defected back from the target
*/
inline Parameter&
Parameter::assignValueFrom (Parameter const& srcParam)
{
unConst(srcParam).getPayload().assignValueTo (getPayload());
return *this;
}
}} // namespace lib::par
#endif /*LIB_PAR_PARAMETER_H*/