From 5df0252cc1f3e31695072a04367bb901390365d4 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 3 Jul 2026 19:55:55 +0200 Subject: [PATCH] Parameters: preliminary requirement analysis In the current stage, a design-draft is required that is sufficiently adequate to be used as code placeholder and to evolve into the full specification eventually. - we need a single parameter container type with value semantics - parameters must support a selection of basic value types, with conversions - the value range can be limited, and a parameter can be associated with a ''Scale'' (stepped, continuous, logarithmic) - a parameter can be linked to a functional source - parameters are (virtually) copyable --- src/lib/par/domain.hpp | 63 + src/lib/par/param-type.cpp | 37 + src/lib/par/param-type.hpp | 52 + tests/16parameter.tests | 5 + tests/lib/par/parameter-domain-test.cpp | 94 ++ wiki/thinkPad.ichthyo.mm | 1433 ++++++++++++++++++++++- 6 files changed, 1674 insertions(+), 10 deletions(-) create mode 100644 src/lib/par/domain.hpp create mode 100644 src/lib/par/param-type.cpp create mode 100644 src/lib/par/param-type.hpp create mode 100644 tests/lib/par/parameter-domain-test.cpp diff --git a/src/lib/par/domain.hpp b/src/lib/par/domain.hpp new file mode 100644 index 000000000..968a6fe82 --- /dev/null +++ b/src/lib/par/domain.hpp @@ -0,0 +1,63 @@ +/* + DOMAIN.hpp - represent the value domain of parameters + + Copyright (C) + 2026 Hermann Vosseler + +  **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 + + + + +namespace lib { +namespace par { + + + + /** + * 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: + virtual ~Domain(); ///< this is an interface + }; + + + +}} // namespace lib::par +#endif /*LIB_PAR_DOMAIN_H*/ diff --git a/src/lib/par/param-type.cpp b/src/lib/par/param-type.cpp new file mode 100644 index 000000000..69ba479ce --- /dev/null +++ b/src/lib/par/param-type.cpp @@ -0,0 +1,37 @@ +/* + ParamType - parameter scale and type descriptor + + Copyright (C) + 2026 Hermann Vosseler + +  **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/param-type.hpp" + +namespace lib { +namespace par { + + + /* emit VTables in this translation unit... */ + + ParamType::~ParamType() { } + Domain::~Domain() { } + + + +}} // namespace lib::par diff --git a/src/lib/par/param-type.hpp b/src/lib/par/param-type.hpp new file mode 100644 index 000000000..2a1ff834e --- /dev/null +++ b/src/lib/par/param-type.hpp @@ -0,0 +1,52 @@ +/* + PARAM-TYPE.hpp - parameter scale and type descriptor + + Copyright (C) + 2026 Hermann Vosseler + +  **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. + ** A ParamType descriptor implicitly also encodes the concrete value type, 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" + + +namespace lib { +namespace par { + + + + /** + * 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. + * Furthermore, the interface exposes allowed conversion and conforming operations. + */ + class ParamType + { + public: + virtual ~ParamType(); ///< this is an interface + }; + + + +}} // namespace lib::par +#endif /*LIB_PAR_PARAM_TYPE_H*/ diff --git a/tests/16parameter.tests b/tests/16parameter.tests index dd5d84754..7e6ebe492 100644 --- a/tests/16parameter.tests +++ b/tests/16parameter.tests @@ -5,3 +5,8 @@ TESTING "Library Test Suite: parameters" ./test-suite --group=param PLANNED "Parameter Values" Parameter_test < + +  **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/format-cout.hpp" + +//#include +//#include +//#include + +//using std::string; +//using std::vector; +//using std::swap; + + +namespace lib { +namespace par { +namespace test{ + +// using lumiera::error::LUMIERA_ERROR_LOGIC; + + namespace {//Test fixture.... + + class Thing + { }; + + template + struct Some + { + X x; + }; + + typedef Some SomeThing; + + }//(End)Test fixture + + + + + + + + + + /**************************************************************************//** + * @test cover properties of generic parameter containers. + */ + class ParameterDomain_test : public Test + { + + virtual void + run (Arg) + { + simpleUsage(); + verify_typeMarker(); + } + + + void + simpleUsage() + { + } + + + void + verify_typeMarker() + { + } + }; + + + /** Register this test class... */ + LAUNCHER (ParameterDomain_test, "unit lib"); + + + +}}} // namespace lib::par::test diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 3e3360afb..6832905eb 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -51638,6 +51638,22 @@ + + + + + + +

