9#include "../json_helper.h"
27 NodeGraph::NodeGraph()
28 : name(
"Untitled Graph")
29 , type(
"BehaviorTree")
39 , rootNodeId(
other.rootNodeId)
40 , editorMetadata(
other.editorMetadata)
41 , m_Nodes(
other.m_Nodes)
42 , m_NextNodeId(
other.m_NextNodeId)
43 , m_IsDirty(
other.m_IsDirty)
44 , m_Filepath(
other.m_Filepath)
45 , m_eventRootIds(
other.m_eventRootIds)
70 : name(std::move(
other.name))
71 , type(std::move(
other.type))
72 , rootNodeId(
other.rootNodeId)
73 , editorMetadata(std::move(
other.editorMetadata))
74 , m_Nodes(std::move(
other.m_Nodes))
75 , m_NextNodeId(
other.m_NextNodeId)
76 , m_IsDirty(
other.m_IsDirty)
77 , m_Filepath(std::move(
other.m_Filepath))
78 , m_eventRootIds(std::move(
other.m_eventRootIds))
79 , m_commandHistory(std::move(
other.m_commandHistory))
87 name = std::move(
other.name);
88 type = std::move(
other.type);
89 rootNodeId =
other.rootNodeId;
90 editorMetadata = std::move(
other.editorMetadata);
91 m_Nodes = std::move(
other.m_Nodes);
92 m_NextNodeId =
other.m_NextNodeId;
93 m_IsDirty =
other.m_IsDirty;
94 m_Filepath = std::move(
other.m_Filepath);
95 m_commandHistory = std::move(
other.m_commandHistory);
100 int NodeGraph::CreateNode(
NodeType nodeType,
float x,
float y,
const std::string& nodeName)
104 node.type = nodeType;
113 std::cout <<
"[NodeGraph] Created node " <<
node.id <<
" (" <<
node.name <<
")\n";
117 bool NodeGraph::DeleteNode(
int nodeId)
130 auto it = std::find(
node.childIds.begin(),
node.childIds.end(), nodeId);
131 if (
it !=
node.childIds.end())
135 if (
node.decoratorChildId == nodeId)
136 node.decoratorChildId = -1;
141 std::cout <<
"[NodeGraph] Deleted node " << nodeId <<
"\n";
161 std::vector<GraphNode*> NodeGraph::GetAllNodes()
163 std::vector<GraphNode*> result;
165 result.push_back(&
node);
169 std::vector<const GraphNode*> NodeGraph::GetAllNodes()
const
171 std::vector<const GraphNode*> result;
173 result.push_back(&
node);
177 bool NodeGraph::LinkNodes(
int parentId,
int childId)
194 parent->
childIds.push_back(childId);
199 std::cout <<
"[NodeGraph] Linked node " << parentId <<
" -> " << childId <<
"\n";
203 bool NodeGraph::UnlinkNodes(
int parentId,
int childId)
216 std::cout <<
"[NodeGraph] Unlinked node " << parentId <<
" -> " << childId <<
"\n";
224 std::cout <<
"[NodeGraph] Unlinked decorator child " << parentId <<
" -> " << childId <<
"\n";
234 std::vector<GraphLink> NodeGraph::GetAllLinks()
const
236 std::vector<GraphLink>
links;
241 for (
int childId :
node.childIds)
247 if (
node.decoratorChildId >= 0)
256 bool NodeGraph::SetNodeParameter(
int nodeId,
const std::string&
paramName,
const std::string& value)
267 std::string NodeGraph::GetNodeParameter(
int nodeId,
const std::string&
paramName)
const
274 if (
it !=
node->parameters.end())
285 j[
"schema_version"] = 2;
286 j[
"blueprintType"] =
type.empty() ?
"BehaviorTree" :
type;
288 j[
"description"] =
"";
291 j[
"metadata"][
"author"] =
"User";
292 j[
"metadata"][
"created"] =
"";
294 j[
"metadata"][
"tags"] = json::array();
303 j[
"data"][
"nodes"] = json::array();
304 j[
"data"][
"eventRoots"] = json::array();
314 nj[
"position"][
"x"] =
node.posX;
315 nj[
"position"][
"y"] =
node.posY;
317 if (!
node.actionType.empty())
318 nj[
"actionType"] =
node.actionType;
319 if (!
node.conditionType.empty())
320 nj[
"conditionType"] =
node.conditionType;
321 if (!
node.decoratorType.empty())
322 nj[
"decoratorType"] =
node.decoratorType;
323 if (!
node.subgraphUUID.empty())
324 nj[
"subgraphUUID"] =
node.subgraphUUID;
327 if (!
node.eventType.empty())
328 nj[
"eventType"] =
node.eventType;
329 if (!
node.eventMessage.empty())
330 nj[
"eventMessage"] =
node.eventMessage;
333 nj[
"parameters"] = json::object();
334 if (!
node.parameters.empty())
336 for (
const auto&
pair :
node.parameters)
341 nj[
"children"] = json::array();
342 for (
int childId :
node.childIds)
345 if (
node.decoratorChildId >= 0)
346 nj[
"decoratorChild"] =
node.decoratorChildId;
348 j[
"data"][
"nodes"].push_back(
nj);
362 std::cout <<
"[NodeGraph::FromJson] Starting parsing..." << std::endl;
368 bool isV2 =
j.contains(
"schema_version") ||
j.contains(
"data");
369 std::cout <<
"[NodeGraph::FromJson] Format: " << (
isV2 ?
"v2" :
"v1") << std::endl;
373 if (
isV2 &&
j.contains(
"data"))
378 std::cout <<
"[NodeGraph::FromJson] Extracted 'data' section from v2" << std::endl;
382 if (
dataObj.contains(
"rootGraph") &&
dataObj[
"rootGraph"].is_object())
385 std::cout <<
"[NodeGraph::FromJson] Using data.rootGraph (Phase 8 format)" << std::endl;
396 std::cout <<
"[NodeGraph::FromJson] Using root as data section (v1)" << std::endl;
400 std::cout <<
"[NodeGraph::FromJson] Root node ID: " <<
graph.rootNodeId << std::endl;
405 std::cerr <<
"[NodeGraph::FromJson] ERROR: No 'nodes' array in data section" << std::endl;
412 std::cout <<
"[NodeGraph::FromJson] Parsing " << nodeCount <<
" nodes..." << std::endl;
428 if (
nj.contains(
"position") &&
nj[
"position"].is_object())
453 if (
nj.contains(
"parameters") &&
nj[
"parameters"].is_object())
459 node.parameters[
it.key()] =
it.value().is_string() ?
it.value().get<std::string>()
466 if (
nj.contains(
"param"))
467 node.parameters[
"param"] =
nj[
"param"].dump();
468 if (
nj.contains(
"param1"))
469 node.parameters[
"param1"] =
nj[
"param1"].dump();
470 if (
nj.contains(
"param2"))
471 node.parameters[
"param2"] =
nj[
"param2"].dump();
488 std::cout <<
"[NodeGraph::FromJson] Node " <<
node.id <<
": " <<
node.name
490 <<
" children: " <<
node.childIds.size() << std::endl;
501 std::cout <<
"[NodeGraph::FromJson] Loading event roots..." << std::endl;
506 uint32_t eventRootId = eventRootJson.get<uint32_t>();
507 graph.m_eventRootIds.push_back(eventRootId);
508 std::cout <<
"[NodeGraph::FromJson] Event root: " << eventRootId << std::endl;
516 std::cout <<
"[NodeGraph::FromJson] No positions found, calculating hierarchical layout..." << std::endl;
517 graph.CalculateNodePositionsHierarchical();
518 std::cout <<
"[NodeGraph::FromJson] Position calculation complete" << std::endl;
522 std::cout <<
"[NodeGraph::FromJson] Using existing node positions from file" << std::endl;
528 if (
j.contains(
"editorState") &&
j[
"editorState"].is_object())
530 const json& state =
j[
"editorState"];
533 if (state.contains(
"scrollOffset") && state[
"scrollOffset"].is_object())
541 std::cout <<
"[NodeGraph::FromJson] Parsing complete: " <<
graph.m_Nodes.size() <<
" nodes loaded" << std::endl;
545 }
catch (
const std::exception&
e) {
546 std::cerr <<
"[NodeGraph::FromJson] EXCEPTION: " <<
e.what() << std::endl;
551 bool NodeGraph::ValidateGraph(std::string&
errorMsg)
const
557 for (
int childId :
node.childIds)
561 errorMsg =
"Node " + std::to_string(
node.id) +
" has invalid child " + std::to_string(childId);
568 errorMsg =
"Node " + std::to_string(
node.id) +
" has invalid decorator child";
576 void NodeGraph::Clear()
595 bool NodeGraph::CanUndo()
const
600 bool NodeGraph::CanRedo()
const
605 std::string NodeGraph::GetUndoDescription()
const
610 std::string NodeGraph::GetRedoDescription()
const
615 bool NodeGraph::Undo()
620 bool NodeGraph::Redo()
644 std::cout <<
"[NodeGraph] Added event root: " << nodeId << std::endl;
655 std::cout <<
"[NodeGraph] Removed event root: " << nodeId << std::endl;
659 const std::vector<uint32_t>& NodeGraph::GetEventRootIds()
const
664 int NodeGraph::FindNodeIndex(
int nodeId)
const
669 return static_cast<int>(
i);
674 void NodeGraph::CalculateNodePositionsHierarchical()
676 const float HORIZONTAL_SPACING = 350.0f;
677 const float VERTICAL_SPACING = 200.0f;
678 const float START_X = 200.0f;
679 const float START_Y = 300.0f;
681 std::cout <<
"[NodeGraph] Calculating hierarchical positions for " <<
m_Nodes.size() <<
" nodes\n";
687 if (!
node.childIds.empty())
696 std::cerr <<
"[NodeGraph] No root node ID, cannot calculate positions\n";
700 std::queue<std::pair<int, int>>
queue;
706 while (!
queue.empty())
709 int nodeId =
front.first;
710 int depth =
front.second;
713 if (
visited.count(nodeId))
continue;
725 std::cout <<
"[NodeGraph] Node " << nodeId <<
" positioned at ("
736 queue.push({childId, depth + 1});
742 std::cout <<
"[NodeGraph] Position calculation complete\n";
767 std::cout <<
"[NodeGraphManager] Initializing...\n";
776 std::cout <<
"[NodeGraphManager] Shutting down...\n";
784 auto graph = std::make_unique<NodeGraph>();
794 std::cout <<
"[NodeGraphManager] Created graph " << graphId <<
" (" << name <<
")\n";
846 std::cout <<
"[NodeGraphManager] Closed graph " << graphId <<
"\n";
855 return it->second.get();
863 return it->second.get();
921 auto now = std::chrono::system_clock::now();
922 auto time = std::chrono::system_clock::to_time_t(
now);
923 std::stringstream
ss;
928 ss << std::put_time(&
timeinfo,
"%Y-%m-%dT%H:%M:%S");
933 ss << std::put_time(&
timeinfo,
"%Y-%m-%dT%H:%M:%S");
936 graph->editorMetadata.lastModified =
ss.str();
940 std::ofstream
file(filepath);
948 graph->SetFilepath(filepath);
951 std::cout <<
"[NodeGraphManager] Saved graph " << graphId <<
" to " << filepath <<
"\n";
957 std::cout <<
"\n========================================" << std::endl;
958 std::cout <<
"[NodeGraphManager::LoadGraph] CALLED" << std::endl;
959 std::cout <<
"[NodeGraphManager::LoadGraph] Path: " << filepath << std::endl;
960 std::cout <<
"========================================+n" << std::endl;
964 std::cout <<
"[NodeGraphManager] Step 1: Checking file exists..." << std::endl;
968 std::cerr <<
"[NodeGraphManager] ERROR: File not found: " << filepath << std::endl;
969 std::cout <<
"========================================+n" << std::endl;
973 std::cout <<
"[NodeGraphManager] File exists: OK" << std::endl;
976 std::cout <<
"[NodeGraphManager] Step 2: Loading file content..." << std::endl;
977 std::ifstream
file(filepath);
980 std::cerr <<
"[NodeGraphManager] ERROR: Cannot open file: " << filepath << std::endl;
981 std::cout <<
"========================================+n" << std::endl;
985 std::string
content((std::istreambuf_iterator<char>(
file)), std::istreambuf_iterator<char>());
987 std::cout <<
"[NodeGraphManager] File loaded: " <<
content.size() <<
" bytes" << std::endl;
991 std::cerr <<
"[NodeGraphManager] ERROR: File is empty" << std::endl;
992 std::cout <<
"========================================+n" << std::endl;
997 std::cout <<
"[NodeGraphManager] Step 3: Parsing JSON..." << std::endl;
1001 std::cout <<
"[NodeGraphManager] JSON parsed: OK" << std::endl;
1002 }
catch (
const std::exception&
e) {
1003 std::cerr <<
"[NodeGraphManager] ERROR parsing JSON: " <<
e.what() << std::endl;
1004 std::cout <<
"========================================+n" << std::endl;
1013 std::cout <<
"[NodeGraphManager] Applying Phase 8 subgraph migration..." << std::endl;
1018 std::ofstream
migOut(filepath);
1023 std::cout <<
"[NodeGraphManager] Migrated file saved: " << filepath << std::endl;
1025 }
catch (
const std::exception&
saveEx) {
1026 std::cerr <<
"[NodeGraphManager] WARNING: Could not save migrated file: "
1027 <<
saveEx.what() << std::endl;
1033 std::cout <<
"[NodeGraphManager] Step 4: Detecting version..." << std::endl;
1034 bool isV2 =
j.contains(
"schema_version") &&
1035 (
j[
"schema_version"].get<
int>() >= 2);
1036 bool isV1 = !
isV2 && (
j.contains(
"nodes") ||
j.contains(
"rootNodeId"));
1038 std::cout <<
"[NodeGraphManager] Version: " << (
isV2 ?
"v2+" : (
isV1 ?
"v1" :
"Unknown")) << std::endl;
1042 std::cerr <<
"[NodeGraphManager] ERROR: Invalid blueprint format (neither v1 nor v2)" << std::endl;
1043 std::cout <<
"========================================+n" << std::endl;
1048 std::cout <<
"[NodeGraphManager] Step 5: Parsing graph with FromJson..." << std::endl;
1051 graph = NodeGraph::FromJson(
j);
1052 std::cout <<
"[NodeGraphManager] FromJson returned: " <<
graph.GetAllNodes().size() <<
" nodes" << std::endl;
1053 }
catch (
const std::exception&
e) {
1054 std::cerr <<
"[NodeGraphManager] ERROR in FromJson: " <<
e.what() << std::endl;
1055 std::cout <<
"========================================+n" << std::endl;
1062 std::cout <<
"[NodeGraphManager] Step 6: Detected v1 format, migrating to v2..." << std::endl;
1066 v2Json[
"schema_version"] = 2;
1067 v2Json[
"blueprintType"] =
graph.type.empty() ?
"BehaviorTree" :
graph.type;
1069 v2Json[
"description"] =
"";
1072 json metadata = json::object();
1073 metadata[
"author"] =
"Atlasbruce";
1074 metadata[
"created"] =
"2026-01-09T18:26:00Z";
1075 metadata[
"lastModified"] =
"2026-01-09T18:26:00Z";
1076 metadata[
"tags"] = json::array();
1077 v2Json[
"metadata"] = metadata;
1080 json editorState = json::object();
1081 editorState[
"zoom"] = 1.0;
1082 json scrollOffset = json::object();
1083 scrollOffset[
"x"] = 0;
1084 scrollOffset[
"y"] = 0;
1085 editorState[
"scrollOffset"] = scrollOffset;
1086 v2Json[
"editorState"] = editorState;
1092 std::cout <<
"[NodeGraphManager] Saving migrated v2 file..." << std::endl;
1095 std::string
backupPath = filepath +
".v1.backup";
1096 std::ifstream
src(filepath, std::ios::binary);
1101 std::cout <<
"[NodeGraphManager] Original backed up to: " <<
backupPath << std::endl;
1104 std::ofstream
outFile(filepath);
1109 std::cout <<
"[NodeGraphManager] Migrated file saved: " << filepath << std::endl;
1111 }
catch (
const std::exception&
e) {
1112 std::cerr <<
"[NodeGraphManager] WARNING: Could not save migrated file: " <<
e.what() << std::endl;
1117 std::cout <<
"[NodeGraphManager] Step 7: Creating graph in manager..." << std::endl;
1119 auto graphPtr = std::make_unique<NodeGraph>(std::move(
graph));
1130 std::cout <<
"[NodeGraphManager] Graph registered with ID: " << graphId << std::endl;
1131 std::cout <<
"[NodeGraphManager] Graph name: " <<
m_Graphs[graphId]->name << std::endl;
1132 std::cout <<
"[NodeGraphManager] Graph type: " <<
m_Graphs[graphId]->type << std::endl;
1133 std::cout <<
"[NodeGraphManager] Total graphs loaded: " <<
m_Graphs.size() << std::endl;
1134 std::cout <<
"[NodeGraphManager] Active graph ID: " <<
m_ActiveGraphId << std::endl;
1136 std::cout <<
"\n========================================" << std::endl;
1137 std::cout <<
"[NodeGraphManager::LoadGraph] SUCCESS ->" << std::endl;
1138 std::cout <<
"========================================+n" << std::endl;
1142 }
catch (
const std::exception&
e) {
1143 std::cerr <<
"\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
1144 std::cerr <<
"[NodeGraphManager] EXCEPTION: " <<
e.what() << std::endl;
1145 std::cerr <<
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
1146 std::cout <<
"========================================+n" << std::endl;
1149 std::cerr <<
"\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
1150 std::cerr <<
"[NodeGraphManager] UNKNOWN EXCEPTION" << std::endl;
1151 std::cerr <<
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
1152 std::cout <<
"========================================+n" << std::endl;
1167 if (
pair.second &&
pair.second->IsDirty())
1174 void NodeGraph::CopyNodesToClipboard(
const std::vector<int>& nodeIds)
1178 for (
int nodeId : nodeIds)
1181 [nodeId](
const GraphNode&
n) { return n.id == nodeId; });
1206 std::vector<int> NodeGraph::PasteNodesFromClipboard(
float offsetX,
float offsetY)
1232 newNode.decoratorChildId = -1;
1246 std::vector<int> NodeGraph::DuplicateNodes(
const std::vector<int>& nodeIds,
float offsetX,
float offsetY)
1250 for (
int origId : nodeIds)
1263 newNode.decoratorChildId = -1;
Concrete command implementations for BehaviorTree operations.
Undo/redo history manager for graph commands.
ComponentTypeID GetComponentTypeID_Static()
Phase 8 — Migrates legacy blueprint data to the flat-dictionary subgraph format.
Manages undo/redo stacks for graph operations.
NodeGraphManager - Manages multiple node graphs Allows opening multiple behavior trees/FSMs simultane...
bool IsGraphDirty(int graphId) const
NodeGraph * GetActiveGraph()
bool CloseGraph(int graphId)
std::vector< int > GetAllGraphIds() const
void SetGraphOrder(const std::vector< int > &newOrder)
int LoadGraph(const std::string &filepath)
void SetActiveGraph(int graphId)
NodeGraph * GetGraph(int graphId)
std::string GetGraphName(int graphId) const
int CreateGraph(const std::string &name, const std::string &type)
std::vector< int > m_GraphOrder
static NodeGraphManager & Instance()
bool HasUnsavedChanges() const
bool SaveGraph(int graphId, const std::string &filepath)
std::map< int, std::unique_ptr< NodeGraph > > m_Graphs
EditorMetadata editorMetadata
std::vector< ClipboardNode > m_clipboardData
std::unique_ptr< CommandHistory > m_commandHistory
std::vector< uint32_t > m_eventRootIds
Separate array of node IDs that are OnEvent root nodes These nodes represent independent execution tr...
std::vector< GraphNode > m_Nodes
int FindNodeIndex(int nodeId) const
GraphNode * GetNode(int nodeId)
Converts legacy blueprint JSON to the Phase 8 subgraph flat-dict format.
nlohmann::json Migrate(const nlohmann::json &blueprint) const
Migrates a legacy blueprint to the flat-dictionary format.
std::string GetString(const json &j, const std::string &key, const std::string &defaultValue="")
Safely get a string value from JSON.
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.
< Provides AssetID and INVALID_ASSET_ID
const char * NodeTypeToString(NodeType type)
NodeType StringToNodeType(const std::string &str)
Serializable node data for copy/paste operations.
std::vector< int > childIds