From 4dc6885665c366509fc53cbf3d071d2e01bcdcbe Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 13 Apr 2026 23:13:55 +0200 Subject: [PATCH] BufferProvider: complete the clean-up effort Looking through all my work notes, it seems that all the problematic aspects are now solved in a satisfactory way. * it is clear how to dependency-inject a `BufferProvider` * the internals of the `BufferProvider` front-End have been restructured * there are now two ''pillars'' of the implementation: `BufferStage` and `BufferStore` * both can be subclassed independently * `BufferProviderSetup` exposes a proxying-mechanism (used for `DiagnosticBufferProvider`) * the `BufferMetadata` have been reviewed and some minor details were rectified * after a re-Design of `OutputSlot`, `BuffHandle` can now be used as unified front-End * both `DiagnosticBufferProvider` and `DiagnosticOutputSlot` have been rewritten * they are now independent, self-contained specialisations, layered on top Overall, what might seem like a detour, was an important step towards well-balanced structures and a better understanding of the Render Engine. --- src/vault/out/output-slot.hpp | 14 + tests/47engine.tests | 5 + tests/48playout.tests | 5 + tests/steam/engine/engine-ctx-test.cpp | 22 - tests/vault/mem/buffer-store-test.cpp | 238 +++++++++++ tests/vault/out/output-alloc-state-test.cpp | 199 +++++++++ wiki/thinkPad.ichthyo.mm | 443 ++++++++++++++++---- 7 files changed, 823 insertions(+), 103 deletions(-) create mode 100644 tests/vault/mem/buffer-store-test.cpp create mode 100644 tests/vault/out/output-alloc-state-test.cpp diff --git a/src/vault/out/output-slot.hpp b/src/vault/out/output-slot.hpp index 80ab2e615..5f2ce4fc7 100644 --- a/src/vault/out/output-slot.hpp +++ b/src/vault/out/output-slot.hpp @@ -202,6 +202,20 @@ namespace out { { } // standard copy operations acceptable + + + /** + * Builder: allocate a set of connections of the given type + * @note usually there is no need to subclass OutputSlot or AllocState, + * rather this factory should be used to establish an activated + * OutputSlot configured to use a specific OutputSlot::Connection. + */ + template + static OutputSlot + allocate (size_t cnt, FUN&& populator) + { + return OutputSlot{std::make_unique> (cnt, std::forward(populator))}; + } }; diff --git a/tests/47engine.tests b/tests/47engine.tests index ff3948c01..4629fbe60 100644 --- a/tests/47engine.tests +++ b/tests/47engine.tests @@ -32,6 +32,11 @@ return: 0 END +TEST "BufferStore interface" BufferStore_test <() ); } - - - /** @todo automagically post on X with lots of UPERCASE LETTERS */ - void - verify_disaster() - { - UNIMPLEMENTED ("general meltdown"); - } }; diff --git a/tests/vault/mem/buffer-store-test.cpp b/tests/vault/mem/buffer-store-test.cpp new file mode 100644 index 000000000..32d0fad33 --- /dev/null +++ b/tests/vault/mem/buffer-store-test.cpp @@ -0,0 +1,238 @@ +/* + BufferStore(Test) - verify how the BufferStore API is invoked from BufferProvider + + Copyright (C) + 2026, Hermann Vosseler + +  **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 buffer-store-test.cpp + ** unit test \ref BufferStore_test + */ + + +#include "test/run.hpp" +#include "test/event-log.hpp" +#include "test/test-helper.hpp" +#include "vault/mem/buffer-provider-setup.hpp" +#include "vault/mem/simple-buffer-state-registry.hpp" +#include "lib/format-obj.hpp" + +#include +#include + +using test::EventLog; +using std::string; + + +namespace util { // add some rigged string-conversions.... + using vault::mem::Buff; + using vault::mem::LocalTag; + + template<> + struct StringConv + { + static std::string + invoke (Buff const* dummyBuff) noexcept + { + return "Mem:" + util::showHashLSB ((size_t)dummyBuff); + } + }; + template<> + struct StringConv + { + static std::string + invoke (LocalTag const& localTag) noexcept + { + uint64_t dummyMark(localTag); + return "Tag:" + util::showHashLSB (dummyMark); + } + }; +} + +namespace vault::mem::test { + + + namespace { // test fixture... + /** + * Dummy implementation of the BufferProvider interface. + * Instead of a real storage backend, this test setup uses + * an embedded EventLog to mark all calls received by the + * BufferStore API-functions. Furthermore, the value + * transported in the LocalTag is returned as buffer address. + */ + class TestProvider + : public BufferProviderSetup + { + + class LoggingBufferStore + : public BufferProviderSetup::Store + { + /** interpret the LocalTag as buffer address */ + Buff* + asBuffer (LocalTag targetMarker) + { + void* buffMem{targetMarker}; + return static_cast (buffMem); + } + + + /* =========== BufferStore interface =========== */ + + uint + prepareBuffers (HashVal typeID, uint cnt,size_t siz) override + { + log.call ("TestProvider", "prepareBuffers", typeID,cnt,siz); + return cnt; + } + + BuffAlloc + provideBuffer (HashVal typeID, size_t siz, LocalTag localTag, int64_t customArg) override + { + log.call ("TestProvider", "provideBuffer", typeID,siz,localTag,customArg); + + Buff* storage = asBuffer (localTag); + log.event ("allocate").addAttrib("mem", util::toString(storage)) + .addAttrib("tag", util::toString(localTag)) + ; + // A real BufferStore would perform the allocation task here... + BuffAlloc storageSlot{storage, siz, localTag}; + return storageSlot; + } + + void + mark_emitted (HashVal typeID, BuffAlloc storageSlot) override + { + auto& [storage,siz,localTag] = storageSlot; + log.call ("TestProvider", "mark_emitted", typeID,storage,siz,localTag); + } + + void + detachBuffer (HashVal typeID, BuffAlloc storageSlot) override + { + auto& [storage,siz,localTag] = storageSlot; + log.call ("TestProvider", "detachBuffer", typeID,storage,siz,localTag); + } + }; + + + public: + TestProvider() + : BufferProviderSetup{*this} + { + log.event("Setup"); + } + + auto buildStage() { return std::make_unique ("BufferStore_test"); } + auto buildStore() { return std::make_unique(); } + + + static EventLog log; + + + /** prepare a specifically rigged »buffer type« that passes the given \a mark + * as a LocalTag to the BufferStore, which will return it as buffer "address" */ + BuffDescr + specifyBufferType (size_t siz, uint64_t mark) + { + auto& typeKey = bufferStage_->defineBufferType (siz, TypeHandler::RAW, LocalTag(mark)); + return buildDescriptor (typeKey); + } + }; + + EventLog TestProvider::log("static BufferStore_test"); + + + }//(End) test fixture + + + + + + + /***********************************************************************//** + * @test demonstrate how usage of the BufferProvider interface + * translates into calls on the internal BufferStore API. + * - use an embedded EventLog to verify sequence of events + * - use a rigged BufferStore implementation that logs invocations + * - perform a typical buffer usage cycle + * @see BufferProviderProtocol_test + * @see EventLog_test + */ + class BufferStore_test : public Test + { + + virtual void + run (Arg) + { + auto& log = TestProvider::log; + log.clear ("Start BufferStore_test"); + + const size_t BUFFSIZ = 55; + const size_t ANOUNCE = 23; + const int64_t ARG = 88; + const uint64_t MARK = 42; + + // this is how the packaged mark will be rendered... + const string TAG = util::toString (LocalTag{MARK}); + const string MEM = util::toString ((Buff*) MARK); + + + TestProvider pro; + + BuffDescr descr = pro.specifyBufferType(BUFFSIZ, MARK); + HashVal typeID{descr}; + + log.event ("announce buffers"); + descr.announce (ANOUNCE); + + log.event ("lock a buffer"); + BuffHandle han = descr.lockBuffer (ARG); + CHECK (han.isAllotted()); + + Buff* mem = han.rawStorage(); + CHECK (MARK == (uint64_t)mem ); + + log.event ("emit buffer"); + han.emit(); + + log.event ("release buffer"); + han.release(); + CHECK (not han.isValid()); + + + cout << "____Event-Log_________________\n" + << util::join(log, "\n") + << "\n───╼━━━━━━━━━━━╾──────────────"< + +  **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 output-alloc-state-test.cpp + ** unit test \ref OutputAllocState_test + */ + + +#include "test/run.hpp" +#include "test/event-log.hpp" +#include "test/test-helper.hpp" +#include "vault/mem/buffhandle.hpp" +#include "vault/out/output-slot.hpp" +#include "vault/out/output-slot-connection.hpp" +#include "lib/format-obj.hpp" +#include "lib/util.hpp" + +#include +#include + +using std::string; +using test::EventLog; +using vault::mem::Buff; +using vault::out::FrameID; +using util::isSameAdr; + + +namespace vault::out::test { + + namespace { // test fixture... + + uint64_t DUMMY_BUFF{0xAaAaAa}; + + EventLog log("static OutputAllocState_test"); + + + inline std::string + mark (void const* dummyBuff) noexcept + { + return "Mem:" + util::showHashLSB ((size_t)dummyBuff); + } + + /** + * Dummy implementation of the OutputSlot::Connection interface. + * Instead of engaging into actual output operations, this mock + * implementation protocols all received calls into an EventLog. + * A dummy memory address and buffer size is returned. + */ + class MockConnection + : public OutputSlot::Connection + { + + /* === Connection API === */ + + size_t + getBufferSize() const override + { + log.call ("MockConnection", "getBufferSize"); + return sizeof(DUMMY_BUFF); + } + + Buff* + claimBufferFor (FrameID frame) override + { + log.call ("MockConnection", "claimBufferFor", frame); + void* dummy = &DUMMY_BUFF; + return static_cast (dummy); + } + + void + publish (Buff* buff) override + { + log.call ("MockConnection", "publish", mark(buff)); + } + + void + release (Buff* buff) override + { + log.call ("MockConnection", "release", mark(buff)); + } + + void + shutDown() override + { + log.call ("MockConnection", "shutDown"); + } + + public: + MockConnection() { log.call ("MockConnection", "create"); } + ~MockConnection() { log.call ("MockConnection", "destroy"); } + }; + + + inline OutputSlot + makeFakeSlot() + { + const uint NUM_CONNECTIONS = 1; + return OutputSlot::allocate (NUM_CONNECTIONS + ,[](auto& storage) + { + storage.template create(); + }); + } + }//(End) test fixture + + + + + + + /***********************************************************************//** + * @test demonstrate how the internal implementation hub within OutputSlot + * is connected to some OutputSlot::Connection implementation and + * exposes a BuffHandle as front-end. + * - use an embedded EventLog to verify sequence of events + * - use a rigged mock connection that logs invocations + * - perform all operations for a typical output usage + * @see OutputSlotProtocol_test + * @see EventLog_test + */ + class OutputAllocState_test : public Test + { + + virtual void + run (Arg) + { + log.clear ("Start OutputAllocState_test"); + + const FrameCnt FRA_NO = 55; + { + log.event("create FakeSlot"); + auto slot = makeFakeSlot(); + + log.event("retrieve DataSink"); + auto sinks = slot.getOpenedSinks(); + auto sink = *sinks; + ++sinks; + CHECK (not sinks); + + log.event("lock buffer"); + BuffHandle han = sink(FRA_NO); + CHECK (han); + CHECK (han.size() == sizeof(DUMMY_BUFF)); + CHECK (isSameAdr (han.rawStorage(), &DUMMY_BUFF)); + + log.event("emit buffer"); + han.emit(); + CHECK (not han); + + log.event("release buffer"); + han.release(); + + log.event("abandon FakeSlot"); + }// last instance of this slot goes out of scope... + + + cout << "____Event-Log_________________\n" + << util::join(log, "\n") + << "\n───╼━━━━━━━━━━━╾──────────────"< - + - - - - + + + + + + + + + + + + + @@ -116308,8 +116317,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + @@ -116447,7 +116456,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -116470,9 +116479,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + - + @@ -116493,13 +116502,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + - + @@ -116508,6 +116517,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)

+ @@ -116767,7 +116777,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -116885,7 +116895,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -116897,16 +116907,19 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + - + - + + + + @@ -116995,7 +117008,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -117007,6 +117020,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + @@ -117014,7 +117030,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + @@ -117025,6 +117042,20 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + + + + + + + + + + @@ -117094,10 +117125,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + - + @@ -117106,6 +117137,45 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)

