Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
VisualScriptEditorPanel_Verification.cpp
Go to the documentation of this file.
1/**
2 * @file VisualScriptEditorPanel_Verification.cpp
3 * @brief Verification, validation, and preset panel rendering (Phase 12).
4 * @author Olympe Engine
5 * @date 2026-03-09
6 *
7 * Extracted methods:
8 - RunVerification() ~50
9 - RenderVerificationPanel() ~80
10 - RenderVerificationLogsPanel() ~50
11 - RenderValidationOverlay() ~30
12 - RenderPresetBankPanel() ~25
13 - RenderPresetItemCompact() ~15
14 */
15
17#include "../system/system_utils.h"
18#include "../third_party/imgui/imgui.h"
19#include "../TaskSystem/TaskGraphLoader.h"
20
21#include <sstream>
22#include <unordered_set>
23#include <fstream>
24
25namespace Olympe {
26
27// Phase 24 — Helper macro to log simulation traces to both UI and system log
28#define ADD_TRACE(trace_str) \
29 do { \
30 const std::string& _trace_temp = (trace_str); \
31 m_verificationLogs.push_back(_trace_temp); \
32 SYSTEM_LOG << "[SimTrace] " << _trace_temp << "\n"; \
33 } while(0)
34
36{
37 SYSTEM_LOG << "[VisualScriptEditorPanel] RunVerification() called for graph '"
38 << m_template.Name << "'\n";
40 m_verificationDone = true;
41
42 // Phase 24.3 — Populate verification logs for display in the output panel
43 m_verificationLogs.clear();
44 for (size_t i = 0; i < m_verificationResult.issues.size(); ++i)
45 {
46 const VSVerificationIssue& issue = m_verificationResult.issues[i];
47 std::string logEntry;
48
49 // Format: "[SEVERITY] message (Node: nodeID)"
51 logEntry = "[ERROR] ";
52 else if (issue.severity == VSVerificationSeverity::Warning)
53 logEntry = "[WARN] ";
54 else
55 logEntry = "[INFO] ";
56
57 logEntry += issue.message;
58 if (issue.nodeID >= 0)
59 logEntry += " (Node: " + std::to_string(issue.nodeID) + ")";
60
62 }
63
64 SYSTEM_LOG << "[VisualScriptEditorPanel] RunVerification() done: "
65 << m_verificationResult.issues.size() << " issue(s), "
66 << "errors=" << (m_verificationResult.HasErrors() ? "yes" : "no") << ", "
67 << "warnings=" << (m_verificationResult.HasWarnings() ? "yes" : "no") << "\n";
68}
69
71{
72 SYSTEM_LOG << "[VisualScriptEditorPanel] RunGraphSimulation() called for graph '"
73 << m_template.Name << "'\n";
74
75 // Clear previous simulation traces and initialize verification logs for display
76 m_simulationTraces.clear();
77 m_verificationLogs.clear();
78
79 ADD_TRACE("[SIMULATION] Graph execution simulation started");
80 ADD_TRACE("[SIMULATION] Graph: " + m_template.Name);
81 ADD_TRACE("[SIMULATION] Total nodes: " + std::to_string(m_template.Nodes.size()));
82 ADD_TRACE("[SIMULATION] Total connections: " + std::to_string(m_template.ExecConnections.size()));
83 ADD_TRACE("[SIMULATION] Blackboard entries: " + std::to_string(m_template.Blackboard.size()));
84
85 ADD_TRACE("");
86 ADD_TRACE("=== EXECUTION TRACE ===");
87
88 // Initialize blackboard with default values
89 std::map<std::string, TaskValue> blackboard;
90 for (size_t i = 0; i < m_template.Blackboard.size(); ++i)
91 {
93 }
94
95 // Phase 25 — Initialize visited graphs set for cycle detection
96 std::unordered_set<std::string> visitedGraphs;
97
98 // Find entry point
101
103 {
104 ADD_TRACE("[ERROR] No entry point or root node found!");
105 SYSTEM_LOG << "[VisualScriptEditorPanel] Simulation FAILED: No entry point\n";
106 m_simulationDone = true;
107 return;
108 }
109
110 ADD_TRACE("[START] Entry point: Node #" + std::to_string(currentNodeID));
111
112 // Phase 24.4 — Token-based execution for multi-branch support
113 // Initialize execution token stack with entry point
114 m_executionTokenStack.clear();
116
117 // Simulate graph flow
118 int stepCount = 0;
119 int maxSteps = 100;
120 std::unordered_set<int> visitedInPath;
121 int lastTokenDepth = 0; // Track depth to detect branch changes
122
123 while (!m_executionTokenStack.empty() && stepCount < maxSteps)
124 {
125 // Pop execution token from stack
127 m_executionTokenStack.pop_back();
129 int currentDepth = currentToken.depth;
130
131 // Phase 24.5 — Clear visited set when entering a new branch (depth decreased)
132 // This allows the same node to be visited in different branches
134 {
135 visitedInPath.clear();
136 ADD_TRACE("[BRANCH] Entering new execution branch - resetting cycle detection");
137 }
139
140 // Find node definition
141 const TaskNodeDefinition* nodePtr = nullptr;
142 for (size_t i = 0; i < m_template.Nodes.size(); ++i)
143 {
144 if (m_template.Nodes[i].NodeID == currentNodeID)
145 {
147 break;
148 }
149 }
150
151 if (!nodePtr)
152 {
153 ADD_TRACE("[ERROR] Node #" + std::to_string(currentNodeID) + " not found in template!");
154 break;
155 }
156
157 // Detect cycles
158 if (visitedInPath.count(currentNodeID) > 0)
159 {
160 ADD_TRACE("[CYCLE] WARNING: Cycle detected! Node #" + std::to_string(currentNodeID) +
161 " has already been visited in this path");
162 break;
163 }
165
166 // Trace node entry
167 std::ostringstream nodeEntry;
168 nodeEntry << "[ENTER] Step " << (stepCount + 1) << ": Node #" << nodePtr->NodeID;
169 if (!nodePtr->NodeName.empty())
170 nodeEntry << " '" << nodePtr->NodeName << "'";
171 ADD_TRACE(nodeEntry.str());
172
173 // Handle each node type with detailed traces
175
176 switch (nodePtr->Type)
177 {
179 {
180 ADD_TRACE(" +- [EVAL] EntryPoint - start of graph");
181 // Find next connection
182 for (size_t i = 0; i < m_template.ExecConnections.size(); ++i)
183 {
184 if (m_template.ExecConnections[i].SourceNodeID == currentNodeID)
185 {
186 nextNodeID = m_template.ExecConnections[i].TargetNodeID;
187 break;
188 }
189 }
190 ADD_TRACE(" +- [RESULT] EntryPoint completed");
191 break;
192 }
193
195 {
196 ADD_TRACE(" +- [EVAL] GetBBValue node");
198 ADD_TRACE(" | Key: '" + nodePtr->BBKey + "'");
199
200 // Find and display the value
201 auto it = blackboard.find(nodePtr->BBKey);
202 if (it != blackboard.end())
203 {
204 std::ostringstream valTrace;
205 valTrace << " | Value: " << it->second.AsString();
206 ADD_TRACE(valTrace.str());
207 }
208 else
209 {
210 ADD_TRACE(" | Value: [NOT FOUND]");
211 }
212
213 // Phase 24.7 — Trace outgoing data connections
214 ADD_TRACE(" | Outgoing data connections:");
215 bool hasDataOutput = false;
216 for (size_t i = 0; i < m_template.DataConnections.size(); ++i)
217 {
218 if (m_template.DataConnections[i].SourceNodeID == currentNodeID)
219 {
220 hasDataOutput = true;
222 const TaskNodeDefinition* targetNodePtr = nullptr;
223
224 // Find target node
225 for (size_t j = 0; j < m_template.Nodes.size(); ++j)
226 {
227 if (m_template.Nodes[j].NodeID == dataConn.TargetNodeID)
228 {
230 break;
231 }
232 }
233
234 ADD_TRACE(" +- Out -> Node #" + std::to_string(dataConn.TargetNodeID) +
235 " pin '" + dataConn.TargetPinName + "'");
236 if (targetNodePtr && !targetNodePtr->NodeName.empty())
237 ADD_TRACE(" '" + targetNodePtr->NodeName + "'");
238 }
239 }
240
241 if (!hasDataOutput)
242 {
243 ADD_TRACE(" +- (no data outputs)");
244 }
245
246 for (size_t i = 0; i < m_template.ExecConnections.size(); ++i)
247 {
248 if (m_template.ExecConnections[i].SourceNodeID == currentNodeID)
249 {
250 nextNodeID = m_template.ExecConnections[i].TargetNodeID;
251 break;
252 }
253 }
254 ADD_TRACE(" +- [RESULT] Read value from blackboard");
255 break;
256 }
257
259 {
260 ADD_TRACE(" +- [EVAL] SetBBValue node");
262 ADD_TRACE(" | Key: '" + nodePtr->BBKey + "'");
263
264 // Phase 24.7 — Trace incoming data pins (e.g., Value input from MathOp or GetBBValue)
265 // Find all data connections that target this node on a data pin
266 ADD_TRACE(" | Incoming data pins:");
267 bool hasDataInput = false;
268
269 // Phase 24.8 — For each incoming data connection, trace its upstream chain
270 std::unordered_set<int> visitedDataNodes;
271
272 for (size_t i = 0; i < m_template.DataConnections.size(); ++i)
273 {
274 if (m_template.DataConnections[i].TargetNodeID == currentNodeID)
275 {
276 hasDataInput = true;
278 const TaskNodeDefinition* sourceNodePtr = nullptr;
279
280 // Find source node
281 for (size_t j = 0; j < m_template.Nodes.size(); ++j)
282 {
283 if (m_template.Nodes[j].NodeID == dataConn.SourceNodeID)
284 {
286 break;
287 }
288 }
289
290 ADD_TRACE(" +- Pin: '" + dataConn.TargetPinName + "' from Node #" +
291 std::to_string(dataConn.SourceNodeID));
292 if (sourceNodePtr && !sourceNodePtr->NodeName.empty())
293 ADD_TRACE(" '" + sourceNodePtr->NodeName + "'");
294
295 // Phase 24.8 — Recursively trace this source node's upstream data chain
297 }
298 }
299
300 if (!hasDataInput)
301 {
302 ADD_TRACE(" +- (no data inputs)");
303 }
304
305 ADD_TRACE(" | Setting value in blackboard (simulated)");
306
307 for (size_t i = 0; i < m_template.ExecConnections.size(); ++i)
308 {
309 if (m_template.ExecConnections[i].SourceNodeID == currentNodeID)
310 {
311 nextNodeID = m_template.ExecConnections[i].TargetNodeID;
312 break;
313 }
314 }
315 ADD_TRACE(" +- [RESULT] Value written to blackboard");
316 break;
317 }
318
320 {
321 ADD_TRACE(" +- [EVAL] MathOp node");
323
324 // Search DataConnections first (independent of mathOpRef which may be out of date)
325 std::ostringstream leftOp, rightOp;
328
329 // Look for actual data connections to pins A and B
330 for (size_t i = 0; i < m_template.DataConnections.size(); ++i)
331 {
332 if (m_template.DataConnections[i].TargetNodeID == currentNodeID)
333 {
334 if (m_template.DataConnections[i].TargetPinName == "A")
335 {
337 leftOp << "Pin A: [from Node #" << leftSourceNode << "]";
338 }
339 else if (m_template.DataConnections[i].TargetPinName == "B")
340 {
342 rightOp << "Pin B: [from Node #" << rightSourceNode << "]";
343 }
344 }
345 }
346
347 // If no data connections found, fall back to mathOpRef
349 {
350 if (nodePtr->mathOpRef.leftOperand.mode == MathOpOperand::Mode::Variable)
351 leftOp << "Variable: " << nodePtr->mathOpRef.leftOperand.variableName;
352 else if (nodePtr->mathOpRef.leftOperand.mode == MathOpOperand::Mode::Const)
353 leftOp << "Const: " << nodePtr->mathOpRef.leftOperand.constValue;
354 }
355
357 {
358 if (nodePtr->mathOpRef.rightOperand.mode == MathOpOperand::Mode::Variable)
359 rightOp << "Variable: " << nodePtr->mathOpRef.rightOperand.variableName;
360 else if (nodePtr->mathOpRef.rightOperand.mode == MathOpOperand::Mode::Const)
361 rightOp << "Const: " << nodePtr->mathOpRef.rightOperand.constValue;
362 }
363
364 ADD_TRACE(" | Operator: " + nodePtr->MathOperator);
365 ADD_TRACE(" | Input A: " + leftOp.str());
366 ADD_TRACE(" | Input B: " + rightOp.str());
367 ADD_TRACE(" | Result: [computed value]");
368
369 // Phase 24.8 — Recursively trace upstream data nodes for pin inputs
370 std::unordered_set<int> visitedDataNodes;
371
372 // Trace left input (Pin mode)
374 {
375 ADD_TRACE(" | [Upstream Pin A]:");
377 }
378
379 // Trace right input (Pin mode)
381 {
382 ADD_TRACE(" | [Upstream Pin B]:");
384 }
385
386 for (size_t i = 0; i < m_template.ExecConnections.size(); ++i)
387 {
388 if (m_template.ExecConnections[i].SourceNodeID == currentNodeID)
389 {
390 nextNodeID = m_template.ExecConnections[i].TargetNodeID;
391 break;
392 }
393 }
394 ADD_TRACE(" +- [RESULT] Math operation executed");
395 break;
396 }
397
399 {
400 ADD_TRACE(" +- [EVAL] Branch node");
402 ADD_TRACE(" | Evaluating condition...");
403
404 // Simplified: always assume condition is true for simulation
405 bool conditionResult = true;
406 ADD_TRACE(" | Condition result: " + std::string(conditionResult ? "TRUE" : "FALSE"));
407
408 // Phase 24.6 — Push BOTH branches to stack (Then and Else paths)
409 // This way the simulation explores both possible execution paths
410 std::vector<ExecPinConnection> branchOutputs;
411 for (size_t i = 0; i < m_template.ExecConnections.size(); ++i)
412 {
413 if (m_template.ExecConnections[i].SourceNodeID == currentNodeID)
414 {
416 }
417 }
418
419 ADD_TRACE(" | Found " + std::to_string(branchOutputs.size()) + " branch connection(s)");
420
421 // Find Then and Else connections
424
425 for (size_t i = 0; i < branchOutputs.size(); ++i)
426 {
428 std::ostringstream connTrace;
429 connTrace << " | Connection: SourcePin='" << conn.SourcePinName << "' -> Node #" << conn.TargetNodeID;
430 ADD_TRACE(connTrace.str());
431
432 // Match pin names: "Then", "Else", "Out", "OutElse", or empty
433 if (conn.SourcePinName == "Then" || conn.SourcePinName == "Out" || conn.SourcePinName.empty())
434 {
435 thenNodeID = conn.TargetNodeID;
436 ADD_TRACE(" | -> Assigned to THEN branch (Node #" + std::to_string(thenNodeID) + ")");
437 }
438 else if (conn.SourcePinName == "Else" || conn.SourcePinName == "OutElse")
439 {
440 elseNodeID = conn.TargetNodeID;
441 ADD_TRACE(" | -> Assigned to ELSE branch (Node #" + std::to_string(elseNodeID) + ")");
442 }
443 }
444
445 // Push both branches to stack in reverse order (LIFO): Else first, then Then
446 // This ensures Then executes first (LIFO order), followed by Else
448 {
450 ADD_TRACE(" | Pushed ELSE branch to stack: Node #" + std::to_string(elseNodeID));
451 }
452
454 {
456 ADD_TRACE(" | Pushed THEN branch to stack: Node #" + std::to_string(thenNodeID));
457 }
458
460 {
461 ADD_TRACE(" | WARNING: No Then/Else branches found!");
462 }
463
464 std::string branchPath = !conditionResult ? "Else" : "Then";
465 ADD_TRACE(" +- [RESULT] Branch explored: " + branchPath + " (both queued for exploration)");
466
467 // Don't set nextNodeID - branches are already on the stack
469 break;
470 }
471
473 {
474 ADD_TRACE(" +- [EVAL] Switch node");
476 ADD_TRACE(" | Variable: '" + nodePtr->switchVariable + "'");
477 ADD_TRACE(" | Cases: " + std::to_string(nodePtr->switchCases.size()));
478
479 // Find first case connection for simulation
480 for (size_t i = 0; i < m_template.ExecConnections.size(); ++i)
481 {
482 if (m_template.ExecConnections[i].SourceNodeID == currentNodeID)
483 {
484 nextNodeID = m_template.ExecConnections[i].TargetNodeID;
485 ADD_TRACE(" | Case selected: (first available)");
486 break;
487 }
488 }
489 ADD_TRACE(" +- [RESULT] Switch case executed");
490 break;
491 }
492
494 {
495 ADD_TRACE(" +- [EVAL] Delay node");
497 std::ostringstream delayTrace;
498 delayTrace << " | Duration: " << nodePtr->DelaySeconds << " seconds";
499 ADD_TRACE(delayTrace.str());
500 ADD_TRACE(" | (Delay simulated)");
501
502 for (size_t i = 0; i < m_template.ExecConnections.size(); ++i)
503 {
504 if (m_template.ExecConnections[i].SourceNodeID == currentNodeID)
505 {
506 nextNodeID = m_template.ExecConnections[i].TargetNodeID;
507 break;
508 }
509 }
510 ADD_TRACE(" +- [RESULT] Delay completed");
511 break;
512 }
513
515 {
516 ADD_TRACE(" +- [EVAL] AtomicTask node");
517
518 // Special simplified display for Log Message task
519 if (nodePtr->AtomicTaskID == "log_message")
520 {
521 ADD_TRACE(" | Log Message");
522 // Extract and display only the message parameter
523 auto msgIt = nodePtr->Parameters.find("message");
524 if (msgIt != nodePtr->Parameters.end())
525 {
526 const std::string& msgValue = msgIt->second.LiteralValue.to_string();
527 ADD_TRACE(" | Message: " + msgValue);
528 }
529 }
530 else
531 {
532 // Standard display for other AtomicTasks
534 ADD_TRACE(" | Task type: '" + nodePtr->AtomicTaskID + "'");
535
536 // Display only relevant parameters (exclude position and internal params)
537 if (!nodePtr->Parameters.empty())
538 {
539 ADD_TRACE(" | Parameters:");
540 for (const auto& param : nodePtr->Parameters)
541 {
542 const std::string& paramName = param.first;
543 // Skip internal/position parameters
544 if (paramName.find("_pos") == 0 || paramName.find("__") == 0)
545 continue;
546
547 const ParameterBinding& binding = param.second;
548 std::string paramValue;
550 {
552 }
554 {
555 paramValue = "[Variable: " + binding.VariableName + "]";
556 }
557 else
558 {
559 paramValue = "[unknown binding]";
560 }
561
562 ADD_TRACE(" | " + paramName + " = " + paramValue);
563 }
564 }
565 }
566
567 ADD_TRACE(" | (Task execution simulated)");
568
569 // Find Completed connection
570 for (size_t i = 0; i < m_template.ExecConnections.size(); ++i)
571 {
573 if (conn.SourceNodeID == currentNodeID && conn.SourcePinName == "Completed")
574 {
575 nextNodeID = conn.TargetNodeID;
576 break;
577 }
578 }
579 ADD_TRACE(" +- [RESULT] Task completed");
580 break;
581 }
582
584 {
585 ADD_TRACE(" +- [EVAL] Sequence node");
587
588 // Phase 24 Enhancement: Collect ALL outgoing exec connections from this Sequence
589 std::vector<ExecPinConnection> sequenceOutputs;
590 for (size_t i = 0; i < m_template.ExecConnections.size(); ++i)
591 {
592 if (m_template.ExecConnections[i].SourceNodeID == currentNodeID)
593 {
595 }
596 }
597
598 ADD_TRACE(" | Output pins: " + std::to_string(sequenceOutputs.size()));
599 ADD_TRACE(" | Executing sequence outputs:");
600
601 // Execute each output branch in order
602 for (size_t oi = 0; oi < sequenceOutputs.size(); ++oi)
603 {
605
606 // Find output node
607 const TaskNodeDefinition* outNodePtr = nullptr;
608 for (size_t i = 0; i < m_template.Nodes.size(); ++i)
609 {
610 if (m_template.Nodes[i].NodeID == outConn.TargetNodeID)
611 {
613 break;
614 }
615 }
616
617 std::ostringstream outTrace;
618 outTrace << " +- Output [" << (oi + 1) << "] -> Node #" << outConn.TargetNodeID;
619 if (outNodePtr && !outNodePtr->NodeName.empty())
620 outTrace << " '" << outNodePtr->NodeName << "'";
621 ADD_TRACE(outTrace.str());
622 }
623
624 // Phase 24.4 — Push all branch tokens to stack in reverse order (LIFO)
625 for (int oi = static_cast<int>(sequenceOutputs.size()) - 1; oi >= 0; --oi)
626 {
628 m_executionTokenStack.push_back(ExecutionToken(outConn.TargetNodeID, currentDepth + 1));
629 }
630
631 ADD_TRACE(" +- [RESULT] Sequence with " + std::to_string(sequenceOutputs.size()) + " branches");
632 break;
633 }
634
636 {
637 ADD_TRACE(" +- [EVAL] While loop node");
639
640 // Trace condition evaluation
641 ADD_TRACE(" | Conditions:");
642 if (!nodePtr->conditions.empty())
643 {
644 for (size_t ci = 0; ci < nodePtr->conditions.size(); ++ci)
645 {
646 const Condition& cond = nodePtr->conditions[ci];
647 ADD_TRACE(" | Condition #" + std::to_string(ci + 1) +
648 ": " + cond.leftVariable + " " + cond.operatorStr + " " + cond.rightVariable);
649 }
650 }
651 else
652 {
653 ADD_TRACE(" | (no conditions defined)");
654 }
655
656 // Simplified: assume condition is true for simulation
657 ADD_TRACE(" | Evaluating condition... TRUE (Loop continues)");
658
659 // Find Loop and Completed output pins
662
663 for (size_t i = 0; i < m_template.ExecConnections.size(); ++i)
664 {
666 if (conn.SourceNodeID == currentNodeID)
667 {
668 if (conn.SourcePinName == "Loop" || conn.SourcePinName == "OutLoop")
669 loopNodeID = conn.TargetNodeID;
670 else if (conn.SourcePinName == "Completed" || conn.SourcePinName == "OutCompleted")
671 completedNodeID = conn.TargetNodeID;
672 }
673 }
674
676 {
677 ADD_TRACE(" | Loop output -> Node #" + std::to_string(loopNodeID));
679 }
681 {
682 ADD_TRACE(" | (Loop condition false - would exit)");
684 }
685
686 ADD_TRACE(" +- [RESULT] Loop iteration executed");
687 break;
688 }
689
691 {
692 ADD_TRACE(" +- [EVAL] ForEach loop node");
694 ADD_TRACE(" | (ForEach iteration parameters pending implementation)");
695
696 // Find Loop Body and Completed output pins
699
700 for (size_t i = 0; i < m_template.ExecConnections.size(); ++i)
701 {
703 if (conn.SourceNodeID == currentNodeID)
704 {
705 if (conn.SourcePinName == "Loop Body" || conn.SourcePinName == "OutLoop")
706 loopBodyNodeID = conn.TargetNodeID;
707 else if (conn.SourcePinName == "Completed" || conn.SourcePinName == "OutCompleted")
708 completedNodeID = conn.TargetNodeID;
709 }
710 }
711
712 // Simulate iteration
713 ADD_TRACE(" | Iterating list (simulated)");
715 {
716 ADD_TRACE(" | Loop Body -> Node #" + std::to_string(loopBodyNodeID));
718 }
720 {
721 ADD_TRACE(" | List empty or iterations completed");
723 }
724
725 ADD_TRACE(" +- [RESULT] ForEach loop executed");
726 break;
727 }
728
730 {
731 ADD_TRACE(" +- [EVAL] SubGraph node");
733
734 // Phase 25 — Get SubGraphPath from either field or Parameters
735 std::string subGraphPath = nodePtr->SubGraphPath;
736 if (subGraphPath.empty())
737 {
738 auto it = nodePtr->Parameters.find("subgraph_path");
739 if (it != nodePtr->Parameters.end() &&
740 it->second.Type == ParameterBindingType::Literal)
741 {
742 subGraphPath = it->second.LiteralValue.to_string();
743 }
744 }
745
746 ADD_TRACE(" | Path: '" + subGraphPath + "'");
747
748 // Trace input parameters if available
749 if (!nodePtr->InputParams.empty())
750 {
751 ADD_TRACE(" | Input Parameters:");
752 for (const auto& param : nodePtr->InputParams)
753 {
754 const std::string& paramName = param.first;
755 const ParameterBinding& binding = param.second;
756
757 std::string paramValue;
759 {
761 }
763 {
764 paramValue = "[Variable: " + binding.VariableName + "]";
765 }
766 else
767 {
768 paramValue = "[unknown binding]";
769 }
770
771 ADD_TRACE(" | " + paramName + " = " + paramValue);
772 }
773 }
774
775 // Phase 25 — Recursive SubGraph loading and execution
776 if (!subGraphPath.empty())
777 {
778 // Try to resolve and load the SubGraph
779 // Phase 26: Normalize path - convert forward slashes to backslashes for Windows
780 std::string normalizedPath = subGraphPath;
781 for (size_t i = 0; i < normalizedPath.size(); ++i)
782 {
783 if (normalizedPath[i] == '/') normalizedPath[i] = '\\';
784 }
785
786 std::string resolvedPath;
787 std::string searchDirs[] = {
788 "./Blueprints/",
789 "./OlympeBlueprintEditor/Blueprints/",
790 "./Gamedata/"
791 };
792
793 // Try with normalized path first
794 for (size_t dirIdx = 0; dirIdx < 3; ++dirIdx)
795 {
796 std::string candidate = searchDirs[dirIdx] + normalizedPath;
797 std::ifstream testFile(candidate);
798 if (testFile.good())
799 {
801 break;
802 }
803 }
804
805 // If not found, try original path as fallback
806 if (resolvedPath.empty())
807 {
808 for (size_t dirIdx = 0; dirIdx < 3; ++dirIdx)
809 {
810 std::string candidate = searchDirs[dirIdx] + subGraphPath;
811 std::ifstream testFile(candidate);
812 if (testFile.good())
813 {
815 break;
816 }
817 }
818 }
819
820 if (!resolvedPath.empty())
821 {
822 std::vector<std::string> loadErrors;
824
826 {
827 ADD_TRACE(" | === ENTERING SUBGRAPH ===");
828 ADD_TRACE(" | File: " + resolvedPath);
829
830 // Phase 25 — Create isolated blackboard for this SubGraph
831 // Start with copy of current blackboard (parent values available as inputs)
832 std::map<std::string, TaskValue> isolatedBlackboard = blackboard;
833
834 // Phase 25 — Call recursive simulation with cycle detection
839 0, // Recursion depth: 0 for root SubGraph call
840 " | ");
841
842 // Phase 25 — Map output parameters back to parent blackboard
843 if (!nodePtr->OutputParams.empty())
844 {
845 ADD_TRACE(" | Output Parameter Mapping:");
846 for (const auto& output : nodePtr->OutputParams)
847 {
848 const std::string& outputName = output.first;
849 const std::string& bbKey = output.second;
850
851 auto it = isolatedBlackboard.find(outputName);
852 if (it != isolatedBlackboard.end())
853 {
854 blackboard[bbKey] = it->second;
855 ADD_TRACE(" | " + outputName + " -> [" + bbKey + "]");
856 }
857 else
858 {
859 ADD_TRACE(" | [WARNING] Output '" + outputName + "' not found in SubGraph context");
860 }
861 }
862 }
863
864 ADD_TRACE(" | === EXITING SUBGRAPH ===");
865
866 // Clean up
867 delete subGraphTemplate;
868 subGraphTemplate = nullptr;
869 }
870 else
871 {
872 ADD_TRACE(" | [ERROR] Failed to load SubGraph: " + resolvedPath);
873 for (size_t errIdx = 0; errIdx < loadErrors.size(); ++errIdx)
874 {
875 ADD_TRACE(" | - " + loadErrors[errIdx]);
876 }
877 }
878 }
879 else
880 {
881 ADD_TRACE(" | [ERROR] SubGraph file not found in any search directory: " + subGraphPath);
882 }
883 }
884 else
885 {
886 ADD_TRACE(" | [ERROR] SubGraph path is empty!");
887 }
888
889 // Trace output parameter mappings if available
890 if (!nodePtr->OutputParams.empty())
891 {
892 ADD_TRACE(" | Output Parameters (return values):");
893 for (const auto& output : nodePtr->OutputParams)
894 {
895 const std::string& outputName = output.first;
896 const std::string& bbKey = output.second;
897 ADD_TRACE(" | " + outputName + " -> Blackboard['" + bbKey + "']");
898 }
899 }
900
901 // Find Completed output pin
902 for (size_t i = 0; i < m_template.ExecConnections.size(); ++i)
903 {
905 if (conn.SourceNodeID == currentNodeID && conn.SourcePinName == "Completed")
906 {
907 nextNodeID = conn.TargetNodeID;
908 break;
909 }
910 }
911 ADD_TRACE(" +- [RESULT] SubGraph completed");
912 break;
913 }
914
915 default:
916 {
917 std::ostringstream unknownNode;
918 unknownNode << " +- [EVAL] Node type: " << static_cast<int>(nodePtr->Type);
919 ADD_TRACE(unknownNode.str());
920
921 for (size_t i = 0; i < m_template.ExecConnections.size(); ++i)
922 {
923 if (m_template.ExecConnections[i].SourceNodeID == currentNodeID)
924 {
925 nextNodeID = m_template.ExecConnections[i].TargetNodeID;
926 break;
927 }
928 }
929 ADD_TRACE(" +- [RESULT] Node processed");
930 break;
931 }
932 }
933
934 // Phase 24.4 — Token-based exit trace
935 // Trace node exit and token stack status
936 // Always push nextNodeID if set (for single-branch nodes like Branch, Delay, etc.)
938 {
940 ADD_TRACE("[EXIT] -> Pushed: Node #" + std::to_string(nextNodeID));
941 }
942 // If stack still has tokens, show the next one that will execute
943 else if (!m_executionTokenStack.empty())
944 {
946 ADD_TRACE("[EXIT] -> Next token from stack: Node #" + std::to_string(nextToken.nodeID) +
947 " (stack depth: " + std::to_string(m_executionTokenStack.size()) + ")");
948 }
949 else
950 {
951 ADD_TRACE("[EXIT] -> Graph complete (all tokens consumed)");
952 }
953
954 ADD_TRACE(""); // Blank line for readability
955
956 ++stepCount;
957 }
958
959 // Final summary
960 ADD_TRACE("=== EXECUTION SUMMARY ===");
961 if (stepCount >= maxSteps)
962 {
963 ADD_TRACE("[WARNING] Maximum steps reached (" + std::to_string(maxSteps) + ") - possible infinite loop");
964 }
965 else if (m_executionTokenStack.empty())
966 {
967 ADD_TRACE("[SUCCESS] Graph execution completed - all branches finished");
968 }
969 else
970 {
971 ADD_TRACE("[WARNING] Execution incomplete - " + std::to_string(m_executionTokenStack.size()) + " token(s) remaining on stack");
972 }
973 ADD_TRACE("Total steps executed: " + std::to_string(stepCount));
974 ADD_TRACE("Blackboard entries evaluated: " + std::to_string(blackboard.size()));
975
976 m_simulationDone = true;
977 m_verificationDone = true; // Phase 26 — Enable verification output panel display
978
979 SYSTEM_LOG << "[VisualScriptEditorPanel] Simulation completed: " << stepCount << " steps\n";
980}
981
983{
984 ImGui::Separator();
985 ImGui::TextDisabled("Graph Verification");
986
988 {
989 ImGui::TextDisabled("Click 'Verify' in toolbar to run verification.");
990 return;
991 }
992
993 // Global status line
995 {
996 int errorCount = 0;
997 for (size_t i = 0; i < m_verificationResult.issues.size(); ++i)
998 {
1000 ++errorCount;
1001 }
1002 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
1003 "Errors found: %d", errorCount);
1004 }
1006 {
1007 ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.0f, 1.0f), "OK — warnings present");
1008 }
1009 else
1010 {
1011 ImGui::TextColored(ImVec4(0.3f, 1.0f, 0.3f, 1.0f), "OK — no issues");
1012 }
1013
1014 if (m_verificationResult.issues.empty())
1015 return;
1016
1017 // List issues grouped: Errors first, then Warnings, then Info
1022 };
1023
1024 for (int s = 0; s < 3; ++s)
1025 {
1027 for (size_t i = 0; i < m_verificationResult.issues.size(); ++i)
1028 {
1029 const VSVerificationIssue& issue = m_verificationResult.issues[i];
1030 if (issue.severity != sev)
1031 continue;
1032
1033 ImGui::PushID(static_cast<int>(i));
1034
1036 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "[E]");
1038 ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.0f, 1.0f), "[W]");
1039 else
1040 ImGui::TextColored(ImVec4(0.4f, 0.7f, 1.0f, 1.0f), "[I]");
1041
1042 ImGui::SameLine();
1043 ImGui::Text("%s: %s", issue.ruleID.c_str(), issue.message.c_str());
1044
1045 if (issue.nodeID >= 0)
1046 {
1047 ImGui::SameLine();
1048 std::string btnLabel = "Go##go" + std::to_string(i);
1049 if (ImGui::SmallButton(btnLabel.c_str()))
1050 {
1051 m_focusNodeID = issue.nodeID;
1052 m_selectedNodeID = issue.nodeID;
1053 }
1054 }
1055
1056 ImGui::PopID();
1057 }
1058 }
1059}
1060
1062{
1063 // Note: The header "Verification Output" is rendered by the container (BlueprintEditorGUI),
1064 // so we only render the content here (status + logs).
1065
1066 if (!m_verificationDone)
1067 {
1068 ImGui::TextDisabled("(Click 'Verify' or 'Run Graph' button to generate output)");
1069 return;
1070 }
1071
1072 // Display verification result summary
1073 ImGui::Spacing();
1074
1075 // Status line with color coding
1077 {
1078 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
1079 "[ERROR] Graph has %d error(s)",
1080 (int)m_verificationResult.issues.size());
1081 }
1083 {
1084 ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.0f, 1.0f),
1085 "[WARNING] Graph is valid but has warnings");
1086 }
1087 else
1088 {
1089 ImGui::TextColored(ImVec4(0.3f, 1.0f, 0.3f, 1.0f),
1090 "[OK] Graph is valid - no issues found");
1091 }
1092
1093 ImGui::Separator();
1094
1095 // Display all logs (verification issues + simulation traces)
1096 ImGui::BeginChild("VerificationLogsChild", ImVec2(0, 0), true);
1097
1098 // If we have logs (from simulation or verification), display them
1099 if (!m_verificationLogs.empty())
1100 {
1101 for (size_t i = 0; i < m_verificationLogs.size(); ++i)
1102 {
1103 const std::string& logEntry = m_verificationLogs[i];
1104
1105 // Color-code based on log entry content
1106 ImVec4 logColor = ImVec4(0.8f, 0.8f, 0.8f, 1.0f); // Default: white
1107
1108 if (logEntry.find("[ERROR]") != std::string::npos)
1109 logColor = ImVec4(1.0f, 0.3f, 0.3f, 1.0f); // Red
1110 else if (logEntry.find("[WARN]") != std::string::npos)
1111 logColor = ImVec4(1.0f, 0.85f, 0.0f, 1.0f); // Yellow
1112 else if (logEntry.find("[INFO]") != std::string::npos)
1113 logColor = ImVec4(0.5f, 0.8f, 1.0f, 1.0f); // Light blue
1114 else if (logEntry.find("[SIMULATION]") != std::string::npos)
1115 logColor = ImVec4(0.4f, 1.0f, 0.4f, 1.0f); // Green
1116 else if (logEntry.find("[ENTER]") != std::string::npos)
1117 logColor = ImVec4(0.6f, 0.8f, 1.0f, 1.0f); // Light blue
1118 else if (logEntry.find("[EXIT]") != std::string::npos)
1119 logColor = ImVec4(0.8f, 0.8f, 1.0f, 1.0f); // Very light blue
1120 else if (logEntry.find("[EVAL]") != std::string::npos)
1121 logColor = ImVec4(0.9f, 0.7f, 0.9f, 1.0f); // Light purple
1122 else if (logEntry.find("[RESULT]") != std::string::npos)
1123 logColor = ImVec4(0.7f, 1.0f, 0.7f, 1.0f); // Light green
1124 else if (logEntry.find("[CYCLE]") != std::string::npos)
1125 logColor = ImVec4(1.0f, 0.5f, 0.0f, 1.0f); // Orange
1126 else if (logEntry.find("[WARNING]") != std::string::npos)
1127 logColor = ImVec4(1.0f, 1.0f, 0.3f, 1.0f); // Bright yellow
1128 else if (logEntry.find("[SUCCESS]") != std::string::npos)
1129 logColor = ImVec4(0.2f, 1.0f, 0.2f, 1.0f); // Bright green
1130 else if (logEntry.find("===") != std::string::npos)
1131 logColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // White for separators
1132
1133 ImGui::TextColored(logColor, "%s", logEntry.c_str());
1134 }
1135 }
1136 else if (!m_verificationResult.issues.empty())
1137 {
1138 // Fallback: Display verification issues if no simulation logs
1143 };
1144
1145 const char* sevLabels[3] = { "[ERROR]", "[WARN]", "[INFO]" };
1146 ImVec4 sevColors[3] = {
1147 ImVec4(1.0f, 0.3f, 0.3f, 1.0f), // Error: red
1148 ImVec4(1.0f, 0.85f, 0.0f, 1.0f), // Warning: yellow
1149 ImVec4(0.5f, 0.8f, 1.0f, 1.0f) // Info: light blue
1150 };
1151
1152 for (int s = 0; s < 3; ++s)
1153 {
1155
1156 // Display all issues with this severity
1157 for (size_t i = 0; i < m_verificationResult.issues.size(); ++i)
1158 {
1159 const VSVerificationIssue& issue = m_verificationResult.issues[i];
1160 if (issue.severity != sev)
1161 continue;
1162
1163 // Format message: "[SEVERITY] message (NodeID: xxx)"
1164 std::string message = issue.message;
1165 if (issue.nodeID >= 0)
1166 {
1167 message += " (Node: " + std::to_string(issue.nodeID) + ")";
1168 }
1169
1170 ImGui::TextColored(sevColors[s], "%s: %s", sevLabels[s], message.c_str());
1171 }
1172 }
1173 }
1174 else
1175 {
1176 ImGui::TextDisabled("(No logs to display)");
1177 }
1178
1179 ImGui::EndChild();
1180}
1181
1183{
1184 m_validationWarnings.clear();
1185 m_validationErrors.clear();
1186
1187 // Check: every non-EntryPoint node should have at least one exec-in connection
1188 for (size_t i = 0; i < m_editorNodes.size(); ++i)
1189 {
1190 const VSEditorNode& eNode = m_editorNodes[i];
1191 if (eNode.def.Type == TaskNodeType::EntryPoint)
1192 continue;
1193
1194 bool hasExecIn = false;
1195 for (size_t c = 0; c < m_template.ExecConnections.size(); ++c)
1196 {
1197 if (m_template.ExecConnections[c].TargetNodeID == eNode.nodeID)
1198 {
1199 hasExecIn = true;
1200 break;
1201 }
1202 }
1203 if (!hasExecIn)
1204 {
1205 m_validationErrors.push_back(
1206 "Node " + std::to_string(eNode.nodeID) + " (" +
1207 eNode.def.NodeName + "): no exec-in connection");
1208 }
1209
1210 // SubGraph path validation
1211 if (eNode.def.Type == TaskNodeType::SubGraph &&
1212 eNode.def.SubGraphPath.empty())
1213 {
1214 m_validationWarnings.push_back(
1215 "Node " + std::to_string(eNode.nodeID) +
1216 " (SubGraph): SubGraphPath is empty");
1217 }
1218 }
1219}
1220
1222{
1223 ImGui::TextDisabled("Preset Bank (Global)");
1224 ImGui::Separator();
1225
1226 if (!m_libraryPanel)
1227 return;
1228
1230
1231 // Toolbar: Add preset button
1232 if (ImGui::Button("+##addpreset", ImVec2(25, 0)))
1233 {
1234 m_libraryPanel->OnAddPresetClicked();
1235 }
1236 ImGui::SameLine();
1237 ImGui::TextDisabled("New Preset");
1238
1239 ImGui::Separator();
1240 ImGui::TextDisabled("Total: %zu preset(s)", presetCount);
1241 ImGui::Separator();
1242
1243 // List all presets in compact horizontal format
1244 std::vector<ConditionPreset> allPresets = m_presetRegistry.GetFilteredPresets("");
1245
1246 if (allPresets.empty())
1247 {
1248 ImGui::TextDisabled("(no presets - create one to get started)");
1249 }
1250
1251 for (size_t i = 0; i < allPresets.size(); ++i)
1252 {
1253 const ConditionPreset& preset = allPresets[i];
1254 ImGui::PushID(preset.id.c_str());
1255 RenderPresetItemCompact(preset, i + 1); // 1-indexed for display
1256 ImGui::PopID();
1257 }
1258}
1259
1260void VisualScriptEditorPanel::RenderPresetItemCompact(const ConditionPreset& preset, size_t index)
1261{
1262#ifndef OLYMPE_HEADLESS
1263 // Single-line horizontal layout matching mockup:
1264 // [Index: Name (yellow)] [Left▼ mode] [value] [Op▼] [Right▼ mode] [value] [Edit] [Dup] [X]
1265
1266 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.2f, 0.2f, 1.0f));
1267 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
1268 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.4f, 0.4f, 0.4f, 1.0f));
1269
1270 // Get a mutable copy of the preset for editing
1271 ConditionPreset editablePreset = preset;
1272 bool presetModified = false;
1273
1274 // Condition name display with index (yellow)
1275 // Use PushID for unique identification, don't add UUID to visible text
1276 ImGui::PushID(editablePreset.id.c_str());
1277 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), "Condition #%zu", index);
1278 ImGui::PopID();
1279 ImGui::SameLine(0.0f, 12.0f);
1280
1281 // Left operand with unified dropdown (mode + value combined)
1282 if (RenderOperandEditor(editablePreset.left, "##left_op"))
1283 {
1284 presetModified = true;
1285 }
1286 ImGui::SameLine(0.0f, 6.0f);
1287
1288 // Operator dropdown
1289 std::string opStr;
1290 switch (editablePreset.op)
1291 {
1292 case ComparisonOp::Equal: opStr = "=="; break;
1293 case ComparisonOp::NotEqual: opStr = "!="; break;
1294 case ComparisonOp::Less: opStr = "<"; break;
1295 case ComparisonOp::LessEqual: opStr = "<="; break;
1296 case ComparisonOp::Greater: opStr = ">"; break;
1297 case ComparisonOp::GreaterEqual: opStr = ">="; break;
1298 default: opStr = "?"; break;
1299 }
1300
1301 const char* opNames[] = { "==", "!=", "<", "<=", ">", ">=" };
1302 const ComparisonOp opValues[] = {
1306 };
1307 int curOpIdx = 0;
1308 for (int i = 0; i < 6; ++i)
1309 {
1310 if (editablePreset.op == opValues[i])
1311 {
1312 curOpIdx = i;
1313 break;
1314 }
1315 }
1316
1317 ImGui::SetNextItemWidth(50.0f);
1318 if (ImGui::Combo("##op_type", &curOpIdx, opNames, 6))
1319 {
1321 presetModified = true;
1322 }
1323 ImGui::SameLine(0.0f, 6.0f);
1324
1325 // Right operand with unified dropdown (mode + value combined)
1326 if (RenderOperandEditor(editablePreset.right, "##right_op"))
1327 {
1328 presetModified = true;
1329 }
1330 ImGui::SameLine(0.0f, 12.0f);
1331
1332 // Save modified preset if changed
1333 if (presetModified)
1334 {
1336
1337 // Phase 24 — Sync to template presets for graph serialization
1338 // Update the preset in m_template.Presets so it gets saved with the graph
1339 for (size_t pi = 0; pi < m_template.Presets.size(); ++pi)
1340 {
1341 if (m_template.Presets[pi].id == editablePreset.id)
1342 {
1344 break;
1345 }
1346 }
1347
1348 m_dirty = true;
1349 }
1350
1351 // Duplicate button
1352 if (ImGui::Button("Dup##dup_preset", ImVec2(40, 0)))
1353 {
1355
1356 // Phase 24 — Add the duplicate to template presets as well
1357 if (!newPresetID.empty())
1358 {
1359 const ConditionPreset* newPreset = m_presetRegistry.GetPreset(newPresetID);
1360 if (newPreset)
1361 {
1362 m_template.Presets.push_back(*newPreset);
1363 }
1364 }
1365
1366 m_dirty = true;
1367 }
1368 ImGui::SameLine(0.0f, 4.0f);
1369
1370 // Delete button
1371 if (ImGui::Button("X##del_preset", ImVec2(25, 0)))
1372 {
1374 m_pinManager->InvalidatePreset(editablePreset.id);
1375
1376 // Phase 24 — Remove from template presets as well
1377 for (size_t pi = 0; pi < m_template.Presets.size(); ++pi)
1378 {
1379 if (m_template.Presets[pi].id == editablePreset.id)
1380 {
1381 m_template.Presets.erase(m_template.Presets.begin() + pi);
1382 break;
1383 }
1384 }
1385 // Persist the deletion to disk
1386 m_presetRegistry.Save("Blueprints/Presets/condition_presets.json");
1387 }
1388
1389 ImGui::PopStyleColor(3);
1390
1391 // Add visual separator between presets
1392 ImGui::Separator();
1393#endif
1394}
1395
1396// ============================================================================
1397// Phase 24 — Graph Simulation Helper Methods
1398// ============================================================================
1399
1400void VisualScriptEditorPanel::EvaluateDataNode(int32_t nodeID, int depth, const std::string& indent)
1401{
1402 // Find node definition
1403 const TaskNodeDefinition* nodePtr = nullptr;
1404 for (size_t i = 0; i < m_template.Nodes.size(); ++i)
1405 {
1406 if (m_template.Nodes[i].NodeID == nodeID)
1407 {
1409 break;
1410 }
1411 }
1412
1413 if (!nodePtr)
1414 return;
1415
1416 // Only evaluate pure data nodes (non-execution nodes)
1417 switch (nodePtr->Type)
1418 {
1420 {
1421 ADD_TRACE(indent + "+- [DATA] GetBBValue #" + std::to_string(nodeID));
1422 if (!nodePtr->NodeName.empty())
1423 ADD_TRACE(indent + "| Name: '" + nodePtr->NodeName + "'");
1424 ADD_TRACE(indent + "| Key: '" + nodePtr->BBKey + "'");
1425 ADD_TRACE(indent + "+- -> Returns value from blackboard");
1426 break;
1427 }
1428
1430 {
1431 ADD_TRACE(indent + "+- [DATA] MathOp #" + std::to_string(nodeID));
1432 if (!nodePtr->NodeName.empty())
1433 ADD_TRACE(indent + "| Name: '" + nodePtr->NodeName + "'");
1434 ADD_TRACE(indent + "| Op: " + nodePtr->MathOperator);
1435
1436 // Trace left operand recursively if it's a data node reference
1437 std::string leftDesc = "[const/var]";
1438 if (nodePtr->mathOpRef.leftOperand.mode == MathOpOperand::Mode::Variable)
1439 leftDesc = "Var: " + nodePtr->mathOpRef.leftOperand.variableName;
1440 else if (nodePtr->mathOpRef.leftOperand.mode == MathOpOperand::Mode::Const)
1441 leftDesc = "Const: " + nodePtr->mathOpRef.leftOperand.constValue;
1442 else if (nodePtr->mathOpRef.leftOperand.mode == MathOpOperand::Mode::Pin)
1443 leftDesc = "Pin input";
1444
1445 ADD_TRACE(indent + "| Left: " + leftDesc);
1446
1447 // Trace right operand recursively if it's a data node reference
1448 std::string rightDesc = "[const/var]";
1449 if (nodePtr->mathOpRef.rightOperand.mode == MathOpOperand::Mode::Variable)
1450 rightDesc = "Var: " + nodePtr->mathOpRef.rightOperand.variableName;
1451 else if (nodePtr->mathOpRef.rightOperand.mode == MathOpOperand::Mode::Const)
1452 rightDesc = "Const: " + nodePtr->mathOpRef.rightOperand.constValue;
1453 else if (nodePtr->mathOpRef.rightOperand.mode == MathOpOperand::Mode::Pin)
1454 rightDesc = "Pin input";
1455
1456 ADD_TRACE(indent + "| Right: " + rightDesc);
1457 ADD_TRACE(indent + "+- -> Result value");
1458 break;
1459 }
1460
1461 default:
1462 // Other data node types can be added here
1463 break;
1464 }
1465}
1466
1467// Phase 24.10 — Format task parameters for display in traces
1468// Extracts and formats all parameters from a ParameterBinding map
1469std::string VisualScriptEditorPanel::FormatTaskParameters(const std::unordered_map<std::string, ParameterBinding>& parameters,
1470 const std::string& indent)
1471{
1472 if (parameters.empty())
1473 return "";
1474
1475 std::ostringstream ss;
1476 ss << "\n" << indent << "Parameters:";
1477
1478 for (const auto& param : parameters)
1479 {
1480 const std::string& paramName = param.first;
1481 const ParameterBinding& binding = param.second;
1482
1483 ss << "\n" << indent << " " << paramName << " = ";
1484
1486 {
1488 }
1490 {
1491 ss << "[Variable: " << binding.VariableName << "]";
1492 }
1493 }
1494
1495 return ss.str();
1496}
1497
1498// Phase 24.8 — Recursive tracing of upstream data nodes (pure data chains)
1499// Traces all upstream pure data nodes (GetBBValue, MathOp, etc.) with recursive depth
1501 const std::string& indent,
1502 std::unordered_set<int>& visitedDataNodes)
1503{
1504 // Prevent infinite loops in data graphs
1505 if (visitedDataNodes.count(sourceNodeID) > 0)
1506 {
1507 ADD_TRACE(indent + "+- [CYCLE] Already traced: Node #" + std::to_string(sourceNodeID));
1508 return;
1509 }
1511
1512 // Find node definition
1513 const TaskNodeDefinition* nodePtr = nullptr;
1514 for (size_t i = 0; i < m_template.Nodes.size(); ++i)
1515 {
1516 if (m_template.Nodes[i].NodeID == sourceNodeID)
1517 {
1519 break;
1520 }
1521 }
1522
1523 if (!nodePtr)
1524 {
1525 ADD_TRACE(indent + "+- [ERROR] Node #" + std::to_string(sourceNodeID) + " not found");
1526 return;
1527 }
1528
1529 // Determine if this is a pure data node (non-execution)
1530 bool isPureDataNode = false;
1531 std::string nodeTypeStr = "";
1532
1533 switch (nodePtr->Type)
1534 {
1536 isPureDataNode = true;
1537 nodeTypeStr = "GetBBValue";
1538 break;
1540 isPureDataNode = true;
1541 nodeTypeStr = "MathOp";
1542 break;
1543 default:
1544 break;
1545 }
1546
1547 if (!isPureDataNode)
1548 {
1549 // Non-data node: trace once and stop recursion
1550 ADD_TRACE(indent + "+- [NON-DATA] Node #" + std::to_string(sourceNodeID) +
1551 " (" + nodeTypeStr + ")");
1552 return;
1553 }
1554
1555 // Trace this data node with its properties
1556 ADD_TRACE(indent + "+- [DATA] " + nodeTypeStr + " #" + std::to_string(sourceNodeID));
1557 if (!nodePtr->NodeName.empty())
1558 ADD_TRACE(indent + "| Name: '" + nodePtr->NodeName + "'");
1559
1560 // Add type-specific properties
1561 if (nodePtr->Type == TaskNodeType::GetBBValue)
1562 {
1563 ADD_TRACE(indent + "| Key: '" + nodePtr->BBKey + "'");
1564 ADD_TRACE(indent + "| (Source: blackboard)");
1565 }
1566 else if (nodePtr->Type == TaskNodeType::MathOp)
1567 {
1568 ADD_TRACE(indent + "| Operator: " + nodePtr->MathOperator);
1569
1570 // Trace left and right operands by searching DataConnections directly
1571 // (instead of relying on mathOpRef which may be out of date)
1574 std::string leftDesc = "";
1575 std::string rightDesc = "";
1576
1577 // Search for data connections where this node's input pins are targeted
1578 for (size_t i = 0; i < m_template.DataConnections.size(); ++i)
1579 {
1580 if (m_template.DataConnections[i].TargetNodeID == sourceNodeID)
1581 {
1582 if (m_template.DataConnections[i].TargetPinName == "A")
1583 {
1585 leftDesc = "[Pin A from Node #" + std::to_string(leftSourceNode) + "]";
1586 }
1587 else if (m_template.DataConnections[i].TargetPinName == "B")
1588 {
1590 rightDesc = "[Pin B from Node #" + std::to_string(rightSourceNode) + "]";
1591 }
1592 }
1593 }
1594
1595 // If no data connections found for pins, fall back to mathOpRef
1597 {
1598 if (nodePtr->mathOpRef.leftOperand.mode == MathOpOperand::Mode::Variable)
1599 leftDesc = "Variable: " + nodePtr->mathOpRef.leftOperand.variableName;
1600 else if (nodePtr->mathOpRef.leftOperand.mode == MathOpOperand::Mode::Const)
1601 leftDesc = "Const: " + nodePtr->mathOpRef.leftOperand.constValue;
1602 }
1603
1605 {
1606 if (nodePtr->mathOpRef.rightOperand.mode == MathOpOperand::Mode::Variable)
1607 rightDesc = "Variable: " + nodePtr->mathOpRef.rightOperand.variableName;
1608 else if (nodePtr->mathOpRef.rightOperand.mode == MathOpOperand::Mode::Const)
1609 rightDesc = "Const: " + nodePtr->mathOpRef.rightOperand.constValue;
1610 }
1611
1612 ADD_TRACE(indent + "| Left: " + leftDesc);
1613 ADD_TRACE(indent + "| Right: " + rightDesc);
1614
1615 // Recursively trace left pin input if it's a data node
1617 {
1618 ADD_TRACE(indent + "| [Upstream Left (Pin A)]:");
1620 }
1621
1622 // Recursively trace right pin input if it's a data node
1624 {
1625 ADD_TRACE(indent + "| [Upstream Right (Pin B)]:");
1627 }
1628 }
1629}
1630
1631// Phase 24.9 — Get comprehensive property string for any node type
1632// Returns a formatted string with all relevant properties for display
1634{
1635 std::ostringstream ss;
1636
1637 switch (node.Type)
1638 {
1640 ss << "EntryPoint";
1641 break;
1642
1644 ss << "GetBBValue | Key: '" << node.BBKey << "'";
1645 break;
1646
1648 ss << "SetBBValue | Key: '" << node.BBKey << "'";
1649 break;
1650
1652 ss << "MathOp | Op: " << node.MathOperator;
1653 break;
1654
1656 ss << "Branch | Type: Conditional";
1657 break;
1658
1660 ss << "Switch | Var: '" << node.switchVariable << "' | Cases: " << node.switchCases.size();
1661 break;
1662
1664 ss << "Delay | Duration: " << node.DelaySeconds << "s";
1665 break;
1666
1668 {
1669 ss << "AtomicTask | TaskID: '" << node.AtomicTaskID << "'";
1670 // Add first parameter or message for display
1671 if (!node.Parameters.empty())
1672 {
1673 auto it = node.Parameters.begin();
1674 ss << " | " << it->first << ": " << it->second.LiteralValue.to_string();
1675 }
1676 break;
1677 }
1678
1680 ss << "Sequence";
1681 break;
1682
1684 ss << "While loop";
1685 break;
1686
1688 ss << "SubGraph | Path: '" << node.SubGraphPath << "'";
1689 break;
1690
1692 ss << "ForEach";
1693 break;
1694
1696 ss << "DoOnce";
1697 break;
1698
1699 default:
1700 ss << "Unknown node type";
1701 break;
1702 }
1703
1704 return ss.str();
1705}
1706
1707// Phase 25 — Recursive SubGraph execution with cycle detection
1709 const TaskGraphTemplate* tmpl,
1710 std::map<std::string, TaskValue>& blackboard,
1711 std::unordered_set<std::string>& visitedGraphs,
1712 int recursionDepth,
1713 const std::string& traceIndent)
1714{
1715 // Phase 25 — Depth validation
1716 if (recursionDepth >= 20)
1717 {
1718 ADD_TRACE(traceIndent + "[ERROR] SubGraph recursion depth limit exceeded (>= 20)");
1719 ADD_TRACE(traceIndent + "[ERROR] Possible infinite loop detected - stopping execution");
1720 return;
1721 }
1722
1723 if (recursionDepth > 10)
1724 {
1725 ADD_TRACE(traceIndent + "[WARNING] SubGraph recursion depth > 10 - possible infinite loop");
1726 }
1727
1728 // Phase 25 — Find entry point
1729 int32_t currentNodeID = tmpl->EntryPointID != NODE_INDEX_NONE ?
1730 tmpl->EntryPointID : tmpl->RootNodeID;
1731
1733 {
1734 ADD_TRACE(traceIndent + "[ERROR] No entry point in SubGraph");
1735 return;
1736 }
1737
1738 ADD_TRACE(traceIndent + "[SUBGRAPH] Starting execution at Node #" + std::to_string(currentNodeID));
1739
1740 // Phase 25 — Token-based execution (same as main simulation)
1741 std::vector<ExecutionToken> tokenStack;
1743
1744 int stepCount = 0;
1745 int maxSteps = 100;
1746 std::unordered_set<int> visitedInPath;
1747
1748 while (!tokenStack.empty() && stepCount < maxSteps)
1749 {
1751 tokenStack.pop_back();
1753
1754 // Find node definition
1755 const TaskNodeDefinition* nodePtr = nullptr;
1756 for (size_t i = 0; i < tmpl->Nodes.size(); ++i)
1757 {
1758 if (tmpl->Nodes[i].NodeID == currentNodeID)
1759 {
1760 nodePtr = &tmpl->Nodes[i];
1761 break;
1762 }
1763 }
1764
1765 if (!nodePtr)
1766 {
1767 ADD_TRACE(traceIndent + "[ERROR] Node #" + std::to_string(currentNodeID) + " not found");
1768 break;
1769 }
1770
1771 // Detect cycles in this SubGraph context
1772 if (visitedInPath.count(currentNodeID) > 0)
1773 {
1774 ADD_TRACE(traceIndent + "[CYCLE] Node #" + std::to_string(currentNodeID) + " already visited");
1775 break;
1776 }
1778
1779 // Trace node entry
1780 ADD_TRACE(traceIndent + "[NODE] #" + std::to_string(nodePtr->NodeID) + " - " + nodePtr->NodeName);
1781
1783
1784 // Phase 25 — Handle key node types (simplified for SubGraph context)
1785 switch (nodePtr->Type)
1786 {
1788 {
1789 // Find next connection
1790 for (size_t i = 0; i < tmpl->ExecConnections.size(); ++i)
1791 {
1792 if (tmpl->ExecConnections[i].SourceNodeID == currentNodeID)
1793 {
1794 nextNodeID = tmpl->ExecConnections[i].TargetNodeID;
1795 break;
1796 }
1797 }
1798 break;
1799 }
1800
1802 {
1803 ADD_TRACE(traceIndent + " [EVAL] GetBBValue - Key: '" + nodePtr->BBKey + "'");
1804 for (size_t i = 0; i < tmpl->ExecConnections.size(); ++i)
1805 {
1806 if (tmpl->ExecConnections[i].SourceNodeID == currentNodeID)
1807 {
1808 nextNodeID = tmpl->ExecConnections[i].TargetNodeID;
1809 break;
1810 }
1811 }
1812 break;
1813 }
1814
1816 {
1817 ADD_TRACE(traceIndent + " [EVAL] SetBBValue - Key: '" + nodePtr->BBKey + "'");
1818 for (size_t i = 0; i < tmpl->ExecConnections.size(); ++i)
1819 {
1820 if (tmpl->ExecConnections[i].SourceNodeID == currentNodeID)
1821 {
1822 nextNodeID = tmpl->ExecConnections[i].TargetNodeID;
1823 break;
1824 }
1825 }
1826 break;
1827 }
1828
1830 {
1831 ADD_TRACE(traceIndent + " [TASK] " + nodePtr->AtomicTaskID);
1832 for (size_t i = 0; i < tmpl->ExecConnections.size(); ++i)
1833 {
1834 const ExecPinConnection& conn = tmpl->ExecConnections[i];
1835 if (conn.SourceNodeID == currentNodeID && conn.SourcePinName == "Completed")
1836 {
1838 break;
1839 }
1840 }
1841 break;
1842 }
1843
1845 {
1846 ADD_TRACE(traceIndent + " [SEQUENCE] executing branches");
1847 std::vector<ExecPinConnection> outputs;
1848 for (size_t i = 0; i < tmpl->ExecConnections.size(); ++i)
1849 {
1850 if (tmpl->ExecConnections[i].SourceNodeID == currentNodeID)
1851 {
1852 outputs.push_back(tmpl->ExecConnections[i]);
1853 }
1854 }
1855
1856 // Push branches in reverse order (LIFO)
1857 for (int oi = static_cast<int>(outputs.size()) - 1; oi >= 0; --oi)
1858 {
1859 tokenStack.push_back(ExecutionToken(outputs[oi].TargetNodeID, currentToken.depth + 1));
1860 }
1861 break;
1862 }
1863
1865 {
1866 ADD_TRACE(traceIndent + " [BRANCH] evaluating condition");
1867 std::vector<ExecPinConnection> outputs;
1868 for (size_t i = 0; i < tmpl->ExecConnections.size(); ++i)
1869 {
1870 if (tmpl->ExecConnections[i].SourceNodeID == currentNodeID)
1871 {
1872 outputs.push_back(tmpl->ExecConnections[i]);
1873 }
1874 }
1875
1876 // Push both branches (simplified: always explore both)
1877 for (int oi = static_cast<int>(outputs.size()) - 1; oi >= 0; --oi)
1878 {
1879 tokenStack.push_back(ExecutionToken(outputs[oi].TargetNodeID, currentToken.depth + 1));
1880 }
1881 break;
1882 }
1883
1884 // Phase 25 — RECURSIVE SubGraph call within SubGraph
1886 {
1887 std::string subGraphPath = nodePtr->SubGraphPath;
1888 if (subGraphPath.empty())
1889 {
1890 auto it = nodePtr->Parameters.find("subgraph_path");
1891 if (it != nodePtr->Parameters.end() &&
1892 it->second.Type == ParameterBindingType::Literal)
1893 {
1894 subGraphPath = it->second.LiteralValue.to_string();
1895 }
1896 }
1897
1898 ADD_TRACE(traceIndent + " [SUBGRAPH] Path: '" + subGraphPath + "'");
1899
1900 if (!subGraphPath.empty())
1901 {
1902 // Phase 25 — Check if already visited (cycle detection by file name)
1903 if (visitedGraphs.count(subGraphPath) > 0)
1904 {
1905 ADD_TRACE(traceIndent + " [CYCLE] SubGraph '" + subGraphPath + "' already in execution chain");
1906 break;
1907 }
1908
1909 // Resolve file path
1910 std::string resolvedPath;
1911 std::string searchDirs[] = {
1912 "./Blueprints/",
1913 "./OlympeBlueprintEditor/Blueprints/",
1914 "./Gamedata/"
1915 };
1916
1917 for (size_t dirIdx = 0; dirIdx < 3; ++dirIdx)
1918 {
1919 std::string candidate = searchDirs[dirIdx] + subGraphPath;
1920 std::ifstream testFile(candidate);
1921 if (testFile.good())
1922 {
1924 break;
1925 }
1926 }
1927
1928 if (!resolvedPath.empty())
1929 {
1930 // Load SubGraph
1931 std::vector<std::string> loadErrors;
1933
1934 if (subGraphTemplate)
1935 {
1936 // Phase 25 — Track this graph in visited set
1938
1939 // Phase 25 — Create isolated blackboard for SubGraph (copy of current state)
1940 std::map<std::string, TaskValue> isolatedBlackboard = blackboard;
1941
1942 // Phase 25 — Recursively execute SubGraph with new depth
1943 ADD_TRACE(traceIndent + " [ENTER] Recursion depth: " + std::to_string(recursionDepth + 1));
1948 recursionDepth + 1,
1949 traceIndent + " ");
1950 ADD_TRACE(traceIndent + " [EXIT] Returning from SubGraph");
1951
1952 // Phase 25 — Merge output parameters from SubGraph back to parent blackboard
1953 if (!nodePtr->OutputParams.empty())
1954 {
1955 for (const auto& output : nodePtr->OutputParams)
1956 {
1957 const std::string& outputName = output.first;
1958 const std::string& bbKey = output.second;
1959
1960 auto it = isolatedBlackboard.find(outputName);
1961 if (it != isolatedBlackboard.end())
1962 {
1963 blackboard[bbKey] = it->second;
1964 ADD_TRACE(traceIndent + " [OUTPUT] " + outputName + " -> [" + bbKey + "]");
1965 }
1966 }
1967 }
1968
1969 // Remove from visited set (allow revisit at different path)
1971
1972 delete subGraphTemplate;
1973 subGraphTemplate = nullptr;
1974 }
1975 else
1976 {
1977 ADD_TRACE(traceIndent + " [ERROR] Failed to load SubGraph: " + resolvedPath);
1978 for (size_t errIdx = 0; errIdx < loadErrors.size(); ++errIdx)
1979 {
1981 }
1982 }
1983 }
1984 else
1985 {
1986 ADD_TRACE(traceIndent + " [ERROR] SubGraph file not found: " + subGraphPath);
1987 }
1988 }
1989 else
1990 {
1991 ADD_TRACE(traceIndent + " [ERROR] SubGraph path is empty");
1992 }
1993
1994 // Find Completed pin
1995 for (size_t i = 0; i < tmpl->ExecConnections.size(); ++i)
1996 {
1997 const ExecPinConnection& conn = tmpl->ExecConnections[i];
1998 if (conn.SourceNodeID == currentNodeID && conn.SourcePinName == "Completed")
1999 {
2001 break;
2002 }
2003 }
2004 break;
2005 }
2006
2007 default:
2008 {
2009 // Other node types: just find next execution node
2010 for (size_t i = 0; i < tmpl->ExecConnections.size(); ++i)
2011 {
2012 if (tmpl->ExecConnections[i].SourceNodeID == currentNodeID)
2013 {
2014 nextNodeID = tmpl->ExecConnections[i].TargetNodeID;
2015 break;
2016 }
2017 }
2018 break;
2019 }
2020 }
2021
2022 // Push next node if set
2024 {
2026 }
2027
2028 ++stepCount;
2029 }
2030
2031 if (stepCount >= maxSteps)
2032 {
2033 ADD_TRACE(traceIndent + "[WARNING] SubGraph max steps reached - possible infinite loop");
2034 }
2035}
2036
2037} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
ImNodes-based graph editor for ATS Visual Script graphs (Phase 5).
#define ADD_TRACE(trace_str)
size_t GetPresetCount() const
Returns the total number of presets in the registry.
void DeletePreset(const std::string &id)
Removes a preset from the registry.
void UpdatePreset(const std::string &id, const ConditionPreset &updated)
Replaces the data of an existing preset (id must already exist).
std::string DuplicatePreset(const std::string &id)
Creates an independent copy of an existing preset with a new UUID.
bool Save(const std::string &filepath) const
Saves all presets to a JSON file.
std::vector< ConditionPreset > GetFilteredPresets(const std::string &filter) const
Returns all presets whose name contains the filter substring.
ConditionPreset * GetPreset(const std::string &id)
Returns a mutable pointer to the preset, or nullptr if not found.
static TaskGraphTemplate * LoadFromFile(const std::string &path, std::vector< std::string > &outErrors)
Loads a TaskGraphTemplate from a JSON file on disk.
Immutable, shareable task graph asset.
std::vector< TaskNodeDefinition > Nodes
All graph nodes.
std::vector< ExecPinConnection > ExecConnections
Explicit exec connections (ATS VS only)
std::string Name
Friendly name of this template (e.g. "PatrolBehaviour")
std::vector< BlackboardEntry > Blackboard
Local blackboard declared in this graph.
int32_t RootNodeID
ID of the root node (must exist in Nodes)
int32_t EntryPointID
ID of the EntryPoint node (for VS graphs)
std::vector< DataPinConnection > DataConnections
Explicit data connections (ATS VS only)
std::vector< ConditionPreset > Presets
Presets are now stored in the graph JSON, not in external files.
std::string to_string() const
Converts the stored value to a string representation.
static VSVerificationResult Verify(const TaskGraphTemplate &graph)
Run all verification rules on the given graph.
void TraceUpstreamDataNodes(int32_t sourceNodeID, const std::string &indent, std::unordered_set< int > &visitedDataNodes)
Recursively traces all upstream pure data nodes in the graph.
std::vector< std::string > m_validationErrors
std::unique_ptr< DynamicDataPinManager > m_pinManager
Dynamic pin manager shared across all Branch nodes in this panel.
void RunGraphSimulation()
Simulates runtime execution of the current graph and logs traces.
void RenderPresetItemCompact(const ConditionPreset &preset, size_t index)
Render a single preset item in compact horizontal format with index.
TaskGraphTemplate m_template
The template currently being edited.
void EvaluateDataNode(int32_t nodeID, int depth, const std::string &indent)
Helper to recursively evaluate data nodes (MathOp, GetBBValue, etc.) and trace their execution.
std::string FormatTaskParameters(const std::unordered_map< std::string, ParameterBinding > &parameters, const std::string &indent)
Format task parameters into a readable string.
bool m_simulationDone
True if simulation has been run.
std::vector< std::string > m_verificationLogs
Verification log messages (populated by RunVerification()) Phase 24.3 — for display in the verificati...
std::string GetNodePropertyString(const TaskNodeDefinition &node)
Gets a comprehensive property string for any node type.
std::vector< ExecutionToken > m_executionTokenStack
Phase 24.4 — Execution token stack for multi-branch simulation Enables proper handling of Sequence no...
std::vector< std::string > m_validationWarnings
Validation messages (rebuilt each frame)
VSVerificationResult m_verificationResult
Latest verification result (produced by RunVerification())
void RunVerification()
Runs VSGraphVerifier on the current graph and stores the result.
void RenderVerificationPanel()
Renders the verification results panel (Phase 21-B).
int m_focusNodeID
Node ID to focus/scroll to on next RenderCanvas() frame (-1 = none)
void RunGraphSimulationRecursive(const TaskGraphTemplate *tmpl, std::map< std::string, TaskValue > &blackboard, std::unordered_set< std::string > &visitedGraphs, int recursionDepth, const std::string &traceIndent)
Internal recursive simulation function with cycle detection.
bool RenderOperandEditor(Operand &operand, const char *labelSuffix)
Render a single operand with dropdown for mode and value editor Returns true if the operand was modif...
bool m_verificationDone
True once RunVerification() has been called at least once for the current graph.
void RenderPresetBankPanel()
Part B: Preset Bank panel (middle of right panel)
std::vector< VSEditorNode > m_editorNodes
Editor nodes (mirrors m_template.Nodes + position/selection state)
int m_selectedNodeID
Currently selected node (for properties panel)
void RenderVerificationLogsPanel()
Public render method for verification logs panel.
ConditionPresetRegistry m_presetRegistry
Global registry of ConditionPreset objects.
std::vector< std::string > m_simulationTraces
Simulation execution traces (populated by RunGraphSimulation()) Phase 24.4 — added to verification lo...
std::unique_ptr< ConditionPresetLibraryPanel > m_libraryPanel
Global condition preset library panel (UI for creating/editing/deleting presets).
< Provides AssetID and INVALID_ASSET_ID
@ LocalVariable
Value is read from the local blackboard at runtime.
@ Literal
Value is embedded directly in the template.
ComparisonOp
The relational operator used in a ConditionPreset.
@ AtomicTask
Leaf node that executes a single atomic task.
@ While
Conditional loop (Loop / Completed exec outputs)
@ SubGraph
Sub-graph call (SubTask)
@ DoOnce
Single-fire execution (reset via Reset pin)
@ Delay
Timer (Completed exec output after N seconds)
@ GetBBValue
Data node – reads a Blackboard key.
@ MathOp
Data node – arithmetic operation (+, -, *, /)
@ SetBBValue
Data node – writes a Blackboard key.
@ ForEach
Iterate over BB list (Loop Body / Completed exec outputs)
@ Switch
Multi-branch on value (N exec outputs)
@ EntryPoint
Unique entry node for VS graphs (replaces Root)
@ Branch
If/Else conditional (Then / Else exec outputs)
@ VSSequence
Execute N outputs in order ("VS" prefix avoids collision with BT Sequence=1)
constexpr int32_t NODE_INDEX_NONE
Sentinel value for "no node" in node index / ID fields.
Describes a single condition expression for Branch/While nodes.
Explicit connection between an output data pin of a source node and an input data pin of a target nod...
Explicit connection between a named exec-out pin of a source node and the exec-in pin of a target nod...
std::string SourcePinName
e.g. "Then", "Else", "Loop", "Completed"
Represents a single execution point in graph simulation (Phase 24).
int32_t nodeID
Current node to execute.
@ Variable
References a blackboard variable by name.
@ Const
Literal constant value.
@ Pin
External data-input pin on the owning node.
Describes how a single parameter value is supplied to a task node.
TaskValue LiteralValue
Used when Type == Literal.
Full description of a single node in the task graph.
std::vector< VSVerificationIssue > issues
#define SYSTEM_LOG