From 1230cbae122921f9747d7dfe031f51e396428f98 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 15 Feb 2026 22:26:39 +0100 Subject: [PATCH] BufferProvider: re-think through the process of Key creation ...while the implementation provided herein is trivial, it represents the entrance point into the process covered by the ''Buffer Provider Protocol''; thus I went again through all the stages of the implementation, and confirmed my analyiss from 2024: 9a23aa773b93b0be8a3d85437f5f267d405bd87c Today I'd even go so far as to call this a **crietrion of consistency**: The path leading to the LOCK-transition must be reproducible. The existing implementation, now re-cast into a new API, ensures this by requiring an explicitly given Buffer Descriptor at that point, and ''does not offer any one-step shortcut.'' __The point to note is__: the system used for implementing those type-IDs, which is a tree of chained hashes, is deliberately open and extensible. ''In theory'' it would be possible thus to arrive at different hash-IDs for the same criteria, if these are added gradually, in several steps, as a refinement. This leeway is acceptable, as long as it can not happen that two competing allocation processes want to lock the same memory address, but tracked through different parent type-IDs. --- src/steam/engine/buffer-provider.cpp | 9 +- src/steam/engine/buffer-provider.hpp | 1 + .../engine/simple-buffer-state-registry.hpp | 12 +- src/steam/engine/type-handler.hpp | 1 + wiki/renderengine.html | 15 +- wiki/thinkPad.ichthyo.mm | 348 +++++++++++++++++- 6 files changed, 368 insertions(+), 18 deletions(-) diff --git a/src/steam/engine/buffer-provider.cpp b/src/steam/engine/buffer-provider.cpp index 0004c8a68..7f2766b6f 100644 --- a/src/steam/engine/buffer-provider.cpp +++ b/src/steam/engine/buffer-provider.cpp @@ -20,6 +20,7 @@ #include "lib/error.hpp" #include "steam/engine/buffer-provider.hpp" #include "steam/engine/buffer-metadata.hpp" /////////////////////////OOO must be removed from here +#include "steam/engine/simple-buffer-state-registry.hpp" /////////////OOO better use constexpr for hash-chaining? #include "lib/util.hpp" using util::isSameAdr; @@ -30,7 +31,7 @@ namespace engine { // storage for the default-marker constants const TypeHandler TypeHandler::RAW{}; const LocalTag LocalTag::UNKNOWN{}; - + const metadata::Key SimpleBufferStateRegistry::NULL_KEY{HashVal(0), size_t(0)}; //////OOO try to make this init into a constexpr and then do it in-class! namespace { // impl. details and definitions @@ -59,14 +60,16 @@ namespace engine { BuffDescr BufferProvider::getDescriptorFor (size_t storageSize) { - return BuffDescr (*this, bufferStage_->key (storageSize)); + auto& typeKey = bufferStage_->defineBufferType (storageSize, TypeHandler::RAW); + return BuffDescr (*this, typeKey); } BuffDescr BufferProvider::getDescriptorFor(size_t storageSize, TypeHandler specialTreatment) { - return BuffDescr (*this, bufferStage_->key (storageSize, specialTreatment)); + auto& typeKey = bufferStage_->defineBufferType (storageSize, move (specialTreatment)); + return BuffDescr (*this, typeKey); } diff --git a/src/steam/engine/buffer-provider.hpp b/src/steam/engine/buffer-provider.hpp index 02bdc2ba8..f8ddafb18 100644 --- a/src/steam/engine/buffer-provider.hpp +++ b/src/steam/engine/buffer-provider.hpp @@ -193,6 +193,7 @@ namespace engine { virtual ~BufferStage() { } ///< this is an interface using BufferMetadata::BufferMetadata; ///////////////////////////////////////////////////////////TICKET #1410 : actual implementation structures should move down into this classes implementation + virtual ID defineBufferType (size_t, TypeHandler) =0; virtual ID lookup (HashVal) =0; virtual ID mark_locked (ID typeKey, Buff*, LocalTag) =0; virtual ID mark_emitted (HashVal stateKey) =0; diff --git a/src/steam/engine/simple-buffer-state-registry.hpp b/src/steam/engine/simple-buffer-state-registry.hpp index 24259c1b6..039c3091c 100644 --- a/src/steam/engine/simple-buffer-state-registry.hpp +++ b/src/steam/engine/simple-buffer-state-registry.hpp @@ -50,15 +50,23 @@ namespace engine { : public BufferProviderSetup::Stage { ///////////////////////////////////////////////////////////////////////////////////////////////////////TICKET 1410 : rather hold BufferMetatdata object here + static const metadata::Key NULL_KEY; ////////////////////////////OOO better init directly here as constexpr (to keep it really an implementation detail) /* === BufferStage interface === */ - + ID lookup (HashVal key) override { - return this->get (key); + return this->isKnown(key)? this->get (key) + : NULL_KEY; } + ID + defineBufferType (size_t buffSiz, TypeHandler handlerFunctions) + { + return lookup (this->key (buffSiz, move (handlerFunctions))); + } // deliberately: create storage, and return reference to it + ID mark_locked (ID typeKey, Buff* storage, LocalTag implMark) override { diff --git a/src/steam/engine/type-handler.hpp b/src/steam/engine/type-handler.hpp index 27d44eeed..d20055dac 100644 --- a/src/steam/engine/type-handler.hpp +++ b/src/steam/engine/type-handler.hpp @@ -118,6 +118,7 @@ namespace engine { , destroyAttached() , identity{0} { } + // has value semantics; default copy OK /** build a TypeHandler * binding to arbitrary constructor and destructor functions. diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 794750f2e..908e307da 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -1252,16 +1252,23 @@ Mostly, the //client code,// i.e. code in need of using buffers, can access some :a //naive// implementation is used for unit-tests: each buffer request is simply allocated from the heap and this memory is not released or re-used, which allows to verify memory content after invoking the test subject. -
-
the generic BufferProvider implementation exposes a service to attach and maintain additional metadata with individual buffers. Using this service is not mandatory -- a concrete buffer provider implementation may chose to maintain more specific metadata right on the implementation level, especially if more elaborate management is necessary within the implementation anyway (e.g. the frame index). We can expect most buffer provider implementations to utilise at least the generic buffer type id service though.
+
+
The generic BufferProvider implementation involves a service to attach and maintain additional metadata with individual buffers. The scheme used for forming this metadata became effectively part of the standard, to the degree that this generic high-level implementation of the //|Buffer Proivder Protocol«// is considered a façade, leaving room to configure the actual implementation backend. However, a concrete buffer provider implementation may chose to maintain more specific metadata right on the implementation level, especially if more elaborate management is necessary within the implementation anyway (e.g. the frame index). The metadata scheme offers a {{{LocalTag}}} to attach and mark such additional implementation-level qualification.
+
+We can expect every buffer provider implementation to utilise at least the generic buffer type-ID to keep track of the allocation size.
+
 
 !buffer types and descriptors