+ +
+ + + + +
    +
  • + zunächst einmal war der BufferProvider selber zu kohäsiv, und hatte keine Innen-Struktur; insofern war es schwierig, konkrete Varianten durch Subclassing zu schaffen — wodurch es nahezu unmöglich wurde, eine Proxy-Variante zu schaffen, die etwas anderes macht als »point-and-shot« direkt in ein Memory-Management +
  • +
  • + und zudem hing das Thema »OutputSlot« komplett in der Luft, und der DiagnosticOutputSlot war die einzige Implementierung, die sich mit vertretbarem Aufwand realisieren ließ. Dieser erhebliche Aufwand hat mich unter Druck gesetzt, und deshalb habe ich die Arbeit abgekürzt, und direkt die Dummy-Implementierung des BufferProvider huckepack genommen (teilweise sogar am DiagnosticBufferProivder vorbei). Wegen dem ersten Problem aber konnte ich an der Stelle nicht direkt delegieren, und hab stattdessen neue API-Funktionen auf der DataSink geschaffen +
  • +
+

+ Insofern gab es keine guten Gründe — aber ich hab's nicht besser hinbekommen damals, und ich hab die Bedeutung dieser Kongruenz auch noch nicht wirklich verstanden (weil die Engine nie soweit durchentwickelt worden war). +

