BufferProvider: draft mechanism for buffer metadata synchronisation

Since the intention is to run several thread-local instances of `BufferMetadata`
in a concurrent setup, we need a way to publish and exchange chains of
registered type-keys and metadata entries.

The first step is to develop a mechanic for data exchange,
and to define the rules when such synchronisation is (not) allowed.
This commit is contained in:
Fischlurch 2026-04-18 23:49:12 +02:00
parent 9bd3e550dc
commit c05a73d68a
5 changed files with 519 additions and 19 deletions

View file

@ -58,12 +58,12 @@
#include "lib/error.hpp"
#include "lib/hash-value.h"
#include "lib/util-foreach.hpp"
#include "include/logging.h"
#include "vault/mem/buffhandle.hpp"
#include "vault/mem/type-handler.hpp"
#include "vault/mem/buffer-local-tag.hpp"
#include "lib/nocopy.hpp"
#include "lib/util.hpp"
#include <unordered_map>
@ -72,7 +72,7 @@ namespace vault{
namespace mem {
using lib::HashVal;
using util::for_each;
using util::unConst;
namespace err = lumiera::error;
@ -98,6 +98,18 @@ namespace mem {
BLOCKED ///< allocated buffer blocked by protocol failure
};
inline bool
isValidBufferTransition (BufferState oldState, BufferState newState)
{
return ( (oldState == FREE and newState == LOCKED)
or (oldState == LOCKED and newState == EMITTED)
or (oldState == LOCKED and newState == BLOCKED)
or (oldState == LOCKED and newState == FREE)
or (oldState == EMITTED and newState == BLOCKED)
or (oldState == EMITTED and newState == FREE)
or (oldState == BLOCKED and newState == FREE)
);
}
@ -357,15 +369,8 @@ namespace mem {
{
__must_not_be_NIL();
if ( (state_ == FREE and newState == LOCKED)
or (state_ == LOCKED and newState == EMITTED)
or (state_ == LOCKED and newState == BLOCKED)
or (state_ == LOCKED and newState == FREE)
or (state_ == EMITTED and newState == BLOCKED)
or (state_ == EMITTED and newState == FREE)
or (state_ == BLOCKED and newState == FREE))
{
// allowed transition
if (isValidBufferTransition (state_, newState))
{ // perform transition
if (newState == FREE)
invokeEmbeddedDtor_and_clear();
if (newState == LOCKED)
@ -534,7 +539,8 @@ namespace mem {
verify_all_buffers_freed()
try
{
for_each (entries_, verify_is_free);
for (auto const& entry : entries_)
verify_is_free(entry);
}
ERROR_LOG_AND_IGNORE (engine,"Shutdown of BufferProvider metadata store")
@ -671,6 +677,12 @@ namespace mem {
return *entry;
}
Entry const&
get (HashVal hashID) const
{
return unConst(this)->get (hashID);
}
bool
isKnown (HashVal key) const
{
@ -766,6 +778,22 @@ namespace mem {
}
/** Synchronise metadata, including the key path up to root
* @param key start point of data exchange
* @param srcReg another BufferMetadata registry from which
* metadata shall be retrieved and imported
*/
void
import (HashVal key, BufferMetadata const& srcReg)
{
if (not srcReg.isKnown (key))
return;
Entry const& entry{srcReg.get(key)};
doSynchronise (entry);
import (entry.parentKey(), srcReg);
}
private:
/** Core low-level operation to access or create a buffer metadata entry.
* The hashID of the entry in question is built, based on the parentKey,
@ -861,6 +889,35 @@ namespace mem {
}
return newEntry;
}
/** import data from another metadata entry,
* without violating sanity rules
* @remark usually this is used to import new type-keys;
* an active buffer entry can only be handled if it is new,
* or implies a valid state transition.
* @note if the buffer is not NULL, it must be the same address,
* otherwise the keys would differ, since they are generated
* with \ref Key::forEntry()
*/
void
doSynchronise (Entry const& otherEntry)
{
Entry* entry = table_.fetch (otherEntry);
if (not entry)
table_.store (otherEntry);
else
if (not otherEntry.isTypeKey()
and otherEntry.state() != entry->state())
{
ASSERT (not entry->isTypeKey(), "Metadata State-Engine corrupted");
if ( otherEntry.state() == FREE
or otherEntry.state() == LOCKED)
throw err::Logic ("Metadata synchronisation can not imply allocation/deallocation.");
else
entry->mark(otherEntry.state());
}
}
};

View file

@ -0,0 +1,141 @@
/*
SIMPLE-BUFFER-STATE-REGISTRY.hpp - buffer state management with thread-local sub-services
Copyright (C)
2026 Hermann Vosseler <Ichthyostega@web.de>
  **Lumiera** is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version. See the file COPYING for further details.
*/
/** @file local-buffer-stage.hpp
** Production-grade implementation of engine::BufferProvider state coordination.
** Requests to the BufferProvider will be issued from all threads concurrently,
** which requires to distinguish between known common state and particular state
** relevant only for the current worker. Whenever a _buffer type_ is registered,
** this information could become relevant later, when actually a buffer is locked
** with this type, and such a request for an actual buffer can be encountered
** in any thread. While, on the other hand, the state transitions for actual
** buffer usage will happen within a single job invocation, and can thus be
** handled locally within the corresponding worker thread. An allocated buffer
** that is passed into the frame cache is yet another story, and will be handled
** by a dedicated _cache provider_.
**
** Together these observations lead to a structure with locally replicated copies
** of the buffer metadata store, where additional type registration information
** is requested from the central metadata hub on-demand. Yet the front-end for
** accessing the \ref BufferProvider is a common service of the Render Engine,
** and thus the switch to the thread-local metadata management has to happen
** somewhere in-between. This can be achieved by connecting the BufferMetadata
** as a \ref lib::LocalSlice as part of the \ref BufferProvider::BufferStage
** implementation — as provided by this class, LocalBufferStage.
**
** @see buffer-provider.hpp
** @see buffer-provider-protocol-test.cpp
** @see simple-buffer-state-registry.hpp
*/
#ifndef VAULT_MEM_LOCAL_BUFFER_STAGE_H
#define VAULT_MEM_LOCAL_BUFFER_STAGE_H
#include "vault/mem/buffer-metadata.hpp"
#include "vault/mem/buffer-provider-setup.hpp"
#include <memory>
namespace vault {
namespace mem {
/**
* Simple Buffer type and state tracking registry, for test and demonstration.
* Relies on a central hashtable, without considering any concurrency concerns.
*/
class LocalBufferStage
: public BufferProviderSetup::Stage
{
HashVal familyID_;
BufferMetadata metadata_;
/* === BufferStage interface === */
ID
lookup (HashVal key) override
{
return metadata_.isKnown(key)? metadata_.get (key)
: metadata::Key::INVALID;
}
bool
isAllotted (HashVal stateKey) const override
{
return metadata_.isLocked (stateKey);
}
bool
isAccessible (HashVal stateKey) const override
{
return metadata_.isAccessible (stateKey);
}
ID
defineBufferType (size_t buffSiz, TypeHandler handlerFunctions, LocalTag localTag)
{
return lookup (metadata_.key (familyID_, buffSiz, move (handlerFunctions), localTag));
} // deliberately: maybe create storage, and return reference to it
ID
mark_locked (ID typeKey, BuffAlloc alloc) override
{
auto& [storage,size,implMark] = alloc;
return metadata_.markLocked (typeKey, storage, size, implMark);
}
ID
mark_emitted (HashVal stateKey) override
{
metadata::Entry& metaEntry = metadata_.get (stateKey);
metaEntry.mark(EMITTED);
return metaEntry; // contains also the key
}
ID
mark_released (HashVal stateKey) override
{
metadata::Entry& metaEntry = metadata_.get (stateKey);
metaEntry.mark(FREE); // might invoke embedded dtor function
return metaEntry;
}
ID
abandon (HashVal stateKey, bool invokeDtor) override
{
metadata::Entry& metaEntry = metadata_.get (stateKey);
metaEntry.invalidate (invokeDtor);
return metaEntry;
}
void
discard (HashVal stateKey) override
{
metadata_.release (stateKey);
}
public:
LocalBufferStage (Literal implementationID)
: familyID_{hash_value (implementationID)}
, metadata_{}
{ }
};
}} // namespace vault::mem
#endif /*VAULT_MEM_LOCAL_BUFFER_STAGE_H*/

View file

@ -72,6 +72,11 @@ return: 0
END
TEST "Local buffer state management" LocalBufferStage_test <<END
return: 0
END
TEST "Mock support for render job planning" MockSupport_test <<END
return: 0
END

View file

@ -0,0 +1,195 @@
/*
LocalBufferStage(Test) - verify thread-local buffer state management
Copyright (C)
2026, Hermann Vosseler <Ichthyostega@web.de>
  **Lumiera** is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version. See the file COPYING for further details.
* *****************************************************************/
/** @file local-buffer-stage-test.cpp
** unit test \ref LocalBufferStage_test
*/
#include "lib/error.hpp"
#include "test/run.hpp"
#include "test/test-frame.hpp"
#include "test/test-helper.hpp"
#include "vault/mem/buffhandle.hpp"
#include "vault/mem/buffer-metadata.hpp"
#include "vault/mem/local-buffer-stage.hpp"
#include "lib/symbol.hpp"
#include "lib/util.hpp"
#include <memory>
using std::strncpy;
using std::unique_ptr;
using test::TestFrame;
using test::testData;
using util::isSameObject;
using util::isnil;
using lib::randStr;
using lib::Literal;
namespace vault {
namespace mem {
namespace test {
using LERR_(LOGIC);
// using LERR_(FATAL);
// using LERR_(INVALID);
using LERR_(LIFECYCLE);
namespace { // Test fixture
template<typename TY>
TY&
accessAs (metadata::Entry& entry)
{
TY* ptr = reinterpret_cast<TY*> (entry.access());
ASSERT (ptr);
return *ptr;
}
template<typename X>
Buff*
mark_as_Buffer(X& something)
{
return reinterpret_cast<Buff*> (std::addressof(something));
}
const size_t TEST_MAX_SIZE = 1024 * 1024;
HashVal JUST_SOMETHING = 123;
auto SOME_POINTER = mark_as_Buffer(JUST_SOMETHING);
}//(End) Test fixture and helpers
/***************************************************************//**
* @test verify mechanism for a thread-local BufferStage implementation
* - verify data synchronisation between several BufferMetadata registries
* - verify typical usage situations, where a thread-local registry
* has to synchronise itself with a central metadata hub.
*/
class LocalBufferStage_test : public Test
{
virtual void
run (Arg)
{
seedRand();
verify_DataTransfer();
verify_publishNewKey();
verify_fetchNewKeyOnUse();
}
/** @test key-chains can be exported and imported */
void
verify_DataTransfer()
{
BufferMetadata meta1, meta2;
const HashVal FAM = ranHash();
const size_t SIZ_D = sizeof(double);
const double RAND1 = 1.0 + defaultGen.uni();
// create one type-key in Registry-1
metadata::Key keyD = meta1.key (FAM,SIZ_D);
CHECK (meta1.isKnown (keyD));
CHECK (not meta2.isKnown (keyD));
CHECK (meta1.get(keyD).isTypeKey());
// create another type-key in Registry-2, this time with a constructor
metadata::Key keyDD = meta2.key (FAM,SIZ_D, TypeHandler::create<double> (RAND1));
CHECK (meta2.isKnown (keyDD));
CHECK (not meta1.isKnown (keyDD));
CHECK (meta2.get(keyDD).isTypeKey());
// Keys are systematic => KeyD happens to be parent of KeyDD
CHECK (keyD.storageSize() == keyDD.storageSize());
CHECK (keyDD.parentKey() == HashVal(keyD));
// synchronise a sub-type
meta1.import (keyDD, meta2);
CHECK (meta1.isKnown (keyDD));
// now Registry-1 is able to use this subtype as well
double testBuffer{0};
metadata::Entry& eDD = meta1.markLocked (keyDD, mark_as_Buffer(testBuffer));
CHECK (eDD.state() == LOCKED);
CHECK (eDD.parentKey() == HashVal(keyDD));
// and the »constructor« was indeed invoked...
CHECK (testBuffer == RAND1);
testBuffer += 1.1; // add marker...
// within the confines of allowed state transitions,
// it is even possible to sync the state of active buffer entries
CHECK (meta1.isKnown (eDD));
CHECK (not meta2.isKnown (eDD));
meta2.import (eDD, meta1);
CHECK (meta2.isKnown (eDD));
CHECK (meta2.get(eDD).state() == LOCKED);
// importing the state record did not re-invoke the constructor
CHECK (testBuffer == RAND1 + 1.1);
meta2.get(eDD).mark(EMITTED);
CHECK (meta2.get(eDD).state() == EMITTED);
CHECK (meta1.get(eDD).state() == LOCKED);
meta1.import (eDD, meta2);
CHECK (meta1.get(eDD).state() == EMITTED);
// now mark the buffer as released in Registry-1
CHECK (eDD.state() == EMITTED);
eDD.mark(FREE);
CHECK (eDD.state() == FREE);
CHECK (meta2.get(eDD).state() == EMITTED);
// it is not allowed to mark de-allocated through synchronisation
VERIFY_ERROR (LOGIC, meta2.import (eDD, meta1) );
VERIFY_ERROR (LIFECYCLE, meta2.release(HashVal(eDD)) );
CHECK (meta2.get(eDD).state() == EMITTED);
meta2.get(eDD).invalidate (false);
CHECK (meta2.get(eDD).state() == FREE);
meta2.release(eDD);
meta1.release(eDD);
}
/** @test simulate a standard buffer provider usage cycle
*/
void
verify_publishNewKey()
{
}
void
verify_fetchNewKeyOnUse()
{
}
};
/** Register this test class... */
LAUNCHER (LocalBufferStage_test, "unit engine");
}}} // namespace vault::mem::test

View file

@ -144223,13 +144223,35 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1776204526672" ID="ID_961403309" MODIFIED="1776477618192" TEXT="Prototyping (testgetrieben)">
<icon BUILTIN="pencil"/>
<node CREATED="1776204565654" ID="ID_45614742" MODIFIED="1776204569140" TEXT="generischer Slice">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1776293021744" ID="ID_290625266" MODIFIED="1776522855937" TEXT="Problem: dieses Setup ist effektiv statisch/global">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Das erfordert Vorsicht, weil man dadurch die Eigenschaft einer geschlossenen Service-Instanz verliert; es ist also leider kein drop-in-replacement, mit dem man einfach eine tief eingebettete Koponente thread-local macht; theoretisch w&#252;rde letzteres zwar gehen, aber dann auf einen zweistufigen Dispatch hinauslaufen, d.h. nach dem thread-local-Zugriff m&#252;&#223;te noch ein Dispatch &#252;ber eine Map per umschlie&#223;ender Service-Instanz erfolgen.
</p>
<p>
</p>
<p>
In dem Fall beschr&#228;nke ich mich aber letztlich auf unsere Anforderungen hier, und da brauchen wir tats&#228;hchlich nur eine globale Instanz &#8212; ich hatte mir ja sogar schon &#252;berlegt, ob man nicht sogar die BufferMetadata koplett als separaten Service herausl&#246;sen m&#252;&#223;te; das bekommen wir hier nun als Nebeneffekt geschenkt
</p>
</body>
</html></richcontent>
<arrowlink COLOR="#a61032" DESTINATION="ID_808171568" ENDARROW="Default" ENDINCLINATION="11;-94;" ID="Arrow_ID_66477268" STARTARROW="None" STARTINCLINATION="259;15;"/>
<icon BUILTIN="messagebox_warning"/>
</node>
<node COLOR="#338800" CREATED="1776204565654" ID="ID_45614742" MODIFIED="1776522493293" TEXT="generischer Slice">
<icon BUILTIN="button_ok"/>
<node CREATED="1776292208608" ID="ID_1141218614" MODIFIED="1776477482889" TEXT="Zweck: Anbinden eines Slice in einem normalen Objekt-Kontext">
<icon BUILTIN="info"/>
<node CREATED="1776292987880" ID="ID_808037956" MODIFIED="1776477504910" TEXT="Zugriff &#xfc;ber Pointer-artige Syntax">
<icon BUILTIN="yes"/>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1776293021744" ID="ID_808171568" MODIFIED="1776477489785" TEXT="Problem: thread_local ist implizit statisch">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1776293021744" ID="ID_808171568" MODIFIED="1776522852160" TEXT="Problem: thread_local ist implizit statisch">
<linktarget COLOR="#a61032" DESTINATION="ID_808171568" ENDARROW="Default" ENDINCLINATION="11;-94;" ID="Arrow_ID_66477268" SOURCE="ID_290625266" STARTARROW="None" STARTINCLINATION="259;15;"/>
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1776293040334" ID="ID_555811092" MODIFIED="1776293060463" TEXT="das hei&#xdf;t es ist eine &#xbb;versteckte globale Variable&#xab;"/>
<node CREATED="1776295872712" ID="ID_906429004" MODIFIED="1776295889501" TEXT="stellt das f&#xfc;r den konkteten Use-Case &#xfc;berhaupt eine Beschr&#xe4;nkung dar?">
@ -144324,8 +144346,7 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
man mu&#223; den aktuellen Namespace schlie&#223;en, in lib gehen, dort die explizite Spezialisierung definieren...
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<icon BUILTIN="smiley-angry"/>
</node>
<node CREATED="1776521934417" ID="ID_130169122" MODIFIED="1776522032176" TEXT="L&#xf6;sung: 2.Template-Parameter mit Default-Init">
@ -144341,8 +144362,7 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
direkt bei LocalSlice definiert; erzeugt eine default-konstruierte Instanz vom Service
</p>
</body>
</html>
</richcontent>
</html></richcontent>
</node>
<node COLOR="#338800" CREATED="1776522006689" ID="ID_1991943573" MODIFIED="1776522019517" TEXT="getested: Spezialisierung ist m&#xf6;glich">
<icon BUILTIN="button_ok"/>
@ -144391,7 +144411,89 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</node>
</node>
</node>
<node CREATED="1776204569932" ID="ID_1285006287" MODIFIED="1776204574453" TEXT="Stage-Slice"/>
<node CREATED="1776204569932" ID="ID_1285006287" MODIFIED="1776204574453" TEXT="Stage-Slice">
<node COLOR="#338800" CREATED="1776522893322" ID="ID_1694212347" MODIFIED="1776566332195" TEXT="BufferMetadata um Migration erweitern">
<icon BUILTIN="button_ok"/>
<node CREATED="1776556044669" ID="ID_857328588" MODIFIED="1776556055571" TEXT="Begriff: Key-chain">
<node CREATED="1776556089572" ID="ID_565019641" MODIFIED="1776556103328" TEXT="ein Entry mit allen seinen Parent-Entries"/>
</node>
<node CREATED="1776556171704" ID="ID_1788136226" MODIFIED="1776556247063" TEXT="Zweck: Abgleich zwischen zwei Metadata-Tabellen">
<node CREATED="1776556712396" ID="ID_346498254" MODIFIED="1776556720533" TEXT="Erg&#xe4;nzen fehlender Informationen"/>
<node CREATED="1776556725461" ID="ID_154374768" MODIFIED="1776556776087" TEXT="beschr&#xe4;nkter Abgleich bestimmter Daten"/>
<node CREATED="1776556777026" ID="ID_441313668" MODIFIED="1776556800521" TEXT="Exception falls inkonsistenter Abgleich verlangt wird">
<node CREATED="1776556809443" ID="ID_480351503" MODIFIED="1776556822013" TEXT="ein Buffer darf nicht NULL werden"/>
<node CREATED="1776556823233" ID="ID_964120138" MODIFIED="1776556838587" TEXT="State darf nur erlaubten Transitionen folgen"/>
<node CREATED="1776556866299" ID="ID_1595480658" MODIFIED="1776557157499" TEXT="setzt implizit einen logisch koh&#xe4;renten Aufruf der Synchronisation vorraus">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Das bedeutet, die hier aufgestellten Regeln/Verbote sind so konstruiert, da&#223; sie bei einem sinnvollen Abgleich niemals auftreten k&#246;nnen.&#160;&#160;Der Sinn ergibt sich dabei aus den vorgesehenen Zustands&#252;berg&#228;ngen; es sollten nur Typ-Informationen angereichert werden, und konkrete Eintr&#228;ge sollten eigentlich gar nicht synchronisiert werden; hilfsweise sehe ich die M&#246;glichkeit vor, da&#223; konkrete Eintr&#228;ge sich gem&#228;&#223; State-Machine vorw&#228;rts bewegen (falls wir tats&#228;chlich konkrete Eintr&#228;ge zentral ablegen, wenn sie in den Cache wandern &#8212; ich kann diese M&#246;glichkeit derzeit nicht ausschlie&#223;en)
</p>
</body>
</html>
</richcontent>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1776557583845" ID="ID_1234349568" MODIFIED="1776566359211" TEXT="Schnittstelle / Mechanismus">
<icon BUILTIN="list"/>
<node CREATED="1776557598572" ID="ID_930030080" MODIFIED="1776557685683" TEXT="hier tritt das Problem der Storage auf">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
es sollte nur alloziert werden, wenn das auch notwendig ist; man k&#246;nnte entweder einen Buffer vorsehen, oder direkt von Quelle zu Ziel arbeiten.
</p>
</body>
</html>
</richcontent>
</node>
<node CREATED="1776557687442" ID="ID_1438523066" MODIFIED="1776557735700" TEXT="Einsicht: das hier ist Implementierung / es geht nur um Komplexit&#xe4;t">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
es m&#252;ssen keine Komponenten gegeneinander isoliert werden; die Schnittstelle dient nur dazu, den vorgang leicht beherrschbar zu machen
</p>
</body>
</html>
</richcontent>
</node>
<node CREATED="1776557770647" ID="ID_1041865636" MODIFIED="1776557793535" TEXT="&#x27f9; Operation sitzt auf dem Empf&#xe4;nger und bekommt die Quelle als const"/>
<node CREATED="1776557806144" ID="ID_1595165986" MODIFIED="1776557808303" TEXT="Logik">
<node CREATED="1776557909652" ID="ID_1185060059" MODIFIED="1776557938771" TEXT="wenn Quelle den Key nicht hat &#x27f9; fertig">
<linktarget COLOR="#a9b4c1" DESTINATION="ID_1185060059" ENDARROW="Default" ENDINCLINATION="102;0;" ID="Arrow_ID_680660049" SOURCE="ID_294261855" STARTARROW="None" STARTINCLINATION="102;0;"/>
</node>
<node CREATED="1776557809545" ID="ID_1932336352" MODIFIED="1776557831449" TEXT="wenn ich den Key nicht habe &#x27f9; Daten kopieren"/>
<node CREATED="1776557832463" ID="ID_727929224" MODIFIED="1776557843944" TEXT="wenn ich den Key habe &#x27f9; Abgleich mit Konsistenzcheck"/>
<node CREATED="1776557887075" ID="ID_294261855" MODIFIED="1776557938772" TEXT="rekursiv den Abgleich f&#xfc;r den parent-Hash ausl&#xf6;sen">
<arrowlink DESTINATION="ID_1185060059" ENDARROW="Default" ENDINCLINATION="102;0;" ID="Arrow_ID_680660049" STARTARROW="None" STARTINCLINATION="102;0;"/>
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1776566204691" ID="ID_1993707384" MODIFIED="1776566327373" TEXT="Test">
<icon BUILTIN="button_ok"/>
<node CREATED="1776566208368" ID="ID_318163591" MODIFIED="1776566219745" TEXT="verwende zwei lokal instantiierte BufferMetadata-Registries"/>
<node CREATED="1776566220517" ID="ID_268834894" MODIFIED="1776566230815" TEXT="bef&#xfc;lle sie schrittweise mit Typ-Keys"/>
<node CREATED="1776566231540" ID="ID_368942183" MODIFIED="1776566246638" TEXT="synchronsiere jeweils hin- und her"/>
<node CREATED="1776566247466" ID="ID_268191870" MODIFIED="1776566267353" TEXT="zeige da&#xdf; die Metadaten auch in der enderen Registry sind"/>
<node CREATED="1776566268399" ID="ID_1584729655" MODIFIED="1776566286040" TEXT="lege sogar einen aktiven Buffer-Entry an..."/>
<node CREATED="1776566286951" ID="ID_1237749199" MODIFIED="1776566301833" TEXT="kann auch hier synchronisieren, solange die State-Transitions valide sind"/>
<node CREATED="1776566302722" ID="ID_563548473" MODIFIED="1776566323627" TEXT="ein release / dtor-call kann nicht synchronisiert werden"/>
</node>
</node>
<node CREATED="1776566422332" ID="ID_773286904" MODIFIED="1776566436334" TEXT="Verbindung zur zentral-Registry">
<node CREATED="1776566441432" ID="ID_83703051" MODIFIED="1776566458537" TEXT="neue Typ-Registrierung im Thread &#x27f9; zur Zentrale publiziert"/>
<node CREATED="1776566459396" ID="ID_20651536" MODIFIED="1776566492396" TEXT="Verwendung im Thread &#x27f8; Typ aus Zentrale synchronisiert"/>
</node>
</node>
<node CREATED="1776204575221" ID="ID_869340284" MODIFIED="1776204579271" TEXT="Store-Slice"/>
<node CREATED="1776204694205" ID="ID_1617552510" MODIFIED="1776204701641" TEXT="WorkerBufferStage">
<node CREATED="1776436907819" ID="ID_1105234662" MODIFIED="1776439761005" TEXT="Rolle der RootID kl&#xe4;ren">