-Client code accesses buffer through [[smart buffer handles|BuffHandle]], including some kind of buffer type information, encoded into a type ID within the ''buffer descriptor''. These descriptors are used like prototypes, relating the type-~IDs hierarchically. Obviously, the most fundamental distinction is the BufferProvider in charge for that specific buffer. Below that, the next mandatory level of distinction is the ''buffer size''. In some cases, additional distinctions can be necessary. Each BufferProvider exposes a service to yield unique type ~IDs governed by such a hierarchical scheme.
+Client code accesses buffer through [[opaque buffer handles|BuffHandle]], including some kind of buffer type information, encoded into a type ID within the ''buffer descriptor'' ({{{BuffDescr}}}). These descriptors are used like prototypes, relating the type-~IDs hierarchically. Obviously, the most fundamental distinction is the BufferProvider in charge for that specific buffer. Below that, the next mandatory level of distinction is the ''buffer size''. In some cases, additional distinctions can be necessary. Each BufferProvider exposes a service to yield unique type ~IDs governed by such a hierarchical scheme.
+
+Notably the //actual meaning of such a buffer type// remains implementation-defined. The scheme to generate and refine metadata, which is part of the //Buffer Provider Protocol,// only serves to keep different usage patterns disjoint. When following this protocol, some //type// of buffer is qualified first, and materialised as Descriptor. This may include some additional qualification steps, like the definition of a //type handler//, with functions to initialise and de-initialise the storage within the buffer; furthermore, an implementation-defined {{{LocalTag}}} can be attached. Together, this leads to a tree of hierarchically related hash-IDs.
+
+There is one crucial ''consistency criterion'' though: irrespective how a given usage arrives at some specific sub-type, there must be //only one single way// to transition into {{{LOCKED}}} state. The implementation //must ensure// that it is not possible to qualify different sub-types for //effectively the same storage location.// The high-level façade implementation „hooked behind“ the BufferProvider interface indeed fulfils this criterion: simply by requiring the client to present a Buffer Descriptor, that has been set-up and qualified in preceding steps. The implementation does not care //how// the client has obtained the type-ID embedded into the descriptor, but it provides //only one code path// to qualify any given type-ID with an actual //memory location//, so to obtain a //state-ID//, that can proceed through the stages of buffer lifecycle.
 
 !state and metadata for individual buffers
 Beyond that, it can be necessary to associate at least a state flag with //individual buffers.// Doing so requires the buffer to be in //locked state,// otherwise it wouldn't be distinguishable as an separate entity (a client is able to access the buffer memory address only after "locking" this buffer). Especially when using a buffer provider in conjunction with an OutputSlot, these states and transitions are crucial for performing an orderly handover of generated data from the producer (render engine) to the consumer (external output sink).
 