+ +
+ +
+ + + + +

+ Dazu ist zweierlei festzustellen +

+
    +
  • + der OutputSlot verwendet nun ein BuffHandle als front-End für das Protokoll. Das ist fest verdrahtet, und so bekommt es der user von der DataSink +
  • +
  • + allerdings zur Implementierung wird keinerlei Vorgabe mehr gemacht; tatsächlich ist der OutputSlot nun komplett von der Implementierung des Output-Mechanismus abstrahiert. Stattdessen muß nun die Implementierung des Interface OutputSlot::Connection dafür sorgen, irgendwo eine Buffer-Addresse herzubekommen. Dafür kann sie, muß aber nicht, intern einen BufferProvider ansprechen +
  • +
+ +
+
@@ -117119,10 +117189,45 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + +

+ Fazit 3/26: nach Überarbeitung gilt dieses Thema als befriedigend gelöst +

+ +
+ + + +

+ Der bestehende Stand der Entwürfe von 2012 war problematisch, und schwer zu greifen; es war eine zähe und schwierige Aufgabe, das entstandene Geflecht systematisch auseinanderzunehmen. Als Ergebnis ist nun +

+
    +
  • + im BufferProvider eine Implementierungs-Struktur definiert, welche Metadaten (BufferStage) und Storage-Handling (BufferStore) sauber trennt und sparat erweiterbar macht +
  • +
  • + nach einem kompletten re-Design die Implementierung des OutputSlot generisch und muß nicht mehr durch Subclassing für den jeweilgen Fall eingerichtet werden +
  • +
  • + die Möglichkeit geschaffen, durch das Buffer-Handle als front-End die Protokoll-Schritte über einen Proxy-Buffer-Provider durchzureichen +
  • +
  • + die Test/Diagnose-Implementierung in beiden Fällen komplett neu geschrieben worden, und zwar als eigenständiger Layer oberhalb einer nun ebenfalls eigenständigen default-Implementierung +
  • +
