LUMIERA.clone/src/steam/engine/job-planning.hpp
Ichthyostega 806db414dd Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
 * there is no entity "Lumiera.org" which holds any copyrights
 * Lumiera source code is provided under the GPL Version 2+

== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''

The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!

The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00

225 lines
9.5 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
JOB-PLANNING.hpp - steps to prepare and build render jobs
Copyright (C)
2012, Hermann Vosseler <Ichthyostega@web.de>
  **Lumiera** 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. See the file COPYING for further details.
*/
/** @file job-planning.hpp
** 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.
**
** # 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 IterExplorer::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::IterExplorer_test::verify_expandOperation)
** to understand this recursive on-demand processing in greater detail.
**
** @see JobPlanning_test
** @see JobTicket
** @see Dispatcher
** @see EngineService
**
*/
#ifndef STEAM_ENGINE_JOB_PLANNING_H
#define STEAM_ENGINE_JOB_PLANNING_H
#include "steam/common.hpp"
#include "vault/gear/job.h"
#include "steam/engine/job-ticket.hpp"
#include "steam/play/output-slot.hpp"
#include "steam/play/timings.hpp"
#include "lib/time/timevalue.hpp"
#include "lib/itertools.hpp"
#include "lib/nocopy.hpp"
namespace steam {
namespace engine {
namespace error = lumiera::error;
using play::DataSink;
using play::Timings;
using lib::time::Time;
using lib::time::TimeVar;
using lib::time::Duration;
using vault::gear::Job;
/**
* View on the execution planning for a single calculation step.
* When this view-frontend becomes accessible, behind the scenes all
* the necessary information has been pulled and collected from the
* 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.
*/
class JobPlanning
: util::MoveOnly
{
JobTicket& jobTicket_;
TimeVar const& nominalTime_;
FrameCnt const& frameNr_;
public:
JobPlanning (JobTicket& ticket, TimeVar const& nominalTime, FrameCnt const& frameNr)
: jobTicket_{ticket}
, nominalTime_{nominalTime}
, frameNr_{frameNr}
{ }
// move construction is possible
JobTicket& ticket() { return jobTicket_; }
bool isTopLevel() const { return not dependentPlan_; }
/**
* Connect and complete the planning information assembled thus far
* to create a frame job descriptor, ready to be scheduled.
*/
Job
buildJob()
{
Job job = jobTicket_.createJobFor (Time{nominalTime_});
//////////////////////////////////////////////////////TICKET #1295 : somehow package and communicate the DataSink info
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 doCalcDeadline (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");
}
/**
* 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}
{ }
Time
doCalcDeadline(Timings const& timings)
{
if (isTopLevel())
return timings.getTimeDue(frameNr_) // anchor at timing grid
- jobTicket_.getExpectedRuntime() // deduce the presumably runtime
- timings.engineLatency // and the generic engine overhead
- timings.outputLatency; // Note: output latency only on top-level job
else
return dependentPlan_->determineDeadline (timings) ////////////////////////////////////////////TICKET #1310 : WARNING - quadratic in the depth of the dependency chain
- jobTicket_.getExpectedRuntime()
- timings.engineLatency;
}
};
}}// namespace steam::engine
#endif /*STEAM_ENGINE_JOB_PLANNING_H*/