From 2c3b85a122f38f96ba0583132e81fe769e684a0b Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 9 Jun 2023 02:48:38 +0200 Subject: [PATCH] Dispatcher-Pipeline: allocate JobTicket in Segment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PROBLEM: can not implement Spec-generation, since - we must use a λ for internal allocation of JobTickets - but recursive type inference is not possible Will thus need to abandon the Spec-Tuple and relocate this traversal-and-generation code into JobTicket itself --- src/steam/engine/exit-node.hpp | 22 ++- src/steam/engine/job-ticket.hpp | 28 +-- src/steam/fixture/node-graph-attachment.hpp | 7 + src/steam/fixture/segment.hpp | 77 ++++++-- .../steam/engine/job-planning-setup-test.cpp | 2 +- tests/core/steam/engine/mock-dispatcher.hpp | 5 +- tests/core/steam/engine/mock-support-test.cpp | 26 +-- .../steam/fixture/fixture-segment-test.cpp | 33 ++-- wiki/thinkPad.ichthyo.mm | 165 ++++++++++++++++-- 9 files changed, 299 insertions(+), 66 deletions(-) diff --git a/src/steam/engine/exit-node.hpp b/src/steam/engine/exit-node.hpp index 60423a005..e5fadc3d0 100644 --- a/src/steam/engine/exit-node.hpp +++ b/src/steam/engine/exit-node.hpp @@ -34,6 +34,7 @@ #include "lib/nocopy.hpp" #include "lib/hash-value.h" #include "lib/iter-adapter-stl.hpp" +#include "vault/engine/job.h" //////////////////////////////////////////////////////////////////TICKET #1295 : rather need a way to retrieve a real JobFunctor building block from the ProcNode #include @@ -46,6 +47,7 @@ namespace engine { class ExitNode; using ExitNodes = std::deque; + using vault::engine::JobFunctor; /** @@ -64,6 +66,7 @@ namespace engine { { HashVal pipelineIdentity_; //////////////////////////////////////////////////////////TICKET #1293 : Hash-Chaining for invocation-ID... derive from ProcNode wiring ExitNodes prerequisites_; ///////////////////////////////////////////////////////////TICKET #1306 : actual access to low-level-Model (ProcNode) + JobFunctor* action_{nullptr}; ////////////////////////////////////////////////////////////TICKET #1295 : link to actual implementation action in low-level-Model public: ExitNode() @@ -71,14 +74,24 @@ namespace engine { , prerequisites_{} { } - ExitNode (HashVal id, ExitNodes&& prereq =ExitNodes{}) + ExitNode (HashVal id + ,ExitNodes&& prereq =ExitNodes{} + ,JobFunctor* functor =nullptr) : pipelineIdentity_{id} , prerequisites_{std::move (prereq)} + , action_{functor} { } static ExitNode NIL; + bool + empty() const + { + return 0 == pipelineIdentity_ + or not action_; + } + HashVal getPipelineIdentity() const { @@ -90,6 +103,13 @@ namespace engine { { return lib::iter_stl::eachElm (prerequisites_); } + + JobFunctor& + getInvocationFunctor() const + { + REQUIRE (action_); + return *action_; ///////////////////////////////////////////////////////////////////////TICKET #1295 : decision on actual JobFunctor and invocation parameters + } }; diff --git a/src/steam/engine/job-ticket.hpp b/src/steam/engine/job-ticket.hpp index 9339745aa..37c5d3b80 100644 --- a/src/steam/engine/job-ticket.hpp +++ b/src/steam/engine/job-ticket.hpp @@ -39,6 +39,7 @@ //#include "steam/state.hpp" #include "vault/engine/job.h" #include "steam/engine/frame-coord.hpp" +#include "steam/engine/exit-node.hpp" //#include "lib/time/timevalue.hpp" //#include "lib/time/timequant.hpp" #include "lib/hierarchy-orientation-indicator.hpp" @@ -111,12 +112,14 @@ using lib::LUID; struct Provision { Provision* next{nullptr}; - JobFunctor& jobFunctor; + JobFunctor& jobFunctor; + ExitNode const& exitNode; InvocationInstanceID invocationSeed; - Prerequisites requirements{}; - ////////////////////TODO some channel or format descriptor here - Provision (JobFunctor& func, HashVal seed =0) + Prerequisites requirements{}; + + Provision (JobFunctor& func, ExitNode const& node, HashVal seed =0) : jobFunctor{func} + , exitNode{node} , invocationSeed(static_cast(func).buildInstanceID(seed)) ////////////////TICKET #1287 : fix actual interface down to JobFunctor (after removing C structs) { } /////////////////TICKET #1293 : Hash-Chaining for invocation-ID... size_t hash? or a LUID? }; @@ -302,12 +305,14 @@ using lib::LUID; 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 Node = typename std::tuple_element<0, Spec>::type; using Seed = typename std::tuple_element<1, Spec>::type; - using Preq = typename std::tuple_element<2, Spec>::type; - static_assert (lib::meta::is_basically()); // -- first component specifies the JobFunctor to use + using Func = typename std::tuple_element<2, Spec>::type; + using Preq = typename std::tuple_element<3, Spec>::type; + static_assert (lib::meta::is_basically()); // -- first component refers to the ExitNode in the model static_assert (lib::meta::is_basically()); // -- second component provides a specific seed for Invocation-IDs - static_assert (lib::meta::can_IterForEach::value); // -- third component is again an iterable sequence + static_assert (lib::meta::is_basically()); // -- third component specifies the JobFunctor to use + static_assert (lib::meta::can_IterForEach::value); // -- fourth 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"); @@ -315,10 +320,11 @@ using lib::LUID; LinkedElements provisionSpec; //////////////////////////////////////////////////TICKET #1292 : need to pass in Allocator as argument for ( ; featureSpec; ++featureSpec) ///////////////////////////////////////////////////TICKET #1297 : this additional iteration over channels will go away { - JobFunctor& func = std::get<0> (*featureSpec); + ExitNode& node = std::get<0> (*featureSpec); HashVal invoSeed = std::get<1> (*featureSpec); - auto& provision = provisionSpec.emplace (func, invoSeed); - for (Preq pre = std::get<2> (*featureSpec); pre; ++pre) + JobFunctor& func = std::get<2> (*featureSpec); + auto& provision = provisionSpec.emplace (func, node, invoSeed); + for (Preq pre = std::get<3> (*featureSpec); pre; ++pre) provision.requirements.emplace (*pre); } provisionSpec.reverse(); // retain order of given definitions per channel ////////////TICKET #1297 : obsolete; instead we differentiate by OutputSlot in the Segment diff --git a/src/steam/fixture/node-graph-attachment.hpp b/src/steam/fixture/node-graph-attachment.hpp index b7b453714..1f89586e4 100644 --- a/src/steam/fixture/node-graph-attachment.hpp +++ b/src/steam/fixture/node-graph-attachment.hpp @@ -38,6 +38,7 @@ #include "steam/engine/exit-node.hpp" //#include "lib/time/timevalue.hpp" +#include "lib/util.hpp" #include @@ -80,6 +81,12 @@ namespace fixture { return idx < exitNodes_.size()? exitNodes_[idx] : ExitNode::NIL; } + + bool + empty() const + { + return util::isnil (exitNodes_); + } }; diff --git a/src/steam/fixture/segment.hpp b/src/steam/fixture/segment.hpp index 9cfbd05d1..2c6dc51f2 100644 --- a/src/steam/fixture/segment.hpp +++ b/src/steam/fixture/segment.hpp @@ -37,9 +37,12 @@ #include "steam/mobject/explicitplacement.hpp" #include "steam/engine/job-ticket.hpp" #include "lib/time/timevalue.hpp" +#include "lib/itertools.hpp" +#include "lib/util.hpp" #include -#include +#include +#include namespace steam { @@ -48,7 +51,7 @@ namespace fixture { using mobject::ExplicitPlacement; using lib::time::TimeSpan; using lib::time::Time; - using std::list; + using util::unConst; using std::move; /** @@ -64,17 +67,21 @@ namespace fixture { */ class Segment { + using TicketAlloc = std::deque; + using PortTable = std::deque>; + protected: /** begin of this timeline segment. */ TimeSpan span_; /** render plan / blueprint to use for this segment */ - const engine::JobTicket* jobTicket_; ////////////////////////////////////////////////////TICKET #1297 : probably we'll get an array per ModelPort here + TicketAlloc tickets_; + PortTable portTab_; ///////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #725 : placeholder code /** relevant MObjects comprising this segment. */ - list elements; + std::deque elements; // TODO: actually necessary?? // TODO: ownership?? ///////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #725 : placeholder code @@ -83,18 +90,21 @@ namespace fixture { Segment (TimeSpan covered =TimeSpan::ALL ,const engine::JobTicket* ticket =nullptr) : span_{covered} - , jobTicket_{ticket? ticket : &engine::JobTicket::NOP} //////////////////////////////////////////TICKET #1297 : ensure to provide a JobTicket for each ModelPort in initialisation +// , tickets_{ticket? ticket : &engine::JobTicket::NOP} //////////////////////////////////////////TICKET #1297 : ensure to provide a JobTicket for each ModelPort in initialisation { } /////////////////////////////////////////////////////////////////////////////////////////////////////////////TODO Obsolet ... nur für Umbau erhalten!! Segment (TimeSpan covered ,NodeGraphAttachment&& modelLink) : span_{covered} + , tickets_{} + , portTab_{} , exitNode{move (modelLink)} { } Segment (Segment const& original, TimeSpan changed) : span_{changed} - , jobTicket_{original.jobTicket_} + , tickets_{} // Note: not cloning tickets owned by Segment + , portTab_{} , exitNode{original.exitNode} /////////////////////////////////////////////////////////////////////OOO really? cloning ExitNodes? { } @@ -108,16 +118,63 @@ namespace fixture { engine::JobTicket const& - jobTicket() const /////////////////////////////////////////////////////////////////TICKET #1297 : introduce additional key per ModelPort here + jobTicket (size_t portNr) const { - REQUIRE (jobTicket_); - return *jobTicket_; + if (portNr >= portTab_.size()) + unConst(this)->generateTickets_onDemand (portNr); + ASSERT (portNr < portTab_.size()); + return portTab_[portNr]; } bool empty() const { - return jobTicket().empty(); + return exitNode.empty(); + } + + + private: + /** @internal Generate sequence of prerequisite JobTicket */ + auto + assemblePrerequisites (ExitNode const& exitNode) + { + return lib::transformIterator (exitNode.getPrerequisites() + ,[this](ExitNode const& prereq) -> engine::JobTicket& + { + tickets_.emplace_back( + assembleTicketSpec (prereq)); + return tickets_.back(); + }); + } + + + /** @internal Traverse ExitNode structure and prepare JobTicket */ + auto + assembleTicketSpec (ExitNode const& exitNode) + { + REQUIRE (not isnil (exitNode)); // has valid functor + using vault::engine::JobFunctor; + using Prereqs = decltype(assemblePrerequisites (exitNode)); + using SpecTuple = std::tuple; + return lib::singleValIterator( /////////////////////////////////////////TICKET #1297 : multiplicity per channel will be removed here + SpecTuple(exitNode + ,exitNode.getPipelineIdentity() + ,exitNode.getInvocationFunctor() + ,assemblePrerequisites (exitNode) + )); + } + + void + generateTickets_onDemand (size_t portNr) + { + for (size_t i = portTab_.size(); i <= portNr; ++i) + if (isnil (exitNode[portNr])) + portTab_.emplace_back (engine::JobTicket::NOP); // disable this slot + else + {// Ticket was not generated yet... + tickets_.emplace_back (assembleTicketSpec (exitNode[portNr])); + portTab_.emplace_back (tickets_.back()); // ref to new ticket ‣ slot + } } }; diff --git a/tests/core/steam/engine/job-planning-setup-test.cpp b/tests/core/steam/engine/job-planning-setup-test.cpp index 8e8555a18..e14402250 100644 --- a/tests/core/steam/engine/job-planning-setup-test.cpp +++ b/tests/core/steam/engine/job-planning-setup-test.cpp @@ -130,7 +130,7 @@ namespace test { ) .genNode()}; fixture::Segment const& seg = mockSegs[Time{0,15}]; // access anywhere 10s <= t < 20s - JobTicket const& ticket = seg.jobTicket(); // get the master-JobTicket from this segment + JobTicket const& ticket = seg.jobTicket(0); // get the master-JobTicket from this segment JobTicket const& prereq = *(ticket.getPrerequisites()); // pull a prerequisite JobTicket FrameCoord coord; // Frame coordinates for invocation (placeholder) diff --git a/tests/core/steam/engine/mock-dispatcher.hpp b/tests/core/steam/engine/mock-dispatcher.hpp index 8243e9181..1878ff565 100644 --- a/tests/core/steam/engine/mock-dispatcher.hpp +++ b/tests/core/steam/engine/mock-dispatcher.hpp @@ -97,9 +97,10 @@ namespace test { inline auto defineSpec (HashVal seed, IT&& prereq) { - using SpecTuple = std::tuple; + using SpecTuple = std::tuple; return lib::singleValIterator( /////////////////////////////////////////////TICKET #1297 : multiplicity per channel will be removed here - SpecTuple(DummyJob::getFunctor() + SpecTuple(ExitNode::NIL + ,DummyJob::getFunctor() , seed , std::forward (prereq))); } diff --git a/tests/core/steam/engine/mock-support-test.cpp b/tests/core/steam/engine/mock-support-test.cpp index 368b36498..129a9223a 100644 --- a/tests/core/steam/engine/mock-support-test.cpp +++ b/tests/core/steam/engine/mock-support-test.cpp @@ -86,7 +86,7 @@ namespace test { CHECK (3 == mockSegs.size()); fixture::Segment const& seg = mockSegs[Time{0,15}]; // access anywhere 10s <= t < 20s - JobTicket const& ticket = seg.jobTicket(); ///////////////////////////////////////////////////TICKET #1297 : will need to pass a ModelPort number here (use the first one, i.e. 0) + JobTicket const& ticket = seg.jobTicket(0); FrameCoord coord; coord.absoluteNominalTime = Time(0,15); @@ -166,7 +166,7 @@ namespace test { { MockSegmentation mockSeg; CHECK (1 == mockSeg.size()); - JobTicket const& ticket = mockSeg[someTime].jobTicket(); + JobTicket const& ticket = mockSeg[someTime].jobTicket(0); // just probe JobTicket generated for Model-Port-Nr.0 CHECK (util::isSameObject (ticket, JobTicket::NOP)); } //-----------------------------------------------------------------/// Segmentation with one default segment spanning complete timeline @@ -175,7 +175,7 @@ namespace test { CHECK (1 == mockSegs.size()); CHECK (Time::MIN == mockSegs[someTime].start()); CHECK (Time::MAX == mockSegs[someTime].after()); - JobTicket const& ticket = mockSegs[someTime].jobTicket(); + JobTicket const& ticket = mockSegs[someTime].jobTicket(0); CHECK (not util::isSameObject (ticket, JobTicket::NOP)); Job someJob = ticket.createJobFor(coord); // JobTicket uses, but does not check the time given in FrameCoord @@ -204,12 +204,12 @@ namespace test { // while the left part of the axis is marked as NOP / empty fixture::Segment const& seg1 = mockSegs[Time::ZERO]; // access anywhere < 10s fixture::Segment const& seg2 = mockSegs[Time{0,20}]; // access anywhere >= 10s - CHECK ( util::isSameObject (seg1.jobTicket(), JobTicket::NOP)); - CHECK (not util::isSameObject (seg2.jobTicket(), JobTicket::NOP));// this one is the active segment + CHECK ( util::isSameObject (seg1.jobTicket(0),JobTicket::NOP)); + CHECK (not util::isSameObject (seg2.jobTicket(0),JobTicket::NOP));// this one is the active segment - Job job = seg2.jobTicket().createJobFor(coord); - CHECK (not MockJobTicket::isAssociated (job, seg1.jobTicket())); - CHECK ( MockJobTicket::isAssociated (job, seg2.jobTicket())); + Job job = seg2.jobTicket(0).createJobFor(coord); + CHECK (not MockJobTicket::isAssociated (job, seg1.jobTicket(0))); + CHECK ( MockJobTicket::isAssociated (job, seg2.jobTicket(0))); CHECK (marker == job.parameter.invoKey.part.a); job.triggerJob(); @@ -218,10 +218,10 @@ namespace test { CHECK (marker == DummyJob::invocationAdditionalKey (job)); // DummyClosure is rigged such as to feed back the seed in `part.a` // and thus we can prove this job really belongs to the marked segment // create another job from the (empty) seg1 - job = seg1.jobTicket().createJobFor (coord); + job = seg1.jobTicket(0).createJobFor (coord); InvocationInstanceID empty; /////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs CHECK (lumiera_invokey_eq (&job.parameter.invoKey, &empty)); // indicates that it's just a placeholder to mark a "NOP"-Job - CHECK (seg1.jobTicket().empty()); + CHECK (seg1.jobTicket(0).empty()); CHECK (seg1.empty()); CHECK (not seg2.empty()); } @@ -290,7 +290,7 @@ namespace test { { if (segment.empty()) return 0; - Job job = segment.jobTicket().createJobFor(coord); + Job job = segment.jobTicket(0).createJobFor(coord); job.triggerJob(); CHECK (DummyJob::was_invoked (job)); CHECK (RealClock::wasRecently (DummyJob::invocationTime (job))); @@ -325,7 +325,7 @@ namespace test { .genNode()) .genNode()}; CHECK (1 == mockSegs.size()); - JobTicket const& ticket = mockSegs[Time::ZERO].jobTicket(); + JobTicket const& ticket = mockSegs[Time::ZERO].jobTicket(0); // Model-PortNr.0 auto prereq = ticket.getPrerequisites(); CHECK (not isnil (prereq)); @@ -360,7 +360,7 @@ namespace test { .genNode()) .genNode()}; - auto start = singleValIterator (mockSegs[Time::ZERO].jobTicket()); + auto start = singleValIterator (mockSegs[Time::ZERO].jobTicket(0)); auto it = lib::explore(start) .expand ([](JobTicket const& ticket) diff --git a/tests/core/steam/fixture/fixture-segment-test.cpp b/tests/core/steam/fixture/fixture-segment-test.cpp index 7249800f6..0d369229b 100644 --- a/tests/core/steam/fixture/fixture-segment-test.cpp +++ b/tests/core/steam/fixture/fixture-segment-test.cpp @@ -43,6 +43,9 @@ namespace test { using util::isSameObject; using engine::ExitNode; using lib::diff::MakeRec; + + using engine::Job; + using engine::JobTicket; using engine::test::MockSegmentation; @@ -101,16 +104,26 @@ namespace test { .genNode() ) .genNode()}; - UNIMPLEMENTED("fabricate JobTicket from ExitNode-Graph"); -// // verify generated Node is assembled according to above spec... -// CHECK (13 == node.getPipelineIdentity()); -// auto feed = node.getPrerequisites(); -// CHECK (not isnil (feed)); -// CHECK (23 == feed->getPipelineIdentity()); -// ++feed; -// CHECK (55 == feed->getPipelineIdentity()); -// ++feed; -// CHECK (isnil (feed)); + CHECK (1 == segmentation.size()); // whole time axis covered by one segment + Segment const& seg = segmentation[Time::ANYTIME]; // thus accessed time point is irrelevant + + // verify mapped JobTicket is assembled according to above spec... + auto getMarker = [](JobTicket const& ticket) + { + FrameCoord dummyFrame; + Job job = ticket.createJobFor(dummyFrame); + return job.parameter.invoKey.part.a; + }; + + JobTicket const& ticket = seg.jobTicket(0); + CHECK (13 == getMarker (ticket)); + auto prereq = ticket.getPrerequisites(); + CHECK (not isnil(prereq)); + CHECK (23 == getMarker (*prereq)); + ++prereq; + CHECK (55 == getMarker (*prereq)); + ++prereq; + CHECK (isnil(prereq)); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index e346571dd..c65a776d3 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -74111,8 +74111,76 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ich will für den ganzen Mock-Support eine Lösung, die „einfach funktioniert“ — sofern man mit einer Instanz von MockSegmentation bzw. MockDispatcher arbeitet (und sonst nichts beeinflußt) +

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

+ ...denn das entspricht noch am Meisten dem später mal erwarteten Ablauf +

+ +
+ +
+ + + +
+
@@ -74177,28 +74245,32 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - + + + + - - + + - - + + - - + + + + + + - - - - + + + + - + @@ -74227,9 +74299,50 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + + + + + + + + +

+ damit sitz ich in der Falle... +

+
    +
  • + da ich rekursiv die Vorläufer-Tickets allozieren muß, ist ein λ unabdingbar +
  • +
  • + aber damit kann ich den Typ des Transform-Iterators nicht mehr explizit anschreiben +
  • +
  • + anderseits kann ich aus dem gleichen Grund den Typ des Spec-Tuples nicht explizit anschreiben +
  • +
+ +
+ + +
+
+ + + + + + + + + + + @@ -75182,6 +75295,22 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + +

+ ↯ ABER dann brauchen wir einen Ref-count... +

+ +
+ +
+
+ +