+

+ Insgesamt habe ich nun den Eindruck, daß dieses Design insgesamt eine Errungenschaft ist, und ich mich damit durchaus blicken lassen kann +

+ +
+
- + @@ -117131,7 +117236,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)

- + @@ -117216,28 +117321,28 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
- - + + - - + + - + - - + + - + - + - + - + @@ -117246,9 +117351,23 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)

- + + + + + +

+ ...insofern reicht der einfache Testfall, denn der Rest sind generische Mechanismen. +

+

+ .....grade eben wollte ich clever werden und einen raffinierten Testfall demonstrieren, in dem ctx().mem durch einen DiagnostricBufferProvider ausgetauscht wird; es zeigte sich, daß das tatsächlich unmöglich ist, weil ich auf mehreren Ebenen das Design »zugenagelt« habe. Und zwar aus der Motivation, daß es niemals gut sein kann, in solchen Angelegenheiten der Applikations-Struktur »clever« zu werden +

+ +
+
- + + @@ -117329,12 +117448,12 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + - + @@ -117360,7 +117479,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -117408,9 +117527,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - + + + @@ -117452,7 +117571,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -117461,12 +117580,36 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)

- + + + + + +

+ nicht daß das einfach gewesen wäre.... +

+
    +
  • + wir haben jetzt eine sauber gegliederte innere Struktur im BufferProvider +
  • +
  • + der Konfigurationspunkt liegt im BufferProviderSetup +
  • +
  • + dort können die beiden Säulen der Implementierung (BufferStage und BufferStore) durch geeignete Subklassen realisiert werdn +
  • +
  • + es gibt im BufferProviderSetup nun sogar einen generischen / Template-basierten Proxying-mechanismus, mit dem man nachträglich einen Proxy über eine bestehende Implementierung stülpen kann. Das wird vom DiagnosticBufferProvider genutzt +
  • +
