2007-09-03 02:33:47 +02:00
|
|
|
|
/*
|
2024-12-06 23:43:18 +01:00
|
|
|
|
NodeOpera(Test) - verify proper render node operation modes
|
2010-12-17 23:28:49 +01:00
|
|
|
|
|
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
|
|
|
|
Copyright (C)
|
2024-12-06 23:43:18 +01:00
|
|
|
|
2024, Hermann Vosseler <Ichthyostega@web.de>
|
2010-12-17 23:28:49 +01:00
|
|
|
|
|
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
|
|
|
|
**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.
|
2010-12-17 23:28:49 +01:00
|
|
|
|
|
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
|
|
|
|
* *****************************************************************/
|
2007-09-03 02:33:47 +02:00
|
|
|
|
|
2024-12-06 23:43:18 +01:00
|
|
|
|
/** @file node-opera-test.cpp
|
|
|
|
|
|
** Let the nodes sing with \ref NodeOpera_test.
|
2016-11-03 18:20:10 +01:00
|
|
|
|
*/
|
|
|
|
|
|
|
2007-09-03 02:33:47 +02:00
|
|
|
|
|
2026-03-06 21:34:20 +01:00
|
|
|
|
#include "test/run.hpp"
|
2026-06-25 19:36:45 +02:00
|
|
|
|
#include "lib/allocation-cluster.hpp"
|
|
|
|
|
|
#include "test/test-rand-ontology.hpp"
|
2026-06-27 23:51:41 +02:00
|
|
|
|
#include "vault/out/diagnostic-output-slot.hpp"
|
2026-06-24 23:48:15 +02:00
|
|
|
|
#include "vault/mem/buffer-provider-setup.hpp"
|
|
|
|
|
|
#include "vault/mem/engine-buffer-metadata.hpp"
|
|
|
|
|
|
#include "vault/mem/engine-buffer-manager.hpp"
|
|
|
|
|
|
#include "vault/mem/local-buffer-stage.hpp"
|
|
|
|
|
|
#include "vault/mem/local-buffer-store.hpp"
|
|
|
|
|
|
#include "steam/engine/engine-facilities.hpp"
|
|
|
|
|
|
#include "steam/engine/node-builder.hpp"
|
2026-06-25 19:36:45 +02:00
|
|
|
|
#include "steam/asset/meta/time-grid.hpp"
|
|
|
|
|
|
#include "lib/time/timequant.hpp"
|
|
|
|
|
|
#include "lib/time/timecode.hpp"
|
2026-06-24 23:48:15 +02:00
|
|
|
|
#include "lib/depend-inject.hpp"
|
|
|
|
|
|
#include "lib/thread.hpp"
|
2026-06-25 19:36:45 +02:00
|
|
|
|
//#include "lib/symbol.hpp"
|
2026-06-26 19:25:25 +02:00
|
|
|
|
#include "lib/util.hpp"
|
2026-06-24 23:48:15 +02:00
|
|
|
|
#include "test/diagnostic-output.hpp"///////////////TODO
|
2007-09-03 02:33:47 +02:00
|
|
|
|
|
|
|
|
|
|
|
2016-01-07 03:58:29 +01:00
|
|
|
|
//using std::string;
|
2026-06-24 23:48:15 +02:00
|
|
|
|
using lib::Thread;
|
|
|
|
|
|
using std::this_thread::yield;
|
|
|
|
|
|
using std::this_thread::sleep_for;
|
|
|
|
|
|
using std::chrono_literals::operator ""us;
|
2007-09-03 02:33:47 +02:00
|
|
|
|
|
2026-06-25 19:36:45 +02:00
|
|
|
|
using lib::time::Time;
|
|
|
|
|
|
using lib::time::QuTime;
|
|
|
|
|
|
using lib::time::FrameNr;
|
|
|
|
|
|
using lib::time::FrameCnt;
|
|
|
|
|
|
using lib::time::FrameRate;
|
|
|
|
|
|
using lib::time::PGrid;
|
|
|
|
|
|
using steam::asset::meta::TimeGrid;
|
2026-06-27 23:51:41 +02:00
|
|
|
|
using vault::out::DiagnosticOutputSlot;
|
2026-06-25 19:36:45 +02:00
|
|
|
|
//using lib::Symbol;
|
|
|
|
|
|
//using util::isnil;
|
|
|
|
|
|
//using util::isSameObject;
|
2026-06-26 19:25:25 +02:00
|
|
|
|
using util::isLimited;
|
2026-06-25 19:36:45 +02:00
|
|
|
|
using test::TestFrame;
|
|
|
|
|
|
using test::testRand;
|
|
|
|
|
|
namespace ont = test::ont;
|
2007-09-03 02:33:47 +02:00
|
|
|
|
|
2018-11-15 23:55:13 +01:00
|
|
|
|
namespace steam {
|
2009-08-31 00:49:08 +02:00
|
|
|
|
namespace engine{
|
|
|
|
|
|
namespace test {
|
|
|
|
|
|
|
2026-06-24 23:48:15 +02:00
|
|
|
|
namespace { // ========== Test Setup ==========
|
|
|
|
|
|
|
|
|
|
|
|
using vault::mem::BufferProviderSetup;
|
|
|
|
|
|
using vault::mem::LocalBufferStage;
|
|
|
|
|
|
using vault::mem::LocalBufferStore;
|
|
|
|
|
|
|
|
|
|
|
|
using vault::mem::EngineBufferMetadata;
|
|
|
|
|
|
using vault::mem::EngineBufferManager;
|
|
|
|
|
|
|
|
|
|
|
|
class TestEngineBufferSetup
|
|
|
|
|
|
: public BufferProviderSetup
|
|
|
|
|
|
, public EngineFacilities
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
TestEngineBufferSetup()
|
|
|
|
|
|
: BufferProviderSetup{*this}
|
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
auto buildStage() { return std::make_unique<LocalBufferStage>("NodeOpera_test"); }
|
|
|
|
|
|
auto buildStore() { return std::make_unique<LocalBufferStore>(); }
|
|
|
|
|
|
|
|
|
|
|
|
BufferProvider& setupBufferProvider() override { return *this; }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct TestEngineCtx
|
|
|
|
|
|
{
|
|
|
|
|
|
// install mock instances for the relevant services
|
|
|
|
|
|
lib::DependInject<EngineBufferMetadata>::Local<> metaHub;
|
|
|
|
|
|
lib::DependInject<EngineBufferManager>::Local<> globalPool;
|
|
|
|
|
|
|
|
|
|
|
|
// install a rigged access front-end for the buffer management
|
|
|
|
|
|
lib::DependInject<EngineFacilities>::Local<TestEngineBufferSetup> bufferSetup;
|
|
|
|
|
|
lib::DependInject<EngineCtx>::Local<EngineCtx> frontEndCtx;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2009-08-31 00:49:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-10-24 23:06:36 +02:00
|
|
|
|
/***************************************************************//**
|
2009-08-31 00:49:08 +02:00
|
|
|
|
* @test check render node operation modes and collaboration.
|
2026-06-23 23:18:08 +02:00
|
|
|
|
* @todo 6/2026 this test shell demonstrate all relevant operation modes
|
|
|
|
|
|
* of render nodes, at the level of an component-integration-test.
|
|
|
|
|
|
* It is not clear yet what further operation modes this entails,
|
|
|
|
|
|
* beyond (obviously) invoking a render job... ////////////////////////////////////////////////TICKET #1367 : build a node invocation
|
|
|
|
|
|
* @remark the purpose of this test is to accomplish component integration
|
|
|
|
|
|
* regarding relevant usages of the render node network, seen from the
|
|
|
|
|
|
* perspective of an (exit) render node that is used / invoked / pulled.
|
2009-08-31 00:49:08 +02:00
|
|
|
|
*/
|
2024-12-06 23:43:18 +01:00
|
|
|
|
class NodeOpera_test : public Test
|
2007-09-03 02:33:47 +02:00
|
|
|
|
{
|
2026-06-23 23:18:08 +02:00
|
|
|
|
virtual void
|
|
|
|
|
|
run (Arg)
|
2009-08-31 00:49:08 +02:00
|
|
|
|
{
|
2026-06-24 23:48:15 +02:00
|
|
|
|
seedRand();
|
|
|
|
|
|
|
|
|
|
|
|
invokeSimpleNode();
|
2026-06-25 19:36:45 +02:00
|
|
|
|
invokeNodeTree();
|
2026-06-24 23:48:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @test directly invoke a simple source node.
|
|
|
|
|
|
* - use the node builder to create a simple node
|
|
|
|
|
|
* immediately into the current stack frame
|
|
|
|
|
|
* - the processing function feeds a fixed random number
|
|
|
|
|
|
* into the given output buffer
|
|
|
|
|
|
* - then launch a worker thread to pull this node
|
|
|
|
|
|
* - create an output buffer directly from the worker thread
|
|
|
|
|
|
* - invoke the single port of the node, which is visible
|
|
|
|
|
|
* since it was created before starting the local worker thread
|
|
|
|
|
|
* - after the invocation, the fixed random number should have been
|
|
|
|
|
|
* placed into the output buffer
|
|
|
|
|
|
* - after the worker thread has finished, a single buffer allocation
|
|
|
|
|
|
* should have happened; this allocation was sent back automatically
|
|
|
|
|
|
* and can be claimed and released by the global buffer manager.
|
|
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
invokeSimpleNode()
|
|
|
|
|
|
{
|
|
|
|
|
|
int randNum{rani()};
|
|
|
|
|
|
auto fun = [&](int* buff){ *buff = randNum; };
|
|
|
|
|
|
|
|
|
|
|
|
TestEngineCtx mockEngine;
|
|
|
|
|
|
|
|
|
|
|
|
ProcNode node{prepareNode("Test")
|
|
|
|
|
|
.preparePort()
|
|
|
|
|
|
.invoke("fun()", fun)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.build()};
|
|
|
|
|
|
|
|
|
|
|
|
Thread testWorker{[&] /* === directly perform a node invocation === */
|
|
|
|
|
|
{
|
|
|
|
|
|
BuffHandle buff = EngineCtx::access().mem.lockBufferFor<int> (-55);
|
|
|
|
|
|
Time nomTime{Time::ZERO};
|
|
|
|
|
|
ProcessKey key{0};
|
|
|
|
|
|
uint port{0};
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (-55 == buff.accessAs<int>());
|
|
|
|
|
|
|
|
|
|
|
|
// Trigger Node invocation...
|
|
|
|
|
|
buff = node.pull (port, buff, nomTime, key);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (randNum == buff.accessAs<int>());
|
|
|
|
|
|
buff.release();
|
|
|
|
|
|
}};
|
|
|
|
|
|
|
|
|
|
|
|
while (testWorker)
|
|
|
|
|
|
yield(); // wait for worker to finish
|
|
|
|
|
|
|
|
|
|
|
|
// the central services were indeed requested / created
|
|
|
|
|
|
CHECK (mockEngine.bufferSetup);
|
|
|
|
|
|
CHECK (mockEngine.globalPool);
|
|
|
|
|
|
CHECK (mockEngine.metaHub);
|
|
|
|
|
|
|
|
|
|
|
|
// at least two buffer types registered (one generic for int)
|
|
|
|
|
|
CHECK (2 <= mockEngine.metaHub->cntEntries());
|
|
|
|
|
|
|
|
|
|
|
|
// one buffer was requested...
|
|
|
|
|
|
CHECK (1 == watch(*mockEngine.globalPool).numAllocs());
|
|
|
|
|
|
|
|
|
|
|
|
sleep_for (500us); // additional delay for the local pool's destructor
|
|
|
|
|
|
// (which is invoked after the thread has terminated)
|
|
|
|
|
|
|
|
|
|
|
|
// process and close remaining allocations
|
|
|
|
|
|
// sent back by the local pool's destructor...
|
|
|
|
|
|
mockEngine.globalPool->processPendingRequests();
|
|
|
|
|
|
// now there should be no memory leaks.......
|
|
|
|
|
|
CHECK (0 == watch(*mockEngine.globalPool).bytesLeased());
|
2025-06-07 23:59:57 +02:00
|
|
|
|
}
|
2026-06-25 19:36:45 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @test perform a RenderInvocation to pull from a complex node tree into an output sink.
|
|
|
|
|
|
* - use a test helper class to build a node topology similar to NodeLink_test
|
|
|
|
|
|
* - the ProcNodes and all backing structures are managed by an AllocationCluster
|
2026-06-27 23:51:41 +02:00
|
|
|
|
* - use a DiagnosticOutputSlot (by default configured for buffers sized for TestFrame)
|
2026-06-25 19:36:45 +02:00
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
invokeNodeTree()
|
|
|
|
|
|
{
|
|
|
|
|
|
TestEngineCtx mockEngine;
|
|
|
|
|
|
RenderPipeline pipeline;
|
|
|
|
|
|
|
2026-06-27 23:51:41 +02:00
|
|
|
|
DiagnosticOutputSlot oSlot;
|
|
|
|
|
|
auto sink0 = * oSlot.getOpenedSinks();
|
|
|
|
|
|
|
|
|
|
|
|
const uint OUTPUT_FRAME{123};
|
|
|
|
|
|
|
2026-06-25 19:36:45 +02:00
|
|
|
|
Thread testWorker{[&] /* === perform a render invocation === */
|
|
|
|
|
|
{
|
2026-06-26 00:37:11 +02:00
|
|
|
|
Time nomTime{rani(60'000),0}; // drive test with a random »nominal Time« <60s with ms granularity
|
2026-06-25 19:36:45 +02:00
|
|
|
|
ProcessKey key{0};
|
|
|
|
|
|
uint port{0};
|
|
|
|
|
|
|
2026-06-27 23:51:41 +02:00
|
|
|
|
BuffHandle buff = sink0.lockFrame (OUTPUT_FRAME); // claim output buffer
|
|
|
|
|
|
CHECK (not buff.accessAs<TestFrame>().isValid()); // underlying buffer is zero initialised, thus no valid TestFrame yet
|
2026-06-25 19:36:45 +02:00
|
|
|
|
|
|
|
|
|
|
// Trigger Node invocation...
|
|
|
|
|
|
buff = pipeline.exitNode.pull (port, buff, nomTime, key);
|
2026-06-27 23:51:41 +02:00
|
|
|
|
buff.release();
|
2026-06-25 19:36:45 +02:00
|
|
|
|
|
2026-06-27 23:51:41 +02:00
|
|
|
|
// DiagnosticOutputSlot allows to investigate the data sent into the DataSink...
|
|
|
|
|
|
TestFrame const& result = watch(oSlot).feed(0).frame(OUTPUT_FRAME).accessAs<TestFrame>();
|
|
|
|
|
|
CHECK (result.isValid()); // data matches checksum
|
|
|
|
|
|
CHECK (not result.isPristine()); // is not a source frame (but processed)
|
2026-06-26 00:37:11 +02:00
|
|
|
|
CHECK (result.getChecksum() == pipeline.expectedChecksum (nomTime));
|
2026-06-25 19:36:45 +02:00
|
|
|
|
}};
|
|
|
|
|
|
|
|
|
|
|
|
while (testWorker)
|
2026-06-26 00:37:11 +02:00
|
|
|
|
yield(); // wait for worker to finish
|
2026-06-25 19:36:45 +02:00
|
|
|
|
sleep_for (500us);
|
|
|
|
|
|
|
|
|
|
|
|
mockEngine.globalPool->processPendingRequests();
|
|
|
|
|
|
// now there should be no memory leaks.......
|
|
|
|
|
|
CHECK (0 == watch(*mockEngine.globalPool).bytesLeased());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Helper context to build and manage a processing chain of Render Nodes.
|
|
|
|
|
|
* In the real Render Engine, these processing chains are generated by the Builder,
|
|
|
|
|
|
* and organised into »Segments« of the timeline, where each segment features a
|
|
|
|
|
|
* distinct node connectivity (i.e. typically for one clip).
|
|
|
|
|
|
* For this test, a single top-level _exit node_ is exposed.
|
|
|
|
|
|
* @note this helper context is constructed in _RAII style_,
|
|
|
|
|
|
* and the embedded AllocationCluster unwinds automatically.
|
|
|
|
|
|
* @see \ref NodeLink_test for a detailed breakdown of a similar topology
|
|
|
|
|
|
*/
|
2026-06-26 00:37:11 +02:00
|
|
|
|
class RenderPipeline
|
2026-06-25 19:36:45 +02:00
|
|
|
|
: util::NonCopyable
|
|
|
|
|
|
{
|
|
|
|
|
|
static constexpr ont::Flavr SRC_A = 10; ///< »chain-A« arbitrary source frame marker
|
|
|
|
|
|
static constexpr ont::Flavr SRC_B = 20; ///< similar for »chain-B«
|
2026-06-26 00:37:11 +02:00
|
|
|
|
|
|
|
|
|
|
PGrid secondsGrid_{TimeGrid::build (FrameRate{1})};
|
|
|
|
|
|
FrameCnt quantSecs (Time time) { return secondsGrid_->gridPoint (time); };
|
|
|
|
|
|
|
|
|
|
|
|
/** custom allocator for this node tree */
|
|
|
|
|
|
lib::AllocationCluster alloc_;
|
|
|
|
|
|
|
|
|
|
|
|
public:
|
2026-06-25 19:36:45 +02:00
|
|
|
|
ProcNode& exitNode{buildTree()};
|
|
|
|
|
|
|
|
|
|
|
|
HashVal
|
|
|
|
|
|
expectedChecksum (Time nomTime)
|
|
|
|
|
|
{
|
2026-06-26 00:37:11 +02:00
|
|
|
|
ont::FraNo fraNo = quantSecs(nomTime);
|
|
|
|
|
|
ont::Param param = stepFilter(fraNo);
|
|
|
|
|
|
ont::Factr mix = stepMixer (fraNo);
|
|
|
|
|
|
|
|
|
|
|
|
TestFrame f1{uint(fraNo),SRC_A};
|
|
|
|
|
|
TestFrame f2{uint(fraNo),SRC_B};
|
|
|
|
|
|
|
|
|
|
|
|
ont::manipulateFrame (&f1, &f1, param);
|
|
|
|
|
|
ont::combineFrames (&f1, &f1, &f2, mix);
|
|
|
|
|
|
return f1.getChecksum();
|
2026-06-25 19:36:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
ont::Param stepFilter(FrameCnt id) { return util::limited (10, -10 + id, 50); };
|
|
|
|
|
|
ont::Factr stepMixer (FrameCnt id) { return util::limited (0, + id, 50) / 50.0; };
|
|
|
|
|
|
|
|
|
|
|
|
/** @internal wire the processing node topology.
|
|
|
|
|
|
* This fake "render pipeline" is based on hash chaining computations,
|
|
|
|
|
|
* operating on TestFrame (with reproducible random data content).
|
|
|
|
|
|
* Two processing chains are built, each producing test frames;
|
|
|
|
|
|
* the first chain even adds a "filter". Some "parameters" are added
|
|
|
|
|
|
* to change the random data in a deterministic way. Finally, both chains
|
|
|
|
|
|
* are "mixed" together into a single result TestFrame. Furthermore, the
|
|
|
|
|
|
* parameter automation is based on a precomputed _frame number_ parameter,
|
|
|
|
|
|
* that is derived from the _absolute nominal time_ by quantisation into a
|
|
|
|
|
|
* time grid with a stepping of 1 second (-> `FrameRate{1}`). This precomputed
|
|
|
|
|
|
* frame number parameter is stored in a parameter data block on the call stack,
|
|
|
|
|
|
* prior to recursively evaluating the node chain attached below.
|
|
|
|
|
|
* @note the generated Render Nodes are placed into the AllocationCluster,
|
|
|
|
|
|
* together with all the further storage used by those nodes internally.
|
|
|
|
|
|
* The real Render Engine uses an AllocationCluster per Segment of the timeline.
|
|
|
|
|
|
*/
|
|
|
|
|
|
ProcNode&
|
|
|
|
|
|
buildTree()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Prepare a precomputed parameter for the complete tree
|
|
|
|
|
|
auto selectFrameNo = [&](TurnoutSystem& turSys){ return quantSecs (turSys.getNomTime()); };
|
|
|
|
|
|
auto paramSpec = buildParamSpec()
|
|
|
|
|
|
.addSlot (selectFrameNo);
|
|
|
|
|
|
auto accFrameNo = paramSpec.makeAccessor<0>();
|
|
|
|
|
|
|
|
|
|
|
|
// Automation functions using the accessor for the precomputed FrameNo-parameter
|
|
|
|
|
|
auto autoFilter = [&](TurnoutSystem& turSys){ return stepFilter (turSys.get (accFrameNo)); };
|
|
|
|
|
|
auto autoMixer = [&](TurnoutSystem& turSys){ return stepMixer (turSys.get (accFrameNo)); };
|
|
|
|
|
|
|
|
|
|
|
|
// use processing functions from the »TestRand Ontology«
|
|
|
|
|
|
auto testGen = testRand().setupGenerator();
|
|
|
|
|
|
auto testMan = testRand().setupManipulator();
|
|
|
|
|
|
auto testMix = testRand().setupCombinator();
|
|
|
|
|
|
|
|
|
|
|
|
// A source node to produce random test frames
|
|
|
|
|
|
auto& n1s = alloc_.create<ProcNode>(
|
|
|
|
|
|
prepareNode("srcA")
|
2026-06-26 19:25:25 +02:00
|
|
|
|
.withAllocator(alloc_)
|
2026-06-25 19:36:45 +02:00
|
|
|
|
.preparePort()
|
|
|
|
|
|
.invoke(testGen.procID(), testGen.makeFun()) // params(frameNo, flavour)
|
|
|
|
|
|
.closeParam<1>(SRC_A) // --> fix the flavour ≔ SRC_A
|
|
|
|
|
|
.retrieveParam(accFrameNo)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.build());
|
|
|
|
|
|
|
|
|
|
|
|
// A node to »filter« the data in chain-A
|
|
|
|
|
|
auto& n1f = alloc_.create<ProcNode>(
|
|
|
|
|
|
prepareNode("filterA")
|
2026-06-26 19:25:25 +02:00
|
|
|
|
.withAllocator(alloc_)
|
2026-06-25 19:36:45 +02:00
|
|
|
|
.preparePort()
|
|
|
|
|
|
.invoke(testMan.procID(), testMan.makeFun())
|
|
|
|
|
|
.attachParamFun(autoFilter) // filter-param <-- autoFilter(frameNo)
|
|
|
|
|
|
.connectLead(n1s)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.build());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// A secondary source Node for the »chain-B«
|
|
|
|
|
|
auto& n2s = alloc_.create<ProcNode>(
|
|
|
|
|
|
prepareNode("srcB")
|
2026-06-26 19:25:25 +02:00
|
|
|
|
.withAllocator(alloc_)
|
2026-06-25 19:36:45 +02:00
|
|
|
|
.preparePort()
|
|
|
|
|
|
.invoke(testGen.procID(), testGen.makeFun()) // params(frameNo, flavour)
|
|
|
|
|
|
.closeParam<1>(SRC_B) // --> fix the flavour ≔ SRC_B
|
|
|
|
|
|
.retrieveParam(accFrameNo)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.build());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// A mixing Node to combine both chains
|
|
|
|
|
|
auto& mix = alloc_.create<ProcNode>(
|
|
|
|
|
|
prepareNode("mix")
|
2026-06-26 19:25:25 +02:00
|
|
|
|
.withAllocator(alloc_)
|
2026-06-25 19:36:45 +02:00
|
|
|
|
.preparePort()
|
|
|
|
|
|
.invoke(testMix.procID(), testMix.makeFun())
|
|
|
|
|
|
.attachParamFun(autoMixer) // mixer-param <-- autoMixer(frameNo)
|
|
|
|
|
|
.connectLead(n1f)
|
|
|
|
|
|
.connectLead(n2s)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.build());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Place a »Param-Agent«-Node on top to pre-compute the FrameNo
|
|
|
|
|
|
auto& parNode = alloc_.create<ProcNode>(
|
|
|
|
|
|
prepareNode("Param")
|
2026-06-26 19:25:25 +02:00
|
|
|
|
.withAllocator(alloc_)
|
2026-06-25 19:36:45 +02:00
|
|
|
|
.preparePort()
|
|
|
|
|
|
.computeParam(paramSpec)
|
|
|
|
|
|
.delegateLead(mix)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.build());
|
|
|
|
|
|
|
2026-06-26 19:25:25 +02:00
|
|
|
|
// the AllocationCluster managed some storage...
|
|
|
|
|
|
CHECK (isLimited (1_KiB, alloc_.numBytes(), 5_KiB));
|
|
|
|
|
|
|
2026-06-25 19:36:45 +02:00
|
|
|
|
// here the »Param-Agent« also acts as the Exit-Node
|
|
|
|
|
|
return parNode;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2009-08-31 00:49:08 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Register this test class... */
|
2024-12-06 23:43:18 +01:00
|
|
|
|
LAUNCHER (NodeOpera_test, "unit node");
|
2009-08-31 00:49:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-11-15 23:55:13 +01:00
|
|
|
|
}}} // namespace steam::engine::test
|