2007-09-03 02:33:47 +02:00
|
|
|
|
/*
|
2024-12-06 23:43:18 +01:00
|
|
|
|
NodeLink(Test) - render node connectivity and collaboration
|
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-link-test.cpp
|
|
|
|
|
|
** The \ref NodeLink_test covers the essence of connected render nodes.
|
2016-11-03 18:20:10 +01:00
|
|
|
|
*/
|
|
|
|
|
|
|
2007-09-03 02:33:47 +02:00
|
|
|
|
|
2008-12-18 04:47:41 +01:00
|
|
|
|
#include "lib/test/run.hpp"
|
2024-06-29 04:23:55 +02:00
|
|
|
|
#include "steam/engine/proc-node.hpp"
|
2024-07-04 23:54:13 +02:00
|
|
|
|
#include "steam/engine/node-builder.hpp"
|
2025-02-08 22:42:13 +01:00
|
|
|
|
#include "steam/engine/test-rand-ontology.hpp"
|
2025-02-09 01:44:45 +01:00
|
|
|
|
#include "steam/engine/diagnostic-buffer-provider.hpp"
|
2025-02-19 19:37:55 +01:00
|
|
|
|
#include "steam/asset/meta/time-grid.hpp"
|
|
|
|
|
|
#include "lib/time/timequant.hpp"
|
|
|
|
|
|
#include "lib/time/timecode.hpp"
|
2024-10-14 04:07:47 +02:00
|
|
|
|
#include "lib/util.hpp"
|
2007-09-03 02:33:47 +02:00
|
|
|
|
|
2025-01-08 16:39:00 +01:00
|
|
|
|
#include <array>
|
2007-09-03 02:33:47 +02:00
|
|
|
|
|
2025-01-08 16:39:00 +01:00
|
|
|
|
using std::array;
|
2024-10-14 04:07:47 +02:00
|
|
|
|
using util::isnil;
|
2024-11-03 23:58:25 +01:00
|
|
|
|
using util::isSameObject;
|
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 {
|
|
|
|
|
|
|
2025-02-19 19:37:55 +01:00
|
|
|
|
using lib::time::Time;
|
|
|
|
|
|
using lib::time::QuTime;
|
|
|
|
|
|
using lib::time::FrameNr;
|
|
|
|
|
|
using lib::time::FrameCnt;
|
2009-08-31 00:49:08 +02:00
|
|
|
|
|
2025-02-19 23:27:52 +01:00
|
|
|
|
namespace {
|
|
|
|
|
|
ont::Flavr SRC_A = 10; ///< »chain-A« arbitrary source frame marker
|
|
|
|
|
|
ont::Flavr SRC_B = 20; ///< similar for »chain-B«
|
|
|
|
|
|
Symbol SECONDS_GRID = "grid_sec"; ///< 1-seconds grid for translation Time -> Frame-#
|
|
|
|
|
|
|
|
|
|
|
|
const uint NUM_INVOCATIONS = 100;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2009-08-31 00:49:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
2013-10-24 23:06:36 +02:00
|
|
|
|
/***************************************************************//**
|
2024-06-29 04:23:55 +02:00
|
|
|
|
* @test demonstrate and document how [render nodes](\ref proc-node.hpp)
|
|
|
|
|
|
* are connected into a processing network, allowing to _invoke_
|
|
|
|
|
|
* a \ref Port on a node to pull-generate a render result.
|
2025-02-19 23:27:52 +01:00
|
|
|
|
* - Nodes can be built and ID metadata can be inspected
|
|
|
|
|
|
* - several Nodes can be linked into a render graph
|
|
|
|
|
|
* - connectivity can be verified to match definition
|
|
|
|
|
|
* - TestFrame data can be computed in a complex processing network
|
|
|
|
|
|
* - parameters can be derived from time and fed into the nodes.
|
2009-08-31 00:49:08 +02:00
|
|
|
|
*/
|
2024-12-06 23:43:18 +01:00
|
|
|
|
class NodeLink_test : public Test
|
2007-09-03 02:33:47 +02:00
|
|
|
|
{
|
2024-06-29 04:23:55 +02:00
|
|
|
|
virtual void
|
|
|
|
|
|
run (Arg)
|
|
|
|
|
|
{
|
2024-11-12 22:35:54 +01:00
|
|
|
|
seedRand();
|
|
|
|
|
|
|
2025-01-06 22:51:00 +01:00
|
|
|
|
build_simple_node();
|
2024-06-29 04:23:55 +02:00
|
|
|
|
build_connected_nodes();
|
|
|
|
|
|
trigger_node_port_invocation();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-01-06 22:51:00 +01:00
|
|
|
|
/** @test Build Node Port for simple function
|
|
|
|
|
|
* and verify observable properties of a Render Node
|
|
|
|
|
|
* @todo 7/24 ✔ define ⟶ ✔ implement
|
2024-06-29 04:23:55 +02:00
|
|
|
|
*/
|
|
|
|
|
|
void
|
2025-01-06 22:51:00 +01:00
|
|
|
|
build_simple_node()
|
2024-06-29 04:23:55 +02:00
|
|
|
|
{
|
2025-01-06 22:00:29 +01:00
|
|
|
|
// use some dummy specs and a dummy operation....
|
|
|
|
|
|
StrView nodeID{ont::DUMMY_NODE_ID};
|
|
|
|
|
|
StrView procID{ont::DUMMY_PROC_ID};
|
|
|
|
|
|
CHECK (nodeID == "Test:dummy"_expect);
|
|
|
|
|
|
CHECK (procID == "op(int)"_expect);
|
|
|
|
|
|
|
|
|
|
|
|
// use the NodeBuilder to construct a simple source-node connectivity
|
|
|
|
|
|
auto con = prepareNode(nodeID)
|
2024-10-13 03:49:01 +02:00
|
|
|
|
.preparePort()
|
2025-01-06 22:00:29 +01:00
|
|
|
|
.invoke(procID, ont::dummyOp)
|
2024-10-13 03:49:01 +02:00
|
|
|
|
.completePort()
|
2024-06-29 04:23:55 +02:00
|
|
|
|
.build();
|
2024-10-14 04:07:47 +02:00
|
|
|
|
CHECK (isnil (con.leads));
|
|
|
|
|
|
CHECK (1 == con.ports.size());
|
2024-10-26 23:44:42 +02:00
|
|
|
|
|
|
|
|
|
|
// can build a ProcNode with this connectivity
|
|
|
|
|
|
ProcNode n1{move(con)};
|
|
|
|
|
|
CHECK (watch(n1).isValid());
|
|
|
|
|
|
CHECK (watch(n1).leads().empty());
|
|
|
|
|
|
CHECK (watch(n1).ports().size() == 1);
|
2024-11-04 23:56:16 +01:00
|
|
|
|
|
2024-11-04 01:04:01 +01:00
|
|
|
|
// can generate a symbolic spec to describe the Port's processing functionality...
|
2025-02-05 00:25:02 +01:00
|
|
|
|
CHECK (watch(n1).getPortSpec(0) == "dummy.op(int)"_expect);
|
2024-11-03 23:58:25 +01:00
|
|
|
|
CHECK (watch(n1).getPortSpec(1) == "↯"_expect);
|
2024-11-04 01:04:01 +01:00
|
|
|
|
|
|
|
|
|
|
// such a symbolic spec is actually generated by a deduplicated metadata descriptor
|
|
|
|
|
|
auto& meta1 = ProcID::describe("N1","(arg)");
|
|
|
|
|
|
auto& meta1b = ProcID::describe("N1","(arg)");
|
|
|
|
|
|
auto& meta2 = ProcID::describe("N2","(arg)");
|
|
|
|
|
|
auto& meta3 = ProcID::describe("N1","uga()");
|
|
|
|
|
|
CHECK ( isSameObject (meta1,meta1b));
|
|
|
|
|
|
CHECK (not isSameObject (meta1,meta2));
|
|
|
|
|
|
CHECK (not isSameObject (meta1,meta3));
|
|
|
|
|
|
CHECK (hash_value(meta1) == hash_value(meta1b));
|
|
|
|
|
|
CHECK (hash_value(meta1) != hash_value(meta2));
|
|
|
|
|
|
CHECK (hash_value(meta1) != hash_value(meta3));
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (meta1.genProcSpec() == "N1(arg)"_expect);
|
|
|
|
|
|
CHECK (meta2.genProcSpec() == "N2(arg)"_expect);
|
|
|
|
|
|
CHECK (meta3.genProcSpec() == "N1.uga()"_expect);
|
2024-11-04 23:56:16 +01:00
|
|
|
|
|
|
|
|
|
|
// re-generate the descriptor for the source node (n1)
|
2025-01-06 22:00:29 +01:00
|
|
|
|
auto& metaN1 = ProcID::describe("Test:dummy","op(int)");
|
2025-02-05 00:25:02 +01:00
|
|
|
|
CHECK (metaN1.genProcSpec() == "dummy.op(int)"_expect);
|
|
|
|
|
|
CHECK (metaN1.genProcName() == "dummy.op"_expect);
|
2025-01-06 22:00:29 +01:00
|
|
|
|
CHECK (metaN1.genNodeName() == "Test:dummy"_expect);
|
|
|
|
|
|
CHECK (metaN1.genNodeSpec(con.leads) == "Test:dummy-◎"_expect);
|
2024-06-29 04:23:55 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-02-05 00:25:02 +01:00
|
|
|
|
/** @test Build more elaborate Render Nodes linked into a connectivity network
|
2025-02-08 22:42:13 +01:00
|
|
|
|
* - verify nodes with several ports; at exit-level, 3 ports are available
|
|
|
|
|
|
* - using two different source nodes, one with two, one with three ports
|
|
|
|
|
|
* - the 2-port source is linearly chained to a 2-port filter node
|
|
|
|
|
|
* - the exit-level is a mix node, combining data from both chains
|
|
|
|
|
|
* - apply the automatic wiring of ports with the same number, whereby
|
|
|
|
|
|
* the first port connects to the first port on the lead, and so on.
|
|
|
|
|
|
* - yet for the 3rd port at the mix node, on one side the port number
|
|
|
|
|
|
* must be given explicitly, since the »A-side« chain offers only
|
|
|
|
|
|
* two ports.
|
|
|
|
|
|
* @todo 1/25 ✔ define ⟶ ✔ implement
|
2024-06-29 04:23:55 +02:00
|
|
|
|
*/
|
|
|
|
|
|
void
|
2025-01-06 22:51:00 +01:00
|
|
|
|
build_connected_nodes()
|
2024-06-29 04:23:55 +02:00
|
|
|
|
{
|
2025-01-08 16:39:00 +01:00
|
|
|
|
// This operation emulates a data source
|
|
|
|
|
|
auto src_op = [](int param, int* res){ *res = param; };
|
2025-01-06 22:51:00 +01:00
|
|
|
|
|
|
|
|
|
|
// A Node with two (source) ports
|
2025-02-08 19:39:00 +01:00
|
|
|
|
ProcNode n1s{prepareNode("srcA")
|
2025-01-06 22:51:00 +01:00
|
|
|
|
.preparePort()
|
2025-01-08 16:39:00 +01:00
|
|
|
|
.invoke("a(int)", src_op)
|
2025-01-06 22:51:00 +01:00
|
|
|
|
.setParam(5)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.preparePort()
|
2025-01-08 16:39:00 +01:00
|
|
|
|
.invoke("b(int)", src_op)
|
2025-01-06 22:51:00 +01:00
|
|
|
|
.setParam(23)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.build()};
|
|
|
|
|
|
|
2025-01-08 16:39:00 +01:00
|
|
|
|
// A node to add some "processing" to each data chain
|
|
|
|
|
|
auto add1_op = [](int* src, int* res){ *res = 1 + *src; };
|
2025-02-08 19:39:00 +01:00
|
|
|
|
ProcNode n1f{prepareNode("filterA")
|
2025-01-06 22:51:00 +01:00
|
|
|
|
.preparePort()
|
2025-02-08 22:42:13 +01:00
|
|
|
|
.invoke("a+1(int)(int)", add1_op)
|
2025-02-08 19:39:00 +01:00
|
|
|
|
.connectLead(n1s)
|
2025-01-06 22:51:00 +01:00
|
|
|
|
.completePort()
|
|
|
|
|
|
.preparePort()
|
2025-02-08 22:42:13 +01:00
|
|
|
|
.invoke("b+1(int)(int)", add1_op)
|
2025-02-08 19:39:00 +01:00
|
|
|
|
.connectLead(n1s)
|
2025-01-06 22:51:00 +01:00
|
|
|
|
.completePort()
|
|
|
|
|
|
.build()};
|
2025-01-08 16:39:00 +01:00
|
|
|
|
|
|
|
|
|
|
// Need a secondary source, this time with three ports
|
2025-02-08 19:39:00 +01:00
|
|
|
|
ProcNode n2s{prepareNode("srcB")
|
2025-01-08 16:39:00 +01:00
|
|
|
|
.preparePort()
|
|
|
|
|
|
.invoke("a(int)", src_op)
|
|
|
|
|
|
.setParam(7)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.preparePort()
|
|
|
|
|
|
.invoke("b(int)", src_op)
|
|
|
|
|
|
.setParam(13)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.preparePort()
|
|
|
|
|
|
.invoke("c(int)", src_op)
|
|
|
|
|
|
.setParam(17)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.build()};
|
|
|
|
|
|
|
|
|
|
|
|
// This operation emulates mixing of two source chains
|
|
|
|
|
|
auto mix_op = [](array<int*,2> src, int* res){ *res = (*src[0] + *src[1]) / 2; };
|
|
|
|
|
|
|
|
|
|
|
|
// Wiring for the Mix, building up three ports
|
|
|
|
|
|
// Since the first source-chain has only two ports,
|
|
|
|
|
|
// for the third result port we'll re-use the second source
|
2025-02-08 19:39:00 +01:00
|
|
|
|
ProcNode mix{prepareNode("mix")
|
2025-01-08 16:39:00 +01:00
|
|
|
|
.preparePort()
|
2025-02-08 19:39:00 +01:00
|
|
|
|
.invoke("a-mix(int/2)(int)", mix_op)
|
|
|
|
|
|
.connectLead(n1f)
|
|
|
|
|
|
.connectLead(n2s)
|
2025-01-08 16:39:00 +01:00
|
|
|
|
.completePort()
|
|
|
|
|
|
.preparePort()
|
2025-02-08 19:39:00 +01:00
|
|
|
|
.invoke("b-mix(int/2)(int)", mix_op)
|
|
|
|
|
|
.connectLead(n1f)
|
|
|
|
|
|
.connectLead(n2s)
|
2025-01-08 16:39:00 +01:00
|
|
|
|
.completePort()
|
|
|
|
|
|
.preparePort()
|
2025-02-08 19:39:00 +01:00
|
|
|
|
.invoke("c-mix(int/2)(int)", mix_op)
|
|
|
|
|
|
.connectLeadPort(n1f,1)
|
|
|
|
|
|
.connectLead(n2s)
|
2025-01-08 16:39:00 +01:00
|
|
|
|
.completePort()
|
|
|
|
|
|
.build()};
|
|
|
|
|
|
|
2025-02-08 19:39:00 +01:00
|
|
|
|
// verify Node-level connectivity
|
|
|
|
|
|
CHECK ( is_linked(n1f).to(n1s));
|
|
|
|
|
|
CHECK (not is_linked(n2s).to(n1s));
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (not is_linked(mix).to(n1s));
|
|
|
|
|
|
CHECK ( is_linked(mix).to(n2s));
|
|
|
|
|
|
CHECK ( is_linked(mix).to(n1f));
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (watch(n1s).leads().size() == 0 );
|
|
|
|
|
|
CHECK (watch(n1f).leads().size() == 1 );
|
|
|
|
|
|
CHECK (watch(n2s).leads().size() == 0 );
|
|
|
|
|
|
CHECK (watch(mix).leads().size() == 2 );
|
|
|
|
|
|
|
|
|
|
|
|
// verify Node and connectivity spec
|
|
|
|
|
|
CHECK (watch(n1s).getNodeSpec() == "srcA-◎"_expect );
|
|
|
|
|
|
CHECK (watch(n1f).getNodeSpec() == "filterA◁—srcA-◎"_expect );
|
|
|
|
|
|
CHECK (watch(n2s).getNodeSpec() == "srcB-◎"_expect );
|
|
|
|
|
|
CHECK (watch(mix).getNodeSpec() == "mix┉┉{srcA, srcB}"_expect);
|
|
|
|
|
|
|
2025-02-08 22:42:13 +01:00
|
|
|
|
// verify setup of the source nodes
|
2025-02-08 19:39:00 +01:00
|
|
|
|
CHECK (watch(n1s).ports().size() == 2 );
|
|
|
|
|
|
CHECK (watch(n1s).watchPort(0).isSrc());
|
|
|
|
|
|
CHECK (watch(n1s).watchPort(1).isSrc());
|
|
|
|
|
|
CHECK (watch(n1s).watchPort(0).getProcSpec() == "srcA.a(int)"_expect );
|
|
|
|
|
|
CHECK (watch(n1s).watchPort(1).getProcSpec() == "srcA.b(int)"_expect );
|
|
|
|
|
|
CHECK (watch(n1s).getPortSpec(0) == "srcA.a(int)"_expect );
|
|
|
|
|
|
CHECK (watch(n1s).getPortSpec(1) == "srcA.b(int)"_expect );
|
2025-02-05 00:25:02 +01:00
|
|
|
|
|
2025-02-08 22:42:13 +01:00
|
|
|
|
// second source node has 3 ports....
|
2025-02-08 19:39:00 +01:00
|
|
|
|
CHECK (watch(n2s).ports().size() == 3 );
|
|
|
|
|
|
CHECK (watch(n2s).watchPort(0).isSrc());
|
|
|
|
|
|
CHECK (watch(n2s).watchPort(1).isSrc());
|
|
|
|
|
|
CHECK (watch(n2s).watchPort(2).isSrc());
|
|
|
|
|
|
CHECK (watch(n2s).watchPort(0).getProcSpec() == "srcB.a(int)"_expect );
|
|
|
|
|
|
CHECK (watch(n2s).watchPort(1).getProcSpec() == "srcB.b(int)"_expect );
|
|
|
|
|
|
CHECK (watch(n2s).watchPort(2).getProcSpec() == "srcB.c(int)"_expect );
|
|
|
|
|
|
CHECK (watch(n2s).getPortSpec(0) == "srcB.a(int)"_expect );
|
|
|
|
|
|
CHECK (watch(n2s).getPortSpec(1) == "srcB.b(int)"_expect );
|
|
|
|
|
|
CHECK (watch(n2s).getPortSpec(2) == "srcB.c(int)"_expect );
|
2025-02-05 00:25:02 +01:00
|
|
|
|
|
2025-02-08 19:39:00 +01:00
|
|
|
|
// verify 2-chain
|
|
|
|
|
|
CHECK (watch(n1f).leads().size() == 1 );
|
|
|
|
|
|
CHECK (watch(n1f).ports().size() == 2 );
|
|
|
|
|
|
CHECK (watch(n1f).watchPort(0).srcPorts().size() == 1 );
|
|
|
|
|
|
CHECK (watch(n1f).watchLead(0).ports().size() == 2 );
|
|
|
|
|
|
CHECK (watch(n1f).watchLead(0).getNodeName() == "srcA"_expect);
|
|
|
|
|
|
CHECK (watch(n1f).watchPort(0).watchLead(0).getProcSpec() == "srcA.a(int)"_expect );
|
|
|
|
|
|
CHECK (watch(n1f).watchLead(0).watchPort(0).getProcSpec() == "srcA.a(int)"_expect );
|
|
|
|
|
|
CHECK (watch(n1f).watchPort(0).srcPorts()[0] == watch(n1f).watchLead(0).ports()[0]);
|
|
|
|
|
|
CHECK (watch(n1f).watchPort(1).srcPorts()[0] == watch(n1f).watchLead(0).ports()[1]);
|
2025-02-08 22:42:13 +01:00
|
|
|
|
|
|
|
|
|
|
// verify mix with 3 ports
|
|
|
|
|
|
CHECK (watch(mix).leads().size() == 2);
|
|
|
|
|
|
CHECK (watch(mix).leads()[0] == n1f );
|
|
|
|
|
|
CHECK (watch(mix).leads()[1] == n2s );
|
|
|
|
|
|
CHECK (watch(mix).ports().size() == 3);
|
|
|
|
|
|
CHECK (watch(mix).watchPort(0).srcPorts().size() == 2 );
|
|
|
|
|
|
CHECK (watch(mix).watchPort(1).srcPorts().size() == 2 );
|
|
|
|
|
|
CHECK (watch(mix).watchPort(2).srcPorts().size() == 2 );
|
|
|
|
|
|
CHECK (watch(mix).watchLead(0).ports().size() == 2 );
|
|
|
|
|
|
CHECK (watch(mix).watchLead(1).ports().size() == 3 );
|
|
|
|
|
|
CHECK (watch(mix).watchPort(0).watchLead(0).getProcName() == "filterA.a+1"_expect );
|
|
|
|
|
|
CHECK (watch(mix).watchLead(0).watchPort(0).getProcName() == "filterA.a+1"_expect );
|
|
|
|
|
|
CHECK (watch(mix).watchPort(1).watchLead(0).getProcName() == "filterA.b+1"_expect );
|
|
|
|
|
|
CHECK (watch(mix).watchLead(0).watchPort(1).getProcName() == "filterA.b+1"_expect );
|
|
|
|
|
|
CHECK (watch(mix).watchPort(2).watchLead(0).getProcName() == "filterA.b+1"_expect ); // special connection to port 1 on lead
|
|
|
|
|
|
CHECK (watch(mix).watchLead(0).watchPort(1).getProcName() == "filterA.b+1"_expect );
|
|
|
|
|
|
CHECK (watch(mix).watchPort(0).srcPorts()[0] == watch(mix).watchLead(0).ports()[0]);
|
|
|
|
|
|
CHECK (watch(mix).watchPort(1).srcPorts()[0] == watch(mix).watchLead(0).ports()[1]);
|
|
|
|
|
|
CHECK (watch(mix).watchPort(2).srcPorts()[0] == watch(mix).watchLead(0).ports()[1]);
|
|
|
|
|
|
CHECK (watch(mix).watchPort(0).watchLead(1).getProcName() == "srcB.a"_expect );
|
|
|
|
|
|
CHECK (watch(mix).watchLead(1).watchPort(0).getProcName() == "srcB.a"_expect );
|
|
|
|
|
|
CHECK (watch(mix).watchPort(1).watchLead(1).getProcName() == "srcB.b"_expect );
|
|
|
|
|
|
CHECK (watch(mix).watchLead(1).watchPort(1).getProcName() == "srcB.b"_expect );
|
|
|
|
|
|
CHECK (watch(mix).watchPort(2).watchLead(1).getProcName() == "srcB.c"_expect );
|
|
|
|
|
|
CHECK (watch(mix).watchLead(1).watchPort(2).getProcName() == "srcB.c"_expect );
|
|
|
|
|
|
CHECK (watch(mix).watchPort(0).srcPorts()[1] == watch(mix).watchLead(1).ports()[0]);
|
|
|
|
|
|
CHECK (watch(mix).watchPort(1).srcPorts()[1] == watch(mix).watchLead(1).ports()[1]);
|
|
|
|
|
|
CHECK (watch(mix).watchPort(2).srcPorts()[1] == watch(mix).watchLead(1).ports()[2]);
|
2025-02-09 01:44:45 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//________________________________________________________
|
|
|
|
|
|
// for sake of completeness: all these nodes can be invoked
|
|
|
|
|
|
|
|
|
|
|
|
BufferProvider& provider = DiagnosticBufferProvider::build();
|
|
|
|
|
|
auto invoke = [&](ProcNode& node, uint port)
|
|
|
|
|
|
{ // Sequence to invoke a Node...
|
|
|
|
|
|
BuffHandle buff = provider.lockBufferFor<int> (-55);
|
|
|
|
|
|
CHECK (-55 == buff.accessAs<int>());
|
|
|
|
|
|
buff = node.pull (port, buff, Time::ZERO, ProcessKey{0});
|
|
|
|
|
|
int result = buff.accessAs<int>();
|
|
|
|
|
|
buff.release();
|
|
|
|
|
|
return result;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// node|port
|
|
|
|
|
|
CHECK (invoke (n1s, 0 ) == 5);
|
|
|
|
|
|
CHECK (invoke (n1s, 1 ) == 23);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (invoke (n1f, 0 ) == 5+1);
|
|
|
|
|
|
CHECK (invoke (n1f, 1 ) == 23+1);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (invoke (n2s, 0 ) == 7);
|
|
|
|
|
|
CHECK (invoke (n2s, 1 ) == 13);
|
|
|
|
|
|
CHECK (invoke (n2s, 2 ) == 17);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (invoke (mix, 0 ) == (5+1 + 7 )/2);
|
|
|
|
|
|
CHECK (invoke (mix, 1 ) == (23+1 + 13)/2);
|
|
|
|
|
|
CHECK (invoke (mix, 2 ) == (23+1 + 17)/2);
|
2024-06-29 04:23:55 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-02-19 19:37:55 +01:00
|
|
|
|
|
2025-02-19 23:27:52 +01:00
|
|
|
|
/** @test Invoke some render nodes as linked together.
|
|
|
|
|
|
* - use exactly the same topology as in the preceding test
|
|
|
|
|
|
* - but this time use TestFrame (random data) and configure
|
|
|
|
|
|
* hash-chaining operations provided by »Test Random«
|
|
|
|
|
|
* - setup various automation functions, based on the frame-#
|
|
|
|
|
|
* - use a pre-computation step to _quantise_ time into frame-#
|
|
|
|
|
|
* - install this pre-computation as »Param Agent Node«
|
|
|
|
|
|
* - configure individual parameters to consume precomputed frame-#
|
|
|
|
|
|
* - use _partial closure_ to supply the source-»flavour« parameter
|
|
|
|
|
|
* - also rebuild the expected computations by direct invocation
|
|
|
|
|
|
* - sample various test runs with randomly chosen time and port-#
|
|
|
|
|
|
* - verify computed data checksums match with expected computation.
|
|
|
|
|
|
* @todo 2/25 ✔ define ⟶ ✔ implement
|
2024-06-29 04:23:55 +02:00
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
trigger_node_port_invocation()
|
2009-08-31 00:49:08 +02:00
|
|
|
|
{
|
2025-02-11 01:10:25 +01:00
|
|
|
|
auto testGen = testRand().setupGenerator();
|
|
|
|
|
|
auto testMan = testRand().setupManipulator();
|
|
|
|
|
|
auto testMix = testRand().setupCombinator();
|
|
|
|
|
|
|
2025-02-19 19:37:55 +01:00
|
|
|
|
|
|
|
|
|
|
// Prepare for Time-Quantisation --> Frame-# or Offset parameter
|
|
|
|
|
|
steam::asset::meta::TimeGrid::build (SECONDS_GRID, 1);
|
2025-02-19 23:27:52 +01:00
|
|
|
|
auto quantSecs = [&](Time time){ return FrameNr::quant (time, SECONDS_GRID); };
|
2025-02-19 19:37:55 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Prepare a precomputed parameter for the complete tree
|
2025-02-19 23:27:52 +01:00
|
|
|
|
auto selectFrameNo = [&](TurnoutSystem& tuSys){ return quantSecs(tuSys.getNomTime()); };
|
2025-02-19 19:37:55 +01:00
|
|
|
|
auto paramSpec = buildParamSpec()
|
|
|
|
|
|
.addSlot (selectFrameNo);
|
|
|
|
|
|
|
|
|
|
|
|
auto accFrameNo = paramSpec.makeAccessor<0>();
|
|
|
|
|
|
|
|
|
|
|
|
// Prepare mapping- and automation-functions
|
2025-02-19 23:27:52 +01:00
|
|
|
|
auto stepFilter = [] (FrameCnt id)-> ont::Param { return util::limited (10, -10 + id, 50); };
|
|
|
|
|
|
auto stepMixer = [] (FrameCnt id)-> ont::Factr { return util::limited (0, + id, 50) / 50.0; };
|
2025-02-19 19:37:55 +01:00
|
|
|
|
|
|
|
|
|
|
// note: binds the accessor for the precomputed FrameNo-parameter
|
|
|
|
|
|
auto autoFilter = [=](TurnoutSystem& tuSys){ return stepFilter (tuSys.get (accFrameNo)); };
|
|
|
|
|
|
auto autoMixer = [=](TurnoutSystem& tuSys){ return stepMixer (tuSys.get (accFrameNo)); };
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-02-11 01:10:25 +01:00
|
|
|
|
// A Node with two (source) ports
|
|
|
|
|
|
ProcNode n1s{prepareNode("srcA")
|
|
|
|
|
|
.preparePort()
|
2025-02-19 19:37:55 +01:00
|
|
|
|
.invoke(testGen.procID(), testGen.makeFun()) // params(frameNo, flavour)
|
|
|
|
|
|
.closeParam<1>(SRC_A + 0) // --> flavour ≔ SRC_A + port#0
|
|
|
|
|
|
.retrieveParam(accFrameNo)
|
2025-02-11 01:10:25 +01:00
|
|
|
|
.completePort()
|
|
|
|
|
|
.preparePort()
|
2025-02-19 19:37:55 +01:00
|
|
|
|
.invoke(testGen.procID(), testGen.makeFun())
|
|
|
|
|
|
.closeParam<1>(SRC_A + 1) // --> flavour ≔ SRC_A + port#1
|
|
|
|
|
|
.retrieveParam(accFrameNo)
|
2025-02-11 01:10:25 +01:00
|
|
|
|
.completePort()
|
|
|
|
|
|
.build()};
|
|
|
|
|
|
|
2025-02-19 19:37:55 +01:00
|
|
|
|
// A node to »filter« the data in chain-A
|
2025-02-11 01:10:25 +01:00
|
|
|
|
ProcNode n1f{prepareNode("filterA")
|
|
|
|
|
|
.preparePort()
|
2025-02-19 19:37:55 +01:00
|
|
|
|
.invoke(testMan.procID(), testMan.makeFun())
|
|
|
|
|
|
.attachParamFun(autoFilter) // filter-param <-- autoFilter(frameNo)
|
2025-02-11 01:10:25 +01:00
|
|
|
|
.connectLead(n1s)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.preparePort()
|
2025-02-19 19:37:55 +01:00
|
|
|
|
.invoke(testMan.procID(), testMan.makeFun())
|
|
|
|
|
|
.attachParamFun(autoFilter)
|
2025-02-11 01:10:25 +01:00
|
|
|
|
.connectLead(n1s)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.build()};
|
2025-02-19 19:37:55 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// A secondary source Node, this time with three ports
|
2025-02-11 01:10:25 +01:00
|
|
|
|
ProcNode n2s{prepareNode("srcB")
|
|
|
|
|
|
.preparePort()
|
2025-02-19 19:37:55 +01:00
|
|
|
|
.invoke(testGen.procID(), testGen.makeFun()) // params(frameNo, flavour)
|
|
|
|
|
|
.closeParam<1>(SRC_B + 0) // --> flavour ≔ SRC_B + port#0
|
|
|
|
|
|
.retrieveParam(accFrameNo)
|
2025-02-11 01:10:25 +01:00
|
|
|
|
.completePort()
|
|
|
|
|
|
.preparePort()
|
2025-02-19 19:37:55 +01:00
|
|
|
|
.invoke(testGen.procID(), testGen.makeFun())
|
|
|
|
|
|
.closeParam<1>(SRC_B + 1) // --> flavour ≔ SRC_B + port#1
|
|
|
|
|
|
.retrieveParam(accFrameNo)
|
2025-02-11 01:10:25 +01:00
|
|
|
|
.completePort()
|
|
|
|
|
|
.preparePort()
|
2025-02-19 19:37:55 +01:00
|
|
|
|
.invoke(testGen.procID(), testGen.makeFun())
|
|
|
|
|
|
.closeParam<1>(SRC_B + 2) // --> flavour ≔ SRC_B + port#2
|
|
|
|
|
|
.retrieveParam(accFrameNo)
|
2025-02-11 01:10:25 +01:00
|
|
|
|
.completePort()
|
|
|
|
|
|
.build()};
|
|
|
|
|
|
|
2025-02-19 19:37:55 +01:00
|
|
|
|
|
|
|
|
|
|
// Wiring for the Mix, building three ports,
|
|
|
|
|
|
// drawing from both source-chains
|
2025-02-11 01:10:25 +01:00
|
|
|
|
ProcNode mix{prepareNode("mix")
|
|
|
|
|
|
.preparePort()
|
2025-02-19 19:37:55 +01:00
|
|
|
|
.invoke(testMix.procID(), testMix.makeFun())
|
|
|
|
|
|
.attachParamFun(autoMixer) // mixer-param <-- autoMixer(frameNo)
|
2025-02-11 01:10:25 +01:00
|
|
|
|
.connectLead(n1f)
|
|
|
|
|
|
.connectLead(n2s)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.preparePort()
|
2025-02-19 19:37:55 +01:00
|
|
|
|
.invoke(testMix.procID(), testMix.makeFun())
|
|
|
|
|
|
.attachParamFun(autoMixer)
|
2025-02-11 01:10:25 +01:00
|
|
|
|
.connectLead(n1f)
|
|
|
|
|
|
.connectLead(n2s)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.preparePort()
|
2025-02-19 19:37:55 +01:00
|
|
|
|
.invoke(testMix.procID(), testMix.makeFun())
|
|
|
|
|
|
.attachParamFun(autoMixer)
|
|
|
|
|
|
.connectLeadPort(n1f,1) // note: using 2nd port from chain-A, which only has two ports
|
2025-02-11 01:10:25 +01:00
|
|
|
|
.connectLead(n2s)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.build()};
|
2025-02-19 19:37:55 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Set a »Param-Agent«-Node on top to pre-compute the FrameNo
|
|
|
|
|
|
ProcNode parNode{prepareNode("Param")
|
|
|
|
|
|
.preparePort()
|
|
|
|
|
|
.computeParam(paramSpec)
|
|
|
|
|
|
.delegateLead(mix)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.preparePort()
|
|
|
|
|
|
.computeParam(paramSpec)
|
|
|
|
|
|
.delegateLead(mix)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.preparePort()
|
|
|
|
|
|
.computeParam(paramSpec)
|
|
|
|
|
|
.delegateLead(mix)
|
|
|
|
|
|
.completePort()
|
|
|
|
|
|
.build()};
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-02-19 23:27:52 +01:00
|
|
|
|
// Effectively, the following computation is expected to happen...
|
|
|
|
|
|
auto verify = [&](Time nomTime, uint port)
|
|
|
|
|
|
{
|
|
|
|
|
|
ont::FraNo fraNo = quantSecs(nomTime);
|
|
|
|
|
|
ont::Flavr fla_A = SRC_A + util::min (port, 1u);
|
|
|
|
|
|
ont::Flavr fla_B = SRC_B + util::min (port, 2u);
|
|
|
|
|
|
ont::Param param = stepFilter(fraNo);
|
|
|
|
|
|
ont::Factr mix = stepMixer (fraNo);
|
|
|
|
|
|
|
|
|
|
|
|
TestFrame f1{uint(fraNo),fla_A};
|
|
|
|
|
|
TestFrame f2{uint(fraNo),fla_B};
|
|
|
|
|
|
|
|
|
|
|
|
ont::manipulateFrame (&f1, &f1, param);
|
|
|
|
|
|
ont::combineFrames (&f1, &f1, &f2, mix);
|
|
|
|
|
|
CHECK (not f1.isPristine());
|
|
|
|
|
|
CHECK ( f2.isPristine());
|
|
|
|
|
|
return f1.getChecksum();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-02-19 19:37:55 +01:00
|
|
|
|
BufferProvider& provider = DiagnosticBufferProvider::build();
|
2025-02-19 23:27:52 +01:00
|
|
|
|
const BuffDescr buffDescr = provider.getDescriptor<TestFrame>();
|
2025-02-19 19:37:55 +01:00
|
|
|
|
|
|
|
|
|
|
auto invoke = [&](Time nomTime, uint port)
|
|
|
|
|
|
{ // Sequence to invoke a Node...
|
|
|
|
|
|
BuffHandle buff = provider.lockBuffer(buffDescr);
|
2025-02-19 23:27:52 +01:00
|
|
|
|
TestFrame& result = buff.accessAs<TestFrame>();
|
|
|
|
|
|
CHECK ( result.isPristine());
|
2025-02-19 19:37:55 +01:00
|
|
|
|
buff = parNode.pull (port, buff, nomTime, ProcessKey{});
|
2025-02-19 23:27:52 +01:00
|
|
|
|
CHECK ( result.isValid());
|
|
|
|
|
|
CHECK (not result.isPristine());
|
|
|
|
|
|
HashVal checksum = result.getChecksum();
|
2025-02-19 19:37:55 +01:00
|
|
|
|
buff.release();
|
|
|
|
|
|
return checksum;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-02-19 23:27:52 +01:00
|
|
|
|
// Computations should be pure (not depending on order)
|
|
|
|
|
|
// Thus sample various random times and ports
|
|
|
|
|
|
for (uint i=0; i < NUM_INVOCATIONS; ++i)
|
|
|
|
|
|
{
|
|
|
|
|
|
uint port = rani(3);
|
|
|
|
|
|
Time nomTime{rani(60'000),0}; // drive test with a random »nominal Time« <60s with ms granularity
|
|
|
|
|
|
|
|
|
|
|
|
// Invoke -- and compare checksum with direct computation
|
|
|
|
|
|
CHECK (invoke (nomTime,port) == verify (nomTime,port));
|
|
|
|
|
|
}
|
2024-06-29 04:23:55 +02:00
|
|
|
|
}
|
2009-08-31 00:49:08 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Register this test class... */
|
2024-12-06 23:43:18 +01:00
|
|
|
|
LAUNCHER (NodeLink_test, "unit node");
|
2009-08-31 00:49:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-11-15 23:55:13 +01:00
|
|
|
|
}}} // namespace steam::engine::test
|