diff --git a/src/steam/engine/dispatcher.hpp b/src/steam/engine/dispatcher.hpp index 47dfe5420..b071115bc 100644 --- a/src/steam/engine/dispatcher.hpp +++ b/src/steam/engine/dispatcher.hpp @@ -38,10 +38,10 @@ #include "steam/common.hpp" #include "steam/mobject/model-port.hpp" -#include "steam/engine/time-anchor.hpp" #include "steam/engine/frame-coord.hpp" #include "steam/engine/job-ticket.hpp" #include "steam/engine/job-planning.hpp" +#include "steam/play/timings.hpp" #include "steam/play/output-slot.hpp" #include "lib/iter-tree-explorer.hpp" #include "lib/time/timevalue.hpp" diff --git a/src/steam/engine/exit-node.hpp b/src/steam/engine/exit-node.hpp index ed746c13c..2bc137973 100644 --- a/src/steam/engine/exit-node.hpp +++ b/src/steam/engine/exit-node.hpp @@ -34,11 +34,14 @@ #include "lib/nocopy.hpp" #include "lib/hash-value.h" #include "lib/iter-adapter-stl.hpp" +#include "lib/time/timevalue.hpp" #include "vault/engine/job.h" //////////////////////////////////////////////////////////////////TICKET #1295 : rather need a way to retrieve a real JobFunctor building block from the ProcNode #include using lib::HashVal; +using lib::time::FSecs; ////TODO using hard wired value +using lib::time::Duration; namespace steam { @@ -117,6 +120,13 @@ namespace engine { REQUIRE (action_); return *action_; ///////////////////////////////////////////////////////////////////////TICKET #1295 : decision on actual JobFunctor and invocation parameters } + + Duration + getUpperBoundRuntime() const + { + ///////////////////////////////////////////////////////////////////////////TICKET #1283 : lay foundation how to observe timing behaviour for a render pipeline + return Duration{FSecs{1,50}}; // Uh-Oh booo + } }; diff --git a/src/steam/engine/job-planning.hpp b/src/steam/engine/job-planning.hpp index a7d04c0d3..bf7ee07fa 100644 --- a/src/steam/engine/job-planning.hpp +++ b/src/steam/engine/job-planning.hpp @@ -50,6 +50,7 @@ #include "steam/engine/job-ticket.hpp" #include "steam/engine/frame-coord.hpp" #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" @@ -63,7 +64,10 @@ namespace engine { namespace error = lumiera::error; using play::DataSink; - using lib::time::TimeValue; + 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; @@ -97,16 +101,6 @@ namespace engine { , outputSink_{sink} { } -#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1276 :: to be refactored... - /** further job planning can be initiated by continuing off a given previous planning state. - * This is how the forks are created, expanding into a multitude of prerequisites for - * the job in question. - */ - JobPlanning (JobTicket::ExplorationState const& startingPoint, FrameCoord requestedFrame) - : plannedOperations_(startingPoint) - , point_to_calculate_(requestedFrame) - { } -#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1276 :: to be refactored... // using the standard copy operations @@ -123,6 +117,52 @@ namespace engine { return job; } + /** + * Calculate the latest time point when to _start_ the job, + * so it can still possibly reach the timing goal. + * @return time point in wall-clock-time, or Time::ANYTIME if unconstrained + */ + Time + determineDeadline(Timings const& timings) + { + switch (timings.playbackUrgency) + { + case play::ASAP: + case play::NICE: + return Time::ANYTIME; + + case play::TIMEBOUND: + return timings.getTimeDue(frameCoord_.absoluteFrameNumber) + - totalLatency(timings); + } + NOTREACHED ("unexpected playbackUrgency"); + } + + /** + * Determine a timing buffer for flexibility to allow starting the job + * already before its deadline; especially for real-time playback this leeway + * is rather limited, and constrained by the earliest time the target buffer + * is already allotted and ready to receive data. + * @return tolerance duration + * - Duration::NIL if deadline has to be matched with maximum precision + * - Duration::MAX for unlimited leeway to start anytime before the deadline + */ + Duration + determineLeeway(Timings const&) + { + UNIMPLEMENTED ("Job planning logic to establish Leeway for scheduling"); + } + + + private: + Duration + totalLatency (Timings const& timings) + { + return jobTicket_.getExpectedRuntime() + + timings.currentEngineLatency() + + timings.outputLatency; + } + #if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1276 :: to be refactored... /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1301 : likely to become obsolete @@ -288,13 +328,13 @@ namespace engine { return accessJobTicket (location.modelPortIDX, location.absoluteNominalTime); } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1301 : likely to become obsolete +#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1301 : likely to become obsolete bool canContinue (FrameCoord const& location) { // return not isEndOfChunk (location.absoluteFrameNumber, // location.modelPort); } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1301 : likely to become obsolete +#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1301 : likely to become obsolete protected: virtual JobTicket& accessJobTicket (size_t, TimeValue nominalTime) =0; diff --git a/src/steam/engine/job-ticket.cpp b/src/steam/engine/job-ticket.cpp index 5a1fa681c..5586c6bf4 100644 --- a/src/steam/engine/job-ticket.cpp +++ b/src/steam/engine/job-ticket.cpp @@ -39,14 +39,20 @@ namespace steam { namespace engine { - namespace { // Details... - lib::Depend nopFunctor; - } // (END) Details... - - using vault::engine::JobClosure; using lib::HashVal; using lib::unConst; + using lib::time::FSecs; + + + namespace { // Details... + lib::Depend nopFunctor; + + /* ======== hard wired =================*/ + const FSecs JOB_MINIMUM_RUNTIME{1,1000}; + + } // (END) Details... + @@ -69,7 +75,7 @@ namespace engine { * after the last Builder run created this part of the render network. */ Job - JobTicket::createJobFor (FrameCoord coordinates) const + JobTicket::createJobFor (FrameCoord coordinates) { Time nominalTime = coordinates.absoluteNominalTime; if (this->empty()) @@ -86,6 +92,25 @@ namespace engine { } } + + /** + * Use observed runtime values of past job invocations to guess a sensible bound + * for the calculation time to expect for next invocation. + * @todo 6/2023 placeholder implementation with hard wired values in ExitNode + */ + Duration + JobTicket::getExpectedRuntime() + { + if (this->empty()) + return Duration{JOB_MINIMUM_RUNTIME}; + else + { + REQUIRE (isValid(), "Attempt to determine timings for incomplete or unspecified render plan."); + return provision_.exitNode.getUpperBoundRuntime(); + } + } + + /** * Tag the precomputed invocation ID with the nominal frame time */ diff --git a/src/steam/engine/job-ticket.hpp b/src/steam/engine/job-ticket.hpp index 32f08a367..b34c0c2a5 100644 --- a/src/steam/engine/job-ticket.hpp +++ b/src/steam/engine/job-ticket.hpp @@ -39,7 +39,7 @@ #include "vault/engine/job.h" #include "steam/engine/frame-coord.hpp" #include "steam/engine/exit-node.hpp" -#include "lib/hierarchy-orientation-indicator.hpp" +#include "lib/time/timevalue.hpp" #include "lib/linked-elements.hpp" #include "lib/util-foreach.hpp" #include "lib/iter-adapter.hpp" @@ -56,8 +56,8 @@ namespace engine { using vault::engine::Job; using vault::engine::JobFunctor; using vault::engine::JobClosure; /////////////////////////////////////////////////////////////////////TICKET #1287 : fix actual interface down to JobFunctor (after removing C structs) +using lib::time::Duration; using lib::LinkedElements; -using lib::OrientationIndicator; using util::isnil; using lib::HashVal; using lib::LUID; @@ -142,7 +142,9 @@ using lib::LUID; /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1276 : likely to become obsolete #endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1276 :: to be refactored... - Job createJobFor (FrameCoord coordinates) const; + Job createJobFor (FrameCoord coordinates); + + Duration getExpectedRuntime(); auto getPrerequisites () diff --git a/src/steam/engine/time-anchor.hpp b/src/steam/engine/time-anchor.hpp index 59d9f8a01..d89e0e566 100644 --- a/src/steam/engine/time-anchor.hpp +++ b/src/steam/engine/time-anchor.hpp @@ -105,29 +105,6 @@ namespace engine { FrameCnt anchorPoint_; Time relatedRealTime_; -#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1301 - ////////////////////////////////////////////////////////////////////////////////////////////////OOO sensible calculation, but pointless in TimeAnchor - static Time - expectedTimeofArival (play::Timings const& timings, FrameCnt startFrame, Offset startDelay) - { - Duration totalLatency = startDelay - + timings.currentEngineLatency() - + timings.outputLatency; - TimeVar deadline; - switch (timings.playbackUrgency) - { - case play::ASAP: - case play::NICE: - deadline = RealClock::now() + totalLatency; - break; - - case play::TIMEBOUND: - deadline = timings.getTimeDue(startFrame) - totalLatency; - break; - } - return deadline; - } -#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1301 static Time expectedTimeofArival (play::Timings const& timings, FrameCnt startFrame, Offset startDelay) { diff --git a/src/steam/play/timings.cpp b/src/steam/play/timings.cpp index fa0831e34..c219e1415 100644 --- a/src/steam/play/timings.cpp +++ b/src/steam/play/timings.cpp @@ -70,15 +70,25 @@ namespace play { * and the latency/speed requirements of the output. */ Timings::Timings (FrameRate fps) - : grid_(buildStandardGridForFramerate(fps)) - , playbackUrgency (ASAP) - , playbackSpeed (1) - , scheduledDelivery(Time::NEVER) - , outputLatency (Duration::NIL) + : grid_{buildStandardGridForFramerate(fps)} + , playbackUrgency {ASAP} + , playbackSpeed {1} + , scheduledDelivery{Time::NEVER} + , outputLatency {Duration::NIL} { ENSURE (grid_); } + Timings::Timings (FrameRate fps, Time realTimeAnchor) + : grid_{buildStandardGridForFramerate(fps)} + , playbackUrgency {TIMEBOUND} + , playbackSpeed {1} + , scheduledDelivery{realTimeAnchor} + , outputLatency {Duration::NIL} + { + ENSURE (grid_); + } + //////////////////////////////////////////////////////////////////TODO ctors for use in the real player/engine? diff --git a/src/steam/play/timings.hpp b/src/steam/play/timings.hpp index ea7f46840..1c7f93194 100644 --- a/src/steam/play/timings.hpp +++ b/src/steam/play/timings.hpp @@ -105,6 +105,8 @@ namespace play { explicit Timings (FrameRate fps); + Timings (FrameRate fps, Time realTimeAnchor); + // default copy acceptable diff --git a/src/vault/engine/engine-config.cpp b/src/vault/engine/engine-config.cpp index 6c825c60b..8f27c7120 100644 --- a/src/vault/engine/engine-config.cpp +++ b/src/vault/engine/engine-config.cpp @@ -28,13 +28,13 @@ #include "vault/engine/engine-config.hpp" #include "lib/time/timevalue.hpp" - -#include +#include "lib/rational.hpp" using boost::rational; using lib::time::FrameRate; using lib::time::FSecs; +using util::Rat; namespace vault{ @@ -42,8 +42,7 @@ namespace engine { namespace { // Hard wired placeholder settings... - const rational ONE_THIRD(1,3); - const rational EIGHTY_PERCENT(8,10); + const auto EIGHTY_PERCENT = 8_r/10; const Duration DEFAULT_ENGINE_LATENCY = EIGHTY_PERCENT * Duration(1, FrameRate{25}); ///////TODO: shouldn't be hard wired and shouldn't be calculated in static/global init const Duration DEFAULT_JOB_PLANNING_TURNOVER(FSecs(3,2)); diff --git a/tests/core/steam/engine/dispatcher-interface-test.cpp b/tests/core/steam/engine/dispatcher-interface-test.cpp index 40e0ac5b2..cf7724854 100644 --- a/tests/core/steam/engine/dispatcher-interface-test.cpp +++ b/tests/core/steam/engine/dispatcher-interface-test.cpp @@ -34,6 +34,7 @@ #include "steam/play/dummy-play-connection.hpp" #include "steam/mobject/model-port.hpp" #include "steam/engine/dispatcher.hpp" +#include "steam/engine/time-anchor.hpp" #include "steam/play/timings.hpp" #include "lib/time/timevalue.hpp" //#include "lib/time/timequant.hpp" diff --git a/tests/core/steam/engine/job-planning-setup-test.cpp b/tests/core/steam/engine/job-planning-setup-test.cpp index b6e4e9c28..396b7252e 100644 --- a/tests/core/steam/engine/job-planning-setup-test.cpp +++ b/tests/core/steam/engine/job-planning-setup-test.cpp @@ -205,7 +205,7 @@ namespace test { CHECK (not isnil (pipeline)); CHECK (nullptr == pipeline->first); // is a top-level ticket - JobTicket const& ticket = *pipeline->second; + JobTicket& ticket = *pipeline->second; FrameCoord dummy{Time::ZERO}; // actual time point is irrelevant here Job job = ticket.createJobFor(dummy); diff --git a/tests/core/steam/engine/job-planning-test.cpp b/tests/core/steam/engine/job-planning-test.cpp index 3452c9cfe..ac8762763 100644 --- a/tests/core/steam/engine/job-planning-test.cpp +++ b/tests/core/steam/engine/job-planning-test.cpp @@ -37,7 +37,7 @@ #include "steam/play/timings.hpp" #include "lib/time/timevalue.hpp" //#include "lib/time/timequant.hpp" -//#include "lib/format-cout.hpp" +#include "lib/format-cout.hpp" ///////////////TODO //#include "lib/depend.hpp" //#include "lib/itertools.hpp" //#include "lib/util-coll.hpp" @@ -131,6 +131,24 @@ namespace test { void calculateDeadline() { + MockDispatcher dispatcher; + play::Timings timings (FrameRate::PAL, Time{0,0,5}); + auto [port,sink] = dispatcher.getDummyConnection(1); + + Time nominalTime{200,0}; + size_t portIDX = dispatcher.resolveModelPort (port); + FrameCoord frame{nominalTime, portIDX}; + JobTicket& ticket = dispatcher.getJobTicketFor(frame); + + JobPlanning plan{frame,ticket,sink}; + + timings.playbackUrgency = play::ASAP; + cout << plan.determineDeadline(timings) < nominalTime ?? + cout << plan.determineDeadline(timings) < - - - - - - - - - - - - - -

- unklar: JobTicket::startExploration() — wie wird die eigentliche Planung eingefädelt? -

- -
+ + + + + - - - - + + + + + - + + @@ -69986,7 +69975,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -70024,6 +70013,19 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + +

+ für die Deadline-Berechnung... +

+ +
+ +
@@ -75722,6 +75724,48 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + + + +

+ und zwar wegen Separation of Concerns   —  der JobFunctor ist kein Informations-Service (genau dafür haben wir ja das JobTicket geschaffen) +

+ +
+ +
+ + + + + + + + + + + + + + + +
@@ -75782,6 +75826,43 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + +

+ Definition: der letzte Zeitpunkt an dem der Job starten darf +

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

+ Definition: Pufferzeit, um die der Job bereits früher starten kann +

+ +
+ +
+ +
@@ -75840,11 +75921,22 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + + + + + + + + + + @@ -75932,7 +76024,47 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + + + + + + + + + + + + + +

+ unklar: JobTicket::startExploration() — wie wird die eigentliche Planung eingefädelt? +

+ +
+ +
+
+ + + + + + + + + + + + + + + + +
@@ -76732,6 +76864,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ @@ -76745,6 +76878,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + +