/* NodeLink(Test) - render node connectivity and collaboration Copyright (C) 2024, Hermann Vosseler   **Lumiera** is free software; you can redistribute it and/or modify it   under the terms of the GNU General Public License as published by the   Free Software Foundation; either version 2 of the License, or (at your   option) any later version. See the file COPYING for further details. * *****************************************************************/ /** @file node-link-test.cpp ** The \ref NodeLink_test covers the essence of connected render nodes. */ #include "lib/test/run.hpp" #include "steam/engine/proc-node.hpp" #include "steam/engine/node-builder.hpp" #include "steam/engine/test-rand-ontology.hpp" ///////////TODO #include "lib/test/diagnostic-output.hpp"/////////////////TODO #include "lib/util.hpp" #include using std::array; using util::isnil; //using std::string; using util::isSameObject; namespace steam { namespace engine{ namespace test { /***************************************************************//** * @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. * - the foundation layer is formed by the nodes as linked into a network * - starting from any Port, a TurnoutSystem can be established * - which in turn allows _turn out_ a render result from this port. */ class NodeLink_test : public Test { virtual void run (Arg) { seedRand(); build_simple_node(); build_connected_nodes(); trigger_node_port_invocation(); } /** @test Build Node Port for simple function * and verify observable properties of a Render Node * @todo 7/24 ✔ define ⟶ ✔ implement */ void build_simple_node() { // 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) .preparePort() .invoke(procID, ont::dummyOp) .completePort() .build(); CHECK (isnil (con.leads)); CHECK (1 == con.ports.size()); // 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); // can generate a symbolic spec to describe the Port's processing functionality... CHECK (watch(n1).getPortSpec(0) == "Test:dummy.op(int)"_expect); CHECK (watch(n1).getPortSpec(1) == "↯"_expect); // 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); // re-generate the descriptor for the source node (n1) auto& metaN1 = ProcID::describe("Test:dummy","op(int)"); CHECK (metaN1.genProcSpec() == "Test:dummy.op(int)"_expect); CHECK (metaN1.genProcName() == "Test:dummy.op"_expect); CHECK (metaN1.genNodeName() == "Test:dummy"_expect); CHECK (metaN1.genNodeSpec(con.leads) == "Test:dummy-◎"_expect); } /** @test TODO Build more elaborate Render Nodes linked into a connectivity network * @todo WIP 1/25 🔁 define ⟶ implement */ void build_connected_nodes() { // This operation emulates a data source auto src_op = [](int param, int* res){ *res = param; }; // A Node with two (source) ports ProcNode n1{prepareNode("n1") .preparePort() .invoke("a(int)", src_op) .setParam(5) .completePort() .preparePort() .invoke("b(int)", src_op) .setParam(23) .completePort() .build()}; // A node to add some "processing" to each data chain auto add1_op = [](int* src, int* res){ *res = 1 + *src; }; ProcNode n2{prepareNode("n2") .preparePort() .invoke("+1(int)(int)", add1_op) .connectLead(n1) .completePort() .preparePort() .invoke("+1(int)(int)", add1_op) .connectLead(n1) .completePort() .build()}; // Need a secondary source, this time with three ports ProcNode n1b{prepareNode("n1b") .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 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 ProcNode n3{prepareNode("n2") .preparePort() .invoke("A.mix(int/2)(int)", mix_op) .connectLead(n2) .connectLead(n1b) .completePort() .preparePort() .invoke("B.mix(int/2)(int)", mix_op) .connectLead(n2) .connectLead(n1b) .completePort() .preparePort() .invoke("C.mix(int/2)(int)", mix_op) .connectLeadPort(n2,1) .connectLead(n1b) .completePort() .build()}; SHOW_EXPR(watch(n1).getNodeSpec()) SHOW_EXPR(watch(n1).getPortSpec(0)) SHOW_EXPR(watch(n1).getPortSpec(1)) SHOW_EXPR(watch(n1.getPort(0)).getProcSpec()) SHOW_EXPR(watch(n1.getPort(0)).isSrc()) } /** @test TODO Invoke some render nodes as linked together * @todo WIP 12/24 🔁 define ⟶ implement */ void trigger_node_port_invocation() { UNIMPLEMENTED ("operate some render nodes as linked together"); } }; /** Register this test class... */ LAUNCHER (NodeLink_test, "unit node"); }}} // namespace steam::engine::test