Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
NodeGraphManager.cpp
Go to the documentation of this file.
1/*
2 * Olympe Blueprint Editor - Node Graph Manager Implementation
3 */
4
5#include "NodeGraphManager.h"
6#include "../json_helper.h"
7#include <fstream>
8#include <iostream>
9#include <algorithm>
10#include <string>
11#include <queue>
12#include <set>
13#include <map>
14#include <chrono>
15#include <sstream>
16#include <iomanip>
17
19
20namespace Olympe
21{
22 // ========== NodeGraph Implementation ==========
23
25 : name("Untitled Graph")
26 , type("BehaviorTree")
27 , rootNodeId(-1)
28 , m_NextNodeId(1)
29 {
30 }
31
32 int NodeGraph::CreateNode(NodeType nodeType, float x, float y, const std::string& nodeName)
33 {
36 node.type = nodeType;
37 node.name = nodeName.empty() ? NodeTypeToString(nodeType) : nodeName;
38 node.posX = x;
39 node.posY = y;
40
41 m_Nodes.push_back(node);
42
43 MarkDirty(); // Mark graph as modified
44
45 std::cout << "[NodeGraph] Created node " << node.id << " (" << node.name << ")\n";
46 return node.id;
47 }
48
49 bool NodeGraph::DeleteNode(int nodeId)
50 {
51 int index = FindNodeIndex(nodeId);
52 if (index < 0)
53 return false;
54
55 // Remove node
56 m_Nodes.erase(m_Nodes.begin() + index);
57
58 // Clean up references to this node
59 for (auto& node : m_Nodes)
60 {
61 // Remove from child lists
62 auto it = std::find(node.childIds.begin(), node.childIds.end(), nodeId);
63 if (it != node.childIds.end())
64 node.childIds.erase(it);
65
66 // Clear decorator child if it matches
67 if (node.decoratorChildId == nodeId)
68 node.decoratorChildId = -1;
69 }
70
71 MarkDirty(); // Mark graph as modified
72
73 std::cout << "[NodeGraph] Deleted node " << nodeId << "\n";
74 return true;
75 }
76
78 {
79 int index = FindNodeIndex(nodeId);
80 if (index < 0)
81 return nullptr;
82 return &m_Nodes[index];
83 }
84
85 const GraphNode* NodeGraph::GetNode(int nodeId) const
86 {
87 int index = FindNodeIndex(nodeId);
88 if (index < 0)
89 return nullptr;
90 return &m_Nodes[index];
91 }
92
93 std::vector<GraphNode*> NodeGraph::GetAllNodes()
94 {
95 std::vector<GraphNode*> result;
96 for (auto& node : m_Nodes)
97 result.push_back(&node);
98 return result;
99 }
100
101 std::vector<const GraphNode*> NodeGraph::GetAllNodes() const
102 {
103 std::vector<const GraphNode*> result;
104 for (const auto& node : m_Nodes)
105 result.push_back(&node);
106 return result;
107 }
108
109 bool NodeGraph::LinkNodes(int parentId, int childId)
110 {
111 GraphNode* parent = GetNode(parentId);
112 if (!parent)
113 return false;
114
115 // Check if already linked
116 if (std::find(parent->childIds.begin(), parent->childIds.end(), childId) != parent->childIds.end())
117 return false;
118
119 // For decorator nodes, use decoratorChildId instead
120 if (parent->type == NodeType::BT_Decorator)
121 {
122 parent->decoratorChildId = childId;
123 }
124 else
125 {
126 parent->childIds.push_back(childId);
127 }
128
129 MarkDirty(); // Mark graph as modified
130
131 std::cout << "[NodeGraph] Linked node " << parentId << " -> " << childId << "\n";
132 return true;
133 }
134
135 bool NodeGraph::UnlinkNodes(int parentId, int childId)
136 {
137 GraphNode* parent = GetNode(parentId);
138 if (!parent)
139 return false;
140
141 bool unlinked = false;
142
143 // Remove from child list
144 auto it = std::find(parent->childIds.begin(), parent->childIds.end(), childId);
145 if (it != parent->childIds.end())
146 {
147 parent->childIds.erase(it);
148 std::cout << "[NodeGraph] Unlinked node " << parentId << " -> " << childId << "\n";
149 unlinked = true;
150 }
151
152 // Check decorator child
153 if (parent->decoratorChildId == childId)
154 {
155 parent->decoratorChildId = -1;
156 std::cout << "[NodeGraph] Unlinked decorator child " << parentId << " -> " << childId << "\n";
157 unlinked = true;
158 }
159
160 if (unlinked)
161 MarkDirty(); // Mark graph as modified
162
163 return unlinked;
164 }
165
166 std::vector<GraphLink> NodeGraph::GetAllLinks() const
167 {
168 std::vector<GraphLink> links;
169
170 for (const auto& node : m_Nodes)
171 {
172 // Add links to children
173 for (int childId : node.childIds)
174 {
175 links.push_back(GraphLink(node.id, childId));
176 }
177
178 // Add decorator child link
179 if (node.decoratorChildId >= 0)
180 {
181 links.push_back(GraphLink(node.id, node.decoratorChildId));
182 }
183 }
184
185 return links;
186 }
187
188 bool NodeGraph::SetNodeParameter(int nodeId, const std::string& paramName, const std::string& value)
189 {
190 GraphNode* node = GetNode(nodeId);
191 if (!node)
192 return false;
193
194 node->parameters[paramName] = value;
195 MarkDirty(); // Mark graph as modified
196 return true;
197 }
198
199 std::string NodeGraph::GetNodeParameter(int nodeId, const std::string& paramName) const
200 {
201 const GraphNode* node = GetNode(nodeId);
202 if (!node)
203 return "";
204
205 auto it = node->parameters.find(paramName);
206 if (it != node->parameters.end())
207 return it->second;
208
209 return "";
210 }
211
213 {
214 json j;
215
216 // v2 Schema wrapper
217 j["schema_version"] = 2;
218 j["blueprintType"] = type.empty() ? "BehaviorTree" : type;
219 j["name"] = name;
220 j["description"] = ""; // Could be added to NodeGraph if needed
221
222 // Metadata section
223 j["metadata"]["author"] = "User"; // Could be made configurable
224 j["metadata"]["created"] = ""; // Could be tracked if needed
225 j["metadata"]["lastModified"] = editorMetadata.lastModified;
226 j["metadata"]["tags"] = json::array();
227
228 // Editor state
229 j["editorState"]["zoom"] = editorMetadata.zoom;
230 j["editorState"]["scrollOffset"]["x"] = editorMetadata.scrollOffsetX;
231 j["editorState"]["scrollOffset"]["y"] = editorMetadata.scrollOffsetY;
232
233 // Data section containing the actual tree
234 j["data"]["rootNodeId"] = rootNodeId;
235 j["data"]["nodes"] = json::array();
236
237 for (const auto& node : m_Nodes)
238 {
239 json nj;
240 nj["id"] = node.id;
241 nj["type"] = NodeTypeToString(node.type);
242 nj["name"] = node.name;
243
244 // Save position in a structured format
245 nj["position"]["x"] = node.posX;
246 nj["position"]["y"] = node.posY;
247
248 if (!node.actionType.empty())
249 nj["actionType"] = node.actionType;
250 if (!node.conditionType.empty())
251 nj["conditionType"] = node.conditionType;
252 if (!node.decoratorType.empty())
253 nj["decoratorType"] = node.decoratorType;
254
255 // Parameters as nested object (v2 format)
256 nj["parameters"] = json::object();
257 if (!node.parameters.empty())
258 {
259 for (const auto& pair : node.parameters)
260 nj["parameters"][pair.first] = pair.second;
261 }
262
263 // Children array
264 nj["children"] = json::array();
265 for (int childId : node.childIds)
266 nj["children"].push_back(childId);
267
268 if (node.decoratorChildId >= 0)
269 nj["decoratorChild"] = node.decoratorChildId;
270
271 j["data"]["nodes"].push_back(nj);
272 }
273
274 return j;
275 }
276
278 {
279 std::cout << "[NodeGraph::FromJson] Starting parsing..." << std::endl;
280
282
283 try {
284 // Detect schema version - v2 has nested "data" structure, v1 doesn't
285 bool isV2 = j.contains("schema_version") || j.contains("data");
286 std::cout << "[NodeGraph::FromJson] Format: " << (isV2 ? "v2" : "v1") << std::endl;
287
288 const json* dataSection = &j;
289
290 if (isV2 && j.contains("data"))
291 {
292 dataSection = &j["data"];
293 graph.name = JsonHelper::GetString(j, "name", "Untitled Graph");
294 graph.type = JsonHelper::GetString(j, "blueprintType", "BehaviorTree");
295 std::cout << "[NodeGraph::FromJson] Extracted 'data' section from v2" << std::endl;
296 }
297 else
298 {
299 graph.name = JsonHelper::GetString(j, "name", "Untitled Graph");
300 graph.type = JsonHelper::GetString(j, "type", "BehaviorTree");
301 std::cout << "[NodeGraph::FromJson] Using root as data section (v1)" << std::endl;
302 }
303
304 graph.rootNodeId = JsonHelper::GetInt(*dataSection, "rootNodeId", -1);
305 std::cout << "[NodeGraph::FromJson] Root node ID: " << graph.rootNodeId << std::endl;
306
307 // Parse nodes
308 if (!JsonHelper::IsArray(*dataSection, "nodes"))
309 {
310 std::cerr << "[NodeGraph::FromJson] ERROR: No 'nodes' array in data section" << std::endl;
311 return graph;
312 }
313
314 // Get node count
315 int nodeCount = 0;
316 JsonHelper::ForEachInArray(*dataSection, "nodes", [&](const json& nj, size_t idx) { nodeCount++; });
317 std::cout << "[NodeGraph::FromJson] Parsing " << nodeCount << " nodes..." << std::endl;
318
319 int maxId = 0;
320 bool hasPositions = false;
321
322 // First pass: load nodes
323 JsonHelper::ForEachInArray(*dataSection, "nodes", [&](const json& nj, size_t idx)
324 {
326 node.id = JsonHelper::GetInt(nj, "id", 0);
327 node.type = StringToNodeType(JsonHelper::GetString(nj, "type", "Action"));
328 node.name = JsonHelper::GetString(nj, "name", "");
329
330 std::string typeStr = JsonHelper::GetString(nj, "type", "Action");
331
332 // Load position - try v2 format first
333 if (nj.contains("position") && nj["position"].is_object())
334 {
335 node.posX = JsonHelper::GetFloat(nj["position"], "x", 0.0f);
336 node.posY = JsonHelper::GetFloat(nj["position"], "y", 0.0f);
337 hasPositions = true;
338 }
339 else
340 {
341 // v1 format has no position - will calculate later
342 node.posX = 0.0f;
343 node.posY = 0.0f;
344 }
345
346 node.actionType = JsonHelper::GetString(nj, "actionType", "");
347 node.conditionType = JsonHelper::GetString(nj, "conditionType", "");
348 node.decoratorType = JsonHelper::GetString(nj, "decoratorType", "");
349
350 // Load parameters - v2 has nested "parameters" object, v1 has flat structure
351 if (nj.contains("parameters") && nj["parameters"].is_object())
352 {
353 // v2 format
354 const json& params = nj["parameters"];
355 for (auto it = params.begin(); it != params.end(); ++it)
356 {
357 node.parameters[it.key()] = it.value().is_string() ? it.value().get<std::string>()
358 : it.value().dump();
359 }
360 }
361 else
362 {
363 // v1 format - parameters are flat in node object
364 if (nj.contains("param"))
365 node.parameters["param"] = nj["param"].dump();
366 if (nj.contains("param1"))
367 node.parameters["param1"] = nj["param1"].dump();
368 if (nj.contains("param2"))
369 node.parameters["param2"] = nj["param2"].dump();
370 }
371
372 // Load children
373 if (JsonHelper::IsArray(nj, "children"))
374 {
375 JsonHelper::ForEachInArray(nj, "children", [&](const json& childJson, size_t childIdx)
376 {
377 if (childJson.is_number())
378 node.childIds.push_back(childJson.get<int>());
379 });
380 }
381
382 node.decoratorChildId = JsonHelper::GetInt(nj, "decoratorChild", -1);
383
384 graph.m_Nodes.push_back(node);
385
386 std::cout << "[NodeGraph::FromJson] Node " << node.id << ": " << node.name
387 << " (" << typeStr << ") at (" << node.posX << "," << node.posY << ")"
388 << " children: " << node.childIds.size() << std::endl;
389
390 if (node.id > maxId)
391 maxId = node.id;
392 });
393
394 graph.m_NextNodeId = maxId + 1;
395
396 // Calculate positions if v1 (no positions)
397 if (!hasPositions)
398 {
399 std::cout << "[NodeGraph::FromJson] No positions found, calculating hierarchical layout..." << std::endl;
400 graph.CalculateNodePositionsHierarchical();
401 std::cout << "[NodeGraph::FromJson] Position calculation complete" << std::endl;
402 }
403 else
404 {
405 std::cout << "[NodeGraph::FromJson] Using existing node positions from file" << std::endl;
406 }
407
408 // Load editor metadata if present (v2 only)
409 if (isV2)
410 {
411 if (j.contains("editorState") && j["editorState"].is_object())
412 {
413 const json& state = j["editorState"];
414 graph.editorMetadata.zoom = JsonHelper::GetFloat(state, "zoom", 1.0f);
415
416 if (state.contains("scrollOffset") && state["scrollOffset"].is_object())
417 {
418 graph.editorMetadata.scrollOffsetX = JsonHelper::GetFloat(state["scrollOffset"], "x", 0.0f);
419 graph.editorMetadata.scrollOffsetY = JsonHelper::GetFloat(state["scrollOffset"], "y", 0.0f);
420 }
421 }
422 }
423
424 std::cout << "[NodeGraph::FromJson] Parsing complete: " << graph.m_Nodes.size() << " nodes loaded" << std::endl;
425
426 return graph;
427
428 } catch (const std::exception& e) {
429 std::cerr << "[NodeGraph::FromJson] EXCEPTION: " << e.what() << std::endl;
430 return graph;
431 }
432 }
433
434 bool NodeGraph::ValidateGraph(std::string& errorMsg) const
435 {
436 // Check for cycles (simplified check)
437 // Check that all child references are valid
438 for (const auto& node : m_Nodes)
439 {
440 for (int childId : node.childIds)
441 {
442 if (!GetNode(childId))
443 {
444 errorMsg = "Node " + std::to_string(node.id) + " has invalid child " + std::to_string(childId);
445 return false;
446 }
447 }
448
449 if (node.decoratorChildId >= 0 && !GetNode(node.decoratorChildId))
450 {
451 errorMsg = "Node " + std::to_string(node.id) + " has invalid decorator child";
452 return false;
453 }
454 }
455
456 return true;
457 }
458
460 {
461 m_Nodes.clear();
462 m_NextNodeId = 1;
463 rootNodeId = -1;
464 }
465
466 int NodeGraph::FindNodeIndex(int nodeId) const
467 {
468 for (size_t i = 0; i < m_Nodes.size(); ++i)
469 {
470 if (m_Nodes[i].id == nodeId)
471 return static_cast<int>(i);
472 }
473 return -1;
474 }
475
477 {
478 const float HORIZONTAL_SPACING = 350.0f;
479 const float VERTICAL_SPACING = 200.0f;
480 const float START_X = 200.0f;
481 const float START_Y = 300.0f;
482
483 std::cout << "[NodeGraph] Calculating hierarchical positions for " << m_Nodes.size() << " nodes\n";
484
485 // Build parent-child map
486 std::map<int, std::vector<int>> childrenMap;
487 for (const auto& node : m_Nodes)
488 {
489 if (!node.childIds.empty())
490 {
491 childrenMap[node.id] = node.childIds;
492 }
493 }
494
495 // BFS from root to assign positions by depth
496 if (rootNodeId < 0)
497 {
498 std::cerr << "[NodeGraph] No root node ID, cannot calculate positions\n";
499 return;
500 }
501
502 std::queue<std::pair<int, int>> queue; // nodeId, depth
503 queue.push({rootNodeId, 0});
504
505 std::map<int, int> depthCounter; // tracks sibling index at each depth
506 std::set<int> visited;
507
508 while (!queue.empty())
509 {
510 std::pair<int, int> front = queue.front();
511 int nodeId = front.first;
512 int depth = front.second;
513 queue.pop();
514
515 if (visited.count(nodeId)) continue;
516 visited.insert(nodeId);
517
518 int siblingIndex = depthCounter[depth]++;
519
520 // Find node and set position
521 int nodeIndex = FindNodeIndex(nodeId);
522 if (nodeIndex >= 0)
523 {
524 m_Nodes[nodeIndex].posX = START_X + depth * HORIZONTAL_SPACING;
525 m_Nodes[nodeIndex].posY = START_Y + siblingIndex * VERTICAL_SPACING;
526
527 std::cout << "[NodeGraph] Node " << nodeId << " positioned at ("
528 << m_Nodes[nodeIndex].posX << ", " << m_Nodes[nodeIndex].posY << ")\n";
529 }
530
531 // Queue children
532 if (childrenMap.count(nodeId))
533 {
534 for (int childId : childrenMap[nodeId])
535 {
536 if (!visited.count(childId))
537 {
538 queue.push({childId, depth + 1});
539 }
540 }
541 }
542 }
543
544 std::cout << "[NodeGraph] Position calculation complete\n";
545 }
546
547 // ========== NodeGraphManager Implementation ==========
548
554
558
563
565 {
566 if (m_Initialized)
567 return;
568
569 std::cout << "[NodeGraphManager] Initializing...\n";
570 m_Initialized = true;
571 }
572
574 {
575 if (!m_Initialized)
576 return;
577
578 std::cout << "[NodeGraphManager] Shutting down...\n";
579 m_Graphs.clear();
580 m_ActiveGraphId = -1;
581 m_Initialized = false;
582 }
583
584 int NodeGraphManager::CreateGraph(const std::string& name, const std::string& type)
585 {
586 auto graph = std::make_unique<NodeGraph>();
587 graph->name = name;
588 graph->type = type;
589
590 int graphId = m_NextGraphId++;
591 m_Graphs[graphId] = std::move(graph);
592 m_GraphOrder.push_back(graphId); // Track insertion order
593 m_ActiveGraphId = graphId;
594 m_LastActiveGraphId = graphId; // Update last active
595
596 std::cout << "[NodeGraphManager] Created graph " << graphId << " (" << name << ")\n";
597 return graphId;
598 }
599
601 {
602 auto it = m_Graphs.find(graphId);
603 if (it == m_Graphs.end())
604 return false;
605
606 // Remove from graph order
607 auto orderIt = std::find(m_GraphOrder.begin(), m_GraphOrder.end(), graphId);
608 if (orderIt != m_GraphOrder.end())
609 m_GraphOrder.erase(orderIt);
610
611 m_Graphs.erase(it);
612
613 if (m_ActiveGraphId == graphId)
614 {
615 // Try to select a neighbor from graph order for better UX
616 // Find closest neighbor (prefer next, then previous)
617 if (!m_GraphOrder.empty())
618 {
619 // Find where the closed graph was in order
620 size_t closedIndex = 0;
621 for (size_t i = 0; i < m_GraphOrder.size(); ++i)
622 {
623 if (m_GraphOrder[i] > graphId)
624 {
625 closedIndex = i;
626 break;
627 }
628 closedIndex = i + 1;
629 }
630
631 // Pick the next available tab, or previous if at end
632 if (closedIndex < m_GraphOrder.size())
634 else if (!m_GraphOrder.empty())
636 else
637 m_ActiveGraphId = -1;
638 }
639 else
640 {
641 m_ActiveGraphId = -1;
642 }
643
644 if (m_ActiveGraphId != -1)
646 }
647
648 std::cout << "[NodeGraphManager] Closed graph " << graphId << "\n";
649 return true;
650 }
651
653 {
654 auto it = m_Graphs.find(graphId);
655 if (it == m_Graphs.end())
656 return nullptr;
657 return it->second.get();
658 }
659
660 const NodeGraph* NodeGraphManager::GetGraph(int graphId) const
661 {
662 auto it = m_Graphs.find(graphId);
663 if (it == m_Graphs.end())
664 return nullptr;
665 return it->second.get();
666 }
667
669 {
670 if (m_Graphs.find(graphId) != m_Graphs.end())
671 {
672 m_ActiveGraphId = graphId;
673 m_LastActiveGraphId = graphId; // Update last active for persistence
674 }
675 }
676
681
686
687 std::vector<int> NodeGraphManager::GetAllGraphIds() const
688 {
689 // Return graphs in insertion order for consistent tab rendering
690 return m_GraphOrder;
691 }
692
693 std::string NodeGraphManager::GetGraphName(int graphId) const
694 {
695 const NodeGraph* graph = GetGraph(graphId);
696 return graph ? graph->name : "";
697 }
698
699 void NodeGraphManager::SetGraphOrder(const std::vector<int>& newOrder)
700 {
701 // Update the graph order (e.g., after tab reordering in UI)
702 // Only update if the order contains valid graph IDs
703 if (newOrder.size() != m_GraphOrder.size())
704 return;
705
706 // Verify all IDs in newOrder exist in m_Graphs
707 for (int graphId : newOrder)
708 {
709 if (m_Graphs.find(graphId) == m_Graphs.end())
710 return; // Invalid ID, don't update
711 }
712
714 }
715
716 bool NodeGraphManager::SaveGraph(int graphId, const std::string& filepath)
717 {
718 NodeGraph* graph = GetGraph(graphId);
719 if (!graph)
720 return false;
721
722 // Update lastModified timestamp
723 auto now = std::chrono::system_clock::now();
724 auto time = std::chrono::system_clock::to_time_t(now);
725 std::stringstream ss;
726
727 #ifdef _MSC_VER
728 std::tm timeinfo;
730 ss << std::put_time(&timeinfo, "%Y-%m-%dT%H:%M:%S");
731 #else
732 // Use localtime_r for thread safety on POSIX systems
733 std::tm timeinfo;
735 ss << std::put_time(&timeinfo, "%Y-%m-%dT%H:%M:%S");
736 #endif
737
738 graph->editorMetadata.lastModified = ss.str();
739
740 json j = graph->ToJson();
741
742 std::ofstream file(filepath);
743 if (!file.is_open())
744 return false;
745
746 file << j.dump(2);
747 file.close();
748
749 // Update filepath and clear dirty flag on successful save
750 graph->SetFilepath(filepath);
751 graph->ClearDirty();
752
753 std::cout << "[NodeGraphManager] Saved graph " << graphId << " to " << filepath << "\n";
754 return true;
755 }
756
757 int NodeGraphManager::LoadGraph(const std::string& filepath)
758 {
759 std::cout << "\n========================================" << std::endl;
760 std::cout << "[NodeGraphManager::LoadGraph] CALLED" << std::endl;
761 std::cout << "[NodeGraphManager::LoadGraph] Path: " << filepath << std::endl;
762 std::cout << "========================================+n" << std::endl;
763
764 try {
765 // 1. Check file exists
766 std::cout << "[NodeGraphManager] Step 1: Checking file exists..." << std::endl;
767 std::ifstream testFile(filepath);
768 if (!testFile.is_open())
769 {
770 std::cerr << "[NodeGraphManager] ERROR: File not found: " << filepath << std::endl;
771 std::cout << "========================================+n" << std::endl;
772 return -1;
773 }
774 testFile.close();
775 std::cout << "[NodeGraphManager] File exists: OK" << std::endl;
776
777 // 2. Load file content
778 std::cout << "[NodeGraphManager] Step 2: Loading file content..." << std::endl;
779 std::ifstream file(filepath);
780 if (!file.is_open())
781 {
782 std::cerr << "[NodeGraphManager] ERROR: Cannot open file: " << filepath << std::endl;
783 std::cout << "========================================+n" << std::endl;
784 return -1;
785 }
786
787 std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
788 file.close();
789 std::cout << "[NodeGraphManager] File loaded: " << content.size() << " bytes" << std::endl;
790
791 if (content.empty())
792 {
793 std::cerr << "[NodeGraphManager] ERROR: File is empty" << std::endl;
794 std::cout << "========================================+n" << std::endl;
795 return -1;
796 }
797
798 // 3. Parse JSON
799 std::cout << "[NodeGraphManager] Step 3: Parsing JSON..." << std::endl;
800 json j;
801 try {
802 j = json::parse(content);
803 std::cout << "[NodeGraphManager] JSON parsed: OK" << std::endl;
804 } catch (const std::exception& e) {
805 std::cerr << "[NodeGraphManager] ERROR parsing JSON: " << e.what() << std::endl;
806 std::cout << "========================================+n" << std::endl;
807 return -1;
808 }
809
810 // 4. Detect version
811 std::cout << "[NodeGraphManager] Step 4: Detecting version..." << std::endl;
812 bool isV2 = j.contains("schema_version") && j["schema_version"].get<int>() == 2;
813 bool isV1 = !isV2 && (j.contains("nodes") || j.contains("rootNodeId"));
814
815 std::cout << "[NodeGraphManager] Version: " << (isV2 ? "v2" : (isV1 ? "v1" : "Unknown")) << std::endl;
816
817 if (!isV1 && !isV2)
818 {
819 std::cerr << "[NodeGraphManager] ERROR: Invalid blueprint format (neither v1 nor v2)" << std::endl;
820 std::cout << "========================================+n" << std::endl;
821 return -1;
822 }
823
824 // 5. Parse graph from JSON
825 std::cout << "[NodeGraphManager] Step 5: Parsing graph with FromJson..." << std::endl;
827 try {
829 std::cout << "[NodeGraphManager] FromJson returned: " << graph.GetAllNodes().size() << " nodes" << std::endl;
830 } catch (const std::exception& e) {
831 std::cerr << "[NodeGraphManager] ERROR in FromJson: " << e.what() << std::endl;
832 std::cout << "========================================+n" << std::endl;
833 return -1;
834 }
835
836 // 6. Handle v1 migration if needed
837 if (isV1)
838 {
839 std::cout << "[NodeGraphManager] Step 6: Detected v1 format, migrating to v2..." << std::endl;
840
841 // Create v2 structure
842 json v2Json = json::object();
843 v2Json["schema_version"] = 2;
844 v2Json["blueprintType"] = graph.type.empty() ? "BehaviorTree" : graph.type;
845 v2Json["name"] = graph.name;
846 v2Json["description"] = "";
847
848 // Metadata
849 json metadata = json::object();
850 metadata["author"] = "Atlasbruce";
851 metadata["created"] = "2026-01-09T18:26:00Z";
852 metadata["lastModified"] = "2026-01-09T18:26:00Z";
853 metadata["tags"] = json::array();
854 v2Json["metadata"] = metadata;
855
856 // Editor state
857 json editorState = json::object();
858 editorState["zoom"] = 1.0;
859 json scrollOffset = json::object();
860 scrollOffset["x"] = 0;
861 scrollOffset["y"] = 0;
862 editorState["scrollOffset"] = scrollOffset;
863 v2Json["editorState"] = editorState;
864
865 // Data (re-serialize current graph)
866 v2Json["data"] = graph.ToJson();
867
868 // Save migrated version
869 std::cout << "[NodeGraphManager] Saving migrated v2 file..." << std::endl;
870 try {
871 // Backup original
872 std::string backupPath = filepath + ".v1.backup";
873 std::ifstream src(filepath, std::ios::binary);
874 std::ofstream dst(backupPath, std::ios::binary);
875 dst << src.rdbuf();
876 src.close();
877 dst.close();
878 std::cout << "[NodeGraphManager] Original backed up to: " << backupPath << std::endl;
879
880 // Save new version
881 std::ofstream outFile(filepath);
882 if (outFile.is_open())
883 {
884 outFile << v2Json.dump(2);
885 outFile.close();
886 std::cout << "[NodeGraphManager] Migrated file saved: " << filepath << std::endl;
887 }
888 } catch (const std::exception& e) {
889 std::cerr << "[NodeGraphManager] WARNING: Could not save migrated file: " << e.what() << std::endl;
890 }
891 }
892
893 // 7. Create graph in manager
894 std::cout << "[NodeGraphManager] Step 7: Creating graph in manager..." << std::endl;
895 int graphId = m_NextGraphId++;
896 auto graphPtr = std::make_unique<NodeGraph>(std::move(graph));
897
898 // Set filepath and clear dirty flag for freshly loaded graph
899 graphPtr->SetFilepath(filepath);
900 graphPtr->ClearDirty();
901
902 m_Graphs[graphId] = std::move(graphPtr);
903 m_GraphOrder.push_back(graphId); // Track insertion order
904 m_ActiveGraphId = graphId;
905 m_LastActiveGraphId = graphId; // Update last active
906
907 std::cout << "[NodeGraphManager] Graph registered with ID: " << graphId << std::endl;
908 std::cout << "[NodeGraphManager] Graph name: " << m_Graphs[graphId]->name << std::endl;
909 std::cout << "[NodeGraphManager] Graph type: " << m_Graphs[graphId]->type << std::endl;
910 std::cout << "[NodeGraphManager] Total graphs loaded: " << m_Graphs.size() << std::endl;
911 std::cout << "[NodeGraphManager] Active graph ID: " << m_ActiveGraphId << std::endl;
912
913 std::cout << "\n========================================" << std::endl;
914 std::cout << "[NodeGraphManager::LoadGraph] SUCCESS ->" << std::endl;
915 std::cout << "========================================+n" << std::endl;
916
917 return graphId;
918
919 } catch (const std::exception& e) {
920 std::cerr << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
921 std::cerr << "[NodeGraphManager] EXCEPTION: " << e.what() << std::endl;
922 std::cerr << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
923 std::cout << "========================================+n" << std::endl;
924 return -1;
925 } catch (...) {
926 std::cerr << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
927 std::cerr << "[NodeGraphManager] UNKNOWN EXCEPTION" << std::endl;
928 std::cerr << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
929 std::cout << "========================================+n" << std::endl;
930 return -1;
931 }
932 }
933
934 bool NodeGraphManager::IsGraphDirty(int graphId) const
935 {
936 const NodeGraph* graph = GetGraph(graphId);
937 return graph ? graph->IsDirty() : false;
938 }
939
941 {
942 for (const auto& pair : m_Graphs)
943 {
944 if (pair.second && pair.second->IsDirty())
945 return true;
946 }
947 return false;
948 }
949}
std::cout<< "[BTEditor] Duplicated node: "<< duplicate.name<< " (ID: "<< duplicate.id<< ")"<< std::endl;} } m_selectedNodes=newNodes;m_treeModified=true;m_currentLayout=m_layoutEngine.ComputeLayout(&m_editingTree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);m_validationMessages=m_editingTree.ValidateTreeFull();} bool BehaviorTreeDebugWindow::ValidateConnection(uint32_t parentId, uint32_t childId) const { const BTNode *parent=m_editingTree.GetNode(parentId);const BTNode *child=m_editingTree.GetNode(childId);if(!parent||!child) return false;if(parentId==childId) return false;if(parent->type !=BTNodeType::Selector &&parent->type !=BTNodeType::Sequence &&parent->type !=BTNodeType::Inverter &&parent->type !=BTNodeType::Repeater) { return false;} if((parent->type==BTNodeType::Inverter||parent->type==BTNodeType::Repeater) &&parent->decoratorChildId !=0) { return false;} if(parent->type==BTNodeType::Selector||parent->type==BTNodeType::Sequence) { if(std::find(parent->childIds.begin(), parent->childIds.end(), childId) !=parent->childIds.end()) { return false;} } std::vector< uint32_t > visited
toVisit push_back(childId)
nlohmann::json json
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
NodeGraphManager - Manages multiple node graphs Allows opening multiple behavior trees/FSMs simultane...
bool IsGraphDirty(int graphId) const
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 SaveGraph(int graphId, const std::string &filepath)
std::map< int, std::unique_ptr< NodeGraph > > m_Graphs
bool UnlinkNodes(int parentId, int childId)
bool DeleteNode(int nodeId)
void CalculateNodePositionsHierarchical()
static NodeGraph FromJson(const nlohmann::json &j)
bool LinkNodes(int parentId, int childId)
EditorMetadata editorMetadata
std::vector< GraphLink > GetAllLinks() const
bool SetNodeParameter(int nodeId, const std::string &paramName, const std::string &value)
nlohmann::json ToJson() const
std::vector< GraphNode * > GetAllNodes()
std::vector< GraphNode > m_Nodes
bool ValidateGraph(std::string &errorMsg) const
int FindNodeIndex(int nodeId) const
GraphNode * GetNode(int nodeId)
std::string GetNodeParameter(int nodeId, const std::string &paramName) const
int CreateNode(NodeType type, float x, float y, const std::string &name="")
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.
nlohmann::json json
const char * NodeTypeToString(NodeType type)
NodeType StringToNodeType(const std::string &str)
nlohmann::json json
std::vector< int > childIds