The OutputSlot interface describes a point where generated media data can actually be sent to the external world. It is expected to be implemented by adapters and bridges to existing drivers or external interface libraries, like a viewer widget in the GUI, ALSA or Jack sound output, rendering to file, using an external media format library. The design of this core facility was rather difficult and stretched out over quite some time span -- this page documents the considerations and the decisions taken.
!intention
@@ -5245,12 +5246,12 @@ Besides the sink handles, allocation of an output slot defines some timing const
!!relation to buffer management
{{red{WIP 11/11}}} meanwhile I have worked out the BufferProvider interface in detail, including an explicit buffer handover protocol that is is backed by a tiny state engine to track the BufferMetadata. This mechanism provides an additional transition to {{{BLOCKED}}} state when order or timing constraints are being violated. Yet how to detect and resolve such a floundered state from the engine point of view still remains to be addressed.
+{{red{//At this point,//}}} the initial design draft from 2012 became bogged down in a thicket of confused implementation. Based on, and driven by a placeholder implementation at that time, a {{{BufferProvider}}}, that was first utilised to simplify building the test setup, was then conflated with the //interface// exposed towards prospective clients. Notably the prototype implementation relied on passing a BuffHandle from a backing BufferProvider directly to the client. Yet only the BuffHandle itself can be considered part of the protocol, and acts as client interface, necessitating a mechanism that propagates the //lifecycle events// (locking, emitting, releasing) into a state coordinator within the opened OutputSlot.
+
{{red{WIP 3/2026}}} after a long break, we are now facing the problem how to make BufferProvider and OutputSlot work together. Both define a protocol and data exchange models -- and both exhibit a weakness regarding the releasing of buffers. At which time is it safe to release a buffer, when the handover didn't happen? This is similar to the problem of closing a network connection, i.e. the problem is generally unsolvable, but can be handled pragmatically within certain limits.
* For handles in the Render Engine we can generally assume that the regular call path is always completed, which ensures that every handle gets released. Exceptions may happen, but are //not handled// -- rather the Engine will be shut down in such a case, which could be arranged to discard all buffer memory unconditionally
* Regarding the »Output Slot Protocol« it is not clear yet which part will be responsible for checking the [[timing constraints|Timings]]; the actual {{{Connection}}} implementation looks like a plausible candidate. Furthermore, it is not clear yet {{red{3/26}}} where precisely an underlying buffer provider will be integrated, and if doing so is mandatory
-At this point, the initial design draft from 2012 became bogged down in a thicket of confused implementation. Based on, and driven by a placeholder implementation at that time, a {{{BufferProvider}}} utilised to simplify building the test setup was conflated with the //interface// exposed towards prospective clients. Notably the prototype implementation relied on passing a BuffHandle from a backing BufferProvider directly to the client. Yet only the BuffHandle itself can be considered part of the protocol, and acts as client interface, necessitating a mechanism that propagates the //lifecycle events// (locking, emitting, releasing) into a state coordinator within the opened OutputSlot.
-
-----
!Implementation / design problems
How to handle the selection of those two data exchange models!
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index d7ed6c7bb..5f8163c9a 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -51617,6 +51617,8 @@
+
+
@@ -51625,1006 +51627,22 @@
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
- das heißt, der erste erschöpfte Iterator terminiert die gesamte Sequenz
-
-
-
+
+
+
+
-
-
-
-
-
- damit man auch die Fälle erschlagen kann, in denen irgendwo eine Index-Variable gebraucht wird; oft ist das nämlich der einzige Grund, dann doch eine klassische For-Iteration zu machen
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- stelle fest: ich darf kein Quell-Tupel konstruieren
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Es ist eigentlich nur eine Verpackung für std::apply, welches man mit dem gleichen Aufruf-Aufwand stets auch direkt aufrufen könnte. Hinzu kommt, daß nun die Argument-Ordnung auf dem API entweder links-rum oder rechts-rum nicht paßt und verwirrend ist
-
-
-
-
-
-
-
-
-
- der spezielle Trick mit dem inneren Lambda ist nur für ein Tupel notwendig, um die Tupel-Elemente flexibel aber korrekt wieder in die Hand zu bekommen (alternativ mußte man vor C++17 mit einem Index-Seq-Iterator arbeiten, was sehr undurchsichtig ist)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ...und damit sind nämlich beide Ansätze letzlich wieder versöhnt. Das war die wichtige Einsicht beim zweiten Design-Anlauf. Beim ersten Anlauf wollte ich »brav« sein und Werte liefern, und das ganze Design wurde dadurch extrem undurchschaubar und wackelig, denn natürlich wurde beim ersten Kontakt mit der Realität klar, daß man dann smart-Pointer durch die Gegend schiebt.
-
-
-
-
-
-
-
-
-
- ....aber auch der Punkt, an dem es immer wieder gefährlich wird.
-
-
- Warum ist das so wichtig?
-
-
- Antwort: weil nur dadurch ein Iterator durch eine Abstraktions-Barriere hindurch arbeiten kann. Der Aufbau eines komplexen Such-Algorithmus erfolgt meist in mehreren Layern, und man möchte eben nicht, daß irgendwo von unten Werte geliefert werden müssen, die dann oben »passen«. Sondern man möchte mehrere (oft virtuelle) Funktionsaufrufe aufeinander mappen. Und nicht zuletzt dadurch kann man geziehlt einzelne Werte in einem anderen Implementierungs-Realm zur Manipulation exponieren. Denn tatsächlich ist ein solcher IterExplorer eine View in eine State-Core
-
-
-
-
-
-
-
-
-
-
- Im Normalfall liefert schon der Quell-Iterator eine Referenz (oder const-Referenz). Und die wird dann durch das Value-Tuple durchgemappt und zeigt eigentlich in die State-Core oder einen unterliegenden Container. Wenn aber an irgend einer Stelle tatsächlich ein Wert geliefert werden muß, so wird das auf diesem Level stets abgefedert, und die stabile Storage für den Wert liegt dann im Iterator selber (im ItemWrapper)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- (ob dieserer test portabel ist....)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- andere Tricks mit der _rawCore()-Zugriffsfunktion scheinen nicht zu gehen
-
-
-
-
-
-
-
-
-
- ...da alle Processing-Layer auf State-Cores beruhen, präsentieren wir dem darüberzulegenden IterExplorer die neue Core, und der findet dann die unter diesem Layer liegende CheckedCore des Vorgängers und strippt den neuen Layer weg. YESS es funktioniert!!!!!!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- vorher:
-
-
- IterExplorer<
-
-
- BaseAdapter<
-
-
- IterableDecorator<uint,
-
-
- CheckedCore<
-
-
- IterExplorer<
-
-
- IterableDecorator<uint,
-
-
- CheckedCore<
-
-
- Transformer<
-
-
- BaseAdapter<NumIter<int> >,
-
-
- uint
-
-
- nachher:
-
-
- IterExplorer<
-
-
- IterableDecorator<uint,
-
-
- CheckedCore<
-
-
- Transformer<
-
-
- BaseAdapter<NumIter<int> >,
-
-
- uint
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- NumIter hatte ich bei Gelegenheit aus Test-Code extrahiert, ohne weiter viel Gedanken darauf zu ver(sch)wenden....
-
-
-
-
-
-
-
-
-
- Bestenfalls eliminiert der Optimiser ohnehin alle Indirektionen, aber auch alle Kopien. Wenn das aber nicht geht, weil es von irgendwo noch einen Zugriff geben könnte, dann sitzt man auf einem Speicherzugriff, wo andernfalls direkt ein Register verwendet werden könnte. Und wenn dann auch noch die Referenz non-const ist, versaut man sich leicht internen State und wundert sich dann... Nee, nee! Wenn man wirklich Seiteneffekte über Referenzen möchte, dann soll man sich das bitte explizit auscoden, aber nicht eine Library-Funktion kreativ nutzen
-
-
-
-
-
-
-
-
-
-
-
-
- ich kann mir keinen Anwendungsfall vorstellen, wo so etwas nicht zumindest überraschend wäre.... Ja man könnte INT = custom magic type setzen, aber wie war das nochmal mit Magie und so?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Das heißt: in dem Moment wo wir die transformer-Funktion tatsächlich installieren, packen wir sie in ein std::function-Objekt mit einer neu synthetisierten Signatur. Diese ist (der Intention nach) paßgenau, d.h. sie nimmt den yield des vorgelagerten Iterators als Input
-
-
-
-
-
-
-
-
-
-
-
- typedef typename IT::reference InType;
-
-
- function<VAL(InType)> trafo_;
-
-
-
-
-
-
-
-
-
- damals wollte ich erst mal schnell durch, wie üblich;
-
-
- viele Jahre später habe ich dann den NumIter definiert, und das ist vielleicht das erste Beispiel, bei dem dieses neue Konzept wirklich ausgereitzt wurde. Ich habe so dunkel in Erinnerung, daß es dann nicht funktioniert hat, und ich deshalb „einfach“ das const in die eingebetteten Ergebnis-Typdefinitionen gepackt habe. Was ja nicht falsch war, aber der Weg des geringsten Wiederstandes
-
-
-
-
-
-
-
-
-
-
- Und das ist mir zunehmend wichtiger geworden, vor allem nach meinen Erfahrungen mit dem monadischen IterExplorer v1. Grade weil man manchmal wirklich nur Values bieten kann, muß diese Hintertür offen bleiben. Andererseits wäre es ein gefährlicher Fehlschluß, daß eine Type-def »reference« hier „in Wirkklichkeit“ meint, das was der Iterator liefert. Der Schluß liegt nahe, und deshalb bin ich ihm hier auch verfallen. Aber wenn man mit derartigem Konzept-Mapping erst mal anfängt, dann verliert man schnell die Kontrolle
-
-
-
-
-
-
-
-
-
-
-
-
- in iter-adapter.hpp
-
-
-
-
-
-
-
-
-
-
-
-
-
- also exakt die zentrale Stelle, an der entschieden wird, ob eine nachgeschaltete Funktion auf den Resultat-Wert losgeht, oder direkt auf den Iterator-Typ zugreift
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- jetzt wirds aber brandgefährlich
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ...das würde bedeuten: man reicht eine weitere Typ-Def komplett durch die ganze Kette durch, und jeweils nur am Ende der Kette würde diese für den operator* gelten. Da wir aber Ketten im Builder auch noch nachträglich weiter verlängern, indem eine neue Core darüber gesetzt wird, ist das jedoch nicht ohne Weiteres realisierbar
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ...auch wenn es normalerweise kein Performance-Problem darstellt, weil der Optimiser redundante Aufrufe problemlos eliminiert, macht es die Typen für den Außenstehend noch viel undurchtringbarer, und belastet auch die Debug-Builds durch den Umfang unnötiger Typ-Information
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- das zwingt mich, einen inneren Quell-Layer in den äußeren Wrapper zu schieben; selbst wenn ich das Argument by-value nehme, ist es damit noch nicht in dem Objekt-Feld, wo es hingehört. Das bedeutet dann, entweder eine Kopie (und man ist vom Optimiser abhängig), oder eben ein move(SRC).
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- tatsächlich ist der Filter besonders gefährdet
-
-
-
-
-
-
-
- weil der Filter selber ja nur den Feed pullt, aber selber keine Werte speichert; damit erzeugt er möglicherweise upstream obsolete Referenzen.
-
-
-
-
-
-
-
-
-
-
- ich brauche einen engaged-State
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ...er läßt nämlich den ItemWrapper zunächst leer, und verwendet das für eine Lazy-invocation der Transformation
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ungutes Gefühl...
-
-
- das Design hat hier eine Schwachstelle
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ...dann würden wir eine ganze Pipeline »flippen« können, wenn der erste Result-Typ auf Value schwenkt
-
-
-
-
-
-
-
-
-
- habe die mehrfachen Adapter nun allesamt weg; daher wäre es nun doch denkbar, da die Call-Pfade nun von einem Punkt aus aufspalten (nämlich Core::yield -> entweder operator* oder operator-> )
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Booo! is_const_v<T&> ist immer false
-
-
-
-
-
-
-
-
-
-
- ruck-zuck ein paar Stunden weg ... hätte man doch gleich einen Test geschrieben
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- dediziertes Trait schaffen
-
- -
- Propagations-Logik explizit auscoden
-
- -
- den operator-> auf Values weglassen bzw mit compile-Fehler unterdrücken
-
-
-
-
-
-
-
-
-
-
- ...da bisher alles auf Rückgabe by-ref ausgelegt ist, und dieser Fall deshalb durch die Änderung ohne Modifikation durchgereicht wird
-
-
-
-
-
-
-
-
-
- es wird vermutlich auf wenige Fälle beschränkt bleiben, in denen es aber durchaus hilfreich ist, da sowohl Generatoren als auch Transformatoren einfacher zu schreiben sind
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- der auto-Expander kann und darf ja nicht wissen, wo genau unterhalb die Expansion stattfindet; wenn eben gar keine stattfindet, dann bleiben alle durchgereichten expandChildren()-Aufrufe ohne Wirkung, aber ein iterNext() wird dann auch nicht mehr gesendet
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ich kann mir keinen Fall vorstellen, in dem der Default gelten könnte; vermutlich habe ich den Default damals angeschrieben, als ich mir das Konzept neu ausgedacht habe, und mir noch nicht klar war, wohin das führt (und wie bedeutend das mal wird....)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ...und zwar in LinkedElements.
-
-
- Das ist nicht irgend ein Randfall, sondern eine der zentralen Nutzungsmöglichkeiten: um nämlich einen iterator und einen const-iterator aus der gleichen Core zu erzeugen
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
@@ -57610,7 +56628,1175 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ matchAtStart(str, regex) : partieller Match am Anfang verankert
+
+
+
+
+
+
+
+ wichtig für Text-Parsing, wo man bestimmte Tokens akzeptieren möchte, aber durchaus ein Rest »dahinter« übrig bleibten darf.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ das heißt, diese Funktion darf es nur einmal geben, und das muß dann »die« einschlägige Implementierung sein, ohne Wenn und Aber
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ also...
+
+
+ -
+ nimmt einen Lumiera Forward-Iterator
+
+ -
+ verpackt ihn als »state-core«
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ einfach und minimalistisch implementiert — und zudem sind undendliche Folgen nur etwas für Mathematiker und Freunde der funktionalen Programmierung; in der Praxis braucht man zwingend stets eine Abbruch-Bedingung, allein schon weil sonst die Engine „Amok laufen“ könnte (man bedenke nur, daß die interne Zeit-Repräsentation bereits lange vor der Frame-Nummer wrappt)
+
+
+
+
+
+
+
+
+
+
+
+
+ optional die Möglichkeit für einen getriggerten Stop vorsehen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+ Hilfsfunktionen fehlen (z.B. Adaptieren des Funktors an StateCore)
+
+ -
+ TreeExplorer sollte itertools nicht includieren (gegenwärtig tut er's aber nur wegen IterSource, und das könnte man refactorn)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ das heißt, der erste erschöpfte Iterator terminiert die gesamte Sequenz
+
+
+
+
+
+
+
+
+
+ damit man auch die Fälle erschlagen kann, in denen irgendwo eine Index-Variable gebraucht wird; oft ist das nämlich der einzige Grund, dann doch eine klassische For-Iteration zu machen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ stelle fest: ich darf kein Quell-Tupel konstruieren
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Es ist eigentlich nur eine Verpackung für std::apply, welches man mit dem gleichen Aufruf-Aufwand stets auch direkt aufrufen könnte. Hinzu kommt, daß nun die Argument-Ordnung auf dem API entweder links-rum oder rechts-rum nicht paßt und verwirrend ist
+
+
+
+
+
+
+
+
+
+ der spezielle Trick mit dem inneren Lambda ist nur für ein Tupel notwendig, um die Tupel-Elemente flexibel aber korrekt wieder in die Hand zu bekommen (alternativ mußte man vor C++17 mit einem Index-Seq-Iterator arbeiten, was sehr undurchsichtig ist)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ...und damit sind nämlich beide Ansätze letzlich wieder versöhnt. Das war die wichtige Einsicht beim zweiten Design-Anlauf. Beim ersten Anlauf wollte ich »brav« sein und Werte liefern, und das ganze Design wurde dadurch extrem undurchschaubar und wackelig, denn natürlich wurde beim ersten Kontakt mit der Realität klar, daß man dann smart-Pointer durch die Gegend schiebt.
+
+
+
+
+
+
+
+
+
+ ....aber auch der Punkt, an dem es immer wieder gefährlich wird.
+
+
+ Warum ist das so wichtig?
+
+
+ Antwort: weil nur dadurch ein Iterator durch eine Abstraktions-Barriere hindurch arbeiten kann. Der Aufbau eines komplexen Such-Algorithmus erfolgt meist in mehreren Layern, und man möchte eben nicht, daß irgendwo von unten Werte geliefert werden müssen, die dann oben »passen«. Sondern man möchte mehrere (oft virtuelle) Funktionsaufrufe aufeinander mappen. Und nicht zuletzt dadurch kann man geziehlt einzelne Werte in einem anderen Implementierungs-Realm zur Manipulation exponieren. Denn tatsächlich ist ein solcher IterExplorer eine View in eine State-Core
+
+
+
+
+
+
+
+
+
+
+ Im Normalfall liefert schon der Quell-Iterator eine Referenz (oder const-Referenz). Und die wird dann durch das Value-Tuple durchgemappt und zeigt eigentlich in die State-Core oder einen unterliegenden Container. Wenn aber an irgend einer Stelle tatsächlich ein Wert geliefert werden muß, so wird das auf diesem Level stets abgefedert, und die stabile Storage für den Wert liegt dann im Iterator selber (im ItemWrapper)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (ob dieserer test portabel ist....)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ andere Tricks mit der _rawCore()-Zugriffsfunktion scheinen nicht zu gehen
+
+
+
+
+
+
+
+
+
+ ...da alle Processing-Layer auf State-Cores beruhen, präsentieren wir dem darüberzulegenden IterExplorer die neue Core, und der findet dann die unter diesem Layer liegende CheckedCore des Vorgängers und strippt den neuen Layer weg. YESS es funktioniert!!!!!!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ vorher:
+
+
+ IterExplorer<
+
+
+ BaseAdapter<
+
+
+ IterableDecorator<uint,
+
+
+ CheckedCore<
+
+
+ IterExplorer<
+
+
+ IterableDecorator<uint,
+
+
+ CheckedCore<
+
+
+ Transformer<
+
+
+ BaseAdapter<NumIter<int> >,
+
+
+ uint
+
+
+ nachher:
+
+
+ IterExplorer<
+
+
+ IterableDecorator<uint,
+
+
+ CheckedCore<
+
+
+ Transformer<
+
+
+ BaseAdapter<NumIter<int> >,
+
+
+ uint
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ NumIter hatte ich bei Gelegenheit aus Test-Code extrahiert, ohne weiter viel Gedanken darauf zu ver(sch)wenden....
+
+
+
+
+
+
+
+
+
+ Bestenfalls eliminiert der Optimiser ohnehin alle Indirektionen, aber auch alle Kopien. Wenn das aber nicht geht, weil es von irgendwo noch einen Zugriff geben könnte, dann sitzt man auf einem Speicherzugriff, wo andernfalls direkt ein Register verwendet werden könnte. Und wenn dann auch noch die Referenz non-const ist, versaut man sich leicht internen State und wundert sich dann... Nee, nee! Wenn man wirklich Seiteneffekte über Referenzen möchte, dann soll man sich das bitte explizit auscoden, aber nicht eine Library-Funktion kreativ nutzen
+
+
+
+
+
+
+
+
+
+
+
+
+ ich kann mir keinen Anwendungsfall vorstellen, wo so etwas nicht zumindest überraschend wäre.... Ja man könnte INT = custom magic type setzen, aber wie war das nochmal mit Magie und so?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Das heißt: in dem Moment wo wir die transformer-Funktion tatsächlich installieren, packen wir sie in ein std::function-Objekt mit einer neu synthetisierten Signatur. Diese ist (der Intention nach) paßgenau, d.h. sie nimmt den yield des vorgelagerten Iterators als Input
+
+
+
+
+
+
+
+
+
+
+
+ typedef typename IT::reference InType;
+
+
+ function<VAL(InType)> trafo_;
+
+
+
+
+
+
+
+
+
+ damals wollte ich erst mal schnell durch, wie üblich;
+
+
+ viele Jahre später habe ich dann den NumIter definiert, und das ist vielleicht das erste Beispiel, bei dem dieses neue Konzept wirklich ausgereitzt wurde. Ich habe so dunkel in Erinnerung, daß es dann nicht funktioniert hat, und ich deshalb „einfach“ das const in die eingebetteten Ergebnis-Typdefinitionen gepackt habe. Was ja nicht falsch war, aber der Weg des geringsten Wiederstandes
+
+
+
+
+
+
+
+
+
+
+ Und das ist mir zunehmend wichtiger geworden, vor allem nach meinen Erfahrungen mit dem monadischen IterExplorer v1. Grade weil man manchmal wirklich nur Values bieten kann, muß diese Hintertür offen bleiben. Andererseits wäre es ein gefährlicher Fehlschluß, daß eine Type-def »reference« hier „in Wirkklichkeit“ meint, das was der Iterator liefert. Der Schluß liegt nahe, und deshalb bin ich ihm hier auch verfallen. Aber wenn man mit derartigem Konzept-Mapping erst mal anfängt, dann verliert man schnell die Kontrolle
+
+
+
+
+
+
+
+
+
+
+
+
+ in iter-adapter.hpp
+
+
+
+
+
+
+
+
+
+
+
+
+
+ also exakt die zentrale Stelle, an der entschieden wird, ob eine nachgeschaltete Funktion auf den Resultat-Wert losgeht, oder direkt auf den Iterator-Typ zugreift
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ jetzt wirds aber brandgefährlich
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ...das würde bedeuten: man reicht eine weitere Typ-Def komplett durch die ganze Kette durch, und jeweils nur am Ende der Kette würde diese für den operator* gelten. Da wir aber Ketten im Builder auch noch nachträglich weiter verlängern, indem eine neue Core darüber gesetzt wird, ist das jedoch nicht ohne Weiteres realisierbar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ...auch wenn es normalerweise kein Performance-Problem darstellt, weil der Optimiser redundante Aufrufe problemlos eliminiert, macht es die Typen für den Außenstehend noch viel undurchtringbarer, und belastet auch die Debug-Builds durch den Umfang unnötiger Typ-Information
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ das zwingt mich, einen inneren Quell-Layer in den äußeren Wrapper zu schieben; selbst wenn ich das Argument by-value nehme, ist es damit noch nicht in dem Objekt-Feld, wo es hingehört. Das bedeutet dann, entweder eine Kopie (und man ist vom Optimiser abhängig), oder eben ein move(SRC).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ tatsächlich ist der Filter besonders gefährdet
+
+
+
+
+
+
+
+ weil der Filter selber ja nur den Feed pullt, aber selber keine Werte speichert; damit erzeugt er möglicherweise upstream obsolete Referenzen.
+
+
+
+
+
+
+
+
+
+
+ ich brauche einen engaged-State
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ...er läßt nämlich den ItemWrapper zunächst leer, und verwendet das für eine Lazy-invocation der Transformation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ungutes Gefühl...
+
+
+ das Design hat hier eine Schwachstelle
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ...dann würden wir eine ganze Pipeline »flippen« können, wenn der erste Result-Typ auf Value schwenkt
+
+
+
+
+
+
+
+
+
+ habe die mehrfachen Adapter nun allesamt weg; daher wäre es nun doch denkbar, da die Call-Pfade nun von einem Punkt aus aufspalten (nämlich Core::yield -> entweder operator* oder operator-> )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Booo! is_const_v<T&> ist immer false
+
+
+
+
+
+
+
+
+
+
+ ruck-zuck ein paar Stunden weg ... hätte man doch gleich einen Test geschrieben
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+ dediziertes Trait schaffen
+
+ -
+ Propagations-Logik explizit auscoden
+
+ -
+ den operator-> auf Values weglassen bzw mit compile-Fehler unterdrücken
+
+
+
+
+
+
+
+
+
+
+ ...da bisher alles auf Rückgabe by-ref ausgelegt ist, und dieser Fall deshalb durch die Änderung ohne Modifikation durchgereicht wird
+
+
+
+
+
+
+
+
+
+ es wird vermutlich auf wenige Fälle beschränkt bleiben, in denen es aber durchaus hilfreich ist, da sowohl Generatoren als auch Transformatoren einfacher zu schreiben sind
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ der auto-Expander kann und darf ja nicht wissen, wo genau unterhalb die Expansion stattfindet; wenn eben gar keine stattfindet, dann bleiben alle durchgereichten expandChildren()-Aufrufe ohne Wirkung, aber ein iterNext() wird dann auch nicht mehr gesendet
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ich kann mir keinen Fall vorstellen, in dem der Default gelten könnte; vermutlich habe ich den Default damals angeschrieben, als ich mir das Konzept neu ausgedacht habe, und mir noch nicht klar war, wohin das führt (und wie bedeutend das mal wird....)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ...und zwar in LinkedElements.
+
+
+ Das ist nicht irgend ein Randfall, sondern eine der zentralen Nutzungsmöglichkeiten: um nämlich einen iterator und einen const-iterator aus der gleichen Core zu erzeugen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -60977,7 +61163,6 @@
-
@@ -63354,7 +63539,7 @@
-
+
@@ -63365,7 +63550,7 @@
-
+
@@ -63376,6 +63561,29 @@
+
+
+
+
+
+
+
+
+
+ ....wobei allerdings auch das letztlich fraglich ist — denn was genau wäre der Unterschied zu dem bereits implementierten Mechanismus, der die Pipeline hinter ein IterSource-Interface packt?
+
+
+ ⟹ es wäre ausschließlich der Umstand, daß die Pipeline-Implementierung in lokaler Storage liegt, und keinen Heap-Speicher benötigt. Abgesehen davon sind beide Lösungen äquivalent (unter der Annahme, daß IterSource wie geplant überarbeitet wird, so daß dieses Interface kongruent wird zur »StateCore«). Denn, wie ich durch meine Implementierungs-Studie gesehen habe, es geht hier nicht nur um einen verpackten Function-Pointer, sondern es geht stets um ein virtuelles interface, denn auch das Instance-Management (copy, move, assign, swap, destroy) muß virtualisiert werden.
+
+
+
+
+
+
+
+
+
+
@@ -63501,7 +63709,7 @@
-
+
@@ -63511,6 +63719,7 @@
+
@@ -63716,6 +63925,16 @@
+
+
+
+
+
+ ...denn ich wollte eine Iterator-Funktion definieren, und als Abstraktionsbarriere verwenden. Iteratoren sind aber essentiell kopierbar
+
+
+
+
@@ -63723,8 +63942,16 @@
-
+
+
+
+
+
+
+
+
+
@@ -63734,12 +63961,165 @@
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Kopien und Datenbewegungen machen aktuelle CPUs extrem schnell, und Speicher ist billig
+
+
+
+
+
+
+
+
+
+ das heißt, jede der beiden Operationen wir mit dem absoluten Minimum an Indirektion aufgerufen (nämlich ein Funktionspointer)
+
+
+
+
+
+
+
+
+
+ und zwar einmal als Funktion; das ist u.U dann etwas mehr Code, als wenn man das Roh-Objekt bewegen würde (weil der Optimiser da nichts machen kann), aber auch weniger, als u.U. das direkte Inlinen von mehreren Instanz-Operationen. Jedenfalls kann auch eine VTable-basierte Implementierung nicht mit weniger Code auskommen, sofern virtual copy support gewünscht ist
+
+
+
+
+
+
+
+
+
+ Es gibt jeweils eine Instanz der Invoke und der Manager-Funktion für jeden Typ (also jeweils zwei Funktionen) — aber keine VTable und keine Typeinfo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rein auf Basis der abzählbaren Einträge im Executable sehe ich nur einen geringen Unterschied: nämlich daß die generischen VTable-Operationen wegfallen. Abgesehen davon erfordern beide Ansätze jeweils zwei Funktionen / virtuelle Funktionen.
+
+
+
+
+
+ Der Trade-off besteht also zwischen ein paar tausend (unterer Bereich) zusätzliche Einträge vs. jeweils ein zusätzliches Datenwort in jeder Instanz zur Laufzeit.
+
+
+
+
+
+
+
+
+
+ Und das heißt, ich kann keine Entscheidungen über knapp bemessene Performance-Unterschiede treffen auf einer rein-generischen Basis. Im Einzelfall (wie z.B. für lib::Several) besteht ja immer noch die Möglichkeit einer handgeschriebenen Implementierung, die u.U auch zusätzliches Wissen bezüglich der konkreten Situation nutzbar machen könnte (beispielsweise geht es im ArrayBucket für lib::Several nur um eine abstrahierte Deleter-Funktion, d.h. es genügt tatsächlich ein einziger Funktionspointer).
+
+
+
+
+
+ Insofern gibt es auf generischer Ebene nur die Feststellung, daß man mit einer ansonsten »praktisch äquivalenten« implementierung (halt ein Pointer mehr) für jede Instanz zwei Interface-Einträge und den zugehörigen Assembly einspart.... Im Vergleich dazu ist bezüglich der Laufzeit der Unterschied viel relevanter, wenn für transiente Operationen (wie einen Iterator) eine Heap-Allokation vermieden werden kann.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -64379,6 +64759,62 @@
+
+
+
+
+
+
+ »Polymorphic Value«
+
+
+
+
+
+
+
+
+
+
+ Es gab also keinen unmittelbaren Bedarf, aber ich habe immer wieder bemerkt, daß so etwas nützlich wäre und konzisere APIs ermöglichen würde...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Eine Komponente, die geeignet einen Instance-Manager unterstützt, würde automatisch wie ein einfacher Value handhabbar, mit Move-, Copy-, Asignment und Swap-Support. Zwar wird all dies normalerweise automatisch von der C++ - Sprache unterstützt, erfordert aber komplette Offenlegung des Typs — ein Instance-Manager wird also relevant, wenn man den konkreten Typ verbergen möchte, aber trotzdem Wert-Semantik bieten möchte
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -64700,7 +65136,7 @@
-
+
@@ -64909,7 +65345,7 @@
-
+
@@ -65219,7 +65655,7 @@
-
+
@@ -73813,7 +74249,7 @@
-
- die Factory kann sich selbst erzeugen, indem sie auf einen Monostate zurückgreift. Das ist in der Praxis der wichtigste Fall, der im Besonderen für die normale Heap-Allokation gilt, aber auch Thread-Local und damit Kontext-Bezogen auslegbar ist.
+ die Factory kann sich selbst erzeugen, indem sie auf einen Monostate zurückgreift. Das ist in der Praxis der wichtigste Fall, der im Besonderen für die normale Heap-Allokation gilt, aber auch Thread-Local und damit Kontext-bezogen auslegbar ist.
-
es könnte nur Copy-Construction oder Delegation erlaubt sein, und die Factory ist in diesem Fall nur ein Front-End-Handle
@@ -73916,7 +74352,7 @@
- ...auch hier soll die Verdrahtung verborgen bleiben; jedoch ist man nicht auf einen Objekttyp festgelegt, und außerdem soll es die Möglichkeit geben, esplizit eine destroy()-Funktion aufzurufen, oder alternativ schon aus der Erzeugung ein managing-handle zu bekommen
+ ...auch hier soll die Verdrahtung verborgen bleiben; jedoch ist man nicht auf einen Objekttyp festgelegt, und außerdem soll es die Möglichkeit geben, explizit eine destroy()-Funktion aufzurufen, oder alternativ schon aus der Erzeugung ein managing-handle zu bekommen
@@ -82238,8 +82674,8 @@
-
+
@@ -82247,8 +82683,8 @@
-
+
@@ -86895,6 +87331,7 @@ Date: Thu Apr 20 18:53:17 2023 +0200
+
@@ -104917,7 +105354,7 @@ Date: Thu Apr 20 18:53:17 2023 +0200
-
+
@@ -105021,6 +105458,18 @@ Date: Thu Apr 20 18:53:17 2023 +0200
+
+
+
+
+
+ Stand 2026: nach dem Aufräumen und Restrukturieren der Implementierung von BufferProvider habe ich die Metadaten behutsam verallgemeintert, und nun konsitent auf der internen Ebene zwischen BufferStage und BufferStore zugänglich gemacht. Speziell das LocalTag wird damit zu einem Kommunikations-System, über das sich eine konkrete Einbindung in ein Storage- oder Output-System über das ganze Protokoll hinweg koordinieren kann. Im OutputSlot stellt das LocalTag in jedem DataSink-Handle intern die Verknüpfung zur darunterliegenden Connection-Implementierung her, und ermöglicht so einen BufferStore, der direkt an die Output-Bufferverwaltung in der Connection delegiert.
+
+
+
+
+
+
@@ -105406,6 +105855,7 @@ Date: Thu Apr 20 18:53:17 2023 +0200
+
@@ -105423,6 +105873,9 @@ Date: Thu Apr 20 18:53:17 2023 +0200
+
+
+
@@ -113642,8 +114095,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -118894,7 +119347,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -118922,8 +119375,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
+
+
@@ -118935,8 +119388,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
+
+
@@ -118944,7 +119397,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -118987,7 +119441,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -119022,22 +119477,41 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
- Auf dem Weg einer systematischen Analyse wäre ich (mein typisches Mindset zugrunde gelegt) niemals darauf gekommen, diese Kongruenz zwischen den Interfaces zu sehen. Erfahrungsgemäß finde ich solche Dinge immer erst durch eine extrem mühsame Empirie, viel Aufräumen, und unvorstellbar langwierige Reflexionen. Das alles ist mir hier erstpart geblieben
+ Auf dem Weg einer systematischen Analyse wäre ich (mein typisches Mindset zugrunde gelegt) niemals darauf gekommen, diese Kongruenz zwischen den Interfaces zu sehen. Erfahrungsgemäß finde ich solche Dinge immer erst durch eine extrem mühsame Empirie, viel Aufräumen, und unvorstellbar langwierige Reflexionen....
+
+
+
+
+
+ Das alles ist mir hier erstpart geblieben — und ich nehme das als Wink, daß ich dieser Fährte weiter folgen sollte (und meine Zweifel beiseite legen)
+
+
+
+
+
+ Das sah erst wie ein Hack aus, und wie der Versuch, zwei unzusamenhängende Systeme zusammenzuwürgen. Ich bin dann aber langsam, schrittweise den Widersprüchen nachgegangen, und habe die Struktur rebalanciert. Ich weiß dann gar nicht, wie das gegangen ist, aber plötzlich hatte sich das Design im OutputSlot »gelegt« — und die ganze Struktur (BufferProvider, BufferMetadata, OutputBufferProxy, AllocState) erscheint mir jetzt kohärent und erweiterungsfähig.
+
+
+
+
-
-
-
+
+
+
+
+
+
@@ -119118,15 +119592,26 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
+
+
+
+
+
+ Das war zunächst der Blocker in der OutputSlot-Implementierung; und indem ich die Widersprüche hier aufgelöst habe, hat sich diese Implementierung »gelegt«
+
+
+
-
+
+
-
+
-
+
@@ -119135,12 +119620,22 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
+
+
+
+
+
+
+
+
+
+
-
-
+
@@ -119154,7 +119649,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -119173,7 +119668,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -119182,6 +119677,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
+
+
+
@@ -119484,7 +119983,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -119826,7 +120325,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -119880,6 +120379,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
+
+
@@ -119902,7 +120404,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -121021,8 +121523,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
+
+
+
+
@@ -121044,13 +121548,14 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
-
+
+
+
-
+
+
-
+
@@ -121069,7 +121574,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -121120,7 +121625,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -121143,8 +121648,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
+
+
@@ -121446,7 +121951,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -121482,7 +121988,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -121511,9 +122018,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
-
+
@@ -121561,8 +122068,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -121654,7 +122162,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -121666,7 +122174,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -121692,7 +122200,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
+
@@ -121700,10 +122210,11 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
-
+
@@ -121712,11 +122223,14 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
+
-
-
+
+
+
@@ -121754,7 +122268,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -121805,7 +122320,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -121825,8 +122340,14 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
+
+
+
-
+
+
+
@@ -121842,8 +122363,41 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -122638,19 +123192,48 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
+
+
+
+
-
-
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ Der erste Entwurf von 2012 hatte da so einige Funktionalität, von der ich nicht weiß, ob sie was taugt; allein schon die Definition eines »Time Grid« hängt komplett in der Luft. Seinerzeit hatte ich ein TimeGrid-Asset reingezogen, was die Layer-Ordnung überwirft, denn das Thema »Output« gehört ganz klar zu den low-Level-Services, darf sich also nur auf LIbrary-Funktionen abstützen. Tatsächlich werden wir ein Grid in den »Timings« (Constraints) haben, aber das klingt ehr nach einem runtime-Setup für einen konkreten laufenden Ausgabe-Prozeß, und nicht nach einem Modell-Element.
+
+
+
+
+
+ Ich habe diesen Teil des Entwurfs nun komplett gelöscht, denn ich halte nichts davon, mir hier etwas auszudenken, bevor ich nicht wenigstens eine konkrete laufende Implementierung zustandegebracht habe (seufz)
+
+
+
+
+
-
+
+
+
+
@@ -122678,8 +123261,22 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
+
+
+
+
+
+
+
+
+ der OutputBufferProxy ist jetzt nahtlos in die überarbeitete Standard-Implementierung des OutputSlot integriert ... das war sehr mühsam, am Ende ist aber das Design wunderbar aufgegangen
+
+
+
+
+
+
+
@@ -122690,21 +123287,56 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
-
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+ Der Schlüssel war, die Dependency-Ebenen auseinander zu sortieren, und genau zu prüfen, wer wen wie genau getypt sehen muß. Überraschenderweise hat sich dabei der Proxy-Buffer-Provider von einer Not-Kompromiß in ein entscheidendes Design-Element verwandelt: er stellt nämlich jetzt die Verbindung zwischen front-End (DataSink) und Backend (Connection) her. Der OutputSlot::AllocState ist nur noch dafür zuständig, die Connections zu halten/managen, aber partizipiert nicht mehr im eigentlichen Protokoll. Und der Lebenszyklus wird durch das ref-counting-Handle zusammengehalten, das ganz oben, im OutputSlot sitzt, aber zugleich auch in die λ-capture der DataSinks eingebettet ist.
+
+
+
+
+
+ Im Grunde weiß ich nicht, wie ich da drauf gekommen bin — auf einmal hatte sich alles »gelegt«
+
+
+
+
+
+
+
+
+
@@ -122731,6 +123363,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
@@ -142367,6 +143000,17 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
+
+
+
+
+ Das hat sich als die entscheidende Gelenkstelle herausgestellt, an der ich beim ersten Entwurf 2012 gescheitert bin; nun habe ich unter der Vorgabe, daß beide Protokolle kongruent gemacht werden müssen, die Implementierungen neu verbunden, und dafür ein Proxy-Setup für den BufferProvider geschaffen. Das war nur möglich auf Basis der vorausgegangenen Überarbeitung der internen Struktur im BufferProvider, und einem behutsamen Glattziehen und Verallgemeinern der Metadataten. Damit wurde der OutputBufferProxy, und das LocalTag in den Metadaten, zu dem Bindeglied, das nun die ganze Struktur zusammenhält
+
+
+
+
+
@@ -142626,14 +143270,16 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
-
-
-
+
+
+
+
+
+
+
-
+
@@ -142797,8 +143443,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -142838,7 +143485,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -142883,7 +143530,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -142965,7 +143613,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -143078,7 +143726,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -143223,8 +143871,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
+
+
@@ -143423,7 +144071,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -144014,9 +144663,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
-
+
@@ -144039,10 +144688,16 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
+
+
+
+
+
+
+
+
@@ -144064,15 +144719,68 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -144094,7 +144802,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -144135,7 +144843,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -144150,6 +144858,37 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
+
+
+
+
+ Um es explizit auszuformulieren...
+
+
+ -
+ das Test-Setup erzeugt einen DiagnosticOutputSlot
+
+ -
+ dieser ist aber kopierbar, d.h. das zu testende Subjekt kann eine Kopie bekommen
+
+ -
+ u.U wird das zu testende Output-Slot-Protocol auf dieser Kopie ausgeführt
+
+ -
+ nach dem Testaufruf geht man über das Verifikations-API auf irgend eine Kopie los —
+
+ -
+ aber diese Kopie muß natürlich tatsächlich ein DiagnosticOutputSlot sein, (damit sie den shared-PImpl enthält, denn sonst würde man ja an die Tracking-Daten gar nicht mehr rankommen)
+
+ -
+ außerdem muß während dem Test irgendwo diese DiagnosticOutputSlot-Variante bestehen bleiben, um die Tracking-Infrastruktur am Leben zu halten; weil dem so ist, habe ich auch darauf verzichtet, shared-ptr für die Rück-Kanäle in der Tracking-Instrumentierung zu verwenden. Denn letztlich ist das hier Testcode, also wenn's knallt, dann knallts
+
+
+
+
+
+
@@ -144176,8 +144915,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
+
+
+
@@ -144190,9 +144930,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
-
+
+
@@ -144213,7 +144954,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -144230,8 +144971,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
+
+
+
@@ -144256,7 +144998,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -144285,7 +145027,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -144349,9 +145092,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
-
+
+
+
@@ -144371,17 +145114,60 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
+
+
+
+
+
+
+
+
+ wir erzeugen leere diagn::Frame information-Records bei Bedarf, und diese beruhen auf lib::NullValue, werden also (statsich) shared.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ man muß lediglich einen passenden Populator-Funktor in den Konstruktor geben, der mit der konkreten Connection-Implementierung verdrahtet
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
-
+
+
@@ -159571,8 +160357,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -172638,8 +173424,8 @@ std::cout << tmpl.render({"what", "World"}) << s
-
+
@@ -208129,8 +208915,8 @@ env.Command('out.bin', 'in.bin', my_action)
-
+