diff --git a/src/steam/engine/dispatcher.hpp b/src/steam/engine/dispatcher.hpp index a9adbd549..cdcf851ac 100644 --- a/src/steam/engine/dispatcher.hpp +++ b/src/steam/engine/dispatcher.hpp @@ -280,10 +280,6 @@ namespace engine { } // expected next to invoke pullFrom(port,sink) - /** Package a Ticket together with a direct dependency, - * to allow setup of schedule times in downstream processing */ - using TicketDepend = std::pair; - /** * Builder: connect to the JobTicket defining the actual processing @@ -295,11 +291,11 @@ namespace engine { size_t portIDX = SRC::dispatcher->resolveModelPort(port); return buildPipeline ( SRC::transform( - [portIDX](PipeFrameTick& core) -> TicketDepend + [portIDX](PipeFrameTick& core) { - return {nullptr - ,& core.dispatcher->getJobTicketFor(portIDX, core.currPoint) - }; + return JobPlanning{core.dispatcher->getJobTicketFor(portIDX, core.currPoint) + ,core.currPoint + ,core.frameNr}; })); } @@ -314,15 +310,9 @@ namespace engine { { return buildPipeline ( SRC::expandAll( - [](TicketDepend& currentLevel) + [](JobPlanning& currentLevel) { - JobTicket* parent = currentLevel.second; - return lib::transformIterator (parent->getPrerequisites() - ,[&parent](JobTicket& prereqTicket) - { // parent shifted up to first pos - return TicketDepend{parent, &prereqTicket}; - } - ); + return currentLevel.buildDependencyPlanning(); })); } @@ -335,9 +325,9 @@ namespace engine { { return terminatePipeline ( SRC::transform( - [sink](TicketDepend& currentLevel) + [sink](JobPlanning& currentLevel) -> JobPlanning& { - return currentLevel.second; ///////////////////////////////OOO construct a JobPlanning here + return currentLevel; ///////////////////////////////OOO the purpose of this function is no longer clear })); } diff --git a/src/steam/engine/job-planning.hpp b/src/steam/engine/job-planning.hpp index 87c2878a3..79bda1161 100644 --- a/src/steam/engine/job-planning.hpp +++ b/src/steam/engine/job-planning.hpp @@ -30,7 +30,31 @@ ** From a usage point of view, the _job-planning pipeline_ is an _iterator:_ for each independent ** calculation step a new JobPlanning record appears at the output side of the pipeline, holding ** all collected data, sufficient to generate the actual job definition, which can then be - ** handed over to the Scheduler. + ** handed over to the Scheduler. + ** + ** # Implementation of the Job-Planning pipeline + ** + ** JobPlanning acts as _working data aggregator_ within the Job-Planning pipeline; for this reason + ** all data fields are references, and the optimiser is expected to elide them, since after template + ** instantiation, JobPlanning becomes part of the overall assembled pipeline object, stacked on top + ** of the Dispatcher::PipeFrameTick, which holds and increments the current frame number. The + ** underlying play::Timings will provide a _frame grid_ to translate these frame numbers into + ** the _nominal time values_ used throughout the rest of the render calculations. + ** + ** There is one tricky detail to note regarding the handling of calculation prerequisites. The + ** typical example would be the loading and decoding of media data, which is an IO-bound task and + ** must be complete before the main frame job can be started. Since the Job-Planning pipeline is + ** generic, this kind of detail dependency is modelled as _prerequisite JobTicket,_ leading to + ** an possibly extended depth-first tree expansion, starting from the »master frame ticket« at + ** the root. This _tree exploration_ is implemented by the TreeExplorer::Expander building block, + ** which obviously has to maintain a stack of expanded child dependencies. This leads to the + ** observation, that at any point of this dependency processing, for the complete path from the + ** child prerequisite up to the root tick there is a sequence of JobPlanning instances placed + ** into this stack in the Explorer object (each level in this stack is actually an iterator + ** and handles one level of child prerequisites). The deadline calculation directly exploits + ** this known arrangement, insofar each JobPlanning has a pointer to its parent (sitting in + ** the stack level above). See the [IterExplorer unit test](\ref lib::IterTreeExplorer_test::verify_expandOperation) + ** to understand this recursive on-demand processing in greater detail. ** ** @warning as of 4/2023 a complete rework of the Dispatcher is underway ///////////////////////////////////////////TICKET #1275 ** @@ -52,8 +76,7 @@ #include "steam/play/output-slot.hpp" #include "steam/play/timings.hpp" #include "lib/time/timevalue.hpp" -//#include "lib/iter-explorer.hpp" -//#include "lib/iter-adapter.hpp" +#include "lib/itertools.hpp" #include "lib/nocopy.hpp" #include "lib/util.hpp" @@ -68,7 +91,6 @@ namespace engine { using play::Timings; using lib::time::Time; using lib::time::Duration; - using lib::time::TimeValue; ////////TODO for FrameLocator, could be obsolete using util::unConst; using util::isnil; @@ -95,16 +117,19 @@ namespace engine { JobTicket& jobTicket_; Time const& nominalTime_; FrameCnt const& frameNr_; - + public: - JobPlanning(JobTicket& ticket, Time const& nominalTime, FrameCnt const& frameNr) + JobPlanning (JobTicket& ticket, Time const& nominalTime, FrameCnt const& frameNr) : jobTicket_{ticket} , nominalTime_{nominalTime} , frameNr_{frameNr} { } + // move construction is possible - // using the standard copy operations + + JobTicket& ticket() { return jobTicket_; } + bool isTopLevel() const { return not dependentPlan_; } /** @@ -156,7 +181,46 @@ namespace engine { } + /** + * Build a sequence of dependent JobPlanning scopes for all prerequisites + * of this current JobPlanning, and internally linked back to `*this` + * @return an iterator which explores the prerequisites of the JobTicket. + * @remark typical example would be to load data from file, or to require + * the results from some other extended media calculation. + * @see Dispatcher::PipelineBuilder::expandPrerequisites() + */ + auto + buildDependencyPlanning() + { + return lib::transformIterator (jobTicket_.getPrerequisites() + ,[this](JobTicket& prereqTicket) + { + return JobPlanning{*this, prereqTicket}; + }); + } + private: + /** link to a dependent JobPlanning, for planning of prerequisites */ + JobPlanning* dependentPlan_{nullptr}; + + /** + * @internal construct a chained prerequisite JobPlanning, + * attached to the dependent »parent« JobPlanning, using the same + * frame data, but chaining up the deadlines, so that a Job created + * from this JobPlanning needs to be completed before the »parent« + * Job (which uses the generated data) can start + * @see #buildDependencyPlanning() + * @see JobPlanning_test::setupDependentJob() + */ + JobPlanning (JobPlanning& parent, JobTicket& prerequisite) + : jobTicket_{prerequisite} + , nominalTime_{parent.nominalTime_} + , frameNr_{parent.frameNr_} + , dependentPlan_{&parent} + { } + + + Duration totalLatency (Timings const& timings) { diff --git a/tests/core/steam/engine/job-planning-pipeline-test.cpp b/tests/core/steam/engine/job-planning-pipeline-test.cpp index b918a56a4..42a46f34e 100644 --- a/tests/core/steam/engine/job-planning-pipeline-test.cpp +++ b/tests/core/steam/engine/job-planning-pipeline-test.cpp @@ -201,8 +201,8 @@ namespace test { .pullFrom (port); CHECK (not isnil (pipeline)); - CHECK (nullptr == pipeline->first); // is a top-level ticket - JobTicket& ticket = *pipeline->second; + CHECK (pipeline->isTopLevel()); // is a top-level ticket + JobTicket& ticket = pipeline->ticket(); Job job = ticket.createJobFor(Time::ZERO); // actual time point is irrelevant here CHECK (dispatcher.verify(job, port, sink)); @@ -239,14 +239,14 @@ namespace test { // the first element is identical to previous test CHECK (not isnil (pipeline)); - CHECK (nullptr == pipeline->first); - Job job = pipeline->second->createJobFor (Time::ZERO); + CHECK (pipeline->isTopLevel()); + Job job = pipeline->ticket().createJobFor (Time::ZERO); CHECK (11 == job.parameter.invoKey.part.a); auto visualise = [](auto& pipeline) -> string { Time frame{pipeline.currPoint}; // can access the embedded PipeFrameTick core to get "currPoint" (nominal time) - Job job = pipeline->second->createJobFor(frame); // looking always at the second element, which is the current JobTicket + Job job = pipeline->ticket().createJobFor(frame); // looking always at the second element, which is the current JobTicket TimeValue nominalTime{job.parameter.nominalTime}; // job parameter holds the microseconds (gavl_time_t) int32_t mark = job.parameter.invoKey.part.a; // the MockDispatcher places the given "mark" here return _Fmt{"J(%d|%s)"} % mark % nominalTime; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 107edd0b1..e2819f7bd 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -75823,7 +75823,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -75841,7 +75841,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + @@ -75865,7 +75868,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -75920,22 +75923,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - - - - - -

- ...denn sonst würde es genau zu rekursiven kaskadierenden (exponentiell aufwendigen) Aufrufen der Deadline-Berechnungs-Logik kommen; um das Caching zu steuern, kann ich einen Marker-Wert Time::NEVER verwenden -

- -
- -
- + + + @@ -75946,6 +75936,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

+
@@ -75963,7 +75954,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -75980,7 +75971,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -76083,13 +76074,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + +
- - + + @@ -76156,16 +76147,59 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + +
- - - + + + + - + - + + + + + + + + + + + + + + + + + + + + +

+ ...denn sonst würde es genau zu rekursiven kaskadierenden (exponentiell aufwendigen) Aufrufen der Deadline-Berechnungs-Logik kommen; um das Caching zu steuern, kann ich einen Marker-Wert Time::NEVER verwenden +

+ +
+ + +
+ + + +