Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
VSConnectionValidator.cpp
Go to the documentation of this file.
1/**
2 * @file VSConnectionValidator.cpp
3 * @brief Stateless validator for exec connections in Visual Script graphs (Phase 20-B).
4 * @author Olympe Engine
5 * @date 2026-03-13
6 *
7 * @details
8 * Implements three checks before a new exec connection is added:
9 * Check A — Self-loop (srcNodeID == dstNodeID)
10 * Check B — Duplicate output pin (srcPinName already has an outgoing connection)
11 * Check C — Cycle detection via iterative DFS
12 *
13 * C++14 compliant — no std::optional, structured bindings, std::filesystem,
14 * std::string_view.
15 */
16
18#include "../system/system_utils.h"
19
20#include <map>
21#include <vector>
22#include <unordered_set>
23
24namespace Olympe {
25
26// ---------------------------------------------------------------------------
27// VSConnectionValidator::IsExecConnectionValid
28// ---------------------------------------------------------------------------
29
30// Helper: Check if a node type accepts exec-in connections
32{
33 switch (type)
34 {
36 return false; // EntryPoint has no exec-in
39 return false; // Phase 24: Data-pure nodes (no execution pins)
40 default:
41 return true; // All other nodes accept exec-in (SetBBValue, Branch, etc.)
42 }
43}
44
46 int srcNodeID,
47 const std::string& srcPinName,
48 int dstNodeID)
49{
50 // ------------------------------------------------------------------
51 // Check A — Self-loop
52 // ------------------------------------------------------------------
53 if (srcNodeID == dstNodeID)
54 {
55 SYSTEM_LOG << "[VSEditor] Connection rejected: self-loop on node #"
56 << srcNodeID << "\n";
57 return false;
58 }
59
60 // ------------------------------------------------------------------
61 // Check A2 — Destination node exists and accepts exec-in
62 // ------------------------------------------------------------------
63 const TaskNodeDefinition* dstNode = nullptr;
64 for (size_t i = 0; i < graph.Nodes.size(); ++i)
65 {
66 if (graph.Nodes[i].NodeID == dstNodeID)
67 {
68 dstNode = &graph.Nodes[i];
69 break;
70 }
71 }
72
73 if (dstNode == nullptr)
74 {
75 SYSTEM_LOG << "[VSEditor] Connection rejected (Check A2a): destination node #"
76 << dstNodeID << " not found in template\n";
77 return false;
78 }
79
81 {
82 SYSTEM_LOG << "[VSEditor] Connection rejected (Check A2b): node type "
83 << static_cast<int>(dstNode->Type)
84 << " does not accept exec-in connections\n";
85 return false;
86 }
87
88 // ------------------------------------------------------------------
89 // Check B — Duplicate output pin
90 // ------------------------------------------------------------------
91 for (size_t i = 0; i < graph.ExecConnections.size(); ++i)
92 {
93 const ExecPinConnection& c = graph.ExecConnections[i];
94 if (c.SourceNodeID == srcNodeID && c.SourcePinName == srcPinName)
95 {
96 SYSTEM_LOG << "[VSEditor] Connection rejected (Check B): pin "
97 << srcNodeID << "." << srcPinName
98 << " already has an outgoing connection to node #"
99 << c.TargetNodeID << "." << c.TargetPinName << "\n";
100 return false;
101 }
102 }
103
104 // ------------------------------------------------------------------
105 // Check C — Cycle detection via iterative DFS
106 //
107 // Build an adjacency list from existing exec connections, then DFS
108 // from dstNodeID. If we can reach srcNodeID, adding src -> dst would
109 // close a cycle.
110 // ------------------------------------------------------------------
111 std::map<int, std::vector<int> > adj;
112 for (size_t i = 0; i < graph.ExecConnections.size(); ++i)
113 {
114 const ExecPinConnection& c = graph.ExecConnections[i];
115 adj[c.SourceNodeID].push_back(c.TargetNodeID);
116 }
117
118 std::vector<int> stack;
119 std::unordered_set<int> visited;
120 stack.push_back(dstNodeID);
121 while (!stack.empty())
122 {
123 int cur = stack.back();
124 stack.pop_back();
125 if (visited.count(cur) > 0)
126 continue;
127 visited.insert(cur);
128 if (cur == srcNodeID)
129 {
130 SYSTEM_LOG << "[VSEditor] Connection rejected: would create a cycle ("
131 << srcNodeID << " -> " << dstNodeID << ")\n";
132 return false;
133 }
134 std::map<int, std::vector<int> >::const_iterator it = adj.find(cur);
135 if (it != adj.end())
136 {
137 for (size_t j = 0; j < it->second.size(); ++j)
138 stack.push_back(it->second[j]);
139 }
140 }
141
142 return true;
143}
144
145} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
Stateless validator for exec connections in Visual Script graphs (Phase 20-B).
Immutable, shareable task graph asset.
static bool IsExecConnectionValid(const TaskGraphTemplate &graph, int srcNodeID, const std::string &srcPinName, int dstNodeID)
Returns true if adding an exec connection from srcNodeID/srcPinName to dstNodeID would be valid (no s...
< Provides AssetID and INVALID_ASSET_ID
static bool NodeTypeAcceptsExecInput(TaskNodeType type)
TaskNodeType
Identifies the role of a node in the task graph.
@ GetBBValue
Data node – reads a Blackboard key.
@ MathOp
Data node – arithmetic operation (+, -, *, /)
@ EntryPoint
Unique entry node for VS graphs (replaces Root)
Explicit connection between a named exec-out pin of a source node and the exec-in pin of a target nod...
Full description of a single node in the task graph.
#define SYSTEM_LOG