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
+
+
+
+
+
+
+
+
+
+