+ Hab mir das einmal im Detail durchdacht, und es müßte aufgehen... ...siehe die Notizen im Ticket. +

+

+ Knackpunkt ist aber dann wieder die Storage für den Funktor; wenn dieser kopierbar sein soll (muß er, für Iteratoren), dann kommt die ganze Komplexität hier durch die Hintertür zurück: dann wäre das nämlich ein PolymorphicValue +

+ +
+
+
@@ -63539,7 +63555,7 @@ - + @@ -63620,12 +63636,12 @@ - +

- ...und zwar ist das der Sprung in den »Invoker«. Das ist der Aufruf eines Funktionspointers, und bekommt zusätzlich zur Signatur auch noch eine Instanz-Adresse. Oder eben nicht, wenn in dem Funktor tatsächlich ein reiner Funktionspointer steckt. Der Code des Invokers ist jeweils anders für jeden unterschiedlichen Typ des aufgerufenen Funktor-Objekts. Ist der invocation-Operators des Zielt-Funktor-Objekts inline, dann kann er in die Invocator-Implementierung geinlined werden. Sonst erfolgt dahinter eben ein weiterer delegierender Funktionsaufruf — also wieder eine Indirektion mehr als bei direktem Aufruf. Ist in den Funktor zudem eine Member-Pointer gebunden, oder wurden Argumente closed, dann ist dahinter ein weiterer, delegierender Funktor notwendig + ...und zwar ist das der Sprung in den »Invoker«. Das ist der Aufruf eines Funktionspointers, und bekommt zusätzlich zur Signatur auch noch eine Instanz-Adresse. Oder eben nicht, wenn in dem Funktor tatsächlich ein reiner Funktionspointer steckt. Der Code des Invokers ist jeweils anders für jeden unterschiedlichen Typ des aufgerufenen Funktor-Objekts. Ist der invocation-Operators des Ziel-Funktor-Objekts inline, dann kann er in die Invoker-Implementierung geinlined werden. Sonst erfolgt dahinter eben ein weiterer delegierender Funktionsaufruf — also wieder eine Indirektion mehr als bei direktem Aufruf. Ist in den Funktor zudem ein Member-Pointer gebunden, oder wurden Argumente closed, dann ist dahinter ein weiterer, delegierender Funktor notwendig, mithin eine doppelte Indirektion

@@ -63971,8 +63987,9 @@ - + + @@ -63981,7 +63998,7 @@

- Wenn man für jede auftretende Kombination von Funktions-Signatur und konkret gebundenem Funktor eine Basisklasse generiert, dann erzeugt man eine Menge von Funktionen, die jeweils eine VTable liefern; und da andererseist so ein Function-Wrapper gerne aus generischem Code verwendet wird, kann daraus ein gefährlicher Hebel entstehen + Wenn man für jede auftretende Kombination von Funktions-Signatur und konkret gebundenem Funktor eine Basisklasse generiert, dann erzeugt man eine Menge von Funktionen, die jeweils eine VTable liefern; und da andererseits so ein Function-Wrapper gerne aus generischem Code verwendet wird, kann daraus ein gefährlicher Hebel entstehen

@@ -64022,7 +64039,7 @@

- das heißt, jede der beiden Operationen wir mit dem absoluten Minimum an Indirektion aufgerufen (nämlich ein Funktionspointer) + das heißt, jede der beiden Operationen wird mit dem absoluten Minimum an Indirektion aufgerufen (nämlich ein Funktionspointer)

@@ -81446,7 +81463,1208 @@
- + + + + + + +

+ Thema: Automation +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...bereits unmittelbar vor dem Beginn des Lumiera-Projekts bezogen sich meine Beiträge zu Cinelerra vor allem auf die Automation, und die Schwierigkeiten, damit zuverlässig zu arbeiten. In parktisch allen Software-Paketen, mit denen ich bisher gearbeitet habe, stelle ich den gleichen »gedanklichen Kurzschluß« fest, welcher bereits in dem Begriff »Automation« liegt +

+ +
+
+ + + + + + + + + + + + + + + + + + + + +

+ Paket: lib/auto +

+ +
+
+ + + + + +