-__Note__: while the API to access this service is uniform, conceptually there is a difference between just using the (shared) type information and associating individual metadata, like the buffer state. Type-~IDs, once allocated, will never be discarded (within the lifetime of an Lumiera application instance -- buffer associations aren't persistent). To the contrary, individual metadata //will be discarded,// when releasing the corresponding buffer. According to the ''prototype pattern'', individual metadata is treated as a one-way-off specialisation.
+__Note__: while the API to access this service is uniform, conceptually there is a difference between just using the (shared) type information and associating individual metadata, like the buffer state. Type-~IDs, once allocated, will never be discarded (within the lifetime of an Lumiera application instance -- while buffer associations, and  further individual metadata //will be discarded,// when releasing the corresponding buffer. According to the ''prototype pattern'', individual metadata is treated as a one-way-off specialisation.
 
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index d01483536..22a2f057d 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -84479,7 +84479,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -85218,6 +85218,11 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + @@ -85298,7 +85303,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- +
@@ -99379,9 +99384,14 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + +
- + + @@ -99406,6 +99416,18 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + +

+ Konsistenz-Kriterium: Weg zum State-Entry muß reproduzierbar/eindeutig sein +

+ +
+ + +
@@ -100072,6 +100094,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + +
@@ -100079,6 +100105,17 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + @@ -110847,7 +110884,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -110867,6 +110904,48 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + +

+ Im Zusammenhang mit der alten Implementierung bin ich immer wieder auf die BufferMetadata gestoßen, aber eigentlich nur insofern, als ich immer wieder hier „den Faden verloren“ habe. Das muß aber kein Problem der BufferMetadata sein, denn — für sich betrachtet sind sie offensichtlich und langweilig. Es gibt auch einen Test (der allerdings auch ziemlich feingranular ist, und vielleicht etwas mehr Fokus gebrauchen könnte). +

+

+ +

+

+ Nun habe ich die sonstige Implementierung so hablwegs in den Griff bekommen (oder besser: eine erste Bresche geschlagen, so daß ich mich dort verankern kann, und dann weiter bauen). Vielleicht war das Problem nur, daß die sonstige Implementierung mir entglitten war, und daher die BufferMetadata überall eingewoben wurden, und zwar per direktem Durchgrif auf ihr internes Verhalten. Es kann aber auch sein, daß das Konzept der BufferMetadata einfach nicht auf die Realität paßt — denn, ehrlich gesagt, Kontakt mit »der Realität« haben wir immer noch nicht, auch nicht im Entfernstesten... +

+

+ ☹ +

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

+ sie ist etwas zerfasert, und sehr offen (was man aber nicht monieren kann) — letztlich aber überschaubar, konsistent durchgearbeitet und ohne offensichtliche Taschenspieler-Tricks... +

+

+ +

+

+ Will sagen, bin irgendwie noch nicht glücklich mit dem Stück Code, finde aber keine greifbaren Einwände; vielleicht müßte man nur die Hashtable-Implementierung nach Innen herausziehen, und dann die Formulierung nochmal stringent überarbeiten, und ein paar „auf Verdacht“ dazugebaute Hilfsprädikate zurückbauen? +

+ +
@@ -110883,7 +110962,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
- + @@ -112346,6 +112425,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + @@ -112764,7 +112844,34 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
- + + + + + + + + + + + + + + +

+ BuffHandle sit nun definitiv kein smart-Ptr mehr; damit fällt alles nachträgliche »andocken« und »verfeinern« weg..... +

+ +
+ + +
+
+ + + + +
@@ -112790,7 +112897,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + + @@ -112973,11 +113082,201 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + + + + + +

+ Man müßte dazu die Logik erschöpfend durchdenken; nach einem oberflächlichen Check wäre es durchaus möglich. Ich würde es aber trotzdem nicht machen, da es der zugrunde liegenden Logik widerspricht. +

+ +
- + + + + + + + + +

+ ...da er als Referenz heraugegeben wird +

+ +
+ + + + +

+ ....wegen der impliziten Abhängigkeit der Hashes von der Seed-ID; Knackpunkt ist: das geht über einen reinen Typ-Ausdruck  hinaus... +

+ +
+ +
+ + + + +

+ und zwar, weil bei einer reinen Typ-Registrierung der Parent-Key auch nur ein Platzhalter ist, also selber eine Art Null-Objekt. Wenn ich ehrlich bin, ist das der Grund für die Instanz-ID... +

+ +
+ +
+ + + + +

+ selbst für die PROD-Variante ist das bloß Engine-Infrastruktur und Engine-Init, hat also keinen Hebel +

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

+ da wir uns im internen API-Mapping befinden, ist das hier angemessen; auf dem externen BufferProvier-API dagegen müssen die zwei Fälle als getrennte Funktionen stehen bleiben — denn die dahinter stehenden Use-Cases sind grundverschieden +

+ +
+ + + + + +

+ nachgeprüft: sie führen alle letztlich zu trackKey(Key) — und mit den letzten Refactorings vor ein paar Jahren, passiert das jetzt auch in einer konsistenten Reihenfolge +

+ +
+
+
+ + + + +

+ Stelle mit Erstaunen fest: der Kern der BufferMetadata-Implementierung ist eigentlich kompakt und übersichtlich +

+ +
+ +
+ + + + +

+ Das ist der Kern, worauf sich mutmaßlich die immer noch ungelösten Probleme mit der Type-Handler-Registrierung reduzieren lassen +

+ +
+ +
+ + + + +

+ Namensvorschlag: defineBufferType (storageSiz, typeHandler) +

+ +
+ + + + + +

+ Einsichten hier.... +

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

+ Dadurch ist das Konsistenz-Kriterium von 2024 erfüllt: denn nur mit einem Buffer-Descriptor können wir eine LOCK-Transition machen. An dem Punkt ist es dann egal, wie der Hash im BuffDescr zustande gekommen ist, wir halten uns einfach in dem Zweig des Typ-Baumes fest, in dem wir nun mal geandet sind.... +

+ +
+
+ + + + + + +

+ Jetzt hab ich gestern so endlos lange rumgedödelt mit den Typen, bloß damit das API einfach und schlank aussieht. Und jetzt sowas. Ach grausame Welt! +

+ +
+ +
+ + + + +

+ ...mit dem gleichen Argument, daß es der Optimiser schon richten wird? Vielleicht gitb es noch ein besseres, formales Argument um das zu rechtfertigen... (wiewohl meine Motivation hier ganz klar ist: ich will ein stimmiges API) +

+ +
+
+ + + + +

+ Argument dafür: es muß an dieser Stelle intern auch was persistiert werden +

+ +
+ +
+
+
+
+ + + + + + +
+ + + + + + + +
@@ -113087,6 +113386,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)

+ + + +
@@ -132627,6 +132930,33 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + +

+ Dieses Prototyping war ein zweiter (letztlich ebenfalls nicht erfolgreicher) Anlauf, um zum »Kern von Lumiera« vorzudringen. Ein erster, systematisch-planender Anlauf über die Typen im High-Level-Model war vorher bereits im Ungreifbaren versandet. +

+ +
+ +
+ + + + +

+ habe seinerzeit die erkennbaren Abläufe insgesamt durchdacht, mit Fokus auf eine Typ-Registrierungs-Hierarchie (als Chained-Hash-Tree gedacht). Meine Schlußfolgerung war, daß man das System »offen« lassen kann — unter einer entscheidenden Konsistenz-Bedingung: der Zugangs-Pfad, der dann zu einer konkreten Buffer-Registrierung führt, muß eindeutig und deterministisch sein. Denn sonst besteht die Gefahr, daß zwei konkurrierende Verwendungen jeweils verschiedene State-Entries schaffen für exakt die gleiche Speicher-Adresse. In der seinerzeit (und auch 2026 noch) bestehenden Implementierung kann das nicht passieren, weil der State-Entry mit einer LOCK-Transition erzeugt wird, und diese setzt explizit auf einem bestehenden Type-Key auf, ohne an der Stelle weitere Qualifizierungen vorzunehmen. Ich halte es für wichtig, diesen Gefahrenpunkt zu notieren. +

+ +
+ +
+ + + +