+ +
+
@@ -117481,17 +117624,98 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + + + + + + + + + + + +

+ Und ich wollte das so, explizit, aus zwei Gründen: +

+
    +
  • + damit niemand »clever« werden kann +
  • +
  • + damit der Code in der RenderEngine nicht durch ein double-checked-Locking muß +
  • +
+

+ (Und grade eben merke ich, daß die Versuchung groß ist, hier clever zu werden) +

+ +
+ +
+ + + + +

+ Ja das sind tatsächlich Referenzen — sie zeigen auf ein einziges Objekt im Speicher, und das kann man nie mehr ändern. +

+ +
+ +
+ + + + +

+ Sofern das Monostate lib::Depend<TY> einmal in default-Konfiguration instantiiert wurde, sperrt sich die Dependency-Factory dagegen, irgend etwas anderes hier zu injizieren +

+ +
+ +
+ + + + +

+ Und zwar nicht durch eine einzige bewußte Entscheidung, sondern vielmehr indem ich immer wieder das Prinzip angewendet habe, besonders »cleveren« Code unmöglich zu machen. Das führt nun dazu, daß man tatsächlich auf eine einzige Instanz festgelegt ist: die Tests verwenden das NaiveBufferSetup, und die Lumiera-Applikation wird eine Gelegenheit haben, eine spezielle Instanz für die Render-Engine zu konfigurieren, mehr nicht. +

+

+ +

+

+ Im ersten Moment war ich grade eben schockiert, als ich das realisiert habe — aber nach kurzem Nachdenken komme ich zu dem Schluß, daß es gut ist so. +

+
    +
  • + damit niemand »clever« werden kann +
  • +
  • + damit der Code in der RenderEngine nicht durch ein double-checked-Locking muß +
  • +
+ +
+
- - + +
+ + - + @@ -117537,7 +117761,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
- + + + @@ -117545,9 +117771,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - + + + @@ -117738,10 +117964,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + - - + + @@ -118996,7 +119222,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -119112,7 +119338,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -119388,7 +119614,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -120080,11 +120306,11 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + - + @@ -120746,7 +120972,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + @@ -120771,9 +120998,37 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - + + + + + + + +
+
+

+ if (not buffer) +

+

+ throw err::Fatal{"Attempt to lock for a NULL buffer. Allocation floundered?" +

+

+ , LERR_(BOTTOM_VALUE)}; +

+

+ +

+
+
+ +
+ +
+ + + +
@@ -121718,7 +121973,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
- + @@ -121770,8 +122025,16 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + + + +

+ ...es muß einen regulären Weg geben, wie eine unmögliche Allokation signalisiert wird; tatsächlich erledigt das nun (nach dem Aufräumen und Klären der Rollen) die Implementierung von BufferMetadata::markLocked() +

+ +
+
@@ -122579,7 +122842,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
- + @@ -122630,6 +122893,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + @@ -122682,7 +122946,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -122808,7 +123072,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
- + @@ -123479,7 +123743,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -123571,7 +123835,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -143548,8 +143812,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + @@ -143751,7 +144016,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -143891,7 +144156,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -144053,7 +144318,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -144084,7 +144349,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -144184,7 +144449,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -144946,7 +145211,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -145057,7 +145322,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -168351,6 +168616,22 @@ std::cout << tmpl.render({"what", "World"}) << s + + + + + + + + +

+ ...und das war eine Notlösung, die einige Verwirrungen nach sich gezogen hat; vor allem, weil ich dann die Implementierung mit dem DiagnosticBufferProvider vermischt habe, und noch schlimmer, direkt in dessen Implementierung eingesprungen bin. +

+ +
+
+ +