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
* * The " mechanics " of discovering and planning frame calculation jobs .
2015-01-02 11:48:02 +01:00
* * This is a rather abstract chunk of code , to deal especially with the technicalities
2013-01-12 14:36:01 +01:00
* * of \ em organising the discovery of prerequisites and of joining all the discovered operations
* * into a sequence of planning steps . The net result is to present a < i > sequence of job planing < / i >
* * to the user , while actually encapsulating a depth - first tree exploration , which proceeds on demand .
* *
2021-01-23 16:45:04 +01:00
* * # participating elements
2013-01-12 14:36:01 +01:00
* * All of these job planning operations are implemented on top of the JobTicket . This is where to look
* * for " actual " implementation code . Here , within this header , the following entities cooperate to
* * create a simple sequence out of this implementation level tasks :
* * - JobPlanningSequence is the entry point for client code : it allows to generate a sequence of jobs
* * - JobPlanning is a view on top of all the collected planning information for a single job
* * - PlanningState is an iterator , successively exposing a sequence of JobPlanning views
2018-11-15 23:59:23 +01:00
* * - steam : : engine : : expandPrerequisites ( JobPlanning const & ) is the operation to explore further prerequisite Jobs recursively
2013-01-12 14:36:01 +01:00
* * - PlanningStepGenerator yields the underlying " master beat " : a sequence of frame locations to be planned
* *
2021-01-23 16:45:04 +01:00
* * # how the PlanningState ( sequence ) is advanced
2015-01-02 11:48:02 +01:00
* * PlanningState is an iterator to expose a sequence of JobPlanning elements . On the implementation level ,
2013-04-29 01:36:32 +02:00
* * there is always just a single JobPlanning element , which represents the \ em current element ; this element
* * lives as " state core " within the PlanningState object . Advancing to the next JobPlanning element ( i . e . to
* * consider the next job or prerequisite job to be planned for scheduling ) is performed through the iteration
2017-12-11 01:38:05 +01:00
* * control API exposed by JobPlanning ( the functions ` checkPoint ( ) ` , ` yield ( ) ` and ` iterNext ( ) ` . Actually ,
2013-04-29 01:36:32 +02:00
* * these functions are invoked through the depth - first tree exploration performed by JobPlaningSequence .
* * The implementation of these invocations can be found within the IterExplorer strategy
* * lib : : iter_explorer : : RecursiveSelfIntegration . The net result is
2017-12-11 01:38:05 +01:00
* * - the current element is always accessed through ` yield ( ) `
2013-04-29 01:36:32 +02:00
* * - advancing to the next element happens \ em either
* *
2017-12-05 03:28:00 +01:00
* * - by invoking ` iterNext ( ) ` ( when processing a sequence of sibling job prerequisites )
* * - by invoking ` integrate ( ) ` ( when starting to explore the next level of children )
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
* *
2013-01-12 14:36:01 +01:00
* * @ see DispatcherInterface_test simplified usage examples
* * @ 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"
2013-08-30 02:00:35 +02:00
# include "lib/time/timevalue.hpp"
2012-07-01 03:42:50 +02:00
# include "lib/iter-explorer.hpp"
2013-01-12 14:36:01 +01:00
# include "lib/iter-adapter.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 ;
2013-08-30 02:00:35 +02:00
using lib : : time : : TimeValue ;
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 .
*
* @ remarks on the implementation level , JobPlanning is used as " state core "
* for a PlanningState iterator , to visit and plan subsequently all
* the individual operations necessary to render a timeline chunk .
2013-01-12 14:36:01 +01:00
*/
2012-07-21 20:27:52 +02:00
class JobPlanning
2012-07-01 03:42:50 +02:00
{
2012-08-22 10:43:55 +02:00
JobTicket : : ExplorationState plannedOperations_ ;
FrameCoord point_to_calculate_ ;
2012-07-22 03:11:01 +02:00
2012-07-01 03:42:50 +02:00
public :
2012-07-21 20:27:52 +02:00
/** by default create the bottom element of job planning,
* which happens to to plan no job at all . It is represented
* using an inactive state core ( default constructed )
*/
JobPlanning ( )
{ }
2012-08-22 10:43:55 +02:00
/** 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 .
2012-07-21 20:27:52 +02:00
*/
2012-08-22 10:43:55 +02:00
JobPlanning ( JobTicket : : ExplorationState const & startingPoint , FrameCoord requestedFrame )
2012-07-21 20:27:52 +02:00
: plannedOperations_ ( startingPoint )
, point_to_calculate_ ( requestedFrame )
{ }
2013-01-12 14:36:01 +01:00
// using the standard copy operations
2012-07-01 03:42:50 +02:00
2012-07-21 20:27:52 +02:00
/** cast and explicate this job planning information
* to create a frame job descriptor , ready to be scheduled
*/
operator Job ( )
2012-07-01 03:42:50 +02:00
{
2012-07-21 20:27:52 +02:00
if ( isnil ( plannedOperations_ ) )
throw error : : Logic ( " Attempt to plan a frame-Job based on a missing, "
" unspecified, exhausted or superseded job description "
, error : : LUMIERA_ERROR_BOTTOM_VALUE ) ;
return plannedOperations_ - > createJobFor ( point_to_calculate_ ) ;
2012-07-01 03:42:50 +02:00
}
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
2012-09-03 01:49:14 +02:00
return JobPlanning ( plannedOperations_ - > discoverPrerequisites ( point_to_calculate_ . channelNr )
2012-07-21 20:27:52 +02:00
, this - > point_to_calculate_ ) ;
2012-07-01 03:42:50 +02:00
}
2012-07-21 20:27:52 +02:00
2023-04-17 04:51:38 +02:00
# if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1276 :: to be refactored...
# endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1276 :: to be refactored...
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 ( ) ;
}
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...
# endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////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 ;
}
2012-07-01 03:42:50 +02:00
2012-07-21 20:27:52 +02:00
2013-01-11 16:48:28 +01:00
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
{
return accessJobTicket ( location . modelPort , location . absoluteNominalTime ) ;
}
2013-05-30 02:10:56 +02:00
bool canContinue ( FrameCoord const & location )
{
2015-09-25 02:38:59 +02:00
return not isEndOfChunk ( location . absoluteFrameNumber ,
location . modelPort ) ;
2013-05-30 02:10:56 +02:00
}
2012-10-10 04:35:56 +02:00
protected :
2013-05-30 02:10:56 +02:00
virtual JobTicket & accessJobTicket ( ModelPort , TimeValue nominalTime ) = 0 ;
2013-11-18 00:01:43 +01:00
virtual bool isEndOfChunk ( FrameCnt , ModelPort port ) = 0 ;
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...
# endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////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
} ;
2018-11-15 23:55:13 +01:00
} } // namespace steam::engine
2012-07-01 03:42:50 +02:00
# endif