17#include "../BlueprintEditor/BTtoVSMigrator.h"
18#include "../BlueprintEditor/MathOpOperand.h"
22#include <unordered_map>
27# ifndef WIN32_LEAN_AND_MEAN
28# define WIN32_LEAN_AND_MEAN
35#include "../system/system_utils.h"
36#include "../json_helper.h"
63 SYSTEM_LOG <<
"[TaskGraphLoader] WARNING: Expected .ats extension for: "
87 SYSTEM_LOG <<
"[TaskGraphLoader] Schema version: " << schemaVersion << std::endl;
91 if (schemaVersion == 4)
96 else if (schemaVersion == 3)
106 &&
v4data.contains(
"nodes")
107 &&
v4data[
"nodes"].is_array()
108 && !
v4data[
"nodes"].empty();
113 SYSTEM_LOG <<
"[TaskGraphLoader] v3 migrator produced no nodes; "
114 "using direct v4 parse\n";
129 data[
"data"].is_object() &&
130 data[
"data"].contains(
"data");
154 tmpl->BuildLookupCache();
161 <<
" invalid exec connection(s) - graph is now consistent\n";
164 if (!
tmpl->Validate())
166 outErrors.push_back(
"TaskGraphTemplate::Validate() failed for template '" +
tmpl->Name +
"'");
172 <<
"' (" <<
tmpl->Nodes.size() <<
" nodes)\n";
191 outErrors.push_back(
"Missing required 'data' object or 'nodes' array in JSON");
199 outErrors.push_back(
"Missing required 'nodes' array in data section");
205 outErrors.push_back(
"Missing required 'rootNodeId' in data section");
229 outErrors.push_back(
"[v4] Missing 'nodes' array or 'data' object in JSON");
235 outErrors.push_back(
"[v4] Missing 'nodes' array in data section");
253 (blueprintType ==
"BehaviorTree" || (!
flatFormat && blueprintType.empty()
254 && !data.contains(
"graphType") && !data.contains(
"graph_type")))
267 tmpl->RootNodeID =
tmpl->EntryPointID;
271 const json&
ds = data[
"data"];
276 tmpl->RootNodeID =
tmpl->EntryPointID;
285 tmpl->Nodes.push_back(
nd);
290 tmpl->RootNodeID =
nd.NodeID;
291 tmpl->EntryPointID =
nd.NodeID;
319 SYSTEM_LOG <<
"[TaskGraphLoader] ParseSchemaV4: Phase 24 - deserialized "
320 <<
tmpl->Presets.size() <<
" embedded presets\n";
328 tmpl->GlobalVariableValues = data[
"globalVariableValues"];
329 SYSTEM_LOG <<
"[TaskGraphLoader] ParseSchemaV4: Phase 24 - loaded "
330 <<
"globalVariableValues from graph\n";
368 const std::string& graphType,
389 outErrors.push_back(
"Node " + std::to_string(
nd.NodeID) +
390 " has unknown type '" +
typeStr +
"'");
432 SYSTEM_LOG <<
"[TaskGraphLoader] ParseNodeV4: initialized Parameters[subgraph_path] = '"
433 <<
nd.SubGraphPath <<
"' for SubGraph node " <<
nd.NodeID <<
"\n";
446 SYSTEM_LOG <<
"[TaskGraphLoader] ParseNodeV4: synced SubGraphPath from Parameters[subgraph_path] = '"
447 <<
paramPath <<
"' for SubGraph node " <<
nd.NodeID <<
"\n";
462 if (
nd.MathOperator.empty() && !
nd.mathOpRef.mathOperator.empty())
464 nd.MathOperator =
nd.mathOpRef.mathOperator;
466 SYSTEM_LOG <<
"[TaskGraphLoader] ParseNodeV4: deserialized mathOpRef for MathOp node "
467 <<
nd.NodeID <<
"\n";
481 if (
nd.BBKey.empty())
485 if (
nd.MathOperator.empty())
489 if (
nd.DelaySeconds == 0.0f)
493 if (
nd.SubGraphPath.empty())
500 if (
nd.DelaySeconds == 0.0f)
509 [&](
const json&
c,
size_t ) {
510 if (
c.is_string())
nd.SwitchCases.push_back(
c.get<std::string>());
523 std::unordered_map<std::string, size_t>
seenValues;
534 if (!
scd.value.empty())
539 std::string
msg =
"[E011] Switch node #"
540 + std::to_string(
nd.NodeID)
541 +
": duplicate case value '" +
scd.value
542 +
"' at index " + std::to_string(
idx)
543 +
" (first seen at index "
544 + std::to_string(
it->second) +
")";
555 if (
scd.pinName.empty())
557 std::string
msg =
"[E012] Switch node #"
558 + std::to_string(
nd.NodeID)
559 +
": switchCases[" + std::to_string(
idx)
560 +
"] has empty pin name";
565 nd.switchCases.push_back(
scd);
572 nd.DynamicExecOutputPins.clear();
576 nd.DynamicExecOutputPins.push_back(
caseData.pinName);
579 SYSTEM_LOG <<
"[TaskGraphLoader] ParseNodeV4: Phase 2 FIX - regenerated "
580 <<
nd.DynamicExecOutputPins.size() <<
" dynamic pins for Switch node #"
581 <<
nd.NodeID <<
"\n";
589 nodeJson.contains(
"dynamicExecPins") &&
590 nodeJson[
"dynamicExecPins"].is_array())
599 nd.DynamicExecOutputPins.push_back(
dynPins[
p].get<std::string>());
606 SYSTEM_LOG <<
"[TaskGraphLoader] ParseNodeV4: Switch node #" <<
nd.NodeID
607 <<
" - skipped loading dynamicExecPins from JSON (Phase 2 FIX)\n";
615 for (
size_t i = 0;
i <
ch.size(); ++
i)
637 if (bindingType ==
"Literal")
645 else if (bindingType ==
"LocalVariable")
650 else if (bindingType ==
"AtomicTaskID")
656 else if (bindingType ==
"ConditionID")
662 else if (bindingType ==
"MathOperator")
668 else if (bindingType ==
"ComparisonOp")
674 else if (bindingType ==
"SubGraphPath")
692 if (
it.value().is_string())
704 nd.HasEditorPos =
true;
712 [&](
const json&
cj,
size_t )
723 if (
cj.contains(
"leftConstValue"))
727 if (
cond.leftMode ==
"Variable" &&
cond.leftVariable.empty())
733 SYSTEM_LOG <<
"[TaskGraphLoader] v4->v5 migration: condition "
734 <<
"variable='" <<
legacyVar <<
"' mapped to leftVariable\n";
745 if (
cj.contains(
"rightConstValue"))
749 if (
cond.rightMode ==
"Const" &&
cond.rightConstValue.IsNone())
751 if (
cj.contains(
"compareValue"))
753 const json&
cv =
cj[
"compareValue"];
757 const std::string
cvStr =
cv.get<std::string>();
759 for (
size_t i = 0;
i <
cvStr.size(); ++
i)
765 cond.rightMode =
"Variable";
767 SYSTEM_LOG <<
"[TaskGraphLoader] v4->v5 migration: "
768 <<
"compareValue='" <<
cvStr
769 <<
"' looks like a variable name -> rightMode=Variable\n";
787 nd.conditions.push_back(
cond);
803 if (
refJson.contains(
"conditionIndex") &&
refJson[
"conditionIndex"].is_number_integer())
806 ref.conditionIndex =
static_cast<int>(
idx);
809 if (
refJson.contains(
"leftOperand") &&
refJson[
"leftOperand"].is_object())
833 if (
refJson.contains(
"rightOperand") &&
refJson[
"rightOperand"].is_object())
857 nd.conditionOperandRefs.push_back(
ref);
860 SYSTEM_LOG <<
"[TaskGraphLoader] Phase 24: deserialized "
861 <<
nd.conditionOperandRefs.size() <<
" conditionRefs for node "
862 <<
nd.NodeID <<
"\n";
879 nd.conditionRefs.push_back(
ncref);
882 SYSTEM_LOG <<
"[TaskGraphLoader] Phase 24: deserialized "
883 <<
nd.conditionRefs.size() <<
" nodeConditionRefs for node "
884 <<
nd.NodeID <<
"\n";
905 SYSTEM_LOG <<
"[TaskGraphLoader] ParseNodeV4 final sync: restored SubGraphPath from Parameters = '"
906 <<
paramPath <<
"' for node " <<
nd.NodeID <<
"\n";
912 if (!
nd.SubGraphPath.empty() &&
915 pathParamIt->second.LiteralValue.to_string().empty())))
921 SYSTEM_LOG <<
"[TaskGraphLoader] ParseNodeV4 final sync: initialized Parameters[subgraph_path] = '"
922 <<
nd.SubGraphPath <<
"' for node " <<
nd.NodeID <<
"\n";
935 std::vector<std::string>& )
961 if (
bbArray ==
nullptr)
return;
1002 !
entry.Default.IsNone() &&
1017 if (!
entry.Key.empty())
1036 arr = &
root[
"execConnections"];
1040 arr = &
root[
"ExecConnections"];
1047 arr = &
ds[
"exec_connections"];
1051 arr = &
ds[
"ExecConnections"];
1055 if (
arr ==
nullptr)
return;
1074 tmpl->ExecConnections.push_back(
conn);
1090 arr = &
root[
"dataConnections"];
1094 arr = &
root[
"DataConnections"];
1101 arr = &
ds[
"data_connections"];
1105 arr = &
ds[
"DataConnections"];
1109 if (
arr ==
nullptr)
return;
1125 tmpl->DataConnections.push_back(
conn);
1135 std::unordered_map<std::string, ParameterBinding>&
outParams)
1139 const std::string&
key =
it.key();
1144 if (
val.is_object())
1150 if (
btype ==
"Variable" ||
btype ==
"LocalVariable")
1156 else if (
btype ==
"AtomicTaskID")
1162 else if (
btype ==
"ConditionID")
1168 else if (
btype ==
"MathOperator")
1174 else if (
btype ==
"ComparisonOp")
1180 else if (
btype ==
"SubGraphPath")
1215 if (
val.is_number_float())
return TaskValue(
static_cast<float>(
val.get<
double>()));
1223 const std::string&
key,
1239 if (
top >= 0)
return top;
1248 const std::string& graphType,
1268 if (
s ==
"Sequence")
1270 return (graphType ==
"VisualScript")
1326 std::ifstream
f(path);
1336 std::vector<std::string> result;
1344 SYSTEM_LOG <<
"[TaskGraphLoader] WARNING: Directory not found: " <<
dir << std::endl;
1350 std::string name =
findData.cFileName;
1351 if (name ==
"." || name ==
"..")
continue;
1353 std::string fullPath =
dir +
"/" + name;
1357 result.insert(result.end(),
sub.begin(),
sub.end());
1359 else if (name.size() > 4 && name.substr(name.size() - 4) ==
".ats")
1361 result.push_back(fullPath);
1371 SYSTEM_LOG <<
"[TaskGraphLoader] WARNING: Directory not found: " <<
dir << std::endl;
1378 std::string name =
entry->d_name;
1379 if (name ==
"." || name ==
"..")
continue;
1381 std::string fullPath =
dir +
"/" + name;
1385 result.insert(result.end(),
sub.begin(),
sub.end());
1387 else if (name.size() > 4 && name.substr(name.size() - 4) ==
".ats")
1389 result.push_back(fullPath);
1396 std::sort(result.begin(), result.end());
ComponentTypeID GetComponentTypeID_Static()
Clean schema v4 parser for ATS Visual Script task graphs.
Migrates task graph JSON from schema v3 (BT-style) to schema v4 (VS-style).
static TaskGraphTemplate Convert(const nlohmann::json &btV2Json, std::vector< std::string > &outErrors)
Converts a BT v2 JSON document to a TaskGraphTemplate.
static bool IsDecoratorName(const std::string &nodeName)
Returns true if nodeName indicates a BT Decorator node.
static bool IsBTv2(const nlohmann::json &j)
Returns true if j looks like a BT v2 document.
static ExecPinRole StringToExecPinRole(const std::string &s)
static DataPinDir StringToDataPinDir(const std::string &s)
static TaskNodeDefinition ParseNodeV4(const json &nodeJson, const std::string &graphType, std::vector< std::string > &outErrors)
Parses a single node JSON (v4 flat format) into a TaskNodeDefinition.
static TaskNodeType StringToNodeType(const std::string &s, const std::string &graphType, bool &outOk)
static bool ValidateJson(const json &data, std::vector< std::string > &outErrors)
Validates a JSON object against the expected task graph schema.
static void ParseDataConnectionsV4(const json &root, TaskGraphTemplate *tmpl)
Parses dataConnections (v4) and fills tmpl->DataConnections.
static VariableType StringToVariableType(const std::string &s)
static TaskGraphTemplate * LoadFromFile(const std::string &path, std::vector< std::string > &outErrors)
Loads a TaskGraphTemplate from a JSON file on disk.
static std::vector< std::string > ScanTaskGraphDirectory(const std::string &dir)
Recursively scans a directory for .ats task graph files.
static bool FileExists(const std::string &path)
Returns true if the given file path exists and can be opened.
static int ResolveRootNodeId(const json &data, const json &dataSection)
static TaskGraphTemplate * ParseSchemaV4(const json &data, std::vector< std::string > &outErrors)
Parses a schema v4 flat JSON into a TaskGraphTemplate.
static bool GetChildValue(const json &obj, const std::string &key, json &outVal)
static void ParseExecConnectionsV4(const json &root, TaskGraphTemplate *tmpl)
Parses execConnections (v4) and fills tmpl->ExecConnections.
static TaskValue ParsePrimitiveValue(const json &val)
static void ParseBlackboardV4(const json &root, TaskGraphTemplate *tmpl, std::vector< std::string > &outErrors)
Parses the blackboard array (v4) and fills tmpl->Blackboard.
static void ParseParameters(const json ¶msJson, std::unordered_map< std::string, ParameterBinding > &outParams)
static TaskGraphTemplate * LoadFromJson(const json &data, std::vector< std::string > &outErrors)
Loads a TaskGraphTemplate from an already-parsed JSON object.
static json MigrateJson(const json &v3data, std::vector< std::string > &outErrors)
Performs the v3->v4 JSON transformation in memory.
Immutable, shareable task graph asset.
C++14-compliant type-safe value container for task parameters.
std::string GetString(const json &j, const std::string &key, const std::string &defaultValue="")
Safely get a string value from JSON.
bool LoadJsonFromFile(const std::string &filepath, json &j)
Load and parse a JSON file.
int GetInt(const json &j, const std::string &key, int defaultValue=0)
Safely get an integer value from JSON.
void ForEachInArray(const json &j, const std::string &key, std::function< void(const json &, size_t)> callback)
Iterate over an array with a callback function.
float GetFloat(const json &j, const std::string &key, float defaultValue=0.0f)
Safely get a float value from JSON.
bool IsArray(const json &j, const std::string &key)
Check if a key contains an array.
bool IsObject(const json &j, const std::string &key)
Check if a key contains an object.
bool GetBool(const json &j, const std::string &key, bool defaultValue=false)
Safely get a boolean value from JSON.
< Provides AssetID and INVALID_ASSET_ID
VariableType
Type tags used by TaskValue to identify stored data.
@ Int
32-bit signed integer
@ Float
Single-precision float.
@ GlobalRef
Reference to a global blackboard key (scope "global:")
@ List
std::vector<TaskValue> (used by ForEach node)
@ Vector
3-component vector (Vector from vector.h)
@ None
Uninitialized / empty value.
@ EntityID
Entity identifier (uint64_t)
@ MathOperator
Math operator symbol (+, -, *, /, %) (from OperatorRegistry)
@ ConditionID
ID of a condition type (from ConditionRegistry)
@ AtomicTaskID
ID of an atomic task (from AtomicTaskUIRegistry)
@ SubGraphPath
File path to a sub-graph .ats file.
@ LocalVariable
Value is read from the local blackboard at runtime.
@ Literal
Value is embedded directly in the template.
@ ComparisonOp
Comparison operator (==, !=, <, <=, >, >=) (from OperatorRegistry)
TaskNodeType
Identifies the role of a node in the task graph.
@ Selector
Executes children in order; stops on first success.
@ AtomicTask
Leaf node that executes a single atomic task.
@ While
Conditional loop (Loop / Completed exec outputs)
@ Sequence
Executes children in order; stops on first failure.
@ Decorator
Wraps a single child and modifies its behaviour.
@ SubGraph
Sub-graph call (SubTask)
@ DoOnce
Single-fire execution (reset via Reset pin)
@ Delay
Timer (Completed exec output after N seconds)
@ Parallel
Executes all children simultaneously.
@ 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)
@ Root
Entry point of the graph (exactly one per template)
@ 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.
DataPinDir
Direction of a data pin on a Visual Script node.
@ Output
Value produced by the node.
@ Input
Value consumed by the node.
ExecPinRole
Role of an exec pin on a Visual Script node.
@ OutCompleted
End-of-loop output (While, ForEach, Delay, DoOnce)
@ OutLoop
Loop body output (While, ForEach)
@ Out
Normal output / Then.
@ OutCase
Switch case output (dynamically named)
@ OutElse
Else output (Branch)
@ In
Triggers execution of the node.
static TaskValue GetDefaultValueForType(VariableType type)
Returns a correctly-typed default TaskValue for the given VariableType.
Single entry in the graph's declared blackboard schema (local or global).
A globally-stored, reusable condition expression.
static ConditionPreset FromJson(const nlohmann::json &data)
Deserializes a ConditionPreset from a JSON object.
Stores the complete reference for one condition including operand-to-DynamicDataPin mapping.
int conditionIndex
Index in the Branch/While node's conditions[] array.
Describes a single condition expression for Branch/While nodes.
std::string leftMode
"Pin" | "Variable" | "Const"
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...
static MathOpRef FromJson(const nlohmann::json &data)
Deserializes a MathOpRef from a JSON object.
One entry in a NodeBranch's conditions list.
static NodeConditionRef FromJson(const nlohmann::json &data)
Deserializes a NodeConditionRef from a JSON object.
@ 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.
ParameterBindingType Type
Binding mode.
Describes an input or output parameter declared on a SubGraph file.
std::string Name
Parameter name (must match binding keys)
Describes a single case branch on a Switch node.
std::string value
The value to match (int as decimal string or raw string)
Full description of a single node in the task graph.
int32_t NodeID
Unique ID within this template.