2012-07-01 03:42:50 +02:00
/*
JOB - PLANNING . hpp - steps to prepare and build render jobs
Copyright ( C ) Lumiera . org
2012 , Hermann Vosseler < Ichthyostega @ web . de >
This program is free software ; you can redistribute it and / or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation ; either version 2 of
the License , or ( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
2013-01-12 14:36:01 +01:00
/** @file job-planning.hpp
2023-06-15 03:51:07 +02:00
* * Aggregation of planning data to generate actual frame calculation jobs .
* * These render jobs are generated periodically by an ongoing process while rendering is underway .
* * For this purpose , each CalcStream of the play / render process operates a RenderDrive with a
* * _job - planning pipeline_ , rooted at the » master beat « as defined by the frame grid from the
* * Timings spec of the current render process . This pipeline will assemble the specifications
* * for the render jobs and thereby possibly discover prerequisites , which must be calculated first .
* * 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 .
2013-04-29 01:36:32 +02:00
* *
2023-04-14 04:43:39 +02:00
* * @ warning as of 4 / 2023 a complete rework of the Dispatcher is underway ///////////////////////////////////////////TICKET #1275
2013-04-29 01:36:32 +02:00
* *
2023-06-15 03:51:07 +02:00
* * @ see JobPlanning_test
2013-01-12 14:36:01 +01:00
* * @ see JobTicket
* * @ see Dispatcher
* * @ see EngineService
* *
*/
2012-07-01 03:42:50 +02:00
2018-11-15 23:52:02 +01:00
# ifndef STEAM_ENGINE_JOB_PLANNING_H
# define STEAM_ENGINE_JOB_PLANNING_H
2012-07-01 03:42:50 +02:00
2018-11-15 23:42:43 +01:00
# include "steam/common.hpp"
# include "vault/engine/job.h"
# include "steam/engine/job-ticket.hpp"
# include "steam/engine/frame-coord.hpp"
2023-06-16 01:50:11 +02:00
# include "steam/play/output-slot.hpp"
2023-06-16 04:09:38 +02:00
# include "steam/play/timings.hpp"
2013-08-30 02:00:35 +02:00
# include "lib/time/timevalue.hpp"
2023-06-15 03:51:07 +02:00
//#include "lib/iter-explorer.hpp"
//#include "lib/iter-adapter.hpp"
2023-06-19 01:51:48 +02:00
# include "lib/nocopy.hpp"
2012-07-01 03:42:50 +02:00
# include "lib/util.hpp"
2018-11-15 23:55:13 +01:00
namespace steam {
2012-07-01 03:42:50 +02:00
namespace engine {
2012-07-21 20:27:52 +02:00
namespace error = lumiera : : error ;
2023-06-16 01:50:11 +02:00
using play : : DataSink ;
2023-06-16 04:09:38 +02:00
using play : : Timings ;
using lib : : time : : Time ;
using lib : : time : : Duration ;
using lib : : time : : TimeValue ; ////////TODO for FrameLocator, could be obsolete
2012-07-21 20:27:52 +02:00
using util : : unConst ;
2013-01-12 14:36:01 +01:00
using util : : isnil ;
2012-07-01 03:42:50 +02:00
2013-01-11 16:48:28 +01:00
/**
* View on the execution planning for a single calculation step .
* When this view - frontend becomes accessible , behind the scenes all
2015-09-11 03:36:22 +02:00
* the necessary information has been pulled and collected from the
2013-01-11 16:48:28 +01:00
* low - level model and the relevant rendering / playback configuration .
* Typically , clients will materialise this planning into a Job ( descriptor )
* ready to be entered into the scheduler .
*
* JobPlanning is indeed a view ; the represented planning information is not
* persisted ( other then in the job to be created ) . The implementation draws
* on a recursive exploration of the corresponding JobTicket , which acts as
* a general blueprint for creating jobs within this segment of the timeline .
*
2023-06-15 03:51:07 +02:00
* @ todo WIP - WIP 6 / 2023 reworking the job - planning pipeline for » PlaybackVerticalSlice «
2013-01-12 14:36:01 +01:00
*/
2012-07-21 20:27:52 +02:00
class JobPlanning
2023-06-19 01:51:48 +02:00
: util : : MoveOnly
2012-07-01 03:42:50 +02:00
{
2023-06-19 01:51:48 +02:00
JobTicket & jobTicket_ ;
Time const & nominalTime_ ;
FrameCnt const & frameNr_ ;
2012-07-22 03:11:01 +02:00
2012-07-01 03:42:50 +02:00
public :
2023-06-19 01:51:48 +02:00
JobPlanning ( JobTicket & ticket , Time const & nominalTime , FrameCnt const & frameNr )
: jobTicket_ { ticket }
, nominalTime_ { nominalTime }
, frameNr_ { frameNr }
2012-07-21 20:27:52 +02:00
{ }
2013-01-12 14:36:01 +01:00
// using the standard copy operations
2012-07-01 03:42:50 +02:00
2023-06-16 01:50:11 +02:00
/**
* Connect and complete the planning information assembled thus far
* to create a frame job descriptor , ready to be scheduled .
2012-07-21 20:27:52 +02:00
*/
2023-06-16 01:50:11 +02:00
Job
buildJob ( )
2012-07-01 03:42:50 +02:00
{
2023-06-19 01:51:48 +02:00
Job job = jobTicket_ . createJobFor ( Time { nominalTime_ } ) ;
2023-06-16 01:50:11 +02:00
//////////////////////////////////////////////////////TICKET #1295 : somehow package and communicate the DataSink info
return job ;
2012-07-01 03:42:50 +02:00
}
2023-06-16 04:09:38 +02:00
/**
* 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 :
2023-06-19 01:51:48 +02:00
return timings . getTimeDue ( frameNr_ )
2023-06-16 04:09:38 +02:00
- 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 ;
}
2012-07-21 20:27:52 +02:00
2023-06-15 03:51:07 +02:00
# if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1276 :: to be refactored...
2023-06-12 19:21:14 +02:00
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1301 : likely to become obsolete
2012-07-21 20:27:52 +02:00
/** build a new JobPlanning object,
2013-01-12 14:36:01 +01:00
* set to explore the prerequisites
2012-07-21 20:27:52 +02:00
* at the given planning situation
*/
JobPlanning
discoverPrerequisites ( ) const
2012-07-01 03:42:50 +02:00
{
2012-07-21 20:27:52 +02:00
if ( isnil ( plannedOperations_ ) )
return JobPlanning ( ) ;
else
2023-06-12 19:21:14 +02:00
return JobPlanning ( plannedOperations_ - > discoverPrerequisites ( 0 ) //////////////////////////////TICKET #1301 : was: point_to_calculate_.channelNr)
2012-07-21 20:27:52 +02:00
, this - > point_to_calculate_ ) ;
2012-07-01 03:42:50 +02:00
}
2023-06-12 19:21:14 +02:00
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1301 : likely to become obsolete
2012-07-21 20:27:52 +02:00
2012-08-22 10:43:55 +02:00
/** integrate another chain of prerequisites into the current evaluation line.
* Further evaluation will start to visit prerequisites from the new starting point ,
2012-09-04 01:55:11 +02:00
* and return to the current evaluation chain later on exhaustion of the side chain .
* Especially in case the current evaluation is empty or already exhausted , the
* new starting point effectively replaces the current evaluation point */
2012-07-22 03:11:01 +02:00
friend void
2012-09-03 01:49:14 +02:00
integrate ( JobPlanning const & newStartingPoint , JobPlanning & existingPlan )
2012-07-22 03:11:01 +02:00
{
2012-09-04 01:55:11 +02:00
if ( isnil ( existingPlan . plannedOperations_ ) )
{ // current evaluation is exhausted: switch to new starting point
existingPlan . point_to_calculate_ = newStartingPoint . point_to_calculate_ ;
}
2012-07-22 03:11:01 +02:00
existingPlan . plannedOperations_ . push ( newStartingPoint . plannedOperations_ ) ;
2013-04-29 01:36:32 +02:00
existingPlan . plannedOperations_ . markTreeLocation ( ) ;
2012-07-22 03:11:01 +02:00
}
2012-07-01 03:42:50 +02:00
/* === Iteration control API for IterStateWrapper== */
2017-12-05 03:28:00 +01:00
bool
checkPoint ( ) const
{
return not isnil ( plannedOperations_ ) ;
}
2012-07-01 03:42:50 +02:00
2017-12-05 03:28:00 +01:00
JobPlanning &
yield ( ) const
{
REQUIRE ( checkPoint ( ) ) ;
return unConst ( * this ) ;
}
2012-07-01 03:42:50 +02:00
2017-12-05 03:28:00 +01:00
void
iterNext ( )
{
plannedOperations_ . pullNext ( ) ;
plannedOperations_ . markTreeLocation ( ) ;
}
2023-06-15 03:51:07 +02:00
# endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1276 :: to be refactored...
2012-07-01 03:42:50 +02:00
} ;
2012-07-22 03:11:01 +02:00
2012-07-01 03:42:50 +02:00
2023-04-17 04:51:38 +02:00
# if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1276 :: to be refactored...
2013-01-12 14:36:01 +01:00
/**
* iterator , exposing a sequence of JobPlanning elements
*/
2012-07-21 20:27:52 +02:00
class PlanningState
: public lib : : IterStateWrapper < JobPlanning >
2012-07-01 03:42:50 +02:00
{
2012-07-21 20:27:52 +02:00
typedef lib : : IterStateWrapper < JobPlanning > _Iter ;
2012-07-01 03:42:50 +02:00
public :
2012-07-21 20:27:52 +02:00
/** inactive evaluation */
PlanningState ( )
: _Iter ( )
{ }
2012-07-01 03:42:50 +02:00
2012-07-21 20:27:52 +02:00
explicit
PlanningState ( JobPlanning const & startingPoint )
: _Iter ( startingPoint ) // note: invoking copy ctor on state core
2012-07-01 03:42:50 +02:00
{ }
2012-07-21 20:27:52 +02:00
// using the standard copy operations
2012-07-01 03:42:50 +02:00
2012-07-21 20:27:52 +02:00
2012-07-22 03:11:01 +02:00
/* === API for JobPlanningSequence to expand the tree of prerequisites === */
2012-07-21 20:27:52 +02:00
/** attach and integrate the given planning details into this planning state.
2012-07-22 03:11:01 +02:00
* Actually the evaluation proceeds depth - first with the other state ,
2013-04-29 01:36:32 +02:00
* returning to the current position later for further evaluation */
2012-07-21 20:27:52 +02:00
PlanningState &
wrapping ( JobPlanning const & startingPoint )
{
2012-07-22 03:11:01 +02:00
integrate ( startingPoint , this - > stateCore ( ) ) ;
2012-07-21 20:27:52 +02:00
return * this ;
}
2013-01-12 14:36:01 +01:00
2012-07-21 20:27:52 +02:00
PlanningState &
usingSequence ( PlanningState const & prerequisites )
2012-07-01 03:42:50 +02:00
{
2012-07-21 20:27:52 +02:00
if ( isnil ( prerequisites ) )
return * this ;
else
return this - > wrapping ( * prerequisites ) ;
2012-07-22 03:11:01 +02:00
// explanation: PlanningState represents a sequence of successive planning points.
// actually this is implemented by switching an embedded JobPlanning element
2012-09-01 17:33:42 +02:00
// through a sequence of states. Thus the initial state of an investigation
2012-07-22 03:11:01 +02:00
// (which is a JobPlanning) can stand-in for the sequence of prerequisites
2012-07-01 03:42:50 +02:00
}
2012-07-21 20:27:52 +02:00
/** Extension point to be picked up by ADL.
* Provides access for the JobPlanningSequence
2013-01-12 14:36:01 +01:00
* for combining and expanding partial results .
2012-07-21 20:27:52 +02:00
*/
friend PlanningState &
build ( PlanningState & attachmentPoint )
{
return attachmentPoint ;
}
2012-07-01 03:42:50 +02:00
} ;
2012-07-21 20:27:52 +02:00
2012-07-01 03:42:50 +02:00
2013-01-11 16:48:28 +01:00
/** this is the core operation to drive planning ahead:
* discover the prerequisites of some operation - - here
* " prerequisites " are those operations to be performed
* within separate Jobs beforehand .
* @ note this function is intended to be flat - mapped ( " >>= " )
* onto a tree - like monad representing the evaluation process .
*/
2012-07-21 20:27:52 +02:00
inline PlanningState
2012-09-01 17:33:42 +02:00
expandPrerequisites ( JobPlanning const & calculationStep )
2012-07-21 20:27:52 +02:00
{
PlanningState newSubEvaluation (
2012-09-01 17:33:42 +02:00
calculationStep . discoverPrerequisites ( ) ) ;
2012-07-21 20:27:52 +02:00
return newSubEvaluation ;
}
2023-06-15 03:51:07 +02:00
# endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1276 :: to be refactored...
2012-07-01 03:42:50 +02:00
2012-07-21 20:27:52 +02:00
2013-01-11 16:48:28 +01:00
2023-06-18 03:50:48 +02:00
# if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1301 : obsoleted by rework of Dispatcher-Pipeline
2012-10-10 04:35:56 +02:00
/**
* Abstraction : a Facility to establish frame coordinates
* and identify and access the execution plan for this frame .
* @ see Dispatcher the service interface actually used
*/
class FrameLocator
: public FrameSequencer
{
public :
JobTicket &
2013-05-30 02:10:56 +02:00
getJobTicketFor ( FrameCoord const & location )
2012-10-10 04:35:56 +02:00
{
2023-06-12 19:21:14 +02:00
return accessJobTicket ( location . modelPortIDX , location . absoluteNominalTime ) ;
2012-10-10 04:35:56 +02:00
}
2023-06-16 04:09:38 +02:00
# if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1301 : likely to become obsolete
2013-05-30 02:10:56 +02:00
bool canContinue ( FrameCoord const & location )
{
2023-06-12 19:21:14 +02:00
// return not isEndOfChunk (location.absoluteFrameNumber,
// location.modelPort);
2013-05-30 02:10:56 +02:00
}
2023-06-16 04:09:38 +02:00
# endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1301 : likely to become obsolete
2013-05-30 02:10:56 +02:00
2012-10-10 04:35:56 +02:00
protected :
2023-06-12 19:21:14 +02:00
virtual JobTicket & accessJobTicket ( size_t , TimeValue nominalTime ) = 0 ;
virtual bool isEndOfChunk ( FrameCnt , ModelPort port ) = 0 ;
2012-10-10 04:35:56 +02:00
} ;
2023-06-18 03:50:48 +02:00
# endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1301 : obsoleted by rework of Dispatcher-Pipeline
2012-10-10 04:35:56 +02:00
2013-01-12 14:36:01 +01:00
2023-04-17 04:51:38 +02:00
# if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1276 :: to be refactored...
2012-07-01 03:42:50 +02:00
/**
* Generate a sequence of starting points for Job planning ,
* based on the underlying frame grid . This sequence will be
* used to seed a JobPlanningSequence for generating a chunk
* of frame render jobs within a given CalcStream in the player .
* Evaluation of that seed will then expand each starting point ,
* until all prerequisites for those frames are discovered ,
* resulting in a sequence of Jobs ready to be handed over
* to the scheduler for time - bound activation .
*/
class PlanningStepGenerator
{
2012-10-10 04:35:56 +02:00
FrameLocator * locationGenerator_ ;
FrameCoord currentLocation_ ;
2012-09-01 17:33:42 +02:00
2012-10-10 05:09:03 +02:00
//////////////////////////////////////////TODO duplicated storage of a FrameCoord record
//////////////////////////////////////////TODO nextEvaluation_ is only needed to initialise the "current" sequence
//////////////////////////////////////////TODO within the RecursiveSelfIntegration strategy. Maybe this storage could be collapsed?
2013-02-11 03:23:10 +01:00
JobPlanning nextEvaluation_ ;
2012-10-10 05:09:03 +02:00
2013-02-11 03:23:10 +01:00
void
2013-01-13 23:20:20 +01:00
use_current_location_as_starting_point_for_planning ( )
2012-10-10 05:09:03 +02:00
{
JobTicket & processingPlan = locationGenerator_ - > getJobTicketFor ( currentLocation_ ) ;
nextEvaluation_ = JobPlanning ( processingPlan . startExploration ( )
, currentLocation_ ) ;
}
2012-07-22 03:11:01 +02:00
public :
typedef JobPlanning value_type ;
typedef JobPlanning & reference ;
typedef JobPlanning * pointer ;
2012-09-01 17:33:42 +02:00
2013-02-11 03:19:24 +01:00
PlanningStepGenerator ( FrameCoord startPoint , FrameLocator & locator )
2012-10-10 04:35:56 +02:00
: locationGenerator_ ( & locator )
, currentLocation_ ( startPoint )
2013-02-11 03:23:10 +01:00
{
REQUIRE ( startPoint . isDefined ( ) ) ;
use_current_location_as_starting_point_for_planning ( ) ;
}
2012-10-10 04:35:56 +02:00
// default copyable
2012-07-22 03:11:01 +02:00
2012-07-01 03:42:50 +02:00
/* === Iteration control API for IterStateWrapper== */
2017-12-05 03:28:00 +01:00
bool
checkPoint ( ) const
{
return currentLocation_ . isDefined ( ) ;
} // might indicate end of this planning chunk (or of playback altogether)
2013-05-30 02:10:56 +02:00
2012-07-01 03:42:50 +02:00
2017-12-05 03:28:00 +01:00
JobPlanning &
yield ( ) const
{
ENSURE ( checkPoint ( ) ) ;
return unConst ( this ) - > nextEvaluation_ ;
}
2012-07-01 03:42:50 +02:00
2013-05-30 02:10:56 +02:00
2017-12-05 03:28:00 +01:00
void
iterNext ( )
{
if ( locationGenerator_ - > canContinue ( currentLocation_ ) )
{
currentLocation_ = locationGenerator_ - > getNextFrame ( currentLocation_ ) ;
this - > use_current_location_as_starting_point_for_planning ( ) ;
ENSURE ( this - > checkPoint ( ) ) ;
}
else
{ // indicate end-of playback or a jump to another playback position
currentLocation_ = FrameCoord ( ) ;
}
}
2012-07-01 03:42:50 +02:00
} ;
2012-09-01 17:33:42 +02:00
2013-01-12 14:36:01 +01:00
/* type definitions for building the JobPlaningSequence */
2012-09-01 17:33:42 +02:00
typedef PlanningState ( * SIG_expandPrerequisites ) ( JobPlanning const & ) ;
typedef lib : : IterExplorer < PlanningStepGenerator
, lib : : iter_explorer : : RecursiveSelfIntegration > JobPlanningChunkStartPoint ;
typedef JobPlanningChunkStartPoint : : FlatMapped < SIG_expandPrerequisites > : : Type ExpandedPlanningSequence ;
2012-07-01 03:42:50 +02:00
/**
2012-10-06 02:29:19 +02:00
* This iterator represents a pipeline to pull planned jobs from .
2013-01-12 14:36:01 +01:00
* To dispatch individual frame jobs for rendering , this pipeline is generated
* and wired internally such as to interpret the render node definitions .
2012-10-06 02:29:19 +02:00
*
* \ par Explanation of the structure
*
* The JobPlanningSequence is constructed from several nested layers of functionality
* - for the client , it is an iterator , exposing a sequence of JobPlanning elements
* - a JobPlanning element allows to add a frame render job to the scheduler
* - actually such an element can even be \ em converted directly into a Job ( descriptor )
* - the sequence of such JobPlanning elements ( that is , the iterator ) is called a PlanningState ,
* since evaluating this iterator effectively drives the process of job planning ahead
* - this planning process is \ em implemented as a recursive evaluation and exploration of
* a tree of prerequisites ; these prerequisites are defined in the JobTicket datastructure
* - there is an underlying grid of evaluation starting points , each corresponding to a
* single frame . Typically , each frame generates at least two jobs , one for fetching
* data , and one for the actual calculations . Depending on the actual render network ,
* a lot of additional jobs might be necessary
* - this basic frame grid is generated by the PlanningStepGenerator , which is
* effectively backed by the Dispatcher and thus the render node model .
*
2013-01-12 14:36:01 +01:00
* @ remarks JobPlanningSequence is a monad , and the operation to explore the prerequisites
* is applied by the \ c > > = ( monad flat map operation ) . This approach allows us
* to separate the technicalities of exhausting tree exploration from the actual
* " business code " to deal with frame job dependencies
2012-07-01 03:42:50 +02:00
*/
class JobPlanningSequence
2012-09-01 17:33:42 +02:00
: public ExpandedPlanningSequence
2012-07-01 03:42:50 +02:00
{
public :
2013-02-11 03:23:10 +01:00
JobPlanningSequence ( engine : : FrameCoord startPoint , FrameLocator & locator )
2012-09-01 17:33:42 +02:00
: ExpandedPlanningSequence (
JobPlanningChunkStartPoint (
2013-02-11 03:23:10 +01:00
PlanningStepGenerator ( startPoint , locator ) )
2013-01-12 14:36:01 +01:00
2013-01-11 16:48:28 +01:00
> > = expandPrerequisites ) // "flat map" (monad operation)
2012-09-01 17:33:42 +02:00
{ }
2012-07-01 03:42:50 +02:00
} ;
2023-06-15 03:51:07 +02:00
# endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1276 :: to be refactored...
2012-07-01 03:42:50 +02:00
2018-11-15 23:55:13 +01:00
} } // namespace steam::engine
2012-07-01 03:42:50 +02:00
# endif