From 305eb825afa7c08bd751d37d830b23bdd0f4d05a Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 20 Apr 2023 23:55:02 +0200 Subject: [PATCH] Job-Planning: first testcase - empty `JobTicket` ...requires a first attempt towards defining a `JobTiket`. This turns out quite tricky, due to using those `LinkedElements` (intrusive single linked list), which requires all added records actually to live elsewhere. Since we want to use a custom allocator later (the `AllocationCluster`), this boils down to allocating those records only when about to construct the `JobTicket` itself. What makes matters even worse: at the moment we use a separate spec per Media channel (maybe these specs can be collapsed later non). And thus we need to pass a collection -- or better an iterator with raw specs, which in turn must reveal yet another nested sequence for the prerequisite `JobTickets`. Anyhow, now we're able at least to create an empty `JobTicket`, backed by a dummy `JobFunctor`.... --- src/lib/itertools.hpp | 8 + src/lib/meta/trait.hpp | 38 +-- src/steam/engine/job-ticket.hpp | 115 ++++++--- src/steam/engine/render-drive.hpp | 3 +- src/vault/engine/job.cpp | 5 +- src/vault/engine/job.h | 13 +- .../steam/engine/job-planning-setup-test.cpp | 2 +- tests/core/steam/engine/mock-dispatcher.hpp | 84 ++++++- wiki/thinkPad.ichthyo.mm | 229 +++++++++++++++++- 9 files changed, 432 insertions(+), 65 deletions(-) diff --git a/src/lib/itertools.hpp b/src/lib/itertools.hpp index d1fb2a616..ea30f1077 100644 --- a/src/lib/itertools.hpp +++ b/src/lib/itertools.hpp @@ -672,6 +672,14 @@ namespace lib { return SingleValIter{ref}; } + /** not-anything-at-all iterator */ + template + inline auto + nilIterator() + { + return SingleValIter(); + } + diff --git a/src/lib/meta/trait.hpp b/src/lib/meta/trait.hpp index 95be1bfb1..96729145d 100644 --- a/src/lib/meta/trait.hpp +++ b/src/lib/meta/trait.hpp @@ -474,9 +474,9 @@ namespace meta { public: enum{ value = std::is_constructible::value - && HasNested_value_type::value - && HasOperator_deref::value - && HasOperator_inc::value + and HasNested_value_type::value + and HasOperator_deref::value + and HasOperator_inc::value }; }; @@ -497,8 +497,8 @@ namespace meta { META_DETECT_FUNCTION(typename X::iterator, end ,(void)); enum { value = HasNested_iterator::value - && HasFunSig_begin::value - && HasFunSig_end::value + and HasFunSig_begin::value + and HasFunSig_end::value }; }; @@ -509,8 +509,8 @@ namespace meta { META_DETECT_FUNCTION(typename X::iterator, end ,(void) noexcept); enum { value = HasNested_iterator::value - && HasFunSig_begin::value - && HasFunSig_end::value + and HasFunSig_begin::value + and HasFunSig_end::value }; }; @@ -521,8 +521,8 @@ namespace meta { META_DETECT_FUNCTION(typename X::const_iterator, end ,(void) const); enum { value = HasNested_const_iterator::value - && HasFunSig_begin::value - && HasFunSig_end::value + and HasFunSig_begin::value + and HasFunSig_end::value }; }; @@ -533,8 +533,8 @@ namespace meta { META_DETECT_FUNCTION(typename X::const_iterator, end ,(void) const noexcept); enum { value = HasNested_const_iterator::value - && HasFunSig_begin::value - && HasFunSig_end::value + and HasFunSig_begin::value + and HasFunSig_end::value }; }; @@ -561,8 +561,8 @@ namespace meta { META_DETECT_FUNCTION(typename X::reverse_iterator, rend ,(void)); enum { value = HasNested_reverse_iterator::value - && HasFunSig_rbegin::value - && HasFunSig_rend::value + and HasFunSig_rbegin::value + and HasFunSig_rend::value }; }; @@ -573,8 +573,8 @@ namespace meta { META_DETECT_FUNCTION(typename X::reverse_iterator, rend ,(void) noexcept); enum { value = HasNested_reverse_iterator::value - && HasFunSig_rbegin::value - && HasFunSig_rend::value + and HasFunSig_rbegin::value + and HasFunSig_rend::value }; }; @@ -585,8 +585,8 @@ namespace meta { META_DETECT_FUNCTION(typename X::const_reverse_iterator, rend ,(void) const); enum { value = HasNested_const_reverse_iterator::value - && HasFunSig_rbegin::value - && HasFunSig_rend::value + and HasFunSig_rbegin::value + and HasFunSig_rend::value }; }; @@ -597,8 +597,8 @@ namespace meta { META_DETECT_FUNCTION(typename X::const_reverse_iterator, rend ,(void) const noexcept); enum { value = HasNested_const_reverse_iterator::value - && HasFunSig_rbegin::value - && HasFunSig_rend::value + and HasFunSig_rbegin::value + and HasFunSig_rend::value }; }; diff --git a/src/steam/engine/job-ticket.hpp b/src/steam/engine/job-ticket.hpp index 0b145900e..e3dd84cee 100644 --- a/src/steam/engine/job-ticket.hpp +++ b/src/steam/engine/job-ticket.hpp @@ -44,8 +44,11 @@ #include "lib/hierarchy-orientation-indicator.hpp" #include "lib/linked-elements.hpp" #include "lib/iter-adapter.hpp" +#include "lib/meta/tuple-helper.hpp" +#include "lib/meta/trait.hpp" #include "lib/util.hpp" +#include #include @@ -57,6 +60,7 @@ namespace engine { //using lib::time::FSecs; //using lib::time::Time; using vault::engine::Job; +using vault::engine::JobFunctor; using lib::LinkedElements; using lib::OrientationIndicator; using util::isnil; @@ -88,40 +92,47 @@ using util::isnil; class JobTicket : util::NonCopyable { + struct Prerequisite + { + Prerequisite* next{nullptr}; + JobTicket& descriptor; + + Prerequisite (JobTicket& ticket) + : descriptor{ticket} + { } + }; + using Prerequisites = LinkedElements; + /** what handling this task entails */ struct Provision { - Provision* next; + Provision* next{nullptr}; + JobFunctor& jobFunctor; + Prerequisites requirements{}; ////////////////////TODO some channel or format descriptor here - }; - - struct Prerequisite - { - Prerequisite* next; - JobTicket* descriptor; - }; - - struct Prerequisites ///< per channel - { - Prerequisites* next; - LinkedElements requiredJobs_; + Provision (JobFunctor& func) + : jobFunctor{func} + { } }; - - LinkedElements channelConfig_; - LinkedElements requirement_; + LinkedElements provision_; + template + static LinkedElements buildProvisionSpec (IT); + + protected: + template + JobTicket(IT featureSpec_perChannel) + : provision_{buildProvisionSpec (featureSpec_perChannel)} + { } + public: class ExplorationState; friend class ExplorationState; - - JobTicket() - { - TODO ("job representation, planning and scheduling"); - } + ExplorationState startExploration() const; @@ -133,17 +144,18 @@ using util::isnil; bool isValid() const { - if (channelConfig_.size() != requirement_.size()) + if (isnil (provision_)) return false; TODO ("validity self check"); + return true; } }; class JobTicket::ExplorationState { - typedef LinkedElements::iterator SubTicketSeq; + typedef Prerequisites::iterator SubTicketSeq; typedef std::stack SubTicketStack; //////////////////////////TODO use a custom container to avoid heap allocations SubTicketStack toExplore_; @@ -154,8 +166,8 @@ using util::isnil; ExplorationState (Prerequisites& prerequisites) { - if (not isnil (prerequisites.requiredJobs_)) - toExplore_.push (prerequisites.requiredJobs_.begin()); + if (not isnil (prerequisites)) + toExplore_.push (prerequisites.begin()); } // using default copy operations @@ -208,10 +220,9 @@ using util::isnil; operator->() const { REQUIRE (!empty() && toExplore_.top().isValid()); - REQUIRE (toExplore_.top()->descriptor); - REQUIRE (toExplore_.top()->descriptor->isValid()); + REQUIRE (toExplore_.top()->descriptor.isValid()); - return toExplore_.top()->descriptor; + return & toExplore_.top()->descriptor; } @@ -238,6 +249,50 @@ using util::isnil; + + /** @internal prepare and assemble the working data structure to build a JobTicket. + * @tparam IT iterator to yield a sequence of specifications for each channel, + * given as `std::pair` of a JobFunctor and a further Sequence of + * JobTicket prerequisites. + * @return the final wired instance of the data structure to back the new JobTicket + * @remark Note especially that those data structures linked together for use by the + * JobTicket are themselves allocated "elsewhere", and need to be attached + * to a memory management scheme (typically an AllocationCluster for some + * Segment of the Fixture datastructure). This data layout can be tricky + * to get right, and is chosen here for performance reasons, assuming + * that there is a huge number of segments, and these are updated + * frequently after each strike of edit operations, yet traversed + * and evaluated on a sub-second scale for ongoing playback. + */ + template + inline LinkedElements + JobTicket::buildProvisionSpec (IT featureSpec) + { /* ---- validate parameter type ---- */ + static_assert (lib::meta::can_IterForEach::value); // -- can be iterated + using Spec = typename IT::value_type; + static_assert (lib::meta::is_Tuple()); // -- payload of iterator is a tuple + using Func = typename std::tuple_element<0, Spec>::type; + using Preq = typename std::tuple_element<1, Spec>::type; + static_assert (lib::meta::is_basically()); // -- first component specifies the JobFunctor to use + static_assert (lib::meta::can_IterForEach::value); // -- second component is again an iterable sequence + static_assert (std::is_same()); // -- which yields JobTicket prerequisites + REQUIRE (not isnil (featureSpec) + ,"require at least specification for one channel"); + + LinkedElements provisionSpec; //////////////////////////////////////////////////TICKET #1292 : need to pass in Allocator as argument + for ( ; featureSpec; ++featureSpec) + { + JobFunctor& func = std::get<0> (*featureSpec); + auto& provision = provisionSpec.emplace (func); + for (Preq pre = std::get<1> (*featureSpec); pre; ++pre) + provision.requirements.emplace (*pre); + } + provisionSpec.reverse(); // retain order of given definitions per channel + ENSURE (not isnil (provisionSpec)); + return provisionSpec; + } + + /// @deprecated : could be expendable ... likely incurred solely by the use of Monads as design pattern inline JobTicket::ExplorationState @@ -257,9 +312,9 @@ using util::isnil; inline JobTicket::ExplorationState JobTicket::discoverPrerequisites (uint channelNr) const { - REQUIRE (channelNr < requirement_.size()); + REQUIRE (channelNr < provision_.size()); - return ExplorationState (requirement_[channelNr]); + return ExplorationState (provision_[channelNr].requirements); } diff --git a/src/steam/engine/render-drive.hpp b/src/steam/engine/render-drive.hpp index 284617ed5..e16de2b9d 100644 --- a/src/steam/engine/render-drive.hpp +++ b/src/steam/engine/render-drive.hpp @@ -45,7 +45,7 @@ #include "steam/engine/dispatcher.hpp" #include "steam/play/timings.hpp" #include "vault/engine/job.h" -#include "lib/nocopy.hpp" +//#include "lib/nocopy.hpp" namespace steam { @@ -90,7 +90,6 @@ namespace engine { */ class RenderDrive : public JobClosure - , util::NonCopyable { RenderEnvironment& engine_; diff --git a/src/vault/engine/job.cpp b/src/vault/engine/job.cpp index 13fa07275..5fafe6790 100644 --- a/src/vault/engine/job.cpp +++ b/src/vault/engine/job.cpp @@ -62,11 +62,12 @@ namespace engine { /** - * emit the VTable for JobClosure within this compilation unit, + * emit the VTable for JobFunctor within this compilation unit, * which is still part of the Vault. The actual job implementation * classes are defined in the Steam-Layer */ - JobClosure::~JobClosure() { } + JobFunctor::~JobFunctor() { } + JobClosure::~JobClosure() { } ///< @deprecated 4/23 refactoring to retract C-structs from the Scheduler interface diff --git a/src/vault/engine/job.h b/src/vault/engine/job.h index c8f429dfb..67bdafa4d 100644 --- a/src/vault/engine/job.h +++ b/src/vault/engine/job.h @@ -199,6 +199,14 @@ namespace engine { typedef lumiera_jobParameter const& JobParameter; + + /** @todo refactoring 4/23 -- will replace JobClosure */ + class JobFunctor /////////////////////////////////////////////////////TICKET #1287 : rework Job representation + : util::NonCopyable // ....has distinct identity and stable address + { + public: + virtual ~JobFunctor(); ///< this is an interface + }; /** * Interface of the closure for frame rendering jobs. * Hidden behind this interface resides all of the context re-building @@ -212,10 +220,11 @@ namespace engine { * By virtue of the JobClosure-pointer, embedded into #lumiera_jobDefinition, * the invocation of such a job may re-gain the full context, including the * actual ProcNode to pull and further specifics, like the media channel. - */ + * @deprecated 4/23 plain-C data structures are removed from the Scheduler interface; transition to JobFunctor + */ /////////////////////////////////////////////////////TICKET #1287 : rework Job representation class JobClosure : public lumiera_jobClosure - , util::NonCopyable // ....has distinct identity and stable address + , public JobFunctor /////////////////////////////////////////////////////TICKET #1287 : rework Job representation { public: virtual ~JobClosure(); ///< this is an interface diff --git a/tests/core/steam/engine/job-planning-setup-test.cpp b/tests/core/steam/engine/job-planning-setup-test.cpp index be264eb6f..5af228506 100644 --- a/tests/core/steam/engine/job-planning-setup-test.cpp +++ b/tests/core/steam/engine/job-planning-setup-test.cpp @@ -27,7 +27,7 @@ #include "lib/test/run.hpp" #include "lib/error.hpp" -#include "steam/engine/dispatcher-mock.hpp" +#include "steam/engine/mock-dispatcher.hpp" //#include "steam/engine/job-planning.hpp" diff --git a/tests/core/steam/engine/mock-dispatcher.hpp b/tests/core/steam/engine/mock-dispatcher.hpp index 036bf48be..efee17e50 100644 --- a/tests/core/steam/engine/mock-dispatcher.hpp +++ b/tests/core/steam/engine/mock-dispatcher.hpp @@ -35,17 +35,21 @@ #include "steam/engine/dispatcher.hpp" #include "steam/engine/job-ticket.hpp" //#include "steam/play/timings.hpp" -//#include "lib/time/timevalue.hpp" +#include "lib/time/timevalue.hpp" ////#include "lib/time/timequant.hpp" ////#include "lib/format-cout.hpp" #include "lib/depend.hpp" #include "lib/itertools.hpp" //#include "lib/util-coll.hpp" +#include "vault/real-clock.hpp" #include "lib/test/test-helper.hpp" +#include "lib/test/event-log.hpp" +#include "vault/engine/job.h" //#include "lib/util.hpp" //#include //#include +#include //using test::Test; //using util::isnil; @@ -66,14 +70,18 @@ namespace test { // using lib::time::Duration; // using lib::time::Offset; // using lib::time::TimeVar; + using lib::time::TimeValue; // using lib::time::Time; // using mobject::ModelPort; // using play::Timings; + using std::make_tuple; namespace { // used internally // using play::PlayTestFrames_Strategy; // using play::ModelPorts; + using vault::engine::JobClosure; + using vault::engine::JobParameter; // typedef play::DummyPlayConnection DummyPlaybackSetup; @@ -118,14 +126,74 @@ namespace test { /// @deprecated this setup is confusing and dangerous (instance identity is ambiguous) lib::Depend mockDispatcher; + class MockInvocationLog + : public lib::test::EventLog + { + public: + MockInvocationLog() + : EventLog{"MockInvocation"} + { } + }; + + lib::Depend mockInvocation; + + + + + class DummyJobFunctor + : public vault::engine::JobClosure + { + void + invokeJobOperation (JobParameter param) + { + mockInvocation().call(this, "DummyJob", TimeValue(param.nominalTime) + , RealClock::now() + , param.invoKey.metaInfo.a + , param.invoKey.metaInfo.b + ); + } + + void + signalFailure (JobParameter,JobFailureReason) + { + NOTREACHED ("Job failure is not subject of this test"); + } + + JobKind + getJobKind() const + { + return META_JOB; + } + + bool + verify (Time nominalJobTime, InvocationInstanceID invoKey) const + { + return Time::ANYTIME < nominalJobTime + and 0 <= invoKey.metaInfo.a + ; + } + + size_t + hashOfInstance(InvocationInstanceID invoKey) const + { + return boost::hash_value (invoKey.metaInfo.a); + } + + public: + + }; + lib::Depend dummyJobFunctor; inline auto - defineBottomProvision() + defineBottomSpec() { -// return lib::singleValIterator( -// JobTicket::buildProvision ( -// )); + auto emptyPrereq = lib::nilIterator(); + using Iter = decltype(emptyPrereq); + return lib::singleValIterator( + std::tuple(dummyJobFunctor() + ,emptyPrereq + )); } }//(End)internal test helpers.... @@ -145,9 +213,9 @@ namespace test { { public: -// MockJobTicket() -// : JobTicket{JobTicket::Provisions{defineBottomProvision()}} -// { }; + MockJobTicket() + : JobTicket{defineBottomSpec()} + { }; private: }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 205a4ee2a..fabe221cf 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -69129,7 +69129,7 @@ - + @@ -69696,6 +69696,214 @@ + + + + + + + + + + + + + + + + + + + + + + + +

