Overte C++ Documentation
Task.h
1 //
2 // Task.h
3 // task/src/task
4 //
5 // Created by Zach Pomerantz on 1/6/2016.
6 // Copyright 2016 High Fidelity, Inc.
7 // Copyright 2023 Overte e.V.
8 //
9 // Distributed under the Apache License, Version 2.0.
10 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
11 // SPDX-License-Identifier: Apache-2.0
12 //
13 
14 #ifndef hifi_task_Task_h
15 #define hifi_task_Task_h
16 
17 #include "Config.h"
18 #include "Varying.h"
19 
20 #include <unordered_map>
21 
22 namespace task {
23 
24 class JobConcept;
25 template <class JC, class TP> class JobT;
26 template <class JC, class TP> class TaskT;
27 class JobNoIO {};
28 
29 // Task Flow control class is a simple per value object used to communicate flow control commands trhough the graph of tasks.
30 // From within the Job::Run function, you can access it from the JobContext and issue commands which will be picked up by the Task calling for the Job run.
31 // This is first introduced to provide a way to abort all the work from within a task job. see the "abortTask" call
32 class TaskFlow {
33 public:
34  // A job that wants to abort the rest of the other jobs execution in a task would issue that call "abortTask" and let the task early exit for this run.
35  // All the varyings produced by the aborted branch of jobs are left unmodified.
36  void abortTask();
37 
38  // called by the task::run to perform flow control
39  // This should be considered private but still need to be accessible from the Task<T> class
40  TaskFlow() = default;
41  ~TaskFlow() = default;
42  void reset();
43  bool doAbortTask() const;
44 
45 protected:
46  bool _doAbortTask{ false };
47 };
48 
49 // JobContext class is the base class for the context object which is passed through all the Job::run calls thoughout the graph of jobs
50 // It is used to communicate to the job::run its context and various state information the job relies on.
51 // It specifically provide access to:
52 // - The taskFlow object allowing for messaging control flow commands from within a Job::run
53 // - The current Config object attached to the Job::run currently called.
54 // The JobContext can be derived to add more global state to it that Jobs can access
55 class JobContext {
56 public:
57  JobContext();
58  virtual ~JobContext();
59 
60  std::shared_ptr<JobConfig> jobConfig { nullptr };
61 
62  // Task flow control
63  TaskFlow taskFlow{};
64 
65 protected:
66 };
67 using JobContextPointer = std::shared_ptr<JobContext>;
68 
69 // The guts of a job
70 class JobConcept {
71 public:
72  using Config = JobConfig;
73 
74  JobConcept(const std::string& name, QConfigPointer config) : _config(config), _name(name) { config->_jobConcept = this; }
75  virtual ~JobConcept() = default;
76 
77  const std::string& getName() const { return _name; }
78 
79  virtual const Varying getInput() const { return Varying(); }
80  virtual const Varying getOutput() const { return Varying(); }
81  virtual Varying& editInput() = 0;
82 
83  virtual QConfigPointer& getConfiguration() { return _config; }
84  virtual void applyConfiguration() = 0;
85  void setCPURunTime(const std::chrono::nanoseconds& runtime) { (_config)->setCPURunTime(runtime); }
86 
87  QConfigPointer _config;
88 protected:
89  const std::string _name;
90 };
91 
92 
93 template <class T, class C> void jobConfigure(T& data, const C& configuration) {
94  data.configure(configuration);
95 }
96 template<class T> void jobConfigure(T&, const JobConfig&) {
97  // nop, as the default JobConfig was used, so the data does not need a configure method
98 }
99 
100 template <class T, class JC> void jobRun(T& data, const JC& jobContext, const JobNoIO& input, JobNoIO& output) {
101  data.run(jobContext);
102 }
103 template <class T, class JC, class I> void jobRun(T& data, const JC& jobContext, const I& input, JobNoIO& output) {
104  data.run(jobContext, input);
105 }
106 template <class T, class JC, class O> void jobRun(T& data, const JC& jobContext, const JobNoIO& input, O& output) {
107  data.run(jobContext, output);
108 }
109 template <class T, class JC, class I, class O> void jobRun(T& data, const JC& jobContext, const I& input, O& output) {
110  data.run(jobContext, input, output);
111 }
112 
113 template <class JC, class TP>
114 class Job {
115 public:
116  using Context = JC;
117  using TimeProfiler = TP;
118  using ContextPointer = std::shared_ptr<Context>;
119  using Config = JobConfig;
120  using None = JobNoIO;
121 
122  class Concept : public JobConcept {
123  public:
124  Concept(const std::string& name, QConfigPointer config) : JobConcept(name, config) {}
125  virtual ~Concept() = default;
126 
127  virtual void run(const ContextPointer& jobContext) = 0;
128  };
129  using ConceptPointer = std::shared_ptr<Concept>;
130 
131  template <class T, class C = Config, class I = None, class O = None> class Model : public Concept {
132  public:
133  using Data = T;
134  using Input = I;
135  using Output = O;
136 
137  Data _data;
138  Varying _input;
139  Varying _output;
140 
141  const Varying getInput() const override { return _input; }
142  const Varying getOutput() const override { return _output; }
143  Varying& editInput() override { return _input; }
144 
145  template <class... A>
146  Model(const std::string& name, const Varying& input, QConfigPointer config, A&&... args) :
147  Concept(name, config),
148  _data(Data(std::forward<A>(args)...)),
149  _input(input),
150  _output(Output(), name + ".o") {
151  applyConfiguration();
152  }
153 
154  template <class... A>
155  static std::shared_ptr<Model> create(const std::string& name, const Varying& input, A&&... args) {
156  assert(input.canCast<I>());
157  return std::make_shared<Model>(name, input, std::make_shared<C>(), std::forward<A>(args)...);
158  }
159 
160  void createConfiguration() {
161  // A brand new config
162  auto config = std::make_shared<C>();
163  // Make sure we transfer the former children configs to the new config
164  config->transferChildrenConfigs(Concept::_config);
165  // swap
166  Concept::_config = config;
167  // Capture this
168  Concept::_config->_jobConcept = this;
169  }
170 
171  void applyConfiguration() override {
172  TimeProfiler probe(("configure::" + JobConcept::getName()));
173 
174  jobConfigure(_data, *std::static_pointer_cast<C>(Concept::_config));
175  }
176 
177  void run(const ContextPointer& jobContext) override {
178  jobContext->jobConfig = std::static_pointer_cast<Config>(Concept::_config);
179  if (jobContext->jobConfig->isEnabled()) {
180  jobRun(_data, jobContext, _input.get<I>(), _output.edit<O>());
181  }
182  jobContext->jobConfig.reset();
183  }
184  };
185  template <class T, class I, class C = Config> using ModelI = Model<T, C, I, None>;
186  template <class T, class O, class C = Config> using ModelO = Model<T, C, None, O>;
187  template <class T, class I, class O, class C = Config> using ModelIO = Model<T, C, I, O>;
188 
189  Job() {}
190  Job(const ConceptPointer& conceptPtr) : _conceptPtr(conceptPtr) {}
191  virtual ~Job() = default;
192 
193  const std::string& getName() const { return _conceptPtr->getName(); }
194  const Varying getInput() const { return _conceptPtr->getInput(); }
195  const Varying getOutput() const { return _conceptPtr->getOutput(); }
196 
197  QConfigPointer& getConfiguration() const { return _conceptPtr->getConfiguration(); }
198  void applyConfiguration() { return _conceptPtr->applyConfiguration(); }
199 
200  template <class I> void feedInput(const I& in) { _conceptPtr->editInput().template edit<I>() = in; }
201  template <class I, class S> void feedInput(int index, const S& inS) { (_conceptPtr->editInput().template editN<I>(index)).template edit<S>() = inS; }
202 
203  template <class T> T& edit() {
204  auto conceptPtr = std::static_pointer_cast<typename T::JobModel>(_conceptPtr);
205  assert(conceptPtr);
206  return conceptPtr->_data;
207  }
208 
209  template <class T> const T& get() const {
210  auto conceptPtr = std::static_pointer_cast<typename T::JobModel>(_conceptPtr);
211  assert(conceptPtr);
212  return conceptPtr->_data;
213  }
214 
215  virtual void run(const ContextPointer& jobContext) {
216  TimeProfiler probe(getName());
217  auto startTime = std::chrono::high_resolution_clock::now();
218  _conceptPtr->run(jobContext);
219  _conceptPtr->setCPURunTime((std::chrono::high_resolution_clock::now() - startTime));
220  }
221 
222 protected:
223  ConceptPointer _conceptPtr;
224 };
225 
226 
227 // A task is a specialized job to run a collection of other jobs
228 // It can be created on any type T by aliasing the type JobModel in the class T
229 // using JobModel = Task::Model<T>
230 // The class T is expected to have a "build" method acting as a constructor.
231 // The build method is where child Jobs can be added internally to the task
232 // where the input of the task can be setup to feed the child jobs
233 // and where the output of the task is defined
234 template <class JC, class TP>
235 class Task : public Job<JC, TP> {
236 public:
237  using Context = JC;
238  using TimeProfiler = TP;
239  using ContextPointer = std::shared_ptr<Context>;
240  using Config = JobConfig; //TaskConfig;
241  using JobType = Job<JC, TP>;
242  using None = typename JobType::None;
243  using Concept = typename JobType::Concept;
244  using ConceptPointer = typename JobType::ConceptPointer;
245  using Jobs = std::vector<JobType>;
246 
247  Task(ConceptPointer conceptPtr) : JobType(conceptPtr) {}
248 
249  class TaskConcept : public Concept {
250  public:
251  Varying _input;
252  Varying _output;
253  Jobs _jobs;
254 
255  const Varying getInput() const override { return _input; }
256  const Varying getOutput() const override { return _output; }
257  Varying& editInput() override { return _input; }
258 
259  TaskConcept(const std::string& name, const Varying& input, QConfigPointer config) : Concept(name, config), _input(input) {config->_isTask = true;}
260 
261  // Create a new job in the container's queue; returns the job's output
262  template <class NT, class... NA> const Varying addJob(std::string name, const Varying& input, NA&&... args) {
263  _jobs.emplace_back((NT::JobModel::create(name, input, std::forward<NA>(args)...)));
264 
265  // Conect the child config to this task's config
266  std::static_pointer_cast<JobConfig>(Concept::getConfiguration())->connectChildConfig(_jobs.back().getConfiguration(), name);
267 
268  return _jobs.back().getOutput();
269  }
270  template <class NT, class... NA> const Varying addJob(std::string name, NA&&... args) {
271  const auto input = Varying(typename NT::JobModel::Input());
272  return addJob<NT>(name, input, std::forward<NA>(args)...);
273  }
274  };
275 
276  template <class T, class C = Config, class I = None, class O = None> class TaskModel : public TaskConcept {
277  public:
278  using Data = T;
279  using Input = I;
280  using Output = O;
281 
282  Data _data;
283 
284  TaskModel(const std::string& name, const Varying& input, QConfigPointer config) :
285  TaskConcept(name, input, config),
286  _data(Data()) {}
287 
288  template <class... A>
289  static std::shared_ptr<TaskModel> create(const std::string& name, const Varying& input, A&&... args) {
290  auto model = std::make_shared<TaskModel>(name, input, std::make_shared<C>());
291 
292  {
293  TimeProfiler probe("build::" + model->getName());
294  model->_data.build(*(model), model->_input, model->_output, std::forward<A>(args)...);
295  }
296 
297  return model;
298  }
299 
300  template <class... A>
301  static std::shared_ptr<TaskModel> create(const std::string& name, A&&... args) {
302  const auto input = Varying(Input());
303  return create(name, input, std::forward<A>(args)...);
304  }
305 
306  void createConfiguration() {
307  // A brand new config
308  auto config = std::make_shared<C>();
309  // Make sure we transfer the former children configs to the new config
310  config->transferChildrenConfigs(Concept::_config);
311  // swap
312  Concept::_config = config;
313  // Capture this
314  Concept::_config->_jobConcept = this;
315  }
316 
317  QConfigPointer& getConfiguration() override {
318  if (!Concept::_config) {
319  createConfiguration();
320  }
321  return Concept::_config;
322  }
323 
324  void applyConfiguration() override {
325  TimeProfiler probe("configure::" + JobConcept::getName());
326  jobConfigure(_data, *std::static_pointer_cast<C>(Concept::_config));
327  for (auto& job : TaskConcept::_jobs) {
328  job.applyConfiguration();
329  }
330  }
331 
332  void run(const ContextPointer& jobContext) override {
333  auto config = std::static_pointer_cast<C>(Concept::_config);
334  if (config->isEnabled()) {
335  for (auto job : TaskConcept::_jobs) {
336  job.run(jobContext);
337  if (jobContext->taskFlow.doAbortTask()) {
338  jobContext->taskFlow.reset();
339  return;
340  }
341  }
342  }
343  }
344  };
345  template <class T, class C = Config> using Model = TaskModel<T, C, None, None>;
346  template <class T, class I, class C = Config> using ModelI = TaskModel<T, C, I, None>;
347  template <class T, class O, class C = Config> using ModelO = TaskModel<T, C, None, O>;
348  template <class T, class I, class O, class C = Config> using ModelIO = TaskModel<T, C, I, O>;
349 
350  // Create a new job in the Task's queue; returns the job's output
351  template <class T, class... A> const Varying addJob(std::string name, const Varying& input, A&&... args) {
352  return std::static_pointer_cast<TaskConcept>(JobType::_conceptPtr)->template addJob<T>(name, input, std::forward<A>(args)...);
353  }
354  template <class T, class... A> const Varying addJob(std::string name, A&&... args) {
355  const auto input = Varying(typename T::JobModel::Input());
356  return std::static_pointer_cast<TaskConcept>(JobType::_conceptPtr)->template addJob<T>(name, input, std::forward<A>(args)...);
357  }
358 
359  std::shared_ptr<Config> getConfiguration() {
360  return std::static_pointer_cast<Config>(JobType::_conceptPtr->getConfiguration());
361  }
362 };
363 
364 
365 // A Switch is a specialized job to run a collection of other jobs and switch between different branches at run time
366 // It can be created on any type T by aliasing the type JobModel in the class T
367 // using JobModel = Switch::Model<T>
368 // The class T is expected to have a "build" method acting as a constructor.
369 // The build method is where child Jobs can be added internally to the branches of the switch
370 // where the input of the switch can be setup to feed the child jobs
371 // and where the output of the switch is defined
372 template <class JC, class TP>
373 class Switch : public Job<JC, TP> {
374 public:
375  using Context = JC;
376  using TimeProfiler = TP;
377  using ContextPointer = std::shared_ptr<Context>;
378  using Config = JobConfig; //SwitchConfig;
379  using JobType = Job<JC, TP>;
380  using None = typename JobType::None;
381  using Concept = typename JobType::Concept;
382  using ConceptPointer = typename JobType::ConceptPointer;
383  using Branches = std::unordered_map<uint8_t, JobType>;
384 
385  Switch(ConceptPointer conceptPtr) : JobType(conceptPtr) {}
386 
387  class SwitchConcept : public Concept {
388  public:
389  Varying _input;
390  Varying _output;
391  Branches _branches;
392 
393  const Varying getInput() const override { return _input; }
394  const Varying getOutput() const override { return _output; }
395  Varying& editInput() override { return _input; }
396 
397  SwitchConcept(const std::string& name, const Varying& input, QConfigPointer config) : Concept(name, config), _input(input)
398  {config->_isTask = true; config->_isSwitch = true; }
399 
400  template <class NT, class... NA> const Varying addBranch(std::string name, uint8_t index, const Varying& input, NA&&... args) {
401  auto& branch = _branches[index];
402  branch = JobType(NT::JobModel::create(name, input, std::forward<NA>(args)...));
403 
404  // Conect the child config to this task's config
405  std::static_pointer_cast<JobConfig>(Concept::getConfiguration())->connectChildConfig(branch.getConfiguration(), name);
406 
407  return branch.getOutput();
408  }
409  template <class NT, class... NA> const Varying addBranch(std::string name, uint8_t index, NA&&... args) {
410  const auto input = Varying(typename NT::JobModel::Input());
411  return addBranch<NT>(name, index, input, std::forward<NA>(args)...);
412  }
413  };
414 
415  template <class T, class C = Config, class I = None, class O = None> class SwitchModel : public SwitchConcept {
416  public:
417  using Data = T;
418  using Input = I;
419  using Output = O;
420 
421  Data _data;
422 
423  SwitchModel(const std::string& name, const Varying& input, QConfigPointer config) :
424  SwitchConcept(name, input, config),
425  _data(Data()) {
426  }
427 
428  template <class... A>
429  static std::shared_ptr<SwitchModel> create(const std::string& name, const Varying& input, A&&... args) {
430  auto model = std::make_shared<SwitchModel>(name, input, std::make_shared<C>());
431 
432  {
433  TimeProfiler probe("build::" + model->getName());
434  model->_data.build(*(model), model->_input, model->_output, std::forward<A>(args)...);
435  }
436 
437  return model;
438  }
439 
440  template <class... A>
441  static std::shared_ptr<SwitchModel> create(const std::string& name, A&&... args) {
442  const auto input = Varying(Input());
443  return create(name, input, std::forward<A>(args)...);
444  }
445 
446  void createConfiguration() {
447  // A brand new config
448  auto config = std::make_shared<C>();
449  // Make sure we transfer the former children configs to the new config
450  config->transferChildrenConfigs(Concept::_config);
451  // swap
452  Concept::_config = config;
453  // Capture this
454  Concept::_config->_jobConcept = this;
455  }
456 
457  QConfigPointer& getConfiguration() override {
458  if (!Concept::_config) {
459  createConfiguration();
460  }
461  return Concept::_config;
462  }
463 
464  void applyConfiguration() override {
465  TimeProfiler probe("configure::" + JobConcept::getName());
466  jobConfigure(_data, *std::static_pointer_cast<C>(Concept::_config));
467  for (auto& branch : SwitchConcept::_branches) {
468  branch.second.applyConfiguration();
469  }
470  }
471 
472  void run(const ContextPointer& jobContext) override {
473  auto config = std::static_pointer_cast<C>(Concept::_config);
474  if (config->isEnabled()) {
475  auto jobsIt = SwitchConcept::_branches.find(config->getBranch());
476  if (jobsIt != SwitchConcept::_branches.end()) {
477  jobsIt->second.run(jobContext);
478  }
479  }
480  }
481  };
482  template <class T, class C = Config> using Model = SwitchModel<T, C, None, None>;
483  template <class T, class I, class C = Config> using ModelI = SwitchModel<T, C, I, None>;
484  // TODO: Switches don't support Outputs yet
485  //template <class T, class O, class C = SwitchConfig> using ModelO = SwitchModel<T, C, None, O>;
486  //template <class T, class I, class O, class C = SwitchConfig> using ModelIO = SwitchModel<T, C, I, O>;
487 
488  // Create a new job in the Switches' branches; returns the job's output
489  template <class T, class... A> const Varying addBranch(std::string name, uint8_t index, const Varying& input, A&&... args) {
490  return std::static_pointer_cast<SwitchConcept>(JobType::_conceptPtr)->template addBranch<T>(name, index, input, std::forward<A>(args)...);
491  }
492  template <class T, class... A> const Varying addBranch(std::string name, uint8_t index, A&&... args) {
493  const auto input = Varying(typename T::JobModel::Input());
494  return std::static_pointer_cast<SwitchConcept>(JobType::_conceptPtr)->template addBranch<T>(name, index, input, std::forward<A>(args)...);
495  }
496 
497  std::shared_ptr<Config> getConfiguration() {
498  return std::static_pointer_cast<Config>(JobType::_conceptPtr->getConfiguration());
499  }
500 };
501 
502 template <class JC, class TP>
503 class Engine : public Task<JC, TP> {
504 public:
505  using Context = JC;
506  using ContextPointer = std::shared_ptr<Context>;
507  using Config = JobConfig; //TaskConfig;
508 
509  using TaskType = Task<JC, TP>;
510  using ConceptPointer = typename TaskType::ConceptPointer;
511 
512  Engine(const ConceptPointer& conceptPtr, const ContextPointer& context) : TaskType(conceptPtr), _context(context) {}
513  ~Engine() = default;
514 
515  void reset(const ContextPointer& context) { _context = context; }
516 
517  void run() {
518  if (_context) {
519  run(_context);
520  }
521  }
522 
523 protected:
524  void run(const ContextPointer& jobContext) override {
525  TaskType::run(_context);
526  }
527 
528  ContextPointer _context;
529 };
530 
531 }
532 
533 
534 #define Task_DeclareTypeAliases(ContextType, TimeProfiler) \
535  using JobConfig = task::JobConfig; \
536  using TaskConfig = task::JobConfig; \
537  using SwitchConfig = task::JobConfig; \
538  template <class T> using PersistentConfig = task::PersistentConfig<T>; \
539  using Job = task::Job<ContextType, TimeProfiler>; \
540  using Switch = task::Switch<ContextType, TimeProfiler>; \
541  using Task = task::Task<ContextType, TimeProfiler>; \
542  using Engine = task::Engine<ContextType, TimeProfiler>; \
543  using Varying = task::Varying; \
544  template < typename T0, typename T1 > using VaryingSet2 = task::VaryingSet2<T0, T1>; \
545  template < typename T0, typename T1, typename T2 > using VaryingSet3 = task::VaryingSet3<T0, T1, T2>; \
546  template < typename T0, typename T1, typename T2, typename T3 > using VaryingSet4 = task::VaryingSet4<T0, T1, T2, T3>; \
547  template < typename T0, typename T1, typename T2, typename T3, typename T4 > using VaryingSet5 = task::VaryingSet5<T0, T1, T2, T3, T4>; \
548  template < typename T0, typename T1, typename T2, typename T3, typename T4, typename T5 > using VaryingSet6 = task::VaryingSet6<T0, T1, T2, T3, T4, T5>; \
549  template < typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6 > using VaryingSet7 = task::VaryingSet7<T0, T1, T2, T3, T4, T5, T6>; \
550  template < typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7 > using VaryingSet8 = task::VaryingSet8<T0, T1, T2, T3, T4, T5, T6, T7>; \
551  template < typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8 > using VaryingSet9 = task::VaryingSet9<T0, T1, T2, T3, T4, T5, T6, T7, T8>; \
552  template < typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9 > using VaryingSet10 = task::VaryingSet10<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>; \
553  template < class T, int NUM > using VaryingArray = task::VaryingArray<T, NUM>;
554 
555 
556 
557 #include <Profile.h>
558 #include <PerfStat.h>
559 
560 #define Task_DeclareCategoryTimeProfilerClass(className, category) \
561  class className : public PerformanceTimer { \
562  public: \
563  className(const std::string& label) : PerformanceTimer(label.c_str()), profileRange(category(), label.c_str()) {} \
564  ProfileDuration profileRange; \
565  };
566 
567 #endif // hifi_task_Task_h
A generic 3D model displaying geometry loaded from a URL.
Definition: Model.h:84