From 3764479ebd30e5a3fdef99b063b0df847dee38fd Mon Sep 17 00:00:00 2001
From: Ichthyostega
+ das ist ein essentielles Requirement
+
+ nachfolgende Reflexion und die Formulierung des »Routing«-Konzepts hat das deutlich gemacht: Aus Sicht des Users(Film-Editors) würde das einen immer wiederkehrenden Problemfall im Workflow entschärfen; hinzu kommt, daß ein solches Design sauberer kaskadiert und faktorisiert: wir wollen nicht den »globalen Automaten« oben drauf setzen
+
+ Bis zu dem Punkt war ich geleitet von der effizienten Verwendung der Parameter im Code, und hatte eigentlich nur den Value-Zugriff (und die Zuweisungen) im Blick. Dem entsprechend stand im Vordergund, einen Code-Pfad zu ermöglichen, in dem das ganze Typ-System links liegen bleibt und der Code auf einem direkt gegebenen Value arbeitet. Die Typ-IDs waren vor allem von Bedeutung, wenn man Parameter als einen Interface-Deskriptor deutet, also um z.B. dann im GUI korrekt zu rendern.
+
+ In dem Moment aber, wo ich angefangen habe, eine Parameter-Klasse zu konzipieren, ist mir die Festlegung auf einen Inline-Buffer in den Vordergrund gerückt. Ich habe dann Angst bekommen, daß ein aufgeblähter Parameter-Typ sich pervasiv ausbreitet. Das wurde verstärkt durch die Einsicht, daß Parameter eine 2.Ebene im low-Level-Modell darstellen. (Diese Einsicht »dämmert« mir zunehmend, und ich finde das gar nicht so toll, weil sich damit das ganze Konzept mit den Pipelines und Segmenten auflösen könnte)
+
+ ...sobald man sich tatsächlich alle konkreten low-level-Operationen anschaut, und im Besonderen auch an die Cache-locality denkt. Und ja, mir ist komplett klar, daß ich hier im »Abwägungen im lufleeren Raum« mache
+
+ Erscheint als guter Kompromiß: die Scale wird nicht so häufig gebraucht, nämlich nur beim akzeptieren neuer Werte; zudem könnte man einen entsprechenden Limiter generieren und in die Kette der Provisions (Datenquellen) einschleifen +
+ + +- Im Besonderen ist die ganze Auswertung von Parametern 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. + Im Besonderen ist die ganze Auswertung von Parametern 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 ausgewertet werden, und zwar dort verpackt in einen Automations-Funktor. Möglicherweise wird diese Auswertung aber auch eine Art compilierte Variante sein.
@@ -82517,9 +82590,21 @@
+ ....ich halte selbst den Versuch für gefährlich, mich erst mal nicht um Optimierung zu kümmern, sondern die Eigenschaften des Parameters »generisch« zu repräsentieren. Der Grund ist: es dauert Jahre, bis ich da im Stande bin, eine Rückmeldung zu bekommen — und dann hat sich ein mögliches Design bereits festgesetzt und hat sekundäre Konsequenzen hervorgebracht. Wie man es auch dreht und wendet: es gibt keine Metode wie man's »richtig« macht... +
+ + ++ ...da die VTable in der Regel als Pointer implementiert wird, also 64bit belegt — somit bricht der Datenpuffer immer schon mal einen weiteren Slot an (selbst wenn man die Datentypen weiter limitieren würde) +
+ + ++ und somit ist das gesamte (abstrahierte, virtuelle) Verhalten dennoch bereits zur Compile-Zeit festgelegt, und die Laufzeit-Größe einer Instanz beträgt nur 1 «slot» (für die VTable); dennoch gibt es verschiedene (zur Compile-time generierte) Implementierungen, d.h. das virtuelle Interface abstrahiert tatsächlich etwas, das allerdings bereits statisch (zur compile-time) komplett ausformuliert wurde +
+ +- Jede, wirklich jede Ausprägungs-Variante schlägt sich nieder in Form einiter (deduplizierter) Datenfelder in einem Daten-Segment (private static), sowie einem kompletten Satz an Methoden-Implementierungen im Code-Segment, und einer VTable mit Zeigern auf diese. Muß man eine weitere Methode ins Interface aufnehmen, entsteht sofort ein starker Hebel... + Jede, wirklich jede Ausprägungs-Variante schlägt sich nieder in Form einiger (deduplizierter) Datenfelder in einem Daten-Segment (private static), sowie einem kompletten Satz an Methoden-Implementierungen im Code-Segment, und einer VTable mit Zeigern auf diese. Muß man eine weitere Methode ins Interface aufnehmen, entsteht sofort ein starker Hebel...
@@ -82892,7 +82995,7 @@
- Es zeigt sich, daß dieses Konstrukt den Compiler in eine komplexe Situation bringt, in der wirklich jedes Detail stimmig sein muß. Im Besonderen kann die Diagnostik komplett irreführend sein; wenn etwas mit der Sichtbarkeit nicht stimmt, kommt es zu einer (verdeckten) Ambiguität, die u.U. (abhängig vom konkreten Typ) automatisch vom Compiler „repariert“ wird (ich meine diese problematischen Regeln, daß z.B. ein unsigned an vielen Stellen geräuschlos auf einen Signed zugewiesen werden kann). Außerdem ist es wichtig, das Pragma zum Unterdrücken von -Woverloaded-virtual bereits vor dem Template stehen muß, in dem das Shaddowing auftritt (nicht etwa, wie man intuitiv erwarten würde, vor der Verwendung).
+ Es zeigt sich, daß dieses Konstrukt den Compiler in eine komplexe Situation bringt, in der wirklich jedes Detail stimmig sein muß. Im Besonderen kann die Diagnostik komplett irreführend sein; wenn etwas mit der Sichtbarkeit nicht stimmt, kommt es zu einer (verdeckten) Ambiguität, die u.U. (abhängig vom konkreten Typ) automatisch vom Compiler „repariert“ wird (ich meine diese problematischen Regeln, daß z.B. ein unsigned an vielen Stellen geräuschlos auf einen Signed zugewiesen werden kann). Außerdem ist es wichtig, das Pragma zum Unterdrücken von -Woverloaded-virtual bereits vor dem Template stehen muß, in dem das Shaddowing auftritt (nicht etwa, wie man intuitiv erwarten würde, vor der Verwendung).
- In diesen Experimenten erscheint dann doch das ursprüngliche Baumuster von Alexandrescu die stabilste Lösung zu sein. Also ein multiple-mix-in für das Interface, aber eine Kette für die Implementierung. Das liegt daran, daß dann eine eventuelle Ambiguität beim Aufruf wirklich direkt im Interface passiert, nicht irgendwo in einer Kette; hinzu kommt, daß wir in einer Kette die Sichtbarkeit explizit wieder herstellen müssen durch ein using BAS::functionname; — und das kann weitere Ambiguitäten erzeugen, und erfordert vor allem irgendwo eine catch-all-Definition (z.B. mit leerer Argumentliste). Das ist verwirrend und kann weitere Ambiguitäten erzeugen.
+ In diesen Experimenten erscheint dann doch das ursprüngliche Baumuster von Alexandrescu die stabilste Lösung zu sein. Also ein multiple-mix-in für das Interface, aber eine Kette für die Implementierung. Das liegt daran, daß dann eine eventuelle Ambiguität beim Aufruf wirklich direkt im Interface passiert, nicht irgendwo in einer Kette; hinzu kommt, daß wir in einer Kette die Sichtbarkeit explizit wieder herstellen müssen durch ein using BAS::functionname; — und das erfordert vor allem irgendwo eine catch-all-Definition (z.B. mit leerer Argumentliste). Das wäre noch verwirrender und kann weitere Ambiguitäten erzeugen.
- Wenn der Compiler irgendwo automatisch (falsch) konvertiert, kann es dann zu völlig verwirrenden anderen Problemen kommen, wie z.B. der Zugriff über eine Referenz auf ein Objekt anderen Typs (was nur mit einer const-Referenz erlaubt ist).
+ Wenn der Compiler irgendwo automatisch (falsch) konvertiert, kann es dann zu völlig irreführenden sekundären Problemen kommen, wie z.B. der Zugriff über eine Referenz auf ein Objekt anderen Typs (was nur mit einer const-Referenz erlaubt ist).
- Insofern verstehe ich durchaus die Motivation, warum -Woverloaded-virtual eingeführt wurde; hier gibt es viele Fallen, die einen unerfahren Programmierer vor unlösbare Probleme stellen können
+ Insofern verstehe ich durchaus die Motivation, warum -Woverloaded-virtual eingeführt wurde; hier gibt es viele Fallen, die einen unerfahren Programmierer vor unlösbare Probleme stellen können
- ...denn an der Stelle spielt Sichtbarkeit gar keine Rolle mehr, es wird nur eine schon vorhandene Interface-Methode in der VTable auf eine konkrete Implementierung gebunden. Allerdings nur, wenn man tatsächlich das Interface als multiple-mix-in generiert — denn das umgeht das Sichtbarkeits-Problem
+ ...denn an dieser Stelle in der Hierarchie spielt Sichtbarkeit gar keine Rolle mehr, es wird nur eine schon vorhandene Interface-Methode in der VTable auf eine konkrete Implementierung gebunden. Allerdings nur, wenn man tatsächlich das Interface als multiple-mix-in generiert — denn das umgeht das Sichtbarkeits-Problem (⟶ das ursprüngliche Design von Alexandrescu ist ziemlich clever)
+ Das Thema mit den Parametern hatte ich aufgemacht zwischen »Maynooth« und der FrOSCon ... und während dem die Recherche zu IO_URING und die Vorbereitung auf das Worflow-Meeting mit Wouter komplett im Vordergrund stand
+
+ ...ohne an jeder Stelle wieder in Reflexionen zu verfallen; der Entwurf sollte das Thema soweit aufschließen, daß man dann, im Anschluß, konkretere Überlegungen zur Performance anstellen können wird...
+
+ ...vorläufig genügt die Beobachtung, daß eine Scale aus einer BaseDomain hervorgeht; und die Wirkung der Scale beschränkt sich auf das Setzen/Ändern von Werten (unter der wichtigen Invariante daß vorliegende Werte »konform« sein müssen). Möglicherweise wird man die Wirkung einer Scale auch direkt in einen Automations-Funktor mit integrieren können. (Überlegung am Rande: mehrere Indirektionen hintereinander profitieren ebenfalls von der Zusammenfassung in eine VTable, selbst ohne monomorphic optimisation — unter der Annahme daß der Cache heiß bleibt)
+
+ könnte nun vom vorläufigen Interface ausgehend skizzieren
+
+ der Deskriptor ist kopierbar und allein durch seine manifesten Eigenschaften bestimmt (was auf Wert-Semantik hindeutet), jedoch enthält ein Parameter manipulierbaren Zustand und bestimmte Instanzen werden per ID referenziert (was auf Referenz-Semantik hindeutet)
+
+ Bekanntlich war das mal ein »Experiment nebenbei« aus der Anfangzeit ... das sich dann bei mir im Kopf festgesetzt hat.
+
+ dabei geht es aber letztlich nur darum, wie der Copy-Support eingeschleift wird. Denn die eigentliche Implementierung erledigt der PolymorphicValue::Adapter — die Frage der „Kooperation“ läuft darauf hinaus, ob das CopySupport-API direkt auf der Vererbungs-Linie des Implementierungs-Typs gefunden wird, oder ob es über einen Mix-In dazugebaut wird; dem entsprechend ist auch der Adapter anders aufgehängt.
+
+ da mal mit dem Debugger durchsteppen... dann sieht man was passiert
+
+ Das ist ein 64bit-Wort, das in sub-Felder aufgeteilt wurde, um alle Typ/Registrierungs-IDs kompakt unterzubringen; jede sub-ID ist Index in eine global-statische Registrierungstabelle
+
+ bei einer rein-funktionalen Provision wird das nicht möglich sein; wenn dagegen irgendwo ein »Buffer« wäre, dann könnte man dem auch einen Wert zuweisen
+
+ Das heißt....
+
+ weil man sonst für jeden Aufruf einen double-Dispatch bekommt
+
+ ...dieser gilt global als der »Wert« des Parameter; eine eventuelle Automations-Anwendung wird immer auf diesen Ankerwert draufgesetzt
+
+ das ist eine parametrisierte Subklasse der Domain; daher gibt es nur N BaseDomains (kleine Anzahl), welche allesamt lediglich eine andere VTable definieren, und daher direkt eingeschleift werden können.
+
+ Wenn sich die Praxis etablieren läßt, die meisten Parameter von einem Prototypen zu erstellen, dann kann man die meiste elaborierte Registrierungs-Logik einfach weglassen! Für einen von grund auf neu definierten Typ wird einfach ein neuer Registry-Eintrag hinzugefügt, wohingegen ein vom Prototypen erstellter Parameter die bestehende Registrierung einfach übernimmt. Scale-Änderungen werden dann CoW.
+
+ wodurch viel zustandsbehaftete Logik wegfällt
+
+ selbst im Extermfall erwarte ich nicht mehr als ein paar tausend Scales (vermutlich ehr ein paar hundert); mithin wäre locker Platz für tausende verschiedener Automations-Quellen gegeben
+
+ Und zwar, weil wir es nicht wollen, daß die Engine auf aktuelle Definitionen im Model »durchgreift«. Im Sinne moderner Architekturen und zunehmender Parallelisierung wäre das nicht (mehr) wünschenswert; stattdessen wird jede Änderung durch den Builder propagiert und als eine neue Provision-Instanz realisiert. Das hat zur Konsequenz, daß fortlaufend die nicht mehr benötigten Einträge aufgeräumt werden müssen
+
+ Meine erste Einschätzung ist, daß eine Automation typischerweise für genau einen Parameter zum Einsatz kommt; es könnten allerdings mehrere Automationen zusammengeführt werden zu einem Gesamtresultat. Insofern hängt die Zahl der Mehrfachverwendung nur daran, wie häufig Parameter-Deskriptoren im Zuge der normalen Verarbeitung einfach kopiert werden; da sie prinzipiell kopierbar sind, ist ja der springende Punkt daß man darauf gar nicht achten muß
+
+ Das ist eigentlich eine grundsätzliche Frage: inwiefern können wir uns die Replikation per Segment der Timeline eigentlich »leisten«? Bei den Render-Nodes erscheint das zunächst plausibel, da sich ja (meistens) die Topologie ändert; grundsätzlich wäre aber auch bei Render-Nodes eine deduplikation denkbar. Für Parameter gilt das umso mehr, da hier i.d.R eine die ganze Nutzdauer übergreifende Automation eingesetzt wird. Man könnte nun auch diese einfach per Segment replizieren — oder, falls man deduplizieren möchte/muß, dann wäre die Lebensdauer-Steuerung per Segment insgesamt hinfällig, oder es wäre zumindest eine Mapping-Tabelle notwendig, die dann wieder das Problem mit den Locks aufwirft. Und zu allem Überfluß ist das ganze Thema mit der Lebensdauer von Rendervorgängen für mich im aktuellen Entwicklungsstand überhaupt nicht recht greifbar — ich kann nur spekulieren
+
+ Ich gehe derzeit davon aus, 4 »Slots« für den Param-Deskriptor zu reservieren: einer für die VTable, einer für das ID-Wort, und zwei für die Daten. Allerdings reicht für alle einfachen Datenwerte tatächlich ein einziger 64bit-Slot aus. Zwei »Slots« würden höchstens sowas wie ein Datentupel ermöglichen (und ich weiß nicht recht, wofür das gut sein soll, komplexe Zahlen? oder ein 128bit-Float?); ein einfacher Farbwert allerdings würde dann schon drei »Slots« verlangen..
+
+ ...die einzige Lösung, die mir einfält, und die dann nicht doch wieder auf ein Lock hinausläuft, ist ein »Water-Level«. Also so ähnlich wie die Deadline-basierte Freigabe im Block-Flow (für den Scheduler). Und das Beispiel zeigt auch schon die Tücken, das macht nur Sinn, wenn es für jeden Eintrag eine garantierte Obsoleszenz gibt — andernfalls hat man dann am Ende doch wieder eine Garbage-Collection
+
+ es ist durchaus üblich, einen Abschnitt auf der Kurve zu markieren, und dann wie einen Regler zu verschieben; dabei erwartet man sich einen »fliegenden Feedback«
+
+ insofern der Builder eine Projektion darstellt im Sinne von CQRS, müßte dieser beim Verarbeiten eines Änderungs-Events »irgendwie« wissen, wohin er die Änderungen in das bestehende low-level-Model propagieren soll; wir bräuchten sowas wie Patch-Tables
+
+ hierzu müßte man in die Automationsquelle einen Update-Mechanismus einbauen, der jeweils an einer Gate-Flag erkennt, ob er die Automations-Definition aktualisieren soll, bevor er sie auswertet; das Gate wäre dann letztlich wieder ein atomic-Lock (aber insofern akzeptabel, da es ja einfach ein Update sperrt, wenn die Quelle grade geändert wird, was kein sequentially-consistent erfordert)
+
+ wenn ein Update beginnt, wird der alte Wert erhalten, und der neue Wert im alternativen Segment modifiziert; nach Abschluß der Modifikation wechseln beide Segmente ihre Rolle, wofür eine automic-Flag umgeschaltet wird
+
+ ...und das verletzt die eigentlich beabsichtigte Segregation
+
+ in C++ kann man auch eine Builder-Notation per Typ-Konversion realisieren, was oft elegant ist, aber auch zweideutig; in vielen Fällen ist es letztlich doch besser, eine klare Klammer-Struktur zu erzeugen, mit einer terminal-Instruktion
+
+
+
+
+
+
+
+
+
+
+
+
+
- /** @internal extension point + /** @internal extension point
* Create the environment for rendering a connected and related set of output streams.
@@ -93739,6 +94319,16 @@ Date: Thu Apr 20 18:53:17 2023 +0200
- Denn die Gefahr wäre zu groß, daß irgendjemand das dann als General-Koordinate für alles hernimmt — und den Unterschied zwischen Ausgabe-Grid und Source-Koordinaten per Machtspruch unter den Teppich kehrt. Man darf den all den Schwachmaten dieser Welt auch keinen kleinen Finger geben.... + Denn die Gefahr wäre zu groß, daß irgendjemand das dann als General-Koordinate für alles hernimmt — und den Unterschied zwischen Ausgabe-Grid und Source-Koordinaten per Machtspruch unter den Teppich kehrt. Man darf all den Schwachmaten dieser Welt auch keinen kleinen Finger geben....
@@ -119823,7 +120417,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)+ Mute/Solo, Probe-Points, Automation +
+ + ++ das ermöglicht es, Parameter zu kopieren (shallow-copy), aber auch eine bestimmte Version der Funktion für einen Build-Zustand festzulegen (d.h. Zugriff ist ein DAG), während eine andere Version im GUI bereits weiter verändert wird +
+ + ++ ...dieser Wert gilt, wenn keine Qualifikation per nominal-time erfolgt; das bedeutet, die Automation beaufschlagt diesen Basis-Wert mit einer zeitabhängigken Modifikation +
+ + +- /* + /*
* Gadgets are 'next-generation widgets' - they combine a CSS node @@ -186566,7 +187199,7 @@ unsigned int ThreadIdAsInt = *static_cast<unsigned int*>(static_cast<vo
- also keine "Banner" aus Sternen.
Abhilfe: /*********************//**
+ also keine "Banner" aus Sternen.
Abhilfe: /*********************//**
- (defaults to: -I*.a -I*.la -I*.o -I*.so -I.*.sw? -I*/*~ -I,,* -I.[#~]* -I.arch-ids -I.arch-inventory -I.be -I.bzr -I.bzr.backup -I.bzr.tags -I.bzrignore -I.cvsignore -I.deps -I.git -I.gitattributes -I.gitignore -I.gitmodules -I.gitreview -I.hg -I.hgignore -I.hgsigs -I.hgtags -I.mailmap -I.mtn-ignore -I.shelf -I.svn -ICVS -IDEADJOE -IRCS -I_MTN -I_darcs -I{arch}). + (defaults to: -I*.a -I*.la -I*.o -I*.so -I.*.sw? -I*/*~ -I,,* -I.[#~]* -I.arch-ids -I.arch-inventory -I.be -I.bzr -I.bzr.backup -I.bzr.tags -I.bzrignore -I.cvsignore -I.deps -I.git -I.gitattributes -I.gitignore -I.gitmodules -I.gitreview -I.hg -I.hgignore -I.hgsigs -I.hgtags -I.mailmap -I.mtn-ignore -I.shelf -I.svn -ICVS -IDEADJOE -IRCS -I_MTN -I_darcs -I{arch}).
@@ -188074,8 +188707,8 @@ unsigned int ThreadIdAsInt = *static_cast<unsigned int*>(static_cast<vo- /** derive total ordering from base class */ + /** derive total ordering from base class */
std::strong_ordering operator<=>(FrameRate const&) const = default; @@ -216591,7 +217224,7 @@ Since then others have made contributions, see the log for the history. src/vault/gear/work-force.cpp:69:43: warning: '++' expression of 'volatile'-qualified type is deprecated [-Wvolatile]
- 69 | for (volatile size_t i=0; i<degree; ++i) {/*SPIN*/} + 69 | for (volatile size_t i=0; i<degree; ++i) {/*SPIN*/}
@@ -216976,7 +217609,7 @@ Since then others have made contributions, see the log for the history.
- /** + /**
* Identification tuple for addressing frames unambiguously. @@ -216997,7 +217630,7 @@ Since then others have made contributions, see the log for the history. * be replaced by a combined C/C++ header
- */ + */
class FrameID : boost::totally_ordered<FrameID> ////////////TODO it seems we don't need total ordering, only comparison. Clarify this! @@ -217747,7 +218380,7 @@ Since then others have made contributions, see the log for the history.
- /** + /**
* copy-initialise (or convert) from the given Mutator instance. @@ -217783,7 +218416,7 @@ Since then others have made contributions, see the log for the history. * which in turn becomes the only child of the new Record.
- */ + */
Record (Mutator const& mut) @@ -218680,7 +219313,7 @@ Since then others have made contributions, see the log for the history.
- env.GuiResource(f) for f in env.Glob('stage/*.css') + env.GuiResource(f) for f in env.Glob('stage/*.css')
@@ -220758,7 +221391,7 @@ env.Command('out.bin', 'in.bin', my_action)- /* + /*
INDEX-ITER.hpp - iterator with indexed random-access to referred container @@ -220791,7 +221424,7 @@ env.Command('out.bin', 'in.bin', my_action)
- */ + */