+ damit man bereits die Stellen markieren kann, an denen ein Parameter erwartet wird +

+ +
+
+ + + + + + + + + + + + + + + + + +

+ kein Destruktor-Aufruf möglich +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ effektiv liegen die beiden Varianten nah beieinander, wenngleich sie auch sehr unterschiedlich im Code aussehen +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ das heißt, wünschenswert wäre eine unchecked-selber-schuld-Variante +

+ +
+
+ + + + +

+ Ich erwarte, daß diese Parameter durchaus häufig verwendet werden; daher möchte man kein Gewürge haben, wenn es um Zugriffe wie Integral/Signed/Unsigned geht, oder um Zugriffe auf Strings und String-Konstanten. Dasistleichtergesagtalsgetan +

+ +
+
+
+ + + + + + + + + + + +

+ andernfalls muß ich eine VTable mitschleppen, und das würde mir Bauchschmerzen machen, da die Parameter-Auswertund nun doch in die Render-Engine hineinreicht +

+ +
+
+
+ + + + + +

+ meint: man kann eine opaque Type-ID verwenden, die durch den Zugriff selber erzeugt wird (zur Compile-Zeit), aber dann zur Laufzeit gegen einen Marker im Parameter-Objekt geprüft wird +

+ +
+
+ + + + +

+ Das ist durchaus gravierend; die meisten opaque-Holder, die ich bisher programmiert habe, verwenden entweder so eine Doppel-Kapsel mit innen eingeschlossener VTable (z.B. lib::Variant), oder aber eine bekannte gemeinsame Basisklasse. +

+ +
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Zieltyp kann aus Quelltyp erzeugt werden +

+ +
+
+ + + + +

+ Zieltyp kann den Quellwert vollständig repräsentieren (eindeutig und in-domain) +

+ +
+
+ + + + +

+ Quellwert kann in den Zielwert transformiert werden, allerdings möglicherweise unter Informationsverlust (clamping) +

+ +
+
+ + + + +

+ der Quelltyp kann (eineindeutig?) in den Zietyp transformiert werden; man denke an zwei finite-domain-Skalen mit komplett disjunkten Ennumeratoren — hier ist viel Potential für Komplexität und tückische Fallen gegeben +

+ +
+
+
+
+
+ + + + + + + +

+ ...das setzt natürlich eine direkte Lookup-Tabelle pro Dimension vorraus, und die Skala müßte außerdem (vermutlich) auch noch selber wieder auf einen Basistyp verweisen +

+ +
+
+
+ + + + + + + + + + + + + + + +

+ Das geht nur, wenn man jedwede Verwendung in der Static-Init-Phase zuverlässig ausschließen kann. Denn dann könnte man einfach eine statische Instanz irgendwo in einen statischen Pointer legen, oder einen statisch initialisierten unique-Ptr verwenden +

+ +
+ + + + + +

+ Die ID ist einfach dieser Pointer auf den Typ-Deskriptor. Zugriff ist ein einziger virtueller Speicherzugriff. Den Wert sieht jeder Thread, da er vor allen Threads schon im Speicher abgelegt wurde +

+ +
+
+ + + + +

+ Die static-Init von DSOs erfolgt im Moment des Ladens. Und das kann schlimmstenfalls sogar concurrent passieren. Wenn man zudem eine Index-Lookup-Tabelle braucht, dann ist das sogar ein knock-out-Kriterium (man müßte die Tabelle dynamisch vergrößern, bzw. eine Hashtable reorganisieren können). Andernfalls, wenn nur die Pointer auf die Typ-Deskriptoren direkt verwendet werden, besteht ein Deregistrierungs-Problem: nachdem ein DSO ent-laden wurde, könnte der btr. Deskriptor-Pointer trotzdem noch irgendwo im Speicher »herumfliegen« ⟹ Memory-Corruption (wenn der Deskriptor selber auf dem Heap liegt), bzw. SEGFAULT +

+ +
+
+ + + + +

+ Es ist nicht möglich, irgend ein Ergebnis der Laufzeit-Auswertung in eine Skalen-Grenze einfließen zu lassen. Wirklich nicht! Und das ist eine sehr starke Einschränkung, denn es schließt u.A. aus... +

