diff --git a/tests/steam/engine/engine-interface-test.cpp b/tests/steam/engine/engine-interface-test.cpp index 3833d45b8..fd36831bd 100644 --- a/tests/steam/engine/engine-interface-test.cpp +++ b/tests/steam/engine/engine-interface-test.cpp @@ -86,7 +86,7 @@ namespace test { PID pipe = Pipe::query("id(dummy)"); ModelPort port(pipe); - OutputSlot& oSlot = DiagnosticOutputSlot::build(); + DiagnosticOutputSlot oSlot; Allocation& output = oSlot.allocate(); Timings timings (FrameRate::PAL); /////////TODO diff --git a/tests/steam/play/diagnostic-output-slot.hpp b/tests/steam/play/diagnostic-output-slot.hpp index 76ea24271..060c0f018 100644 --- a/tests/steam/play/diagnostic-output-slot.hpp +++ b/tests/steam/play/diagnostic-output-slot.hpp @@ -27,23 +27,27 @@ #include "include/logging.h" #include "steam/play/output-slot.hpp" #include "steam/play/output-slot-connection.hpp" -#include "steam/asset/meta/time-grid.hpp" #include "vault/mem/buffhandle.hpp" #include "vault/mem/naive-buffer-setup.hpp" #include "lib/time/timevalue.hpp" -#include "lib/scoped-ptrvect.hpp" -#include "lib/iter-source.hpp" +#include "lib/time/grid.hpp" +#include "lib/p.hpp" +#include "lib/null-value.hpp" +#include "lib/iter-explorer.hpp" +#include "lib/iter-source.hpp" /////////////OOO wech #include "lib/symbol.hpp" #include "lib/util.hpp" #include "vessel/advice.hpp" #include "test/test-frame.hpp" #include +#include #include namespace steam { namespace play { + namespace err = lumiera::error; using lib::Symbol; using lib::HashVal; @@ -51,19 +55,23 @@ namespace play { using util::contains; using test::TestFrame; using lib::time::FrameRate; - using steam::asset::meta::PGrid; - using steam::asset::meta::TimeGrid; + using lib::time::TimeVar; + using lib::time::Time; using vault::mem::NaiveBufferSetup; using vault::mem::BuffHandle; using vault::mem::BuffDescr; using std::shared_ptr; + using PGrid = lib::P; + + namespace { // diagnostics & internals.... inline PGrid getTestTimeGrid() { +#if false //////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : disabled code to disentangle BufferProvider implementation Symbol gridID("DiagnosticOutputSlot-buffer-grid"); vessel::advice::Request query4grid(gridID) ; PGrid testGrid25 = query4grid.getAdvice(); @@ -72,7 +80,9 @@ namespace play { testGrid25 = TimeGrid::build (gridID, FrameRate::PAL); ENSURE (testGrid25); - return testGrid25; +#endif /////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (end) disabled code + PGrid testGrid; ////////OOO Kaboooom! + return testGrid; } } @@ -247,10 +257,73 @@ namespace play { }; +/////////////////////////////////////////////////////////////////////// +/// /////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1395 : draft compatible Tracking-API + namespace diagn { + using Buff = vault::mem::Buff; + + struct FraInfo + { + TimeVar locked{Time::NEVER}; + TimeVar emitted{Time::NEVER}; + TimeVar released{Time::NEVER}; + + bool wasLocked() const { return locked != Time::NEVER; } + bool wasEmitted() const { return locked != Time::NEVER; } + bool wasReleased() const { return locked != Time::NEVER; } + + size_t buffSize{0}; + Buff* storage{nullptr}; + Buff* accessMemory() const { return storage; } + + template + BU const& accessAs() const; + + template + bool operator== (BU const&) const; + }; + + struct FeedLog + { + FraInfo const& + frame (uint fraNr) const + { + ///////////////////////////OOO access stored log + return lib::NullValue::get(); + } + + auto + blockIter(uint startFrame =0) const + { + return lib::explore (lib::NumIter{startFrame, std::numeric_limits::max()}) + .transform([this](uint idx){ return frame(idx); }) + ; + } + }; + + template + inline BU const& + FraInfo::accessAs() const + { + if (not storage) + throw err::Logic ("buffer for this frame was never actually locked" + , LERR_(LIFECYCLE)); + return *reinterpret_cast (storage); + } + + template + inline bool + FraInfo::operator== (BU const& refVal) const + { + return storage + and accessAs() == refVal; + } + }//(End)Diagnostic data + - - + class OutputDiagnostic; @@ -271,7 +344,7 @@ namespace play { : public OutputSlotImplBase { - static const uint MAX_CHANNELS = 5; + static const uint MAX_CHANNELS = 5; ///////////////////////////////OOO Build heap-based implementation with arbitrary number of feeds!! /** @note a real OutputSlot implementation * would rely on some kind of embedded @@ -279,7 +352,7 @@ namespace play { uint getOutputChannelCount() { - return MAX_CHANNELS; + return MAX_CHANNELS; /////////////OOO No! } @@ -296,7 +369,9 @@ namespace play { void buildConnection(ConnectionStorage storage) { +#if false //////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : disabled code to disentangle BufferProvider implementation storage.create(); +#endif /////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (end) disabled code } public: @@ -317,9 +392,6 @@ namespace play { getOutputChannelCount()); } - /** @internal is self-managed and non-copyable. - * Clients use #build() to get an instance */ - DiagnosticOutputSlot() { } /** @internal access the implementation object * representing a single stream connection @@ -334,21 +406,8 @@ namespace play { public: - /** build a new Diagnostic Output Slot instance, - * discard the existing one. Use the static query API - * for investigating collected data. */ - static OutputSlot& - build() - { - static lib::ScopedPtrVect diagnosticSlots; - return diagnosticSlots.manage(new DiagnosticOutputSlot); - } + DiagnosticOutputSlot() { } - static DiagnosticOutputSlot& - access (OutputSlot& to_investigate) - { - return dynamic_cast (to_investigate); - } @@ -394,18 +453,22 @@ namespace play { OutFrames getChannel (uint channel) { +#if false //////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : disabled code to disentangle BufferProvider implementation REQUIRE (channel < MAX_CHANNELS); return OutputFramesLog::build( new OutputFramesLog ( accessSequence(channel))); +#endif /////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (end) disabled code } bool frame_was_allocated (uint channel, FrameID nominalFrame) { +#if false //////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : disabled code to disentangle BufferProvider implementation return accessSequence(channel) .wasAllocated(nominalFrame); +#endif /////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (end) disabled code } @@ -431,8 +494,54 @@ namespace play { and block->was_closed(); #endif /////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (end) disabled code } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1395 : draft compatible Tracking-API + + std::vector feed_; + + private: + /// „backdoor“ to watch instrumentation from tests + friend class OutputDiagnostic; }; + + + /** Accessor-proxy to investigate transactions */ + class OutputDiagnostic + : util::MoveOnly + { + DiagnosticOutputSlot& dos_; + public: + OutputDiagnostic (DiagnosticOutputSlot& theSlot) + : dos_{theSlot} + { } + + uint cntLocked() { return 0; } + uint cntEmitted() { return 0; } + uint cntReleased() { return 0; } + + auto const& + feed (uint feedNr) + { + if (feedNr < dos_.feed_.size()) + return dos_.feed_[feedNr]; + else + return lib::NullValue::get(); + } + + auto + getFeed (uint feedNr) + { + return feed(feedNr).blockIter(); + } + }; + + inline OutputDiagnostic + watch (OutputSlot& theSlot) + { + return OutputDiagnostic{static_cast (theSlot)}; + } + }} // namespace steam::play diff --git a/tests/steam/play/output-slot-protocol-test.cpp b/tests/steam/play/output-slot-protocol-test.cpp index 1f4722c5b..3b6685c63 100644 --- a/tests/steam/play/output-slot-protocol-test.cpp +++ b/tests/steam/play/output-slot-protocol-test.cpp @@ -56,7 +56,7 @@ namespace test { // In real usage, the OutputSlot will be preconfigured // (Media format, number of channels, physical connections) // and then registered with / retrieved from an OutputManager - OutputSlot& oSlot = DiagnosticOutputSlot::build(); + DiagnosticOutputSlot oSlot; // Client claims the OutputSlot // and opens it for exclusive use. @@ -66,47 +66,55 @@ namespace test { // "calculation streams" for the individual // Channels to be output through this slot. OutputSlot::OpenedSinks sinks = alloc.getOpenedSinks(); - DataSink sink1 = *sinks; - DataSink sink2 = *++sinks; + DataSink sink0 = *sinks; + DataSink sink1 = *++sinks; // within the frame-calculation "loop" // we perform a data exchange cycle FrameCnt frameNr = 123; - BuffHandle buff00 = sink1.lockBufferFor (frameNr); - BuffHandle buff10 = sink2.lockBufferFor (frameNr); + BuffHandle buff00 = sink0.lockBufferFor (frameNr); + BuffHandle buff10 = sink1.lockBufferFor (frameNr); // rendering process calculates content.... buff00.accessAs() = testData(0,0); // while further frames might be processed in parallel - BuffHandle buff11 = sink2.lockBufferFor (++frameNr); + BuffHandle buff11 = sink1.lockBufferFor (frameNr+1); buff11.accessAs() = testData(1,1); buff10.accessAs() = testData(1,0); // Now it's time to emit the output - sink2.emit (frameNr-1, buff10); - sink2.emit (frameNr , buff11); - sink1.emit (frameNr-1, buff00); + buff11.emit(); + buff00.emit(); + + // Buffers must be marked as released when done + buff00.release(); + buff10.release(); + buff11.release(); // that's all for the client // Verify sane operation.... - DiagnosticOutputSlot& checker = DiagnosticOutputSlot::access(oSlot); - CHECK ( checker.frame_was_allocated (0,123)); - CHECK (!checker.frame_was_allocated (0,124)); - CHECK ( checker.frame_was_allocated (1,123)); - CHECK ( checker.frame_was_allocated (1,124)); + CHECK (watch(oSlot).cntLocked() == 3); + CHECK (watch(oSlot).cntEmitted() == 2); + CHECK (watch(oSlot).cntReleased() == 3); - CHECK (checker.output_was_closed (0,0)); - CHECK (checker.output_was_closed (1,0)); - CHECK (checker.output_was_closed (1,1)); + CHECK ( watch(oSlot).feed(0).frame(123).wasLocked()); + CHECK (not watch(oSlot).feed(0).frame(124).wasLocked()); + CHECK ( watch(oSlot).feed(1).frame(123).wasLocked()); + CHECK ( watch(oSlot).feed(1).frame(124).wasLocked()); - CHECK ( checker.output_was_emitted (0,0)); - CHECK (!checker.output_was_emitted (0,1)); - CHECK ( checker.output_was_emitted (1,0)); - CHECK ( checker.output_was_emitted (1,1)); + CHECK ( watch(oSlot).feed(0).frame(123).wasEmitted()); + CHECK (not watch(oSlot).feed(0).frame(124).wasEmitted()); + CHECK (not watch(oSlot).feed(1).frame(123).wasEmitted()); + CHECK ( watch(oSlot).feed(1).frame(124).wasEmitted()); - DiagnosticOutputSlot::OutFrames stream0 = checker.getChannel(0); - DiagnosticOutputSlot::OutFrames stream1 = checker.getChannel(1); + CHECK ( watch(oSlot).feed(0).frame(123).wasReleased()); + CHECK (not watch(oSlot).feed(0).frame(124).wasReleased()); + CHECK ( watch(oSlot).feed(1).frame(123).wasReleased()); + CHECK ( watch(oSlot).feed(1).frame(124).wasReleased()); + + auto stream0 = watch(oSlot).getFeed(0); + auto stream1 = watch(oSlot).getFeed(1); CHECK ( stream0); CHECK (*stream0 == testData(0,0)); ++stream0; diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 908e307da..01514b7ea 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -5151,7 +5151,7 @@ Thus the mapping is a copyable value object, using an associative array. It may First and foremost, mapping can be seen as a //functional abstraction.// As it's used at implementation level, encapsulation of detail types in't the primary concern, so it's a candidate for generic programming: For each of those use cases outlined above, a distinct mapping type is created by instantiating the {{{OutputMapping<DEF>}}} template with a specifically tailored definition context ({{{DEF}}}), which takes on the role of a strategy. Individual instances of this concrete mapping type may be default created and copied freely. This instantiation process includes picking up the concrete result type and building a functor object for resolving on the fly. Thus, in the way typical for generic programming, the more involved special details are moved out of sight, while being still in scope for the purpose of inlining. But there //is// a concern better to be encapsulated and concealed at the usage site, namely accessing the rules system. Thus mapping leads itself to the frequently used implementation pattern where there is a generic frontend as header, calling into opaque functions embedded within a separate compilation unit. -
+
Within the Lumiera player and output subsystem, actually sending data to an external output requires to allocate an ''output slot''
 This is the central metaphor for the organisation of actual (system level) outputs; using this concept allows to separate and abstract the data calculation and the organisation of playback and rendering from the specifics of the actual output sink. Actual output possibilities (video in GUI window, video fullscreen, using a beamer, sound, Jack, rendering to file) can be added and removed dynamically from various components (Vault, Stage), all using the same resolution and mapping mechanisms (&rarr; OutputManagement)
 
@@ -5164,23 +5164,25 @@ In order to be usable as //output sink,// an output slot needs to be //allocated
 Once allocated, the output slot returns a set of concrete ''sink handles'' (one for each physical channel expecting data). The calculating process feeds its results into those handles. Size and other characteristics of the data frames are assumed to be suitable, which typically won't be verified at that level anymore (but the sink handle provides a hook for assertions). Besides that, the allocation of an output slot reveals detailed ''timing expectations''. The client is required to comply to these [[timings|Timings]] when ''emitting'' data -- he's even required to provide a //current time specification,// alongside with the data. Based on this information, the output slot has the ability to handle timing failures gracefully; the concrete output slot implementation is expected to provide some kind of de-click or de-flicker facility, which kicks in automatically when a timing failure is detected.
 
 !!!usage and implementation
-Clients retrieve a reference to an output slot by asking a suitable OutputManager for an output possibility supporting a specific format. Usually, they just "claim" this slot by invoking {{{allocate()}}}, which behind the scenes causes building of the actual output connections and mechanisms. For each such connection -- corresponding to a single channel within the media format handled by this ~OutputSlot -- the client gets a smart-handle {{{DataSink}}}. The concrete ~OutputSlot implementation performs operations quite specific to the kind of output and external interface in question -- thereby calling directly into a specific connection implementation.
+Clients retrieve a reference to an output slot by asking a suitable OutputManager for an output possibility that supports some specific media and format. Usually, they just "claim" this slot by invoking {{{allocate()}}}, which causes actual output connections to be built and data handover mechanisms to be established, behind the scenes. For each such connection -- corresponding to a single distinct data feed within the media format handled by this ~OutputSlot -- the client gets a smart-handle {{{DataSink}}}. The feed may, or may not correspond to a nominal channel in the media format, since some output interfaces require to deliver the data in separate buffers per channel, while others require data to be interleaved. Generally speaking, the concrete ~OutputSlot implementation performs operations rather specific to the kind of output and external interface in question -- thereby calling directly into a specific connection implementation. The »''Output Slot Protocol''« however is designed in a way to abstract from all those technical details.
 
 !!!timing expectations
-Besides the sink handles, allocation of an output slot defines some timing constraints, to which the client must comply. These [[Timings]] are detailed and explicit, including a grid of deadlines for each frame to deliver, plus a fixed //latency.// Within this context, &raquo;latency&laquo; means the requirement to be ahead of the nominal time by a certain amount, to compensate for the processing time necessary to propagate the media to the physical output pin. The output slot implementation itself is bound by external constraints to deliver data at a fixed framerate and aligned to an externally defined timing grid, plus the data needs to be handed over ahead of these time points by an time amount given by the latency. Depending on the data exchange model, there is an additional time window limiting the buffer management.
+Besides the sink handles, allocation of an output slot defines some timing constraints, to which the client must comply. These [[Timings]] are detailed and explicit, including a grid of deadlines for each frame to deliver, plus a fixed //latency.// Within this context, &raquo;latency&laquo; implies a requirement to be ahead of the nominal time by a certain amount, to compensate for the processing time that is necessary to propagate the media to the physical output pin. The output slot implementation itself is bound by external constraints to deliver data at a fixed framerate and aligned to an externally defined timing grid, and furthermore the data needs to be handed over ahead of these time points by an time amount given by the latency. Depending on the data exchange model, additional timing constraints may be imposed to further limit the time window when the buffer must be provided, transferred and released.
 
-The assumption is for the client to have elaborate timing capabilities at his disposal. More specifically, the client is assumed to be a job running within the engine scheduler and thus can be configured to run //after// another job has finished, and to run within certain time limits. Thus the client is able to provide a //current nominal time// -- which is suitably close to the actual wall clock time. This enables the output slot implementation to work out from this time specification if the call is timely or overdue -- and react accordingly.
+Within this setup, the assumption is for the client to have elaborate timing capabilities at his disposal. More specifically, the „client“ is assumed to be a //job// running within the engine scheduler and thus can be configured to run //after// some other job has finished, and to run within certain time limits. Thus the client is able to run within a //window of current nominal time// -- which is suitably close to the actual system time. On the other hand, the [[Timings]] linked to each {{{DataSink}}} are likewise aligned with the current system time. This enables the output slot implementation to work out from the current system time is a call is timely or overdue -- and to react accordingly.
 
 !!!output modes
-some concrete output connections and drivers embody a specific operation mode (e.g. sample rate or number of channels). The decision and setup of these operational configuration is initiated together with the [[resolution|OutputMapping]] of an OutputDesignation within the OutputManager, finally leading to an output slot (reference), which can be assumed to be suitably configured, before the client allocates this slot for active use. Moreover, an individual output sink (corresponding to a single channel) may just remain unused -- until there is an {{{emit()}}} call and successful data handover, this channel will just feature silence or remain black. (There are some output systems with additional flexibility, e.g. ~Jack-Audio, that allow to generate an arbitrary number of output pins -- Lumiera will support this by allowing to set up additional output slots and attach this information to the current session &rarr; SessionConfigurationAttachment)
+some concrete output connections and drivers embody a specific operation mode (e.g. sample rate or number of channels). The decision and setup of these operational configuration is initiated together with the [[resolution|OutputMapping]] of an OutputDesignation within the OutputManager, finally leading to an output slot (reference), which can be assumed to be suitably configured, before the client allocates this slot for active use. Moreover, an individual output sink (corresponding to some data feed) may just remain unused -- generally speaking, any output channel will just feature silence or remain black, unless or until until there is an {{{emit()}}} call and a successful data handover took place.
+
+As an aside,  it is acknowledged that there are some output systems with additional flexibility, e.g. ~Jack-Audio, that allow to generate an arbitrary number of output pins -- Lumiera will support this flexibility by allowing to set up additional output slots and attach this information to the current session &rarr; SessionConfigurationAttachment
 
 !!!Lifecycle and storage
-The concrete OutputSlot implementation is owned and managed by the facility that actually provides the output capability in question. For example, the GUI provides viewer widgets, while some sound output backend provides sound ports. The associated OutputSlot implementation object is required to stay alive as long as it's registered with some OutputManager. It needs to be de-registered explicitly prior to destruction -- and this deregistration may block until all clients using this slot did terminate. Beyond that, an output slot implementation is expected to handle all kinds of failures gracefully -- preferably just emitting a signal (callback functor).
+The concrete OutputSlot implementation is owned and managed by the facility that actually provides the output capability in question. Once such an output facility becomes usable within Lumiera, it will register this implementation through a suitable OutputManager. For example, the GUI provides several video viewer widgets, while some sound output backend provides sound ports. The associated OutputSlot implementation object is required to stay alive as long as it's registered with an OutputManager. It needs to be de-registered explicitly prior to destruction -- and this deregistration may block until all clients using this slot did terminate. Beyond that, an output slot implementation is responsible to handle all kinds of failures gracefully -- preferably by just emitting a signal (callback functor).
 {{red{TODO 7/11: Deregistration is an unsolved problem....}}}
 
 !!!unified data exchange cycle
 The planned delivery time of a frame is used as an ID throughout that cycle
-# within a defined time window prior to delivery, the client can ''allocate and retrieve the buffer'' from the BufferProvider.
+# within a defined time window prior to delivery, the client can ''allocate and retrieve the buffer'' from the {{{DataSink}}}
 # the client has to ''emit'' within a (short) time window prior to deadline
 # now the slot gets exclusive access to the buffer for output, signalling the buffer release to the buffer provider when done.
 
@@ -5199,8 +5201,8 @@ This data exchange protocol operates on a rather low level; there is only limite
 |~|buffer unavailable ||serious malfunction of playback | request playback stop |
 
 Thus there are two serious problem situations
-* allocating the buffer out of time window bears the danger of output data corruption; but the general assumption is for the scheduler to ensure each job start time remains within the defined window and all prerequisite jobs have terminated successfully. To handle clean-up, we additionally need special jobs running always, in order, and be notified of prerequisite failures.
-* failure to release a buffer in a timely fashion blocks any further use of that buffer, any further jobs in need of that buffer will die immediately. This situation can only be caused by a serious problem //within the slot, related to the output mechanism.// Thus there should be some kind of trigger (e.g. when this happens 2 times consecutively) to request aborting the playback or render as a whole.
+* allocating the buffer out of time window bears the danger of output data corruption; but the general assumption is for the scheduler to ensure each job start time remains within the defined window and all prerequisite jobs have terminated successfully. Yet it is not possible to preclude that such an out-of window access may happen. The {{{DataSink}}} is required to short-circuit in this case and to return an //invalid//-BuffHandle. The Render Node {{{pull()}}} processing has to react to this condition by //unwinding sanely.// -- {{red{not yet implemented as of 3/2026}}} &rarr; [[#1417|https://issues.lumiera.org/ticket/1417]]
+* failure to release a buffer in a timely fashion blocks any further use of that buffer, any further jobs in need of that buffer will die immediately. This situation can either be caused by a serious problem //within the slot, related to the output mechanism.// But unfortunately it can also happen in a »runaway« situation, where a preceding job took too long to complete and thus the output buffer designated for the given frame number is still blocked. The response to this situation is the same as above (//invalid// handle), but, in addition, there should be some kind of trigger (e.g. when this happens 2 times consecutively) to request aborting the playback or render as a whole.
 &rarr; SchedulerRequirements
 &rarr; OutputSlotDesign
 &rarr; OutputSlotImpl
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 546706719..c719ad8ee 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -110780,7 +110780,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
 
 
 
-
+
 
 
 
@@ -110828,6 +110828,31 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
 
 
 
+
+
+
+
+
+
+  
+  
+    

+ Die alten Entwürfe zum Buffer- und Output-Management von 2012 waren zu dem Ergebnis gekommen, daß man praktisch alle Fehlersituationen geräuschlos im Framework abfangen kann. Diese Einschätzung erweist sich nun als vorschnell; tatsächlich kann ein Verfehlen des Ausgabe-Zeitfensters zum schrittweisen Entgleisen des Ausgabevorgangs führen. Daher muß ein Notausgang vorgesehen werden, aber Exceptions schließe ich aus anderen Gründen als Lösung aus (abgesehen davon, daß Exceptions generell nur für Ausnahmesituationen gedacht sind, und jeweils zu einem Subsystem-Shutdown führen; man sollte sie nicht als einen »goto«-Mechanismus verwenden, um regulär auftretende, und auch beherrschbare Codepfade einfach zu implementieren). +

+

+ +

+

+ Also ergibt sich die Konsequenz, daß in den rekursiven pull()-Call ein Mechanismus eingebaut werden muß, der die Call-Sequenz abkürzt, aber trotzdem alle Resourcen regulär handhabt. +

+ +
+ +
+ + + +
@@ -114096,7 +114121,66 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
- + + + + + + +

+ keine statsische Magie, keine implizite Instanz, das zahlt sich nicht aus in der Praxis! +

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

+ Die Situation ist diesbezüglich ganz anders als beim BufferProvider: hier ist es sinnlos, die globale Folge der ausgegebenen Buffer zu betrachten, da jeder Feed separat läuft. +

+ +
+
+ + + + + + + @@ -114104,6 +114188,20 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + + + + +

+ Diese Feststellung bezeichnet einen Knotenpunkt in der bestehenden Platzhalter-Implementierung, welche dadruch unnötigerweise an die Assets gebunden ist. Tatsächlich muß es mir gelingen, die Interfaces so zu strukturieren, daß nur ein Verweis auf ein Grid(Interface) durchgereicht wird. Das dazu gehörige, tatsächliche Grid ist nämlich etwas in der konkreten Session; es wird vermöge der Player-Output-Connection in das Output-System durchgereicht; andererseits gibt es irgendwo in der externen Anbindung an konkrete Hardware-Outputs so etwas wie einen Timing-Anker, an dem das tatsächliche Frame-Grid ausgerichtet wird (zumindest für real-Time output) +

+ +
+
@@ -115620,6 +115718,20 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+ + + + +

+ ...und zwar geht es hier um den »Knackpunkt« mit time::Grid vs asset::Grid. +

+

+ Die Definition der Interfaces läßt sich komplett auf das Grid-Interface in der Library reduzieren; deshalb sollte es auch möglich sein, den OutputSlot und die Basis des OutputManagement letztlich in die »Vault« zu verlegen. Die bestehende Draft/Platzhalter-Implementierung allerdings verwendet asset::Grid auf eine undurchsichtige Weise, die ich längerfristig für obsolet halte, ohne überhaupt noch Aufwand zu betreiben, sie zu verstehen. Der Grund ist, daß die Verwaltung der Grids letztlich mit der Session zusammenhängt, und daher in der geplanten Lösung es nicht Aufgrabe des OutputSlot sein kann, ein Grid zu erzeugen; vielmehr wird ein im Model bereits bestehendes Grid referenziert. Es kommt also darauf an, die Interaces und die Basis-Implementierung so zu bauen, daß nur eine Grid-Abstraktion durchgereicht wird. +

+ +
+ +
@@ -117216,9 +117328,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + @@ -117230,6 +117343,52 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + + + + + + + + + + + + + + + + + + + +

+ »man« hatte sich seinerzeit was ausgedacht, ohne sehen zu können, wohin das führt und wo das vielleicht aneckt. Durch eine halbe Protion Glück hat sich eine Parallele mit dem Buffer-Provider-Protokoll aufgetan, die zudem nicht richtig funktionert. So daß das „eigentlich fertige“ Protokoll-Setup nicht zu gebrauchen ist. Würde ich das ganze Zeug jetzt wegwerfen, und neu bauen, dann wäre es darauf hinausgelaufen, daß das alles ein Irrtum und eine Sackgasse war. Deshalb werde ich das nicht tun, sondern mich an dem vermurksten Entwurf abarbeiten, um aus den Schwierigkeiten eine Rückmeldung der Wirklichkeit zu gewinnen +

+ +
+
+ + + + +

+ Seinerzeit war das Output-Slot-Protokoll der Versuch, einen ganzen Themenbereich erstmals einzufangen — durch einen Entwurf in's Blaue. +

+

+ +

+

+ Folglich ist das Protokoll aus einem prinzipiellen Verständnis des Sachverhalts geschöpft, und die Implementierung zappelt im leeren Raum. Praktisch zeigt (und zeigte) sich das so, daß die Implementierung völlig ohne Gehalt ist, und daher der Test gewissermaßen nach »Selbstbefriedigung« riecht: man kippt irgengendwelches Zeug in die Tasche und fummelt es auf der anderen Seite wieder heraus. Was sollte da denn auch getestet werden — eine Implementierung ist gar nicht gegeben. Diese ganze Veranstaltung kann aber sinnvoll gemacht werden, indem die (nicht-vorhandene) Implementierung auf (noch nicht erfahrenen) Schwierigkeiten der tatsächlichen Realisierung aufbaut +

+ +
+
+
@@ -137006,6 +137165,227 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + + + + + + +

+ Das sage ich hier dem virtuellen Christian: sorry, Junge, das ist nicht was, was wir uns so richten können, wie es uns grad schmeckt..... +

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

+ Eine user-provided-Function wird aufgerufen, mit einer Buffer-Adresse als Argument. Sie hat dann eine (meist nur ungefähr definierte) Zeitspanne, um Daten in diese Adresse zu bekommen +

+ +
+
+ + + + +

+ ein »Client« der Output-Senke triggert die Übergabe +

+ +
+
+ + + + +

+ ein Repräsentant des Output-Mechanismus holt sich Daten oder initiiert den nächsten »Flip« +

+ +
+
+
+ + + + + +

+ Dieser Fall ist sehr häufig, mutmaßlich sogar die Regel: der Ausgabe-Mechanismus läuft blind, und übernimmt die Daten, die eben da sind, oft sogar zu einem Zeitpunkt, der nicht ganz präzise vorherzusagen ist. +

+ +
+ + + + + + + +

+ Dieser Fall hat leider eine gewisse Relevanz; er kommt zustande durch den speziellen Fokus auf Performance, der dem Thema inhärent ist. Absicherung gegen Timing-Fehler verursacht Kosten, und wird als besonders irrelevant eingestuft, da der Benutzer eines IO-Kanals ein natürliches Interesse an korrekter Verwendung haben sollte — was leider ein Kurzschluß ist, denn die wenigsten Probleme werden mutwillig verursacht, und unbekannte Probleme sind schwieriger zu erkennen, reparieren und zu verhindern +

+ +
+ + + + + + + +

+ Dieses Muster ist nicht so häufig anzutreffen, jedoch sehr vorteilhaft, weil dadurch effektiv eine weitere Sicherheitsbarriere gegeben ist; abgesehen davon ist es jedoch nicht relevant für die Implementierund der Datenanlieferung für den Output, denn man sollte ohnehin Sorge tragen, das Protokoll einzuhalten +

+ +
+ + + + + + + +

+ Auch diese Variante (die oft mit der vorherigen einhergeht) stellt ein Sicherheitsnetz dar, da eine aus dem Ruder laufende Datenversorgung rasch gestoppt wird. +

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

+ ...was im Umkehrschluß bedeutet, daß es nicht genügt, lediglich die Fehler unwahrscheinlicher zu machen. In dieser Hinsicht waren meine Überlegungen 2012 voreilig und unausgewogen; zwar wollte ich einen de-Clicker, aber wollte mir dann mit einem Implementierungstrick helfen +

+ +
+
+ + + + +

+ ...die wir größtenteils sowiso haben; neu ist nur, daß wir nun eingestehen, eigens für die Korrektheit sorgen zu müssen. Das Erkennen von Fehlern muß dabei in der Implementierung liegen, denn die abzudeckenden Situationen sind viel zu divers, um sie sinnvoll in einem einzigen Schema fassen zu können +

+ +
+
+ + + + + +

+ Im Besonderen ein lock() außerhalb des Zeitfensters muß aktiv unterbunden werden +

+ +
+
+ + + + + + +

+ Und zwar, weil ich den Overhead für automatisches Resource-Management nicht für gerechtfertigt halte +

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

+ ...weil es realisitischerweise kein generisches Implementierungs-Schema geben wird. Es ist nicht einmal klar, wann genau eine Blockade stattfindet, und dann infolgedessen ein Abbruch getriggert werden muß. Wir können lediglich annehmen, daß, aufgrund einer solchen intern erkannten Problemsituation, der weitere Zugang zu den Ausgabe-Puffern unterbunden werden muß. +

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

+ Eine echte Implementierung für eine spezifische Output-Technologie war damals (und heute leider genauso) noch nicht geschrieben; in einer solchen Lage wäre es brandgefährlich, sofort eine »default-Implementierung« anzulegen. +

+ +
+
+ + + + + +

+ ...das ist nämlich nicht notwendig, um die nächsten beiden Vertical Slices über die Bühne zu bekommen; für Video-Output hat man es i.d.R. mit double-buffering zu tun, und dafür ergeben sich relativ einfache Limits praktisch von selbst, aus der Implementierung +

+ +
+
+ + + + + + +

+ Diagnose bedeutet: nachträglich alle Ausgabevorgänge überprüfen können +

+ +
+ + + + +