Lumiera  0.pre.03
»edit your freedom«
job-planning-pipeline-test.cpp
Go to the documentation of this file.
1 /*
2  JobPlanningPipeline(Test) - structure and setup of the job-planning pipeline
3 
4  Copyright (C)
5  2023, Hermann Vosseler <Ichthyostega@web.de>
6 
7   **Lumiera** is free software; you can redistribute it and/or modify it
8   under the terms of the GNU General Public License as published by the
9   Free Software Foundation; either version 2 of the License, or (at your
10   option) any later version. See the file COPYING for further details.
11 
12 * *****************************************************************/
13 
19 #include "lib/test/run.hpp"
20 #include "lib/test/test-helper.hpp"
22 
23 #include "lib/iter-explorer.hpp"
24 #include "lib/format-string.hpp"
25 #include "lib/format-util.hpp"
26 #include "lib/util.hpp"
27 
28 
29 using test::Test;
30 using lib::eachNum;
31 using lib::explore;
32 using lib::time::PQuant;
34 using util::isnil;
35 using util::_Fmt;
36 
37 
38 namespace steam {
39 namespace engine{
40 namespace test {
41 
43 
44  namespace { // test fixture...
45 
47  template<class II>
48  inline string
49  materialise (II&& ii)
50  {
51  return util::join (std::forward<II> (ii), "-");
52  }
53 
54  inline PQuant
55  frameGrid (FrameRate fps)
56  {
57  return PQuant (new FixedFrameQuantiser (fps));
58  }
59 
60  } // (End) test fixture
61 
62 
63 
64  /****************************************************************************/
81  {
82 
83  virtual void
84  run (Arg)
85  {
86  seedRand();
91  integration();
92  }
93 
94 
96  void
98  {
99  Time nominalTime = lib::test::randTime();
100  int additionalKey = rani(5000);
101 
102  // (1) mocked render Job
103  MockJob mockJob{nominalTime, additionalKey};
104  mockJob.triggerJob();
105  CHECK (MockJob::was_invoked (mockJob));
106  CHECK (RealClock::wasRecently (MockJob::invocationTime (mockJob)));
107  CHECK (nominalTime == MockJob::invocationNominalTime (mockJob) );
108  CHECK (additionalKey == MockJob::invocationAdditionalKey(mockJob));
109 
110  // (2) Build a mocked Segment at [10s ... 20s[
111  MockSegmentation mockSegs{MakeRec()
112  .attrib ("start", Time{0,10} // start time (inclusive) of the Segment at 10sec
113  ,"after", Time{0,20} // the Segment ends *before* 20sec
114  ,"mark", 123) // marker-ID 123 (can be verified from Job invocation)
115  .scope(MakeRec() // this JobTicket also defines a prerequisite ticket
116  .attrib("mark",555) // using a different marker-ID 555
117  .genNode()
118  )
119  .genNode()};
120  fixture::Segment const& seg = mockSegs[Time{0,15}]; // access anywhere 10s <= t < 20s
121  JobTicket& ticket = seg.jobTicket(0); // get the master-JobTicket from this segment
122  JobTicket& prereq = *(ticket.getPrerequisites()); // pull a prerequisite JobTicket
123 
124  Job jobP = prereq.createJobFor(Time{0,15}); // create an instance of the prerequisites for some time(irrelevant)
125  Job jobM = ticket.createJobFor(Time{0,15}); // ...and an instance of the master job for the same time
126  CHECK (MockJobTicket::isAssociated (jobP, prereq));
127  CHECK (MockJobTicket::isAssociated (jobM, ticket));
128  CHECK (not MockJobTicket::isAssociated (jobP, ticket));
129  CHECK (not MockJobTicket::isAssociated (jobM, prereq));
130 
131  jobP.triggerJob();
132  jobM.triggerJob();
133  CHECK (123 == MockJob::invocationAdditionalKey (jobM)); // verify each job was invoked and linked to the correct spec,
134  CHECK (555 == MockJob::invocationAdditionalKey (jobP)); // indicating that in practice it will activate the proper render node
135 
136  // (3) demonstrate mocked frame dispatcher...
137  MockDispatcher dispatcher; // a complete dispatcher backed by a mock Segment for the whole timeline
138  auto [port1,sink1] = dispatcher.getDummyConnection(1); // also some fake ModelPort and DataSink entries are registered
139  Job jobD = dispatcher.createJobFor (1, Time{0,30});
140  CHECK (dispatcher.verify(jobD, port1, sink1)); // the generated job uses the associated ModelPort and DataSink and JobTicket
141  }
142 
143 
144 
151  void
153  {
154  auto grid = frameGrid(FrameRate::PAL); // one frame ≙ 40ms
155 
156  CHECK (materialise(
157  explore (eachNum(5,13))
158  .transform([&](FrameCnt frameNr)
159  {
160  return grid->timeOf (frameNr);
161  })
162  )
163  == "200ms-240ms-280ms-320ms-360ms-400ms-440ms-480ms"_expect);
164 
165 
166  MockDispatcher dispatcher;
167  play::Timings timings (FrameRate::PAL);
168 
169  CHECK (materialise (
170  dispatcher.forCalcStream(timings)
171  .timeRange(Time{200,0}, Time{500,0}) // Note: end point is exclusive
172  )
173  == "200ms-240ms-280ms-320ms-360ms-400ms-440ms-480ms"_expect);
174  }
175 
176 
180  void
182  {
183  MockDispatcher dispatcher;
184 
185  play::Timings timings (FrameRate::PAL);
186  auto [port,sink] = dispatcher.getDummyConnection(0);
187  auto pipeline = dispatcher.forCalcStream (timings)
188  .timeRange(Time{200,0}, Time{300,0})
189  .pullFrom (port);
190 
191  CHECK (not isnil (pipeline));
192  CHECK (pipeline->isTopLevel()); // is a top-level ticket
193  JobTicket& ticket = pipeline->ticket();
194 
195  Job job = ticket.createJobFor(Time::ZERO); // actual time point is irrelevant here
196  CHECK (dispatcher.verify(job, port, sink));
197  }
198 
199 
211  void
213  {
214  MockDispatcher dispatcher{MakeRec() // define a single segment for the complete time axis
215  .attrib("mark", 11) // the »master job« for each frame has pipeline-ID ≔ 11
216  .scope(MakeRec()
217  .attrib("mark",22) // add a »prerequisite job« marked with pipeline-ID ≔ 22
218  .genNode())
219  .genNode()};
220 
221  play::Timings timings (FrameRate::PAL);
222  auto [port,sink] = dispatcher.getDummyConnection(0);
223  auto pipeline = dispatcher.forCalcStream (timings)
224  .timeRange(Time{200,0}, Time{300,0})
225  .pullFrom (port)
226  .expandPrerequisites();
227 
228  // the first element is identical to previous test
229  CHECK (not isnil (pipeline));
230  CHECK (pipeline->isTopLevel());
231  Job job = pipeline->ticket().createJobFor (Time::ZERO);
232  CHECK (11 == job.parameter.invoKey.part.a);
233 
234  auto visualise = [](auto& pipeline) -> string
235  {
236  Time frame{pipeline.currPoint}; // can access the embedded PipeFrameTick core to get "currPoint" (nominal time)
237  Job job = pipeline->ticket().createJobFor(frame); // looking always at the second element, which is the current JobTicket
238  TimeValue nominalTime{job.parameter.nominalTime}; // job parameter holds the microseconds (gavl_time_t)
239  int32_t mark = job.parameter.invoKey.part.a; // the MockDispatcher places the given "mark" here
240  return _Fmt{"J(%d|%s)"} % mark % nominalTime;
241  };
242  CHECK (visualise(pipeline) == "J(11|200ms)"_expect); // first job in pipeline is at t=200ms and has mark=11 (it's the master Job for this frame)
243 
244  CHECK (materialise (pipeline.transform (visualise))
245  == "J(11|200ms)-J(22|200ms)-J(11|240ms)-J(22|240ms)-J(11|280ms)-J(22|280ms)"_expect);
246  }
247 
248 
249 
258  void
260  {
261  MockDispatcher dispatcher{MakeRec() // start with defining a first segment...
262  .attrib("mark", 11) // the »master job« for each frame has pipeline-ID ≔ 11
263  .attrib("runtime", Duration{Time{10,0}})
264  .scope(MakeRec()
265  .attrib("mark",22) // a »prerequisite job« marked with pipeline-ID ≔ 22
266  .attrib("runtime", Duration{Time{20,0}})
267  .scope(MakeRec()
268  .attrib("mark",33) // further »recursive prerequisite«
269  .attrib("runtime", Duration{Time{30,0}})
270  .genNode())
271  .genNode())
272  .genNode()
273  ,MakeRec() // add a second Segment with different calculation structure
274  .attrib("start", Time{250,0}) // partitioning the timeline at 250ms
275  .attrib("mark", 44)
276  .attrib("runtime", Duration{Time{70,0}})
277  .scope(MakeRec() // on 2nd level we have two independent prerequisites here
278  .attrib("mark", 55) // ...both will line up before the deadline of ticket No.44
279  .attrib("runtime", Duration{Time{60,0}})
280  .genNode()
281  ,MakeRec()
282  .attrib("mark", 66)
283  .attrib("runtime", Duration{Time{50,0}})
284  .genNode())
285  .genNode()};
286 
287 
288  play::Timings timings (FrameRate::PAL, Time{0,1}); // Timings anchored at wall-clock-time ≙ 1s
289  auto [port,sink] = dispatcher.getDummyConnection(0);
290  auto pipeline = dispatcher.forCalcStream (timings)
291  .timeRange(Time{200,0}, Time{300,0})
292  .pullFrom (port)
293  .expandPrerequisites()
294  .feedTo (sink);
295 
296  // this is the complete job-planning pipeline now
297  // and it is wrapped into a Dispatcher::PlanningPipeline front-end
298  CHECK (not isnil (pipeline));
299  CHECK (pipeline->isTopLevel());
300  // Invoking convenience functions on the PlanningPipeline front-end...
301  CHECK (5 == pipeline.currFrameNr());
302  CHECK (not pipeline.isBefore (Time{200,0}));
303  CHECK ( pipeline.isBefore (Time{220,0}));
304 
305  Job job = pipeline.buildJob(); // invoke the JobPlanning to build a Job for the first frame
306  CHECK (Time(200,0) == job.parameter.nominalTime);
307  CHECK (11 == job.parameter.invoKey.part.a);
308 
309  auto visualise = [](auto& pipeline) -> string
310  {
311  Job job = pipeline.buildJob(); // let the JobPlanning construct the »current job«
312  TimeValue nominalTime{job.parameter.nominalTime}; // job parameter holds the microseconds (gavl_time_t)
313  int32_t mark = job.parameter.invoKey.part.a; // the MockDispatcher places the given "mark" here
314  TimeValue deadline{pipeline.determineDeadline()};
315  return _Fmt{"J(%d|%s⧐%s)"}
316  % mark % nominalTime % deadline;
317  };
318  CHECK (visualise(pipeline) == "J(11|200ms⧐1s180ms)"_expect); // first job in pipeline: nominal t=200ms,
319  // .... 10ms engine latency + 10ms job runtime ⟶ deadline 1s180ms
320  CHECK (materialise(
321  explore(move(pipeline))
322  .transform(visualise)
323  )
324  == "J(11|200ms⧐1s180ms)-J(22|200ms⧐1s150ms)-J(33|200ms⧐1s110ms)-" // ... -(10+10) | -(10+10)-(10+20) | -(10+10)-(10+20)-(10+30)
325  "J(11|240ms⧐1s220ms)-J(22|240ms⧐1s190ms)-J(33|240ms⧐1s150ms)-"
326  "J(44|280ms⧐1s200ms)-J(66|280ms⧐1s140ms)-J(55|280ms⧐1s130ms)"_expect); // ... these call into the 2nd Segment
327  }
328  };
329 
330 
332  LAUNCHER (JobPlanningPipeline_test, "unit engine");
333 
334 
335 
336 }}} // namespace steam::engine::test
string materialise(II &&ii)
Diagnostic helper: join all the elements from some given container or iterable.
Mock data structures to support implementation testing of render job planning and frame dispatch...
auto explore(IT &&srcSeq)
start building a IterExplorer by suitably wrapping the given iterable source.
Definition: run.hpp:40
Framerate specified as frames per second.
Definition: timevalue.hpp:655
Generic frame timing specification.
Definition: timings.hpp:86
A mocked frame Dispatcher setup without any backing model.
Front-end for printf-style string template interpolation.
int rani(uint bound=_iBOUND())
Definition: random.hpp:135
engine::JobTicket & jobTicket(size_t portNr) const
Access the JobTicket for this segment and the given portNr.
Definition: segment.hpp:122
Mock setup for a render Job with NO action but built-in diagnostics.
play::test::DummyOutputLink getDummyConnection(uint index)
The faked builder/playback setup provides some preconfigured ModelPort and corresponding DataSink han...
Steam-Layer implementation namespace root.
A front-end for using printf-style formatting.
Lumiera&#39;s internal time value datatype.
Definition: timevalue.hpp:299
Abstract Base Class for all testcases.
Definition: run.hpp:53
PipelineBuilder< PipeFrameTick > forCalcStream(Timings timings)
Start a builder sequence to assemble a job-planning pipeline, backed by this Dispatcher.
Definition: dispatcher.hpp:344
bool verify(Job const &job, ModelPort const &port, play::DataSink const &sink)
Test support: verify the given Job is consistent with this Dispatcher.
Simplistic test class runner.
void seedRand()
draw a new random seed from a common nucleus, and re-seed the default-Gen.
Definition: suite.cpp:211
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
Mock setup for a complete Segmentation to emulate the structure of the actual fixture, without the need of building a low-level Model.
A collection of frequently used helper functions to support unit testing.
static bool isAssociated(Job const &, JobTicket const &)
convenience shortcut to perform this test on arbitrary JobTicket and Job instances.
For the purpose of building and rendering, the fixture (for each timeline) is partitioned such that e...
Definition: segment.hpp:59
Collection of small helpers and convenience shortcuts for diagnostics & formatting.
Duration is the internal Lumiera time metric.
Definition: timevalue.hpp:468
Job createJobFor(size_t portIDX, TimeValue nominalTime)
Convenience shortcut for tests: JobTicket ⟼ Job.
Definition: dispatcher.hpp:350
Building tree expanding and backtracking evaluations within hierarchical scopes.
Individual frame rendering task, forwarding to a closure.
Definition: job.h:268
basic constant internal time value.
Definition: timevalue.hpp:133
auto getPrerequisites()
Core operation: iterate over the prerequisites, required to carry out a render operation based on thi...
Definition: job-ticket.hpp:155
Simple stand-alone Quantiser implementation based on a constant sized gird.
Definition: quantiser.hpp:135
static const FrameRate PAL
predefined constant for PAL framerate
Definition: timevalue.hpp:671
execution plan for pulling a specific exit node.
Definition: job-ticket.hpp:78
NumIter< INT > eachNum(INT start=std::numeric_limits< INT >::min(), INT end=std::numeric_limits< INT >::max())
convenience function to iterate "each number"
Job createJobFor(Time nominalTime)
Core operation: build a concrete render job based on this blueprint.
Definition: job-ticket.cpp:71