+
    +
  • + projekt-spezifische Definitionen in die Konfiguration zu legen +
  • +
  • + Ennumeratoren einer Finite-Domain zur Laufzeit zu verwalten / ändern +
  • +
+ +
+ +
+
+ + + + + + + +

+ Diese pragmatische Einschränkung ist meistens völlig unproblematisch, und vermeidet jedwede Probleme mit invalide gewordenen Skalen; Knackpunkt ist allerdings, daß die Typ-Deskriptoren selber in ihrer Implementierung niemals auf dynamisch nachgeladenen Code zurückgreifen können dürfen wollen +

+ +
+
+ + + + +

+ Das läßt sich abmildern auf eine Art double-checked-Locking, was aber wenigstens einen atomoic-Access impliziert. Also hat jeder Laufzeit-Zugriff eine Penalty mit Faktor 2-5 (siehe auch: Zugriff auf Thread-Local). Das ist u.U. so geringfügig, daß es nicht auffällt, vor allem, wenn man dann den Parameter-Wert, bzw. einen Auswertungs-Funktor aus dem generischen System herausziehen und direkt materialisieren kann. +

+ +
+
+ + + + +

+ Auch diese Einschränkung ist vermutlich theoretischer Natur und fällt nicht ins Gewicht +

+ +
+
+ + + + +

+ Das wäre möglich, wenn man die eigentliche Typ-ID dann in den Deskriptor legt. Insofern stellt die Registrierung lediglich sicher, daß keine duplikaten IDs vergeben werden; ansonsten wird sie eigentlich nur für Zugriffe über einen statischen Typ benötigt, oder man fällt für den letzteren Fall dann doch wieder auf Variante-1 zurück und erzeugt ein statisches Trampolin (im letztgenannten Fall macht man allerdings auch wieder die gefährlichen Grenzfälle auf, wenn die erste Verwendung aus einem DSO erfolgt, wobei das dann allerdings eingeschränkt wäre rein auf statisch repräsentierbare Skalen) +

+ +
+
+
+
+
+
+ + + + + + + + +

+ Das ist ein schwierig zu beurteilender Grenzfall. Tatsächlich sind 64bit-Kollisionen ziemlich unwahrscheinlich, und man fährt ganz gut damit, sie zu ignorieren. Allerdings wären die Konsequenzen einer Kollision in dem Fall durchaus dramatisch (Datenkorruption, Speicherkorruption, schleichende nicht-reproduzierbare Fehler). +

+ +
+
+ + + + +

+ ...das wäre nur denkbar, wenn es eine Abkürzung wäre, und von einer echten Verifkation gefolgt wäre; oder wenn man sowiso dann einen direkten Pointer nimmt (aber dann wäre auch die kombinierte Hash-ID eigentlich nicht mehr notwendig) +

+ +
+
+ + + + + + + + + +

+ Denn man kann die Anzahl der Typ-Instanzen ohne größere Bedenken als begrenzt ansetzen. Stark begrenzt sogar, niemand hat Speicher für 1 Million Typ-Deskriptoren +

+ +
+
+ + + + +

+ Allerdings nur unter der Einschränkung, daß die Index-Tabelle selber an einer fixen Stelle im Speicher liegen bleibt; man müßte sie also per compile-Time-Konstante limitieren +

+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ heißt: die effektiven Basis-Typen, mit denen ich auf einen Paramter zugreifen kann, sind zur compile-time limitiert; effektiv ist das Universum möglicher Typen sowiso limitiert +

+ +
+
+
+
+ + + + + + + + +

+ Klingt nach Selbstmord aus Angst vor dem Tod +

+ +
+
+ + + + +

+ und jeder Eintrag hier erfordert dann tatsächlich ein Lock; das wäre verschmerzbar, dann man muß da nur durch, wenn man einen neuen Prototyp anlegt; sobald man einen solchen in der Hand hat, hat man ja den Pointer +

+ +
+
+
+
+ + + + +

+ pragmatisch betrachtet ist das Problem vermutlich nicht relevant +

+ +
+ + + + + +

+ wenn über irgendwelche Umwege dann doch ein weiterer Typ-Deskriptor registriert wird, der mit einem anderen effektiv äquivalent ist; das stellt als Solches überhaupt kein Problem dar, man muß nur bei der Entscheidungslogik auf diesen Fall gefaßt sein +

+ +
+ +
+ + + + +