+ geht nicht: ist privat (und das ist sinnvoll so!) +

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

+ und zwar ist die JobTicket-Struktur explizit darauf angelegt, anderweitig erstellte Deskriptoren zu verlinken; das soll so sein aus Performance-Gründen (Cache Locality ⟹ AllocationCluster) +

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

+ die muß definitiv von „wo anders“ kommen +

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

+ Ziel sollte tatsächlich sein, die Komplexitäten mit der Allokation aus dem funktionalen Code heraus zu verbergen; denn dies dient nur dem separaten Belang der Performance +

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

+ ...ist hier relevant, denn dies scheidet eine einfache Funktions/Konstruktor-Schnittstelle aus; wir müssen einen strukturierten Datensatz bereitstellen +

+ +
+
+ + + + + + +

+ Zunächst wurden LinkedElements lediglich aus Gründen der Konsistenz auch hierfür verwendet. Eine intrusive-single-linked-List mag für die Vernetzung der Prerequisites sinnvoll sein, aber für eine Sprungtafel nach Channel-Nr ließe sich genauso gut eine Array-backed-Implementation konstruieren (vielleicht dann ein neuer Anlauf anstelle der alten Idee des »RefArray« ?) +

+ +
+
+ + + + + + +

+ ...denn es wird noch deutlich über dieses PlaybackVerticalSlice hinaus dauern, bis die erste rudimentäre Implementierung des Builders am Start ist — und erst dann gibt es eine praktischen Bezugspunkt für das Design +

+ +
+ +
+ + + + + + +

+ ...möglicherweise könnte zumindest so der wichtigste Standardfall komplett ohne einen eigens allozierten JobFunktor dargestellt werden — einfach indem alle notwendigen Parameter direkt aus der referenzierten ProcNode gezogen werden. Um diese Möglichkeit abzuschätzen, müßte aber zuerst definiert werden, wie der Übergang zu den Prerequisites konkret im Proc-Node-Graph dargestellt werden: durch spezielle Metadaten? oder durch eine besondere Marker-Node, die wie eine Quelle fungiert? +

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

+ ...nach zwei Tagen Gewürge.... +

+ +
+ +
+
+ + + +
+
+
@@ -69751,6 +69959,9 @@ + + + @@ -69873,6 +70084,22 @@ + + + + + + +

+ das bedeutet: die alten Interfaces müssen von den neuen Interfaces erben, dann kann schon stückweise Code geschrieben werden, der die neuen Interfaces vorraussetzt... +

+ +
+ + + + +