diff --git a/src/lib/several-builder.hpp b/src/lib/several-builder.hpp
index b54f0de8d..e9add188c 100644
--- a/src/lib/several-builder.hpp
+++ b/src/lib/several-builder.hpp
@@ -614,7 +614,7 @@ namespace lib {
,Policy::ALLOC_LIMIT)
,overhead)
,max (2*buffSiz, cnt*spread));
- // round down to an even number of elements
+ // round down to an straight number of elements
size_t newCnt = expandAlloc / spread;
expandAlloc = newCnt * spread;
if (expandAlloc < demand)
diff --git a/src/steam/engine/weaving-pattern-builder.hpp b/src/steam/engine/weaving-pattern-builder.hpp
index bb21896fb..62adb5068 100644
--- a/src/steam/engine/weaving-pattern-builder.hpp
+++ b/src/steam/engine/weaving-pattern-builder.hpp
@@ -22,7 +22,7 @@
/** @file weaving-pattern-builder.hpp
- ** Construction kit for establishing an invocation scheme for media calculations.
+ ** Construction kit to establish an invocation scheme for media calculations.
**
** @see turnout.hpp
** @see node-builder.hpp
@@ -49,7 +49,7 @@
//#include "lib/iter-adapter.hpp"
//#include "lib/meta/function.hpp"
//#include "lib/itertools.hpp"
-//#include "lib/util.hpp"
+#include "lib/util.hpp"
//#include
weil...
@@ -8875,9 +8873,7 @@
denn das ist der sinnvollste Fall für ein generisches Lambda
@@ -9397,9 +9393,7 @@
...also Programmierung analog zum Filter
@@ -10376,9 +10370,7 @@
habs mit FormatUtils_test bewiesen
@@ -11896,9 +11888,7 @@
Support für elided element
@@ -13768,9 +13758,7 @@
Anwendung delegiert an einen Serivce
@@ -16989,9 +16977,7 @@
wird der Link zwischen CoreService und UI-State dangling
@@ -22105,9 +22091,7 @@
und funktioniert gut für Clip-Widgets auf der Timeline
@@ -50369,9 +50353,7 @@
Dienste im UI, erreichbar über den Bus.
@@ -50708,9 +50690,7 @@
muß Instanzen einsetzen
@@ -50874,9 +50854,7 @@
...denn das GUI läuft ja synchron.
@@ -50940,9 +50918,7 @@
GUI: CmdAccessor
@@ -50986,9 +50962,7 @@
es könnte z.B. sein, daß man vom InteractionState
@@ -89593,8 +89567,7 @@ Date: Thu Apr 20 18:53:17 2023 +0200
,void(array<char*,1>,array<char*,1>) >>>
insofern verhält sich hier der Compiler formal und auch inhaltlich logisch korrekt
MoveOnly ◁— CONF ◁— SimpleWeavingPattern ◁— Turnout
indem man die Parameter in diesem Zweig voided
Da ProcNodes regelmäßig auf ihre Leads per direkter Referenz zugreifen, müssen sie sofort mit der Erzeugung im Speicher festgesetzt werden. Das war auch der Grund, warum ich den Builder in dieser limitierten Form aufgebaut habe: man muß sich zwangsläufig von den Quellen durch den Graphen aufwärts bewegen.
»one-shot«-Konstruktor, Objekte werden nur in den Definitionslisten erzeugt. Das erfordert entweder, extrem viele Detail-Konstruktorparameter durchzureichen (Problem der Verkopplung), oder eben ganze Teilkomponenten per RValue-Ref entgegenzunehmen und an den Zielpunkt zu schieben
Aufgrund der Komplexität der aufgebauten Strukturen ist nicht ersichtlich, ob der Compiler sich korrekt verhält; jedenfalls versucht er, den Copy-Konstruktor von engine::Turnout zu verwenden
man müßte lediglich der build()-Methode explizit einen SeveralBuilder übergeben; das ließe sich sogar generalisieren auf etwas Generisches, das eine emplace()-Methode bietet; in erster Näherung (C++17) wäre das ein offener Template-Parameter
+ Turnout ist sicher nicht trivially copyable +
+ + ++ wenn später in der Sequenz eine größere FeedManifold gebraucht wird, läßt sich der Spread nicht mehr durch Verschieben bereits bestehender Elemente korrigieren +
+ + ++ ...weil kein realloc() möglich ist; zudem muß auch die Extent-Größe des AllocationCluster bedacht werden (was sich jetzt hier, im Prototyp-Code noch gar nicht auswirkt, da wir ja Heap-Allokationen machen); wenn es der letzte aktive Builder ist und alle Anforderungen am Stück kommen, dann könnte man im AllocationCluster dynamisch justieren (nicht aber bei Heap-Allokation) +
+ + ++ die erhoffte »dynamische Belegung« +
++ ist unter den vorgegebenen Beschränkungen +
++ so nicht realisierbar +
+ + ++ Vorgabe: +
++ ...denn beide Lösungen unterliegen den strengeren Beschränkungen bezüglich der Allokation; das hat aber auch den Vorteil, daß man diese Entscheidung rein auf Basis der sonstigen Vor- und Nachteile abwägen und treffen kann +
+ + ++ Es besteht die Gefahr, daß für diese genau abgezirkelte Allokation ein festes Belegungsschema notwendig wird, und damit die Idee eines Builder-API im Kern negiert ist; denn in einem solchen Fall würde es zu einer Verkopplung zwischen der Logik in der Domain-Ontology und dem Datenlayout im Modell kommen +
+ + ++ für alle problematischen Daten-Builder gilt..... +
+ + ++ »problematisch« sind alle Daten-Builder, die +
++ es kommt hier nur zu einer gewissen Speicher-Verschwendung, wenn ein realloc() mit Umkopieren gemacht wird, da der AllocationCluster die alte Allokation einfach tot mitschleppt +
+ + ++ Man würde sich damit von dem vertrauten und klaren Builder-API verabschieden, und stattdessen ein Aufrufmuster verwenden, welches von mindestens der Hälfte aller Programmierer als „schwierig“ empfunden wird. Und zwar rein aus Gründen der Implementierungs-Mechanik; nach der inhaltlichen Logik wäre das nicht notwendig +
+ + ++ es kann nicht die eigentliche Processing-Function sein, denn die ist generisch. Sondern in dem Functor steckt eigentlich die verzögerte terminal-build-Operation +
+ + ++ denn der konkrete Funktionstyp und das N (≙Manifold-Größe), sowie ggfs. ein alternativer »InvocationAdapter«-Typ sind zunächst im konkreten Typ des PortBuilders angelegt (als Typ-Kontext); das bedeutet, das Parameter-Tupel muß aus lauter Wrappern bestehen, die die konkreten Typen bereits materialisiert und virtualisiert enthalten. Für lib::Several ist das definitiv der Fall (das verweist auf einen Extent, in dem die Metadaten und das Daten-Array liegen). Auch std::function ist eine Abstraktion. Also wäre wohl noch ein weiterer Generator-Functor notwendig, in dem man den konkreten InvocationAdapter versteckt +
+ + ++ ...wichtig: Wartbarkeit +
+ + ++ und im getypten Kontext im Funktor wird das Ergebnis »abgeworfen« +
+ + ++ ...zwar könnte man jetzt wieder ein Tupel machen, in das den Builder-Functor legen und zusätzliche Steuer-Infos; diese Tupel würden dann in eine Collection auf dem Heap gehen und dort iteriert. Das wäre (scheinbar) simpel, aber nicht einfach und klar. Deshalb bleibt für mich als einzige Alternative, die notwendigen Schrite unmittelbar in eine verkettete Aufrufstruktur zu packen. Das ist dann zwar zunächst fordernd für den Leser (aber das ist die Implementierung des Builders sowiso schon); aber immerhin erschließt sich der Sinn unmittelbar lokal im jeweiligen Aufruf — genauer gesagt, man kann direkt beim lokalen Aufruf den Sinn in einem Kommentar erläutern, und zwar komplett +
+ + ++ diese Datenstruktur ist die Sequenz der Builder selber +
+ + ++ Builder sind generisch, d.h. tragen Typ-Parameter, und die konkreten Typen in diesen durchlaufen eine Sequenz, in welcher sich schrittweise die Datenstruktur aufbaut. Effektiv liegen die Daten in dem äußeren Stack-Frame, welcher das Builder-API aufruft; sie werden jeweils für einen Aufruf in den nächsten Stack-Frame geschoben, und dann wieder zurück geschoben, allerdings wie in einer russischen Puppe immer weiter verpackt +
+ + ++ bekommt (später, zum Aufruf) einen DataBuilder<Port> & +
+ + ++ ...und zwar vor allem zur besseren Verständlichkeit und Wartbarkeit; da es sich um lauter nicht-virtuelle, direkte Aufrufe bekannter Funktionsdefinitionen handelt, kann der Optimiser jedweden Overhead problemlos entfernen +
+ + ++ jeder spätere Aufruf legt sich oben über eine Vererbungs-Kette, wobei lokal ermittelte Größen-Parameter der benötigten Datenstruktur mit eingebettet werden; damit läßt sich dann später auch die erforderliche Gesamt-Größe der Storage bestimmen, bevor das die Several-Collection selber gebaut wird (und das ist der Zweck des ganzen Unterfangens) +
+ + ++ Hier tritt das bekannte Problem der umgekehrten Reihenfolge für einfach verkettete Listen auf: das zuletzt hinzugefügte Element liegt zuoberst... +
+ + ++ Man konstruiert einen (Meta)Typ für den eigentlichen Aufruf, und verwendet das bereits implementierte Anhängen an eine Typliste. Dieser Ansatz birgt zwei schwer abschätzbare Risiken +
++ In der eigentlichen Invocation macht man einfach etwas vor und etwas nach dem rekursiven Aufruf. Gefahr: Stack-Overflow bei sehr vielen Ports (Ambisonics etc....) +
+ + ++ Der Node-Build sammelt Parameterdaten für jeden Port in einer verschachtelten Datenstruktur auf dem Heap, und arbeitet diese rekursiv ab; der so generierte Code ist sehr effizient (Optimiser), bedingt aber eine Limitierung auf eine kleine Anzahl an Ports +
+ + ++ aber nur, falls wirklich Outputs für einzelne Komponenten gerendert werden sollen +
+ + ++ es müßte dann auf Ebene der Fixture die möglichkeit Virtueller oder Iterativer Ports geschaffen werden, ohne dort die Storage ausufern zu lassen; solche Ports müßten dann geeignet in die Aufruf-Parameter repräsentiert werden, so daß das Turnout-System dann jeweils einen einzigen realen Port aktiviert, diesem aber einen iterativ generierten Selektor-Parameter auf dem Weg der Automation durchgibt, wodurch im Node-Graphen selber an geeigneter Stelle ein Kanal-Selektor bei ansonsten gleicher Verarbeitung koordiniert würde. Die eigentliche Schwierigkeit bei diesem Ansatz liegt allerdings darin, daß diese komplexe und globale Struktur im Builder erkannt und transformiert werden muß +
+ + +