+ ...und über deren Korrektheit entscheidet der Code, nicht der Registrierungszustand +

+ +
+
+ + + + +

+ und dafür sorgt bereits std::numeric_limiits +

+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + +

+ obwohl sich beide vehement als "quantitativ" bezeichnen +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + +

+ Es wäre gefährlich, sofort nach einer »generischen Form« zu greifen; denn eine solche Form muß sich herausbilden, indem man darauf zuarbeitet. Also werde ich nun nicht versuchen, ein Konstrukt zu schaffen. +

+ +
+ +
+
+
+ + + + + + + + +

+ Das bedeutet: der Prototyp ist nicht nur irgend eine Ansammlung von Merkmalen, sondern im Setup der Applikation oder des Modells werden bestimmte Prototypen etabliert, um auf Rollen und Arbeitsmuster zu verweisen, welche jenseits des implementierten Konstrukts liegen. Der Prototyp dient auch als Marker auf der Metadaten-Ebene, und die Merkmals-Ausprägungen werden so eingerichtet, daß sich um einen Prototyp herum eine Klasse bilden kann. +

+

+ +

+

+ Für sich genommen ist das alles recht gewöhnlich; bemerkenswert ist nur, daß wir definitorisch eine Lücke zwischen dem Prototyp als Marker lassen, und der Einrichtung der Ausprägungen. Der dadurch geschaffene Spielraum soll den Regeln und der Regelbasierten Konfiguration zugänglich sein. So will ich mit den Stream-Types umgehen, und mit den Placement-Prototypes. Und entsprechend auch mit den Parameter-Prototypes: es gibt also z.B. diskret-Integrale Parameter, oder es gibt Gain-Parameter und Farb-Parameter oder es gibt Auswahl-Konfigurationen. Dem entspricht jeweils eine gewisse Ausprägung an primitiven Eigenschaften (welche auch nicht komplett abgeschlossen sind). Welche Ausprägung aber genau, ist nur durch einer Art Konfiguration festgelegt; der operative Teil des Codes macht heirüber möglichst keine impliziten Annahmen. +

+ +
+
+
+
+ + + + + + + + + + + + + + + + + + +

+ Im Moment mache ich nur eine Skizze zu dem Thema, und die Entwicklung soll lediglich so weit gehen, daß man sieht »wie der Hase läuft« und daß man »Parameter« in eine Map legen könnte (für die Render-Invocation) +

+ +
+ + + + + + + + +

+ Invariante: der Wert im Buffer ist stets konform +

+ +
+ +
+
+ + + + +

+ Implementierung als PolymorphicValue +

+ +
+ + + + +

+ Denn die Design-Analyse hat gezeigt, daß der »Opaque-Buffer«-Ansatz inhaltlich sehr nahe an einer »Inline-Funktion« liegt, und letztere ist aber strukturell am effizientesten zu implementieren als PolymorphicValue — was vor allem an der Notwendigkeit eines »Managers« liegt, um das Instanz- und Copy-Verhalten zu bestimmen. Die Begründung ist, daß ein virtuelles Interface tatsächlich bereits die effizienteste Reallisierung darstellt, sobald es mehrere virtualisierte »methoden« gibt, denn sie faßt das Instanz-Management und den Zugang zu einem gemeinsamen Datenkontext bereits optimal zusammen. Selbst wenn man bewußt nicht die Implementierung aus der C++-Runtime nimmt, (wie im Fall der std::function) so läuft die Struktur doch immer wieder darauf hinaus, Zugang zu einem gemeinsamen Datenpaket zu bekommen (egal ob das nun per PImpl im Heap liegt, oder direkt in einem Inline-Buffer). Denn ohne solchen gemeinsamen Zustand kann es auch kein tangibles Verhalten geben.... +

+ +
+ +
+ + + + +

+ derzeit kann ich nicht entscheiden, ob die Auswertung "pull" oder "push" ist. Wobei allerdings irgend eine Art von Auswertungs-Trigger sehr wahrscheinlich ist; insofern läuft die pull/push-Unterscheidung lediglich darauf hinaus, wie weit das Resultat nach einem solchen Trigger transportiert wird; im Falle von "pull" würde nur eine Auswertung der Basis-Treiber und Kurven erfolgen, während alle nachgelagerten Aggregationen und Transformationen on-demand erfolgen. Die Automationskurven wären mithin eine Art Datenquelle, und die weitere Auswertung würde in Nodes erfolgen. +

