From b3ae3482bc5262435df69b4cf086033a05a6edc8 Mon Sep 17 00:00:00 2001
From: lnd3 <linus.cumselius@gmail.com>
Date: Tue, 1 Oct 2024 08:47:47 +0200
Subject: [PATCH] Make outputtype define input and output types and change its
 name. Add type info for node operations into nodegraphbase. Add getters for
 input and output nodes to be able to interact with the node graph externally.

---
 .../include/nodegraph/NodeGraphSchema.h       |   2 +
 .../include/nodegraph/core/NodeGraphBase.h    |  26 ++++-
 .../include/nodegraph/core/NodeGraphData.h    |   3 +-
 .../include/nodegraph/core/NodeGraphGroup.h   |  12 +-
 .../source/common/NodeGraphSchema.cpp         | 106 ++++++++++--------
 .../source/common/core/NodeGraphBase.cpp      |   2 +-
 .../source/common/core/NodeGraphGroup.cpp     |  16 ++-
 .../tests/common/NodeGraphBatchingTest.cpp    |   4 +-
 .../tests/common/NodeGraphSchemaTest.cpp      |   6 +-
 .../rendering/source/common/ui/UICreator.cpp  |   2 +-
 10 files changed, 112 insertions(+), 67 deletions(-)

diff --git a/packages/nodegraph/include/nodegraph/NodeGraphSchema.h b/packages/nodegraph/include/nodegraph/NodeGraphSchema.h
index 2d850746..6cdb3fd7 100644
--- a/packages/nodegraph/include/nodegraph/NodeGraphSchema.h
+++ b/packages/nodegraph/include/nodegraph/NodeGraphSchema.h
@@ -101,6 +101,8 @@ namespace l::nodegraph {
         int32_t NewNode(int32_t typeId);
         bool RemoveNode(int32_t nodeId);
         NodeGraphBase* GetNode(int32_t nodeId);
+        void ForEachInputNode(std::function<void(NodeGraphBase*)> cb);
+        void ForEachOutputNode(std::function<void(NodeGraphBase*)> cb);
 
         bool HasNodeType(const std::string& typeGroup, int32_t typeId);
         void ForEachNodeType(std::function<void(std::string_view, const std::vector<UINodeDesc>&)> cb) const;
diff --git a/packages/nodegraph/include/nodegraph/core/NodeGraphBase.h b/packages/nodegraph/include/nodegraph/core/NodeGraphBase.h
index a7815179..da7a2cf4 100644
--- a/packages/nodegraph/include/nodegraph/core/NodeGraphBase.h
+++ b/packages/nodegraph/include/nodegraph/core/NodeGraphBase.h
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "logging/LoggingAll.h"
+#include "meta/Reflection.h"
 
 #include <string>
 #include <vector>
@@ -26,8 +27,12 @@ namespace l::nodegraph {
     /**********************************************************************************/
     class NodeGraphBase {
     public:
-        NodeGraphBase(OutputType outputType) : mId(CreateUniqueId()), mOutputType(outputType) {
-        }
+        NodeGraphBase(NodeType outputType) : 
+            mId(CreateUniqueId()), 
+            mOutputType(outputType),
+			mOperationTypeHash(l::meta::class_hash<NodeGraphBase>())
+        {}
+
         virtual ~NodeGraphBase() {
             LOG(LogInfo) << "Node graph base destroyed";
         }
@@ -40,6 +45,7 @@ namespace l::nodegraph {
             this->mLastTickCount = other.mLastTickCount;
             this->mOutputType = other.mOutputType;
             this->mProcessUpdateHasRun = other.mProcessUpdateHasRun;
+            this->mOperationTypeHash = other.mOperationTypeHash;
             return *this;
         }
         NodeGraphBase& operator=(const NodeGraphBase& other) noexcept {
@@ -50,6 +56,7 @@ namespace l::nodegraph {
             this->mLastTickCount = other.mLastTickCount;
             this->mOutputType = other.mOutputType;
             this->mProcessUpdateHasRun = other.mProcessUpdateHasRun;
+            this->mOperationTypeHash = other.mOperationTypeHash;
             return *this;
         }
         NodeGraphBase(NodeGraphBase&& other) noexcept {
@@ -100,7 +107,12 @@ namespace l::nodegraph {
         virtual bool IsDataEditable(int8_t num);
         virtual bool IsOutputPolled(int8_t outputChannel);
 
-        virtual OutputType GetOutputType();
+        virtual NodeType GetOutputType();
+
+        template<class T>
+		bool IsOfOperation() {
+			return l::meta::template class_hash<T> == mOperationTypeId;
+		}
 
     protected:
         virtual void SetNumInputs(int8_t numInputs);
@@ -114,9 +126,10 @@ namespace l::nodegraph {
         std::vector<NodeGraphOutput> mOutputs;
 
         int32_t mId = -1;
-        OutputType mOutputType;
+        NodeType mOutputType;
 
         std::string mName;
+        size_t mOperationTypeHash;
     };
 
     /**********************************************************************************/
@@ -168,9 +181,10 @@ namespace l::nodegraph {
     template<class T, class... Params>
     class NodeGraph : public NodeGraphBase {
     public:
-        NodeGraph(OutputType outputType = OutputType::Default, Params&&... params) :
+        NodeGraph(NodeType outputType = NodeType::Default, Params&&... params) :
             NodeGraphBase(outputType),
-            mOperation(this, std::forward<Params>(params)...)
+            mOperation(this, std::forward<Params>(params)...),
+            mOperationTypeId = typeid(T)
         {
             SetNumInputs(mOperation.GetNumInputs());
             SetNumOutputs(mOperation.GetNumOutputs());
diff --git a/packages/nodegraph/include/nodegraph/core/NodeGraphData.h b/packages/nodegraph/include/nodegraph/core/NodeGraphData.h
index b78c4700..1ad83ecd 100644
--- a/packages/nodegraph/include/nodegraph/core/NodeGraphData.h
+++ b/packages/nodegraph/include/nodegraph/core/NodeGraphData.h
@@ -30,10 +30,11 @@ namespace l::nodegraph {
         INPUT_CUSTOM,
     };
 
-    enum class OutputType {
+    enum class NodeType {
         Default, // node will be processed if it is connected to the groups output by some route
         ExternalOutput, // node does not have meaningful output for other nodes but should still be processed (ex speaker output only has input)
         ExternalVisualOutput, // node has visual output that requires special handling, for example the graph plot node
+        ExternalInput
     };
 
     std::pair<float, float> GetInputBounds(InputBound bound);
diff --git a/packages/nodegraph/include/nodegraph/core/NodeGraphGroup.h b/packages/nodegraph/include/nodegraph/core/NodeGraphGroup.h
index 38fdd53d..2ab9ea83 100644
--- a/packages/nodegraph/include/nodegraph/core/NodeGraphGroup.h
+++ b/packages/nodegraph/include/nodegraph/core/NodeGraphGroup.h
@@ -74,15 +74,22 @@ namespace l::nodegraph {
         bool RemoveNode(int32_t id);
 
         template<class T, class... Params, std::enable_if_t<std::is_base_of_v<NodeGraphOp, T>, int> = 0>
-        l::nodegraph::NodeGraphBase* NewNode(OutputType nodeType, Params&&... params) {
+        l::nodegraph::NodeGraphBase* NewNode(NodeType nodeType, Params&&... params) {
             mNodes.emplace_back(std::make_unique<l::nodegraph::NodeGraph<T, Params...>>(nodeType, std::forward<Params>(params)...));
             auto nodePtr = mNodes.back().get();
-            if (nodeType == OutputType::ExternalOutput || nodeType == OutputType::ExternalVisualOutput) {
+            if (nodeType == NodeType::ExternalOutput || nodeType == NodeType::ExternalVisualOutput) {
                 mOutputNodes.push_back(nodePtr);
+			}
+			else if (nodeType == NodeType::ExternalInput) {
+                mInputNodes.push_back(nodePtr);
             }
+
             return nodePtr;
         }
 
+        void ForEachInputNode(std::function<void(NodeGraphBase*)> cb);
+        void ForEachOutputNode(std::function<void(NodeGraphBase*)> cb);
+
         void ClearProcessFlags();
         void ProcessSubGraph(int32_t numSamples);
         void Tick(int32_t tickCount, float elapsed);
@@ -92,6 +99,7 @@ namespace l::nodegraph {
 
         std::vector<std::unique_ptr<NodeGraphBase>> mNodes;
         std::vector<NodeGraphBase*> mOutputNodes;
+		std::vector<NodeGraphBase*> mInputNodes;
 
         int32_t mLastTickCount = 0;
     };
diff --git a/packages/nodegraph/source/common/NodeGraphSchema.cpp b/packages/nodegraph/source/common/NodeGraphSchema.cpp
index 409cdc1f..40ae9fc3 100644
--- a/packages/nodegraph/source/common/NodeGraphSchema.cpp
+++ b/packages/nodegraph/source/common/NodeGraphSchema.cpp
@@ -26,160 +26,160 @@ namespace l::nodegraph {
         l::nodegraph::NodeGraphBase* node = nullptr;
         switch (typeId) {
         case 0:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSourceConstants>(OutputType::Default, 0);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSourceConstants>(NodeType::Default, 0);
             break;
         case 1:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSourceConstants>(OutputType::Default, 1);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSourceConstants>(NodeType::Default, 1);
             break;
         case 2:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSourceConstants>(OutputType::Default, 2);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSourceConstants>(NodeType::Default, 2);
             break;
         case 3:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSourceConstants>(OutputType::Default, 3);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSourceConstants>(NodeType::Default, 3);
             break;
         case 4:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSourceTime>(OutputType::ExternalOutput, mAudioOutput != nullptr ? mAudioOutput->GetSampleRate() : 44100, 60);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSourceTime>(NodeType::ExternalOutput, mAudioOutput != nullptr ? mAudioOutput->GetSampleRate() : 44100, 60);
             break;
 
         case 50:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphNumericAdd>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphNumericAdd>(NodeType::Default);
             break;
         case 51:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphNumericSubtract>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphNumericSubtract>(NodeType::Default);
             break;
         case 52:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphNumericNegate>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphNumericNegate>(NodeType::Default);
             break;
         case 53:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphNumericMultiply>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphNumericMultiply>(NodeType::Default);
             break;
         case 54:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphNumericIntegral>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphNumericIntegral>(NodeType::Default);
             break;
         case 55:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphNumericMultiply3>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphNumericMultiply3>(NodeType::Default);
             break;
         case 56:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphNumericMultiplyAndAdd>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphNumericMultiplyAndAdd>(NodeType::Default);
             break;
         case 57:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphNumericRound>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphNumericRound>(NodeType::Default);
             break;
 
         case 100:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphLogicalAnd>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphLogicalAnd>(NodeType::Default);
             break;
         case 101:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphLogicalOr>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphLogicalOr>(NodeType::Default);
             break;
         case 102:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphLogicalXor>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphLogicalXor>(NodeType::Default);
             break;
 
         case 150:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphFilterLowpass>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphFilterLowpass>(NodeType::Default);
             break;
         case 151:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphFilterHighpass>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphFilterHighpass>(NodeType::Default);
             break;
         case 152:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphFilterChamberlain2pole>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphFilterChamberlain2pole>(NodeType::Default);
             break;
 
         case 200:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphOutputDebug>(OutputType::ExternalOutput);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphOutputDebug>(NodeType::ExternalOutput);
             break;
         case 201:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphOutputSpeaker>(OutputType::ExternalOutput, mAudioOutput);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphOutputSpeaker>(NodeType::ExternalOutput, mAudioOutput);
             break;
         case 202:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphOutputPlot>(OutputType::ExternalVisualOutput, 100);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphOutputPlot>(NodeType::ExternalVisualOutput, 100);
             break;
         case 203:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphOutputImGuiPlotLine>(OutputType::ExternalOutput);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphOutputImGuiPlotLine>(NodeType::ExternalOutput);
             break;
         case 204:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphOutputImGuiPlotCandles>(OutputType::ExternalOutput);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphOutputImGuiPlotCandles>(NodeType::ExternalOutput);
             break;
 
         case 251:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphEffectReverb1>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphEffectReverb1>(NodeType::Default);
             break;
         case 252:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphEffectReverb2>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphEffectReverb2>(NodeType::Default);
             break;
         case 254:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphEffectLimiter>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphEffectLimiter>(NodeType::Default);
             break;
         case 255:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphEffectEnvelopeFollower>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphEffectEnvelopeFollower>(NodeType::Default);
             break;
         case 256:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphEffectSaturator>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphEffectSaturator>(NodeType::Default);
             break;
         case 257:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphEffectTranceGate>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphEffectTranceGate>(NodeType::Default);
             break;
 
         case 300:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphInputKeyboardPiano>(OutputType::Default, mKeyState);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphInputKeyboardPiano>(NodeType::Default, mKeyState);
             break;
         case 301:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphInputMidiKeyboard>(OutputType::Default, mMidiManager);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphInputMidiKeyboard>(NodeType::Default, mMidiManager);
             break;
         case 302:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphInputMidiKnobs>(OutputType::Default, mMidiManager);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphInputMidiKnobs>(NodeType::Default, mMidiManager);
             break;
         case 303:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphInputMidiButtons>(OutputType::Default, mMidiManager, 0);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphInputMidiButtons>(NodeType::Default, mMidiManager, 0);
             break;
         case 304:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphInputMidiButtons>(OutputType::Default, mMidiManager, 1);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphInputMidiButtons>(NodeType::Default, mMidiManager, 1);
             break;
         case 305:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphInputMidiButtons>(OutputType::Default, mMidiManager, 2);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphInputMidiButtons>(NodeType::Default, mMidiManager, 2);
             break;
         case 306:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphInputMidiButtons>(OutputType::Default, mMidiManager, 3);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphInputMidiButtons>(NodeType::Default, mMidiManager, 3);
             break;
         case 307:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphInputMidiButtons>(OutputType::Default, mMidiManager, 4);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphInputMidiButtons>(NodeType::Default, mMidiManager, 4);
             break;
 
         case 350:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSignalSine>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSignalSine>(NodeType::Default);
             break;
         case 351:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSignalSineFM>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSignalSineFM>(NodeType::Default);
             break;
         case 352:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSignalSineFM2>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSignalSineFM2>(NodeType::Default);
             break;
         case 353:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSignalSineFM3>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSignalSineFM3>(NodeType::Default);
             break;
         case 354:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSignalSaw>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSignalSaw>(NodeType::Default);
             break;
         case 355:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSignalSine2>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSignalSine2>(NodeType::Default);
             break;
         case 356:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSignalSaw2>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphSignalSaw2>(NodeType::Default);
             break;
 
         case 400:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphControlEnvelope>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphControlEnvelope>(NodeType::Default);
             break;
         case 401:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphControlArpeggio>(OutputType::Default);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphControlArpeggio>(NodeType::Default);
             break;
 
         case 450:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphDataBusDataIn>(OutputType::Default, 6);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphDataBusDataIn>(NodeType::Default, 6);
             break;
         case 451:
-            node = mMainNodeGraph.NewNode<l::nodegraph::GraphDataBusDataOut>(OutputType::ExternalOutput, 6);
+            node = mMainNodeGraph.NewNode<l::nodegraph::GraphDataBusDataOut>(NodeType::ExternalOutput, 6);
             break;
 
         default:
@@ -214,6 +214,14 @@ namespace l::nodegraph {
         return false;
     }
 
+    void NodeGraphSchema::ForEachInputNode(std::function<void(NodeGraphBase*)> cb) {
+        mMainNodeGraph.ForEachInputNode(cb);
+    }
+
+    void NodeGraphSchema::ForEachOutputNode(std::function<void(NodeGraphBase*)> cb) {
+        mMainNodeGraph.ForEachOutputNode(cb);
+    }
+
     void NodeGraphSchema::ForEachNodeType(std::function<void(std::string_view, const std::vector<UINodeDesc>&)> cb) const {
         for (auto& it : mRegisteredNodeTypes) {
             cb(it.first, it.second);
diff --git a/packages/nodegraph/source/common/core/NodeGraphBase.cpp b/packages/nodegraph/source/common/core/NodeGraphBase.cpp
index b6ba48de..c00a7f10 100644
--- a/packages/nodegraph/source/common/core/NodeGraphBase.cpp
+++ b/packages/nodegraph/source/common/core/NodeGraphBase.cpp
@@ -264,7 +264,7 @@ namespace l::nodegraph {
         return mOutputs.at(outputChannel).IsPolled();
     }
 
-    OutputType NodeGraphBase::GetOutputType() {
+    NodeType NodeGraphBase::GetOutputType() {
         return mOutputType;
     }
 
diff --git a/packages/nodegraph/source/common/core/NodeGraphGroup.cpp b/packages/nodegraph/source/common/core/NodeGraphGroup.cpp
index 190be5c9..959b8689 100644
--- a/packages/nodegraph/source/common/core/NodeGraphGroup.cpp
+++ b/packages/nodegraph/source/common/core/NodeGraphGroup.cpp
@@ -24,13 +24,13 @@ namespace l::nodegraph {
 
     void NodeGraphGroup::SetNumInputs(int8_t numInputs) {
         if (!mInputNode) {
-            mInputNode = NewNode<GraphDataCopy>(OutputType::Default, numInputs);
+            mInputNode = NewNode<GraphDataCopy>(NodeType::Default, numInputs);
         }
     }
 
     void NodeGraphGroup::SetNumOutputs(int8_t numOutput) {
         if (!mOutputNode) {
-            mOutputNode = NewNode<GraphDataCopy>(OutputType::ExternalOutput, numOutput);
+            mOutputNode = NewNode<GraphDataCopy>(NodeType::ExternalOutput, numOutput);
         }
     }
 
@@ -121,6 +121,18 @@ namespace l::nodegraph {
         return count > 0 ? true : false;
     }
 
+    void NodeGraphGroup::ForEachInputNode(std::function<void(NodeGraphBase*)> cb) {
+        for (auto& it : mInputNodes) {
+            cb(it);
+        }
+    }
+
+    void NodeGraphGroup::ForEachOutputNode(std::function<void(NodeGraphBase*)> cb) {
+        for (auto& it : mOutputNodes) {
+            cb(it);
+        }
+    }
+
     void NodeGraphGroup::ClearProcessFlags() {
         mOutputNode->ClearProcessFlags();
     }
diff --git a/packages/nodegraph/tests/common/NodeGraphBatchingTest.cpp b/packages/nodegraph/tests/common/NodeGraphBatchingTest.cpp
index f29c9e97..958b501b 100644
--- a/packages/nodegraph/tests/common/NodeGraphBatchingTest.cpp
+++ b/packages/nodegraph/tests/common/NodeGraphBatchingTest.cpp
@@ -12,12 +12,12 @@ TEST(NodeGraphBatching, Simple) {
 	NodeGraphGroup group;
 	group.SetNumOutputs(1);
 
-	auto nodeSine = group.NewNode<GraphSignalSine2>(OutputType::Default);
+	auto nodeSine = group.NewNode<GraphSignalSine2>(NodeType::Default);
 	nodeSine->SetInput(1, 4.0f); // sine update rate
 	nodeSine->SetInput(2, 1400.0f); // sine freq
 	nodeSine->SetInput(3, 0.5f); // sine freq
 
-	auto nodeLowpass = group.NewNode<GraphFilterLowpass>(OutputType::Default);
+	auto nodeLowpass = group.NewNode<GraphFilterLowpass>(NodeType::Default);
 
 	nodeLowpass->SetInput(1, *nodeSine, 0);
 	group.SetOutput(0, *nodeLowpass, 0);
diff --git a/packages/nodegraph/tests/common/NodeGraphSchemaTest.cpp b/packages/nodegraph/tests/common/NodeGraphSchemaTest.cpp
index 085315ee..8fae5362 100644
--- a/packages/nodegraph/tests/common/NodeGraphSchemaTest.cpp
+++ b/packages/nodegraph/tests/common/NodeGraphSchemaTest.cpp
@@ -129,8 +129,8 @@ TEST(NodeGraph, GraphGroups) {
 		group.SetInput(2, &input1);
 		group.SetInput(3, &input2);
 
-		auto nodeLowpass1 = group.NewNode<GraphFilterLowpass>(OutputType::Default);
-		auto nodeLowpass2 = group.NewNode<GraphFilterLowpass>(OutputType::Default);
+		auto nodeLowpass1 = group.NewNode<GraphFilterLowpass>(NodeType::Default);
+		auto nodeLowpass2 = group.NewNode<GraphFilterLowpass>(NodeType::Default);
 
 		// left, right
 		nodeLowpass1->SetInput(1, group, 2);
@@ -156,7 +156,7 @@ TEST(NodeGraph, GraphGroups) {
 		group2.SetInput(0, group, 0);
 		group2.SetInput(1, group, 1);
 
-		auto copyNode = group2.NewNode<GraphDataCopy>(OutputType::Default, 2);
+		auto copyNode = group2.NewNode<GraphDataCopy>(NodeType::Default, 2);
 		copyNode->SetInput(0, group2, 0);
 		copyNode->SetInput(1, group2, 1);
 
diff --git a/packages/rendering/source/common/ui/UICreator.cpp b/packages/rendering/source/common/ui/UICreator.cpp
index dc10d7ea..63f90b1c 100644
--- a/packages/rendering/source/common/ui/UICreator.cpp
+++ b/packages/rendering/source/common/ui/UICreator.cpp
@@ -88,7 +88,7 @@ namespace l::ui {
                 }
             }
 
-            if (node.GetOutputType() == l::nodegraph::OutputType::ExternalVisualOutput) {
+            if (node.GetOutputType() == l::nodegraph::NodeType::ExternalVisualOutput) {
                 auto row = CreateContainer(uiManager, l::ui::UIContainer_DrawFlag, l::ui::UIRenderType::Rect, l::ui::UIAlignH::Left, l::ui::UIAlignV::Bottom, l::ui::UILayoutH::Parent, l::ui::UILayoutV::Parent);
                 row->SetPosition(ImVec2(0.0f, 0.0f));
                 row->GetContainerArea().mMargin = ioSize;