Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
VSGraphExecutor.cpp
Go to the documentation of this file.
1/**
2 * @file VSGraphExecutor.cpp
3 * @brief Implémentation de VSGraphExecutor pour les graphes ATS Visual Scripting.
4 * @author Olympe Engine
5 * @date 2026-03-08
6 *
7 * @details
8 * C++14 compliant - no C++17/20 features.
9 */
10
11#include "VSGraphExecutor.h"
12#include "AtomicTaskRegistry.h"
13#include "IAtomicTask.h"
14#include "ConditionEvaluator.h"
15#include "DataPinEvaluator.h"
16#include "../Core/AssetManager.h"
17#include "../system/system_utils.h"
18
19#include <sstream>
20
21namespace Olympe {
22
23// ============================================================================
24// Constants
25// ============================================================================
26
27/// Maximum steps per frame to prevent infinite loops.
28static const int MAX_STEPS_PER_FRAME = 64;
29
30/// Out-of-class definition required in C++14 for ODR-used static const members.
32
33// ============================================================================
34// ExecuteFrame (public)
35// ============================================================================
36
42 float dt)
43{
44 // If no active node, reset to EntryPoint and return — execution starts next frame.
45 if (runner.CurrentNodeID == NODE_INDEX_NONE)
46 {
47 runner.CurrentNodeID = tmpl.EntryPointID;
48 return;
49 }
50
51 // Main execution loop — bounded to MAX_STEPS_PER_FRAME to prevent infinite loops.
52 for (int step = 0; step < MAX_STEPS_PER_FRAME; ++step)
53 {
54 const int32_t currentID = runner.CurrentNodeID;
55
56 const TaskNodeDefinition* node = tmpl.GetNode(currentID);
57 if (node == nullptr)
58 {
59 SYSTEM_LOG << "[VSGraphExecutor] Entity " << entity
60 << ": node ID " << currentID
61 << " not found in template '" << tmpl.Name << "'\n";
62 runner.CurrentNodeID = NODE_INDEX_NONE;
63 return;
64 }
65
66 // Resolve incoming data pins before executing the node.
68
70
71 switch (node->Type)
72 {
75 break;
76
79 break;
80
83 break;
84
87 break;
88
91 break;
92
95 break;
96
99 break;
100
103 break;
104
107 break;
108
111 break;
112
115 break;
116
118 {
119 static thread_local SubGraphCallStack s_callStack;
121 break;
122 }
123
124 default:
125 // Unhandled node type: log and end frame.
126 SYSTEM_LOG << "[VSGraphExecutor] Entity " << entity
127 << ": unhandled node type "
128 << static_cast<int>(node->Type)
129 << " at node " << currentID << "\n";
130 return;
131 }
132
133 // End of frame: node requests to stay active (multi-frame task, delay, etc.).
134 if (nextID == NODE_INDEX_NONE)
135 {
136 return;
137 }
138
139 // Same node returned: multi-frame node that needs to tick again next frame.
140 if (nextID == runner.CurrentNodeID)
141 {
142 return;
143 }
144
145 // Advance to next node.
146 runner.CurrentNodeID = nextID;
147 }
148
149 // Reached step limit — log a warning (likely an infinite loop in the graph).
150 SYSTEM_LOG << "[VSGraphExecutor] Entity " << entity
151 << ": reached MAX_STEPS_PER_FRAME (" << MAX_STEPS_PER_FRAME
152 << ") in template '" << tmpl.Name << "' — possible infinite loop\n";
153}
154
155// ============================================================================
156// FindExecTarget (private)
157// ============================================================================
158
160 const std::string& pinName,
161 const TaskGraphTemplate& tmpl)
162{
163 for (size_t i = 0; i < tmpl.ExecConnections.size(); ++i)
164 {
165 const ExecPinConnection& conn = tmpl.ExecConnections[i];
166 if (conn.SourceNodeID == sourceNodeID && conn.SourcePinName == pinName)
167 {
168 return conn.TargetNodeID;
169 }
170 }
171 return NODE_INDEX_NONE;
172}
173
174// ============================================================================
175// ResolveDataPins (private)
176// ============================================================================
177
180 const TaskGraphTemplate& tmpl,
182{
183 // Phase 24.1: Use stack-based recursive evaluation for data pins
184 // This replaces the simple cache lookup with proper recursive evaluation
185 // of dependent nodes (Variable, MathOp, GetBBValue).
186
188
189 if (!success)
190 {
191 SYSTEM_LOG << "[VSGraphExecutor] Warning: Failed to evaluate all input pins for node "
192 << nodeID << " — some pins may use default values\n";
193 }
194}
195
196// ============================================================================
197// ReadBBValue / WriteBBValue (private)
198
199
202{
203 return localBB.GetValueScoped(scopedKey);
204}
205
207 const TaskValue& value,
209{
210 localBB.SetValueScoped(scopedKey, value);
211}
212
213// ============================================================================
214// HandleEntryPoint (private)
215// ============================================================================
216
218 const TaskGraphTemplate& tmpl)
219{
220 // Immediately advance to the first exec successor.
221 return FindExecTarget(nodeID, "Out", tmpl);
222}
223
224// ============================================================================
225// Helper functions for Phase 24 Condition Preset Evaluation (private)
226// ============================================================================
227
228/**
229 * @brief Populates a RuntimeEnvironment with blackboard variables.
230 *
231 * Copies variable values from localBB into the RuntimeEnvironment for use by
232 * Operand resolution during ConditionPresetEvaluator::EvaluateConditionChain().
233 *
234 * @param env Target RuntimeEnvironment to populate.
235 * @param localBB Source LocalBlackboard.
236 */
240{
241 // Get all variable names from the blackboard
242 const std::vector<std::string>& variableNames = localBB.GetVariableNames();
243
244 for (size_t i = 0; i < variableNames.size(); ++i)
245 {
246 const std::string& varName = variableNames[i];
247 try
248 {
249 const TaskValue& value = localBB.GetValue(varName);
250
251 // Convert TaskValue to float for RuntimeEnvironment
252 float floatValue = 0.0f;
253 if (value.GetType() == VariableType::Float)
254 {
255 floatValue = value.AsFloat();
256 }
257 else if (value.GetType() == VariableType::Int)
258 {
259 floatValue = static_cast<float>(value.AsInt());
260 }
261 else if (value.GetType() == VariableType::Bool)
262 {
263 floatValue = value.AsBool() ? 1.0f : 0.0f;
264 }
265 // Other types (Vector, EntityID, String, List, GlobalRef) are not convertible to float
266
267 env.SetBlackboardVariable(varName, floatValue);
268 }
269 catch (...)
270 {
271 // Variable access failed; skip this variable
272 }
273 }
274}
275
276/**
277 * @brief Populates a RuntimeEnvironment with dynamic pin values.
278 *
279 * Copies resolved data pin values from DataPinCache into the RuntimeEnvironment
280 * for use by Pin-mode Operand resolution.
281 *
282 * @param env Target RuntimeEnvironment to populate.
283 * @param dataPinCache Source DataPinCache (map of "nodeID:pinName" -> TaskValue).
284 */
287 const std::unordered_map<std::string, TaskValue>& dataPinCache)
288{
289 for (auto it = dataPinCache.begin(); it != dataPinCache.end(); ++it)
290 {
291 const std::string& pinID = it->first;
292 const TaskValue& value = it->second;
293
294 // Convert TaskValue to float for RuntimeEnvironment
295 float floatValue = 0.0f;
296 if (value.GetType() == VariableType::Float)
297 {
298 floatValue = value.AsFloat();
299 }
300 else if (value.GetType() == VariableType::Int)
301 {
302 floatValue = static_cast<float>(value.AsInt());
303 }
304 else if (value.GetType() == VariableType::Bool)
305 {
306 floatValue = value.AsBool() ? 1.0f : 0.0f;
307 }
308
309 env.SetDynamicPinValue(pinID, floatValue);
310 }
311}
312
313// ============================================================================
314// HandleBranch (private)
315// ============================================================================
316
319 const TaskGraphTemplate& tmpl,
321{
322 const TaskNodeDefinition* node = tmpl.GetNode(nodeID);
323
324 // Phase 24: If condition presets are defined, evaluate them with AND/OR operators.
325 // Priority 1: Check conditionRefs (Phase 24 Condition Presets with explicit AND/OR)
326 if (node != nullptr && !node->conditionRefs.empty())
327 {
328 // Build RuntimeEnvironment from current blackboard and pin states
332
333 // Get the global condition preset registry
334 // Note: In a production system, this would be obtained from a manager or singleton.
335 // For now, we create a temporary registry instance and try to load from the default location.
337 try
338 {
339 // Attempt to load presets from the standard location
340 const std::string presetPath = "./Blueprints/Presets/condition_presets.json";
342 }
343 catch (...)
344 {
345 // If loading fails, proceed with empty registry
346 // This will result in "Preset not found" errors below, which is appropriate
347 }
348
349 // Evaluate the condition chain
350 std::string errorMsg;
352 node->conditionRefs, registry, env, errorMsg);
353
354 if (!errorMsg.empty())
355 {
356 SYSTEM_LOG << "[VSGraphExecutor] Branch node " << nodeID
357 << ": " << errorMsg << " — defaulting to Else\n";
358 // On error: execute Else branch (fail-safe)
359 return FindExecTarget(nodeID, "Else", tmpl);
360 }
361
362 return condition
363 ? FindExecTarget(nodeID, "Then", tmpl)
364 : FindExecTarget(nodeID, "Else", tmpl);
365 }
366
367 // Phase 23-B.4: If structured conditions are defined, evaluate them next.
368 // Priority 2: Check conditions (Phase 23-B.4 with implicit AND)
369 if (node != nullptr && !node->conditions.empty())
370 {
372 node->conditions, localBB, runner.DataPinCache, nodeID);
373
374 return condition
375 ? FindExecTarget(nodeID, "Then", tmpl)
376 : FindExecTarget(nodeID, "Else", tmpl);
377 }
378
379 // Fallback: read the "Condition" data pin from DataPinCache.
380 std::ostringstream condKey;
381 condKey << nodeID << ":Condition";
382 const std::string condKeyStr = condKey.str();
383
384 bool condition = false;
385
386 auto it = runner.DataPinCache.find(condKeyStr);
387 if (it != runner.DataPinCache.end())
388 {
389 const TaskValue& val = it->second;
390 if (val.GetType() == VariableType::Bool)
391 {
392 try
393 {
394 condition = val.AsBool();
395 }
396 catch (...)
397 {
398 condition = false;
399 }
400 }
401 else if (val.GetType() == VariableType::Int)
402 {
403 try
404 {
405 condition = (val.AsInt() != 0);
406 }
407 catch (...)
408 {
409 condition = false;
410 }
411 }
412 }
413 else
414 {
415 // No data pin and no structured conditions: log fallback.
416 if (node != nullptr && !node->ConditionID.empty())
417 {
418 SYSTEM_LOG << "[VSGraphExecutor] Branch node " << nodeID
419 << ": ConditionID '" << node->ConditionID
420 << "' evaluation not implemented — defaulting to Else\n";
421 }
422 condition = false;
423 }
424
425 if (condition)
426 {
427 return FindExecTarget(nodeID, "Then", tmpl);
428 }
429 else
430 {
431 return FindExecTarget(nodeID, "Else", tmpl);
432 }
433}
434
435// ============================================================================
436// HandleSwitch (private)
437// ============================================================================
438
441 const TaskGraphTemplate& tmpl,
443{
444 const TaskNodeDefinition* node = tmpl.GetNode(nodeID);
445 if (node == nullptr)
446 {
447 return NODE_INDEX_NONE;
448 }
449
450 // Read the value to switch on from the "Value" data pin.
451 std::ostringstream valKey;
452 valKey << nodeID << ":Value";
453 const std::string valKeyStr = valKey.str();
454
455 std::string caseLabel;
456
457 auto it = runner.DataPinCache.find(valKeyStr);
458 if (it != runner.DataPinCache.end())
459 {
460 const TaskValue& val = it->second;
461 if (val.GetType() == VariableType::String)
462 {
463 try { caseLabel = val.AsString(); } catch (...) {}
464 }
465 else if (val.GetType() == VariableType::Int)
466 {
467 try
468 {
469 std::ostringstream oss;
470 oss << val.AsInt();
471 caseLabel = oss.str();
472 }
473 catch (...) {}
474 }
475 }
476
477 // Try each case label.
478 for (size_t i = 0; i < node->SwitchCases.size(); ++i)
479 {
480 if (node->SwitchCases[i] == caseLabel)
481 {
482 return FindExecTarget(nodeID, node->SwitchCases[i], tmpl);
483 }
484 }
485
486 // No matching case: follow Default exec output if connected.
487 return FindExecTarget(nodeID, "Default", tmpl);
488}
489
490// ============================================================================
491// HandleVSSequence (private)
492// ============================================================================
493
496 const TaskGraphTemplate& tmpl)
497{
498 const TaskNodeDefinition* node = tmpl.GetNode(nodeID);
499 if (node == nullptr)
500 {
501 return NODE_INDEX_NONE;
502 }
503
504 if (runner.SequenceChildIndex < static_cast<int32_t>(node->ChildrenIDs.size()))
505 {
506 const int32_t childID = node->ChildrenIDs[static_cast<size_t>(runner.SequenceChildIndex)];
507 runner.SequenceChildIndex++;
508 return childID;
509 }
510
511 // All children executed: reset index and follow Out.
512 runner.SequenceChildIndex = 0;
513 return FindExecTarget(nodeID, "Out", tmpl);
514}
515
516// ============================================================================
517// HandleWhile (private)
518// ============================================================================
519
522 const TaskGraphTemplate& tmpl,
524{
525 // Read condition from "Condition" data pin.
526 std::ostringstream condKey;
527 condKey << nodeID << ":Condition";
528 const std::string condKeyStr = condKey.str();
529
530 bool condition = false;
531
532 auto it = runner.DataPinCache.find(condKeyStr);
533 if (it != runner.DataPinCache.end())
534 {
535 const TaskValue& val = it->second;
536 if (val.GetType() == VariableType::Bool)
537 {
538 try { condition = val.AsBool(); } catch (...) {}
539 }
540 else if (val.GetType() == VariableType::Int)
541 {
542 try { condition = (val.AsInt() != 0); } catch (...) {}
543 }
544 }
545 else
546 {
547 const TaskNodeDefinition* node = tmpl.GetNode(nodeID);
548 if (node != nullptr && !node->ConditionID.empty())
549 {
550 SYSTEM_LOG << "[VSGraphExecutor] While node " << nodeID
551 << ": ConditionID '" << node->ConditionID
552 << "' evaluation not implemented — exiting loop\n";
553 }
554 condition = false;
555 }
556
557 if (condition)
558 {
559 return FindExecTarget(nodeID, "Loop", tmpl);
560 }
561 else
562 {
563 return FindExecTarget(nodeID, "Completed", tmpl);
564 }
565}
566
567// ============================================================================
568// HandleDoOnce (private)
569// ============================================================================
570
573 const TaskGraphTemplate& tmpl)
574{
575 auto it = runner.DoOnceFlags.find(nodeID);
576 if (it != runner.DoOnceFlags.end() && it->second)
577 {
578 // Already fired — block execution.
579 return NODE_INDEX_NONE;
580 }
581
582 // Mark as fired and execute.
583 runner.DoOnceFlags[nodeID] = true;
584 return FindExecTarget(nodeID, "Out", tmpl);
585}
586
587// ============================================================================
588// HandleDelay (private)
589// ============================================================================
590
593 const TaskGraphTemplate& tmpl,
594 float dt)
595{
596 const TaskNodeDefinition* node = tmpl.GetNode(nodeID);
597 if (node == nullptr)
598 {
599 return NODE_INDEX_NONE;
600 }
601
602 runner.StateTimer += dt;
603
604 if (runner.StateTimer >= node->DelaySeconds)
605 {
606 runner.StateTimer = 0.0f;
607 return FindExecTarget(nodeID, "Completed", tmpl);
608 }
609
610 // Still waiting — remain on this node, end frame.
611 return runner.CurrentNodeID;
612}
613
614// ============================================================================
615// HandleGetBBValue (private)
616// ============================================================================
617
620 const TaskGraphTemplate& tmpl,
622{
623 const TaskNodeDefinition* node = tmpl.GetNode(nodeID);
624 if (node == nullptr)
625 {
626 return NODE_INDEX_NONE;
627 }
628
629 const TaskValue val = ReadBBValue(node->BBKey, localBB);
630
631 // Store result in output pin cache.
632 std::ostringstream outKey;
633 outKey << nodeID << ":Value";
634 runner.DataPinCache[outKey.str()] = val;
635
636 return FindExecTarget(nodeID, "Out", tmpl);
637}
638
639// ============================================================================
640// HandleSetBBValue (private)
641// ============================================================================
642
645 const TaskGraphTemplate& tmpl,
647{
648 const TaskNodeDefinition* node = tmpl.GetNode(nodeID);
649 if (node == nullptr)
650 {
651 return NODE_INDEX_NONE;
652 }
653
654 // Read input value from data pin cache.
655 std::ostringstream inKey;
656 inKey << nodeID << ":Value";
657 const std::string inKeyStr = inKey.str();
658
659 auto it = runner.DataPinCache.find(inKeyStr);
660 if (it != runner.DataPinCache.end())
661 {
662 WriteBBValue(node->BBKey, it->second, localBB);
663 }
664 else
665 {
666 SYSTEM_LOG << "[VSGraphExecutor] SetBBValue node " << nodeID
667 << ": no value in DataPinCache for 'Value' pin — skipping write\n";
668 }
669
670 return FindExecTarget(nodeID, "Out", tmpl);
671}
672
673// ============================================================================
674// HandleMathOp (private)
675// ============================================================================
676
679 const TaskGraphTemplate& tmpl)
680{
681 const TaskNodeDefinition* node = tmpl.GetNode(nodeID);
682 if (node == nullptr)
683 {
684 return NODE_INDEX_NONE;
685 }
686
687 // Read operands A and B from data pin cache.
688 std::ostringstream keyA;
689 keyA << nodeID << ":A";
690 std::ostringstream keyB;
691 keyB << nodeID << ":B";
692
693 float a = 0.0f;
694 float b = 0.0f;
695
696 auto itA = runner.DataPinCache.find(keyA.str());
697 if (itA != runner.DataPinCache.end())
698 {
699 const TaskValue& v = itA->second;
700 if (v.GetType() == VariableType::Float)
701 {
702 try { a = v.AsFloat(); } catch (...) {}
703 }
704 else if (v.GetType() == VariableType::Int)
705 {
706 try { a = static_cast<float>(v.AsInt()); } catch (...) {}
707 }
708 }
709
710 auto itB = runner.DataPinCache.find(keyB.str());
711 if (itB != runner.DataPinCache.end())
712 {
713 const TaskValue& v = itB->second;
714 if (v.GetType() == VariableType::Float)
715 {
716 try { b = v.AsFloat(); } catch (...) {}
717 }
718 else if (v.GetType() == VariableType::Int)
719 {
720 try { b = static_cast<float>(v.AsInt()); } catch (...) {}
721 }
722 }
723
724 float result = 0.0f;
725 const std::string& op = node->MathOperator;
726
727 if (op == "+")
728 {
729 result = a + b;
730 }
731 else if (op == "-")
732 {
733 result = a - b;
734 }
735 else if (op == "*")
736 {
737 result = a * b;
738 }
739 else if (op == "/")
740 {
741 if (b != 0.0f)
742 {
743 result = a / b;
744 }
745 else
746 {
747 SYSTEM_LOG << "[VSGraphExecutor] MathOp node " << nodeID
748 << ": division by zero — result is 0\n";
749 result = 0.0f;
750 }
751 }
752 else
753 {
754 SYSTEM_LOG << "[VSGraphExecutor] MathOp node " << nodeID
755 << ": unknown operator '" << op << "'\n";
756 }
757
758 // Store result in output pin cache.
759 std::ostringstream resultKey;
760 resultKey << nodeID << ":Result";
761 runner.DataPinCache[resultKey.str()] = TaskValue(result);
762
763 return FindExecTarget(nodeID, "Out", tmpl);
764}
765
766// ============================================================================
767// HandleAtomicTask (private)
768// ============================================================================
769
771 int32_t nodeID,
773 const TaskGraphTemplate& tmpl,
776 float dt)
777{
778 const TaskNodeDefinition* node = tmpl.GetNode(nodeID);
779 if (node == nullptr)
780 {
781 return NODE_INDEX_NONE;
782 }
783
784 // Create task instance on first entry.
785 if (!runner.activeTask)
786 {
787 runner.activeTask = AtomicTaskRegistry::Get().Create(node->AtomicTaskID);
788 if (!runner.activeTask)
789 {
790 SYSTEM_LOG << "[VSGraphExecutor] Entity " << entity
791 << ": unknown AtomicTaskID '" << node->AtomicTaskID
792 << "' at node " << nodeID << " — skipping\n";
793 return FindExecTarget(nodeID, "Out", tmpl);
794 }
795 }
796
797 // Build parameter map from data pin cache and node parameter bindings.
799
800 for (auto it = node->Parameters.begin(); it != node->Parameters.end(); ++it)
801 {
802 if (it->second.Type == ParameterBindingType::Literal)
803 {
804 params[it->first] = it->second.LiteralValue;
805 }
806 else if (it->second.Type == ParameterBindingType::LocalVariable)
807 {
808 if (localBB.HasVariable(it->second.VariableName))
809 {
810 try
811 {
812 params[it->first] = localBB.GetValue(it->second.VariableName);
813 }
814 catch (...)
815 {
816 SYSTEM_LOG << "[VSGraphExecutor] Entity " << entity
817 << ": failed to read variable '"
818 << it->second.VariableName << "'\n";
819 }
820 }
821 }
822 }
823
824 // Also inject data pin cache values for input pins.
825 for (size_t p = 0; p < node->DataPins.size(); ++p)
826 {
827 const DataPinDefinition& pin = node->DataPins[p];
828 if (pin.Dir == DataPinDir::Input)
829 {
830 std::ostringstream key;
831 key << nodeID << ":" << pin.PinName;
832 auto it = runner.DataPinCache.find(key.str());
833 if (it != runner.DataPinCache.end())
834 {
835 params[pin.PinName] = it->second;
836 }
837 }
838 }
839
840 // Build context and tick.
841 // Note: worldPtr may be nullptr (valid in tests and Phase 2).
842 // AtomicTaskContext documents that WorldPtr may be null; concrete tasks
843 // that need World access must guard against null themselves.
845 ctx.Entity = entity;
846 ctx.WorldPtr = worldPtr;
847 ctx.LocalBB = &localBB;
848 ctx.DeltaTime = dt;
849 ctx.StateTimer = runner.StateTimer;
850
851 TaskStatus status = runner.activeTask->ExecuteWithContext(ctx, params);
852
853 if (status == TaskStatus::Running)
854 {
855 runner.StateTimer += dt;
856 // Remain on this node — end frame.
857 return runner.CurrentNodeID;
858 }
859
860 // Task completed: clean up.
861 runner.activeTask.reset();
862 runner.StateTimer = 0.0f;
863
864 if (status == TaskStatus::Success)
865 {
867 return FindExecTarget(nodeID, "Out", tmpl);
868 }
869 else
870 {
872 const int32_t failTarget = FindExecTarget(nodeID, "Failure", tmpl);
873 return failTarget; // NODE_INDEX_NONE if not connected is intentional
874 }
875}
876
877// ============================================================================
878// HandleSubGraph (private)
879// ============================================================================
880
882 int32_t nodeID,
884 const TaskGraphTemplate& tmpl,
887 float dt,
889{
890 const TaskNodeDefinition* node = tmpl.GetNode(nodeID);
891 if (node == nullptr)
892 {
893 SYSTEM_LOG << "[VSGraphExecutor][ERROR] HandleSubGraph: node " << nodeID
894 << " not found in template '" << tmpl.Name << "'\n";
895 return NODE_INDEX_NONE;
896 }
897
898 if (node->SubGraphPath.empty())
899 {
900 SYSTEM_LOG << "[VSGraphExecutor] Entity " << entity
901 << ": SubGraph node " << nodeID
902 << " has no SubGraphPath — skipping\n";
903 return FindExecTarget(nodeID, "Out", tmpl);
904 }
905
906 // 1. Check depth limit
907 if (callStack.Depth >= MAX_SUBGRAPH_DEPTH)
908 {
909 SYSTEM_LOG << "[VSGraphExecutor][ERROR] SubGraph recursion depth exceeded (max "
910 << MAX_SUBGRAPH_DEPTH << "): '" << node->SubGraphPath << "'\n";
911 return FindExecTarget(nodeID, "Out", tmpl);
912 }
913
914 // 2. Check for cycles (self-reference or circular dependency)
915 if (callStack.Contains(node->SubGraphPath))
916 {
917 SYSTEM_LOG << "[VSGraphExecutor][ERROR] SubGraph cycle detected: '";
918 for (size_t i = 0; i < callStack.PathStack.size(); ++i)
919 {
920 SYSTEM_LOG << callStack.PathStack[i];
921 if (i + 1 < callStack.PathStack.size()) SYSTEM_LOG << " -> ";
922 }
923 SYSTEM_LOG << " -> " << node->SubGraphPath << "'\n";
924 return FindExecTarget(nodeID, "Out", tmpl);
925 }
926
927 // 3. Load SubGraph template
928 std::vector<std::string> loadErrors;
931
932 if (subGraphTmpl == nullptr)
933 {
934 SYSTEM_LOG << "[VSGraphExecutor][ERROR] SubGraph file not found or invalid: '"
935 << node->SubGraphPath << "'\n";
936 for (size_t i = 0; i < loadErrors.size(); ++i)
937 {
938 SYSTEM_LOG << " - " << loadErrors[i] << "\n";
939 }
940 return FindExecTarget(nodeID, "Out", tmpl);
941 }
942
943 // 4. Create child LocalBlackboard and bind input parameters
946
947 for (auto it = node->InputParams.begin(); it != node->InputParams.end(); ++it)
948 {
949 const std::string& paramName = it->first;
950 const ParameterBinding& binding = it->second;
951
952 TaskValue value;
954 {
955 value = binding.LiteralValue;
956 }
958 {
959 if (localBB.HasVariable(binding.VariableName))
960 {
961 value = localBB.GetValue(binding.VariableName);
962 }
963 else
964 {
965 SYSTEM_LOG << "[VSGraphExecutor] SubGraph input param '" << paramName
966 << "': LocalVariable '" << binding.VariableName
967 << "' not found in parent BB — using default\n";
968 }
969 }
970
971 if (childBB.HasVariable(paramName))
972 {
973 try { childBB.SetValue(paramName, value); }
974 catch (...) { /* type mismatch — skip */ }
975 }
976 }
977
978 // 5. Push call stack
979 callStack.Push(node->SubGraphPath);
980
981 // 6. Create temporary TaskRunnerComponent for subgraph execution
983 childRunner.CurrentNodeID = subGraphTmpl->EntryPointID;
984
985 // 7. Execute SubGraph (single frame step)
987
988 // 8. Pop call stack
989 callStack.Pop();
990
991 // 9. Extract output parameters back to parent blackboard
992 for (auto it = node->OutputParams.begin(); it != node->OutputParams.end(); ++it)
993 {
994 const std::string& paramName = it->first;
995 const std::string& targetBBKey = it->second;
996
997 if (childBB.HasVariable(paramName))
998 {
999 try
1000 {
1002 localBB.SetValueScoped(targetBBKey, outputValue);
1003 }
1004 catch (...)
1005 {
1006 SYSTEM_LOG << "[VSGraphExecutor] SubGraph output param '" << paramName
1007 << "': failed to write to '" << targetBBKey << "'\n";
1008 }
1009 }
1010 }
1011
1012 // 10. Continue execution in parent graph
1013 return FindExecTarget(nodeID, "Out", tmpl);
1014}
1015
1016} // namespace Olympe
Singleton registry for atomic task factories.
Evaluates structured Condition expressions for Branch/While nodes.
Stack-based recursive evaluation of data pin networks for "data pure" nodes.
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
std::uint64_t EntityID
Definition ECS_Entity.h:21
Interface for atomic tasks in the Atomic Task System.
Exécuteur de graphes ATS Visual Scripting (flowchart).
const TaskGraphTemplate * LoadTaskGraphFromFile(const std::string &path, std::vector< std::string > &outErrors)
Loads a TaskGraphTemplate from path and returns a direct pointer.
static AssetManager & Get()
Returns the singleton AssetManager instance.
std::unique_ptr< IAtomicTask > Create(const std::string &id) const
Creates a new instance of the task identified by id.
static AtomicTaskRegistry & Get()
Returns the singleton instance.
static bool EvaluateAll(const std::vector< Condition > &conditions, const LocalBlackboard &localBB, const std::unordered_map< std::string, TaskValue > &dataPinCache, int32_t nodeID)
Evaluates all conditions (implicit AND) on a Branch/While node.
static bool EvaluateConditionChain(const std::vector< NodeConditionRef > &conditions, const ConditionPresetRegistry &registry, RuntimeEnvironment &env, std::string &outErrorMsg)
Evaluates a chain of conditions combined with logical operators (AND/OR).
Manages the global pool of ConditionPreset objects.
bool Load(const std::string &filepath)
Loads presets from a JSON file (clears existing data first).
static bool EvaluateNodeInputPins(int32_t nodeID, const TaskGraphTemplate &tmpl, TaskRunnerComponent &runner, LocalBlackboard &localBB)
Evaluate all input data pins for a given node before execution.
std::unordered_map< std::string, TaskValue > ParameterMap
Convenience alias for the parameter map passed to Execute().
Definition IAtomicTask.h:67
Simple map-based blackboard for task graph runtime state.
void InitializeFromEntries(const std::vector< BlackboardEntry > &entries)
Initializes the blackboard from a vector of BlackboardEntry (ATS VS schema v4).
Provides Blackboard variable values and dynamic pin values at runtime.
Immutable, shareable task graph asset.
C++14-compliant type-safe value container for task parameters.
int AsInt() const
Returns the int value.
float AsFloat() const
Returns the float value.
std::string AsString() const
Returns the string value.
bool AsBool() const
Returns the bool value.
VariableType GetType() const
Returns the VariableType tag of the stored value.
static int32_t HandleWhile(int32_t nodeID, TaskRunnerComponent &runner, const TaskGraphTemplate &tmpl, LocalBlackboard &localBB)
static int32_t FindExecTarget(int32_t sourceNodeID, const std::string &pinName, const TaskGraphTemplate &tmpl)
Retourne l'ID du node cible sur un exec pin nommé pinName depuis sourceNodeID.
static int32_t HandleSubGraph(EntityID entity, int32_t nodeID, TaskRunnerComponent &runner, const TaskGraphTemplate &tmpl, LocalBlackboard &localBB, World *worldPtr, float dt, SubGraphCallStack &callStack)
static TaskValue ReadBBValue(const std::string &scopedKey, LocalBlackboard &localBB)
Lit une valeur BB depuis localBB avec support du scope "local:" et "global:".
static int32_t HandleGetBBValue(int32_t nodeID, TaskRunnerComponent &runner, const TaskGraphTemplate &tmpl, LocalBlackboard &localBB)
static void ResolveDataPins(int32_t nodeID, TaskRunnerComponent &runner, const TaskGraphTemplate &tmpl, LocalBlackboard &localBB)
Résout les data pins entrantes d'un node et peuple runner.DataPinCache.
static const int MAX_SUBGRAPH_DEPTH
Out-of-class definition required in C++14 for ODR-used static const members.
static void WriteBBValue(const std::string &scopedKey, const TaskValue &value, LocalBlackboard &localBB)
Écrit une valeur BB dans localBB avec support du scope "local:".
static int32_t HandleMathOp(int32_t nodeID, TaskRunnerComponent &runner, const TaskGraphTemplate &tmpl)
static int32_t HandleSwitch(int32_t nodeID, TaskRunnerComponent &runner, const TaskGraphTemplate &tmpl, LocalBlackboard &localBB)
static int32_t HandleVSSequence(int32_t nodeID, TaskRunnerComponent &runner, const TaskGraphTemplate &tmpl)
static int32_t HandleEntryPoint(int32_t nodeID, const TaskGraphTemplate &tmpl)
static int32_t HandleSetBBValue(int32_t nodeID, TaskRunnerComponent &runner, const TaskGraphTemplate &tmpl, LocalBlackboard &localBB)
static int32_t HandleBranch(int32_t nodeID, TaskRunnerComponent &runner, const TaskGraphTemplate &tmpl, LocalBlackboard &localBB)
static int32_t HandleDoOnce(int32_t nodeID, TaskRunnerComponent &runner, const TaskGraphTemplate &tmpl)
static void ExecuteFrame(EntityID entity, TaskRunnerComponent &runner, const TaskGraphTemplate &tmpl, LocalBlackboard &localBB, World *worldPtr, float dt)
Point d'entrée principal : exécute le graphe pour une entité.
static int32_t HandleAtomicTask(EntityID entity, int32_t nodeID, TaskRunnerComponent &runner, const TaskGraphTemplate &tmpl, LocalBlackboard &localBB, World *worldPtr, float dt)
static int32_t HandleDelay(int32_t nodeID, TaskRunnerComponent &runner, const TaskGraphTemplate &tmpl, float dt)
Core ECS manager and world coordinator.
Definition World.h:210
< Provides AssetID and INVALID_ASSET_ID
@ Int
32-bit signed integer
@ Float
Single-precision float.
@ String
std::string
TaskStatus
Result code returned by IAtomicTask::Execute().
Definition IAtomicTask.h:38
@ Success
Task completed successfully.
@ Running
Task is still in progress (multi-frame tasks)
static void PopulateRuntimeEnvironmentFromDataPins(RuntimeEnvironment &env, const std::unordered_map< std::string, TaskValue > &dataPinCache)
Populates a RuntimeEnvironment with dynamic pin values.
static const int MAX_STEPS_PER_FRAME
Maximum steps per frame to prevent infinite loops.
@ LocalVariable
Value is read from the local blackboard at runtime.
@ Literal
Value is embedded directly in the template.
static void PopulateRuntimeEnvironmentFromBlackboard(RuntimeEnvironment &env, const LocalBlackboard &localBB)
Populates a RuntimeEnvironment with blackboard variables.
@ 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.
@ 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.
@ Input
Value consumed by the node.
Lightweight context bundle passed to IAtomicTask::ExecuteWithContext().
EntityID Entity
The entity whose task graph is being executed.
Describes a data pin declared on a Visual Script node.
std::string PinName
Pin name ("Value", "Result", etc.)
Explicit connection between a named exec-out pin of a source node and the exec-in pin of a target nod...
Describes how a single parameter value is supplied to a task node.
Tracks the SubGraph call chain to detect cycles and enforce depth limits.
Full description of a single node in the task graph.
std::string ConditionID
For Branch/While/Switch: ATS condition ID.
std::vector< int32_t > ChildrenIDs
Child node IDs (control-flow nodes only; empty for AtomicTask/Decorator leaf)
Per-entity runtime state for task graph execution.
@ Success
The node completed successfully.
int32_t CurrentNodeID
ID of the currently active node.
#define SYSTEM_LOG