+

+ +

+

+ Wie dem auch sei — für die Struktur ergibt sich, daß jeder Zugriff eine Methode aufruft, welche aber u.U. rekursiv delegiert. +

+ +
+
+ + + +
+ + + + + + +

+ ...und das hat zwei Vorrausetzungen... +

+
    +
  • + ein lokaler Wert kann direkt ohne Anpassung verwendet werden, weil keine weitere Auswertung notwendig ist +
  • +
  • + und der implizite Ziel-Typ ist mit dem Wert-Typ Domain-kompatibel (beide haben den gleichen Basis-Typ und der Wert hat eine kleinere Domäne) +
  • +
+ +
+
+ + + + + + + +

+ ...auf die die Auswertung zurückfällt, wenn kein direkt-Zugriff möglich ist. Das ist dann wohl einigermaßen komplex zu implementieren, denn dieser indirekt Zugriff erfordert einen double-dispatch (Visitor) +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

+ weil der Buffer woanders liegt, nämlich im Parameter selber +

+ +
+
+ + + + +

+ das ist nicht besonders effizient ! +

+ +
+ +
+ + + + + + +

+ ...sondern nur die eine Indirektion über den Type-Pointer +

+ +
+
+ + + + +

+ Wenn ein Client verlangt get<TY>(), dann kann der Compiler die BaseDomain<TY> erstellen und direkt deren Hash-ID ermitteln (consteval). Diesen 64bit-Wert vergleicht er mit der entsprechenden ID, die per Type-Pointer gefunden wird. Wenn beide gleich sind, kann der Zugriff einfach direkt auf den Buffer durchgehen, mit force-cast. +

+ +
+
+
+
+ + + + + + + + +

+ jedoch nicht für die base-Domain-ID +

+ +
+
+ + +
+ + + + + +

+ lediglich an einem festen Offset, wie ein sub-Objekt; denn der PolymorphicValue ist ja vor allem ein Casting-Trick. Insofern zählt das effektiv nicht als »Indirektion« (es ist i.d.R bereits im CPU-Cache, und der Offest steht fest im Assembly) +

+ +
+
+ + + + + + + +

+ selbst bei formal gleichem Grad an Virtualisierung / Indirektion für die Funktions-Aufrufe +

+ +
+
+ + + + + + + + + + + + + + + +

+ ...also möglichst viele Operationen direkt über dieses verpackte Disposition-Objekt dispatched werden, also dieses direkt auf der Typ-Ebene aus Bausteinen per Vererbung zusammengefügt würde. Denn sonst wäre es nur ein in-Place-Buffer mit virtual-copy-Support (den wir eigentlich hier nicht brauchen, da wir auch bitwise-copy machen könnten, und die Kopierbarkeit für Parameter sowiso nicht besonders bedeutsam ist, da sie i.d.R. zu einem Objekt mit Identität gehören) +

+ +
+
+ + + + +

+ Und das macht mir Sorge, denn ich gehe davon aus, daß Parameter eine sehr häufige Einrichtung werden, und man durchaus dutzende Parameter an einem Objekt haben könnte. Einen Typ-Descriptor würde ich noch akzeptieren (Faktor 2), aber sicher nicht 3-6 weitere Steuerwerte, die obendrein ziemlich redundant sind +

+ +
+
+
+ + + + +

+ wo ich doch heute Abend noch so stolz auf meine Errungenschaften war... +

+ +
+ + + + + + + +

+ nochmal zur Erinnerung: bin in dieses ganze Riesen-Thema nur reingeschlittert, weil ich den processKey als Job-Parameter aufgeben mußte (und der war eine so »schöne Lösung«, die das Thema markiert und auf später verschiebt). Dann habe ich gesagt, einen Zacken nachlegen und mal eben ein Design für Parameter machen. Ich weiß aber noch gar nicht, wo genau die Parameter sonst noch eingesetzt werden sollen. Ich weiß nur, daß ich sie universell einsetzen möchte. +

+

+ Im Besonderen ist die ganze Auswertung von Parameter komplett ungeklärt. Diese Auswertung passiert irgendwie in Response auf Interaktionen im GUI (vor allem: ändern der Playback-Position, aber natürlich auch direkte Wert-Tweaks). Es ist klar, daß die Auswertung nicht im GUI selber stattfinden wird, denn dafür haben wir die Trennung (die Commands, den UI-Bus und das Event-Sourcing). Wahrscheinlich wird eine Auswertung in einer Projektion der Events passieren, entweder nach einem Builder-Lauf, oder in vielen Fällen anstelle eines Solchen (wenn gar nichts neu gebaut werden muß). Auch der Builder ist ja konzeptionell eine Projektion. Dann aber ist andererseits auch klar, daß Parameter irgendwie in der Render-Engine ausgewerter werden, und zwar dort verpackt in einen Automations-Funktor. Möglicherweise wird diese Auswertung aber auch eine Art compilierte Variante sein. +

+

+ +

+

+ In beiden Fällen hab ich keinerlei Anhaltspunkt, welche Größenordnung an Aufwand das ganze Parameter-Thema verursacht; ich hab lediglich ein ungutes Bauchgefühl — denn das ganze Konzept schreit direkt danach, auf einen generischen Level gehoben zu werden, und dann pervasiv zum Einsatz zu kommen, einschließlich Introspektion von Parametern. +

+ +
+
+
+
+ + + + + + + +

+ also das konfigurierbare Verhalten, ob und wann ein pull auf eine Datenquelle stattfinden soll. Sehr wahrscheinlich wird es dafür eben mindestens ein prüf-Prädikat geben, neben der eigentchen Auswertungsfunktion. Genauer gesagt, wenn es mehr als ein einfaches bool-Merkmal ist, dann wird eine VTable effizienter als mehrere Funktoren. Meine Einschätzung ist, daß das höchstwahrscheinlich der Fall sein wird, allein schon, weil die Verknüpfung mit einem lokalen Value auch von der Skala abhängt. +

+ +
+
+ + + + + + + + + + + + +

+ hängt mit der Kopierbarkeit zusammen +

+ +
+ + + +

+ ohne VTable müßte das Objekt non-copyable sein +

+ +
+
+ + + + + + + + + + + +

+ aber sie ist nicht kopierbar (fälschungssicher) +

+ +
+
+ + + + + + + + +

+ Es liegt an ihnen ein Element der Bedeutung und Relevanz, welches aus Sicht des technischen Konstrukts als kontingent erscheint: es läßt sich nicht aus den Eigenschaften des reinen Funktionierens und dem damit verbundenen Informationsgehalt herleiten +

+ +
+
+
+ + + + + + + + + + +

+ Das ist die einzige echte Lösung im Umgang mit der Komplexität. Alle anderen Ansätze verschieben nur die Probleme in einen Bereich, der im Moment nicht so stört, und erkaufen das durch zusätzliche Komplexität. +

+ +
+
+ + + + +

+ eine solche Struktur muß einmal aufgebaut werden, aus einem Build-Kontext mit erweiterten Bezügen; danach werden diese unsichtbar gemacht (Type-Erasure), aber nun ist das subsidiäre Element frei gestellt, und kann nur noch mit den einmal etablierten Beziehungen arbeiten. Entweder, man kann die zusätzliche Komplexität nicht abstreifen (also eigentlich keine echte Subsidiarität), oder das subsidiäre Element wird zum Unikat und muß global alle es betreffenden Aufgaben meistern. Das wirderspricht einer Aufteilung und anderweitigen Strukturierung der Verarbeitungsvorgänge. Sei es, in dem man einen Prozeß ablaufen läßt, oder sei es, in dem man parallelisiert +

+ +
+
+
+ + + +
+
+ + + + +

+ Konsequenz: pull-Prinzip ist verfplichtend +

+ +
+ + + + + + +

+ das ist die Leistung der Subsidiarität: komplexe Zusammenhänge konnen lokal geregelt werden, und müssen nicht externalisiert werden +

+ +
+
+
+
+ + + + +
+ + + + + + + + + + + + + +

+ TY wird identifiziert mit BaseDomain<TY> +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...insofern alle weiteren Belange, die sich nicht auf Anordnung und Berechnung von Mediendaten herunterbrechen lassen, über Parameter, Automation und dynamische Verknüpfung derselben dargestellt werden müssen. +

+ +
+ +
+
+
+
@@ -81776,6 +82994,15 @@
+ + + + + + + + + @@ -112569,7 +113796,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -112579,9 +113806,11 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + @@ -112656,7 +113885,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)

- Es ist eine ungeklärte Frage, ob Abkürzungen in der Render-Engine sinnvoll sind. Diese Frage kann nur empirisch geklärt werden, und vermutlich niemals abschließend. Erfahrung im high-Performance-Computing zeigt, daß Schematisierung oft der Einzelfallbehandlung überlegen ist — es sei denn, der Einzelfalls stellt selbst ein Schema dar + Es ist eine ungeklärte Frage, ob Abkürzungen in der Render-Engine sinnvoll sind. Diese Frage kann nur empirisch geklärt werden, und vermutlich niemals abschließend. Erfahrung im high-Performance-Computing zeigt, daß Schematisierung oft der Einzelfallbehandlung überlegen ist — es sei denn, der Einzelfall stellt selbst ein Schema dar

@@ -117850,6 +119079,42 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+ + + + +

+ Schließlich habe ich nicht umsonst gründlich über die Architektur nachgedacht; sie muß und soll sich nun bewähren, und dann erst wird Bilanz gezogen! +

+ +
+
+ + + + + + + +

+ ...den brauchen wir ohnehin, und ich wollte in dem Bereich bewußt nicht mit Exceptions arbeiten (außer für die wirklich unvorhersehbaren Fälle, in denen aber die gesamte Engine heruntergefahren wird). Insofern würde es genau zur Vision passen, diese Art sekundäre Koordination hier zu bündeln. +

+ +
+ +
+ + + + +

+ Auch in diesem Fall gibt es keinen grundsätzlichen Widerspruch; also muß ich lediglich etwas härter mit mir selber sein, und das eben auch noch jetzt stemmen. Gebraucht wird ein Parameter-System ohnehin, und im Prinzip weiß ich ohne große Überlegung, wie man so etwas anlegt. +

+ +
+ +
+
@@ -185455,6 +186720,45 @@ unsigned int ThreadIdAsInt = *static_cast<unsigned int*>(static_cast<vo
+ + + + +

+ Rolle und Problem der Modelle +

+ +
+ + + + + + + + + + + + + + + + + + +

+ Wenn ein Belang irgendwo »gelöst« ist, kann man dorthin verweisen — aber nur wenn die Identität des Ortes, an den verwiesen wird, eindeutig, klar und endgülitg ist. Wenn dagegen die Koordination flexibel änderbar sein soll, darf es keine lokalen Bedingtheiten geben, die das Durchgreifen behinden.  +

+ +
+
+ + + + + +
@@ -185545,6 +186849,66 @@ unsigned int ThreadIdAsInt = *static_cast<unsigned int*>(static_cast<vo
+ + + + + + + + +

+ Im Zuge der Gestaltung wird vom Benutzer etwas festgestellt und festgelegt; der Benutzer sieht ein Eingriff ⟼ Reaktions - Verhalten, und erwartet instinktiv, daß sein Eingriff und seine Gestaltung vollständig und ursächlich das beobachtete Verhalten bedingen. Das ist in den meisten Fällen eine Täuschung. Man kann nur danch streben, möglichst alle Umstände konsistent und konstant zu halten, solange bis das Resultat materialisiert ist. Besonders brennend wird dieses Problem, sofern das Format eines Werkes gar nicht auf vollständige Materialisierung angelegt ist. +

+ +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +

+ wenn sie auf emergent behaviour einwirken +

+ +
+ + + + +

+ Automation wird in der Regel abgestimmt auf ein wahrgenommenes, resultierendes Verhalten des Gesamtsystems; was in der Automation festgelegt ist, ist nicht greifbar und bestimmbar, wenn man lediglich ihre Definition vorliegen hat. +

+ +
+ +
+
+
@@ -185672,6 +187036,27 @@ unsigned int ThreadIdAsInt = *static_cast<unsigned int*>(static_cast<vo + + + + + + + + + + + + + + + + + + + + + @@ -185718,6 +187103,33 @@ unsigned int ThreadIdAsInt = *static_cast<unsigned int*>(static_cast<vo + + + + +

+ Fragen der Verlässlichkeit +

+ +
+ + + + + +

+ der »Kontrakt«: alles bleibt so wie festgestellt +

+ +
+ +
+ + + + + + @@ -207224,7 +208636,8 @@ runner.run(test_suite())
- + +