diff --git a/src/lib/scoped-collection.hpp b/src/lib/scoped-collection.hpp index bc23b3033..a42948bb7 100644 --- a/src/lib/scoped-collection.hpp +++ b/src/lib/scoped-collection.hpp @@ -12,44 +12,46 @@ */ /** @file scoped-collection.hpp - ** Managing a collection of non-copyable polymorphic objects in compact storage. - ** This helper supports the frequently encountered situation where a service - ** implementation internally manages a collection of implementation related - ** sub-components with reference semantics. Typically, those objects are - ** being used polymorphically, and often they are also added step by step. - ** The storage holding all those child objects is allocated in one chunk - ** and never adjusted. + ** Manage a collection of non-copyable polymorphic objects in compact storage. + ** This helper supports common situation where a service implementation has to + ** manages a fixed collection of implementation related sub-components, treated + ** with reference semantics. Typically, those objects are used polymorphically, + ** and often even with _type erasure_ and added step by step through a builder. + ** The storage to hold all those child objects is allocated in one chunk + ** and never adjusted. Individual children are never re-ordered or removed, + ** only the collection as a whole is discarded eventually. ** ** # usage patterns ** ** The common ground for all usage of this container is to hold some elements ** with exclusive ownership; when the enclosing container goes out of scope, ** all the dtors of the embedded objects will be invoked. Frequently this - ** side effect is the reason for using the container: we want to own some - ** resource handles to be available exactly as long as the managing object - ** needs and accesses them. + ** side effect is one of the reasons for using the container: we want to own + ** some resource handles and be sure they are available exactly as long as + ** the managing scope is alive and able to access them. ** ** There are two different usage patterns for populating a ScopedCollection ** - the "stack style" usage creates an empty container (using the one arg - ** ctor just to specify the maximum size). The storage to hold up to this - ** number of objects is (heap) allocated right away, but no objects are + ** ctor to specify the maximum size only). The storage capacity to hold + ** a number of objects, up to the predefined maximum, is (heap) allocated + ** immediately at construction of the container, yet no child objects are ** created. Later on, individual objects are "pushed" into the collection ** by invoking #emplaceElement() to create a new element of the default ** type `I`) or #emplace(args) to create some subtype. This way, - ** the container is being filled successively. + ** the container is gradually filled up. ** - the "RAII style" usage strives to create all of the content objects ** right away, immediately after the memory allocation. This usage pattern ** avoids any kind of "lifecycle state". Either the container comes up sane ** and fully populated, or the ctor call fails and any already created ** objects are discarded. ** @note intentionally there is no operation to discard individual objects, - ** all you can do is to #clear() the whole container. + ** the only option is to #clear() the whole container. ** @note the container can hold instances of a subclass of the type defined ** by the template parameter `I`. But you need to ensure in this case ** that the defined buffer size for each element (2nt template parameter) ** is sufficient to hold any of these subclass instances. This condition ** is protected by a static assertion (compilation failure). - ** @warning when using subclasses, a virtual dtor is mandatory + ** @warning when using subclasses, defining a virtual dtor is mandatory! ** @warning deliberately **not threadsafe** ** ** @see ScopedCollection_test @@ -160,7 +162,7 @@ namespace lib { * Ctor fails in case of any error during element creation. * @param builder functor to be invoked for each "slot". * It gets an ElementHolder& as parameter, and should - * use this to create an object of some I-subclass + * use this to create an object of suitable I-subclass */ template ScopedCollection (size_t maxElements, CTOR builder) diff --git a/src/vault/out/diagnostic-output-slot.hpp b/src/vault/out/diagnostic-output-slot.hpp index 262c656ca..f4558986a 100644 --- a/src/vault/out/diagnostic-output-slot.hpp +++ b/src/vault/out/diagnostic-output-slot.hpp @@ -342,7 +342,7 @@ namespace out { * remains in memory until shutdown of the current executable */ class DiagnosticOutputSlot - : public OutputSlotImplBase + : public OutputSlot { static const uint MAX_CHANNELS = 5; ///////////////////////////////OOO Build heap-based implementation with arbitrary number of feeds!! @@ -363,30 +363,27 @@ namespace out { * thus allowing to verify the handling of individual buffers */ class SimulatedOutputSequences - : public AllocStateHub + : public AllocState { - typedef AllocStateHub _Base; - - void - buildConnection(ConnectionStorage storage) - { -#if false //////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : disabled code to disentangle BufferProvider implementation - storage.create(); -#endif /////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (end) disabled code - } + using _Base = AllocState; public: SimulatedOutputSequences (uint numChannels) - : _Base(numChannels) - { - init(); - } + : _Base{ numChannels + ,[](ConnectionStorage& storage) + { + UNIMPLEMENTED ("new Tracking implementation for DiagnosticOutputSlot"); +#if false //////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : disabled code to disentangle BufferProvider implementation + storage.create(); +#endif /////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (end) disabled code + }} + { } }; /** hook into the OutputSlot frontend */ - unique_ptr + unique_ptr buildState() { return make_unique ( @@ -402,7 +399,8 @@ namespace out { { REQUIRE (!isFree(), "diagnostic OutputSlot not (yet) connected"); REQUIRE (channel <= getOutputChannelCount()); - return static_cast (state_->access (channel)); +// return static_cast (state_->access (channel)); + UNIMPLEMENTED ("the old tracking API is abandoned, the new one not yet implemented"); } diff --git a/src/vault/out/output-slot-connection.hpp b/src/vault/out/output-slot-connection.hpp index d00209429..990e43dee 100644 --- a/src/vault/out/output-slot-connection.hpp +++ b/src/vault/out/output-slot-connection.hpp @@ -33,19 +33,20 @@ #include "lib/error.hpp" #include "lib/nocopy.hpp" #include "vault/out/output-slot.hpp" -#include "vault/mem/buffhandle.hpp" #include "lib/scoped-collection.hpp" #include "lib/iter-adapter-stl.hpp" #include "lib/iter-source.hpp" #include "lib/handle.hpp" -//#include "lib/time/timevalue.hpp" +#include "lib/time/timevalue.hpp" //#include "steam/engine/buffer-provider.hpp" //#include "steam/play/timings.hpp" //#include "lib/sync.hpp" +#include "vault/mem/buffer-provider.hpp" //#include #include -#include +#include +//#include //#include @@ -53,8 +54,8 @@ namespace vault { namespace out { using vault::mem::BuffHandle; -//using vault::mem::BufferProvider; -//using lib::time::Time; + using vault::mem::BufferProvider; + using lib::time::TimeValue; /////////////////OOO Rly? //using std::string; using lib::iter_stl::eachElm; @@ -102,61 +103,29 @@ namespace out { - /** - * Extension point for Implementation. - * The AllocState is where the concrete output - * handling implementation is expected to reside. - * OutputSlot is a frontend and accesses - * AllocState in the way of a PImpl. - */ - class OutputSlot::AllocState - : public OutputSlot::Allocation - , util::NonCopyable - { - public: - virtual ~AllocState() { } - - virtual Connection& access (uint) const =0; - }; - - - - /* ===== Building blocks for OutputSlot implementation ===== */ - /** Base for OutputSlot standard implementation */ - class OutputSlotImplBase - : public OutputSlot - { - protected: - template - class AllocStateHub; - }; - - /** - * Maintaining a list of active connections. - * Base class for the typical implementation approach. - * Using this class is \em not mandatory. But obviously, - * we'd get to manage a selection of Connection objects - * representing the "active points" in several media channels - * connected through this OutputSlot. These Connection subclasses - * are what is referenced by the DataSink smart-ptrs handed out - * to the client code. As AllocState implements the Allocation - * API, it has the liability to create these DataSink smart-ptrs, - * which means to wire them appropriately and also provide an - * deleter function (here #shutdownConnection) to be invoked - * when the last copy of the smart-handle goes out of scope. - * - * The typical standard/base implementation provided here - * manages a collection of active Connection subclass objects. + * Implementation of active Allocation state: maintaining + * a list of active connections. An instance is created whenever + * a client _locks_ (and thereby »allocates«) the OutputSlot. The + * actual implementation of OutputSlot::buildState() will employ a + * custom Connection, where all the actual state handling and data + * transport is implemented. Such a Connection subclass is also what + * is referenced by the DataSink smart-hendle, that is handed out + * to the client code and allows to mark the state transitions. + * Theses smart-handles are ref counting and thus the individual + * Connection object is kept alive as long as at least one DataSink + * handle is retained. The deleter function #shutdownConnection is + * is invoked for each abandoned connection; once all connections + * are defunct, the AllocState itself can be deconfigured. */ template - class OutputSlotImplBase::AllocStateHub - : public OutputSlot::AllocState + class OutputSlot::AllocState + : public OutputSlot::Allocation { - typedef lib::ScopedCollection Connections; - typedef OutputSlot::OpenedSinks OpenedSinks; + using Connections = lib::ScopedCollection; + using OpenedSinks = OutputSlot::OpenedSinks; Connections connections_; @@ -191,32 +160,22 @@ namespace out { protected: /* == API for OutputSlot-Impl == */ - void - init() ///< derived classes need to invoke this to build the actual connections - { - //////////////////////////TICKET #878 really build all at once? or on demand? - connections_.populate_by (&AllocStateHub::buildConnection, this); - } - using ConnectionStorage = Connections::ElementHolder&; + using ConnectionStorage = Connections::ElementHolder; - /** factory function to build the actual - * connection handling objects per channel */ - virtual void buildConnection(ConnectionStorage) =0; - - - AllocStateHub(uint numChannels) - : connections_(numChannels) - { } - - public: - virtual - ~AllocStateHub() + /** + * The actual implementation of OutputSlot::buildState() needs to build + * an AllocState instance, and has to provide a _population functor_ + * to setup the actual \a CON (Connection implementation) instances. + * @see ScopedCollection_test::building_RAII_Style() + */ + template + AllocState(uint numChannels, CTOR&& populator) + : connections_(numChannels, std::forward(populator)) { } - - private: // Implementation details + private: /* === Implementation details === */ static DataSink connectOutputSink (CON& connection) diff --git a/src/vault/out/output-slot.hpp b/src/vault/out/output-slot.hpp index 38cba0e1d..16040df4b 100644 --- a/src/vault/out/output-slot.hpp +++ b/src/vault/out/output-slot.hpp @@ -59,11 +59,11 @@ #include "lib/error.hpp" #include "lib/handle.hpp" -#include "lib/time/timevalue.hpp" -#include "vault/mem/buffer-provider.hpp" +#include "vault/mem/buffhandle.hpp" #include "vault/out/timings.hpp" #include "lib/iter-source.hpp" #include "lib/nocopy.hpp" +#include "lib/util.hpp" #include @@ -72,11 +72,7 @@ namespace vault { namespace out { using vault::mem::BuffHandle; - using vault::mem::BufferProvider; using lib::time::FrameCnt; - using lib::time::TimeValue; - using lib::time::Time; - using std::unique_ptr; @@ -93,28 +89,15 @@ namespace out { * channels to some kind of external sink (video in GUI window, video full screen, * sound, Jack, rendering to file). Clients are expected to retrieve a suitably * preconfigured implementation from some OutputManager instance. An OutputSlot - * needs to be \em claimed for output by invoking #allocate, which returns a + * needs to be _claimed_ for output by invoking #allocate, which returns a * representation of the connection state. This operation is exclusive. - * The actual \link DataSink output sinks \endlink can be retrieved + * The actual [output sinks](\ref DataSink) can be retrieved * through the Allocation object returned from there. */ class OutputSlot : util::NonCopyable { - protected: - - /** active connections through this OutputSlot */ - class AllocState; - - unique_ptr state_; - - /** build the \em connected state, - * based on the existing configuration - * within this concrete OutputSlot */ - virtual unique_ptr buildState() =0; - - public: virtual ~OutputSlot(); @@ -128,13 +111,9 @@ namespace out { virtual Timings getTimingConstraints() =0; - protected: - ~Allocation(); ///< never to be managed by clients directly + ~Allocation(); ///< this is an interface }; - /** established output channel */ - class Connection; - /** can this OutputSlot be allocated? */ bool isFree() const; @@ -145,6 +124,22 @@ namespace out { /** disconnect from this OutputSlot * @warning may block until DataSinks are gone */ void disconnect(); + + /** established output feed */ + class Connection; + + + protected: + /** active connections through this OutputSlot */ + template + class AllocState; + + unique_ptr state_; + + /** build the _connected state,_ + * based on the existing configuration + * within this concrete OutputSlot */ + virtual unique_ptr buildState() =0; }; @@ -168,16 +163,11 @@ namespace out { { return not (sink1 and sink2) or (sink1 and sink2 - and & sink1.impl() == & sink2.impl()); - } - friend bool operator!= (DataSink const& sink1, DataSink const& sink2) - { - return not (sink1 == sink2); + and util::isSameObject (sink1.impl(), sink2.impl())); } }; - }} // namespace vault::out #endif /*VAULT_OUT_OUTPUT_SLOT_H*/ diff --git a/tests/lib/scoped-collection-test.cpp b/tests/lib/scoped-collection-test.cpp index f2f872fd5..d30e5fb9f 100644 --- a/tests/lib/scoped-collection-test.cpp +++ b/tests/lib/scoped-collection-test.cpp @@ -188,7 +188,7 @@ namespace test{ // Verify correct behaviour of iteration end - CHECK (! (coll.end())); + CHECK (not bool(coll.end())); CHECK (isnil (coll.end())); VERIFY_ERROR (ITER_EXHAUST, *coll.end() ); @@ -303,7 +303,7 @@ namespace test{ CollD coll (6, Populator(rr, trigger)); - CHECK (!isnil (coll)); + CHECK (not isnil (coll)); CHECK (6 == coll.size()); CHECK (0 != Dummy::checksum()); @@ -314,7 +314,7 @@ namespace test{ CHECK (coll[4].calc(0) == 4 + rr); CHECK (coll[5].calc(0) == 5 + rr + trigger); // what does this check prove? - // - the container was indeed populated with DubDummy objects + // - the container was indeed populated with SubDummy objects // since the overridden version of Dummy::acc() did run and // reveal the trigger value // - the population was indeed done with the anonymous Populator diff --git a/tests/stage/test/test-nexus.hpp b/tests/stage/test/test-nexus.hpp index d49bfca2e..83f22073d 100644 --- a/tests/stage/test/test-nexus.hpp +++ b/tests/stage/test/test-nexus.hpp @@ -38,6 +38,7 @@ #include "lib/error.hpp" #include "lib/nocopy.hpp" +#include "lib/symbol.hpp" #include "stage/ctrl/bus-term.hpp" #include "stage/model/tangible.hpp" #include "stage/ctrl/state-manager.hpp" @@ -49,6 +50,8 @@ #include +using lib::Symbol; + namespace stage { namespace test{ diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 2869b52c9..3379f9120 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -137463,6 +137463,131 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + + + + + + + + + + + + + + + +

+ Interessant ist daran, daß ich das damals überahupt nicht gesehen habe; mein Level war also, daß ich zwar ziemlich direkt ein solches Protokoll in OO-Interfaces übersetzen konnte, aber das Denken in Zonen / Domänen und Perspektiven der Implementierung war mir noch fremd, ich konnte dies grade so ansatzweise erahnen, aber hab mich in der konkreten Implementierung nur von Haken zu Öse durchgehangelt... +

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

+ ...damit meine ich +

+
    +
  • + entweder die ganze Allokation (und damit die Verwendung des OutputSlot) ist praktisch einheitlich; es werden zwar N Buffer zugewiesen, aber der Transfer-Mechanismus läuft für alle gemeinsam +
  • +
  • + oder die Allokation ist lediglich eine logische Klammer, und die eigentliche Implementierung ist N-fach aufgedoppelt, für jeden einzelnen Feed +
  • +
+ +
+ +
+
+
+ + + + + +

+ virtual Connection& access  (uint) const  +

+

+ +

+

+ ...natürlich wird die verwendet, aber irgendwo aus der konkreten Implementierung (DiagnosticOutputSlot) heraus. Das ist ein telltale sign — natürlich sollte eine solche Interface-Methode verwendet werden, aber die Verwendung einer Methode sollte direkt vom Interface aus offensichtlich sein, ohne in die Implementierung schauen zu müssen. Das ist hier nicht der Fall; warum sollte ein konkreter OutputSlot von oben herab auf einzelnen Connections zugreifen müssen? Konkret habe ich das verwendet, um das ganze Diagnose-API darauf aufzubauen. Jaja, sowas nennt man »hexagonales Design« — Diagnose-State und Verifikationen sollte man niemals verwenden, um daran die Architektur auszurichten. +

+ +
+
+ + + + +

+ und dafür wird aber die VTable »durchgefädelt« ... erst eine davon abgeleitete konkrete Implementierung bietet dann die Factory-Methode, die den Typ tatsächlich festlegt +

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

+ ....oder war das mit der ScopedCollection ein späterer retro-fit? +

+

+ Jedenfalls halte ich das aus heutiger Sicht immer noch für eine gute Lösung; man könnte höchtstens die ScopedCollection noch etwas ausbauen (Policy für die Storage? UninitialisedStorage für die Impl verwenden?) +

+ +
+
+ + + + +

+ eins ist klar: Lambdas sind schön +

+ +
+ +
+
+
+ + + + + + + + +
+
@@ -168295,7 +168420,7 @@ unsigned int ThreadIdAsInt = *static_cast<unsigned int*>(static_cast<vo - + @@ -168345,6 +168470,21 @@ unsigned int ThreadIdAsInt = *static_cast<unsigned int*>(static_cast<vo + + + + + + + +

+ möglicherweise weil ich eine Version aus Debian/stable verwende? +

+ +
+
+ +
@@ -205197,6 +205337,12 @@ env.Command('out.bin', 'in.bin', my_action)
+ + + + + +