6#include "../json_helper.h"
25 : name(
"Untitled Graph")
26 , type(
"BehaviorTree")
45 std::cout <<
"[NodeGraph] Created node " <<
node.id <<
" (" <<
node.name <<
")\n";
62 auto it = std::find(
node.childIds.begin(),
node.childIds.end(), nodeId);
63 if (
it !=
node.childIds.end())
67 if (
node.decoratorChildId == nodeId)
68 node.decoratorChildId = -1;
73 std::cout <<
"[NodeGraph] Deleted node " << nodeId <<
"\n";
95 std::vector<GraphNode*>
result;
103 std::vector<const GraphNode*>
result;
126 parent->
childIds.push_back(childId);
131 std::cout <<
"[NodeGraph] Linked node " << parentId <<
" -> " << childId <<
"\n";
148 std::cout <<
"[NodeGraph] Unlinked node " << parentId <<
" -> " << childId <<
"\n";
156 std::cout <<
"[NodeGraph] Unlinked decorator child " << parentId <<
" -> " << childId <<
"\n";
168 std::vector<GraphLink>
links;
173 for (
int childId :
node.childIds)
179 if (
node.decoratorChildId >= 0)
206 if (
it !=
node->parameters.end())
217 j[
"schema_version"] = 2;
218 j[
"blueprintType"] =
type.empty() ?
"BehaviorTree" :
type;
220 j[
"description"] =
"";
223 j[
"metadata"][
"author"] =
"User";
224 j[
"metadata"][
"created"] =
"";
226 j[
"metadata"][
"tags"] = json::array();
235 j[
"data"][
"nodes"] = json::array();
245 nj[
"position"][
"x"] =
node.posX;
246 nj[
"position"][
"y"] =
node.posY;
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;
256 nj[
"parameters"] = json::object();
257 if (!
node.parameters.empty())
259 for (
const auto&
pair :
node.parameters)
264 nj[
"children"] = json::array();
265 for (
int childId :
node.childIds)
268 if (
node.decoratorChildId >= 0)
269 nj[
"decoratorChild"] =
node.decoratorChildId;
271 j[
"data"][
"nodes"].push_back(
nj);
279 std::cout <<
"[NodeGraph::FromJson] Starting parsing..." << std::endl;
285 bool isV2 =
j.contains(
"schema_version") ||
j.contains(
"data");
286 std::cout <<
"[NodeGraph::FromJson] Format: " << (
isV2 ?
"v2" :
"v1") << std::endl;
290 if (
isV2 &&
j.contains(
"data"))
295 std::cout <<
"[NodeGraph::FromJson] Extracted 'data' section from v2" << std::endl;
301 std::cout <<
"[NodeGraph::FromJson] Using root as data section (v1)" << std::endl;
305 std::cout <<
"[NodeGraph::FromJson] Root node ID: " <<
graph.rootNodeId << std::endl;
310 std::cerr <<
"[NodeGraph::FromJson] ERROR: No 'nodes' array in data section" << std::endl;
317 std::cout <<
"[NodeGraph::FromJson] Parsing " << nodeCount <<
" nodes..." << std::endl;
333 if (
nj.contains(
"position") &&
nj[
"position"].is_object())
351 if (
nj.contains(
"parameters") &&
nj[
"parameters"].is_object())
357 node.parameters[
it.key()] =
it.value().is_string() ?
it.value().get<std::string>()
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();
386 std::cout <<
"[NodeGraph::FromJson] Node " <<
node.id <<
": " <<
node.name
388 <<
" children: " <<
node.childIds.size() << std::endl;
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;
405 std::cout <<
"[NodeGraph::FromJson] Using existing node positions from file" << std::endl;
411 if (
j.contains(
"editorState") &&
j[
"editorState"].is_object())
413 const json& state =
j[
"editorState"];
416 if (state.contains(
"scrollOffset") && state[
"scrollOffset"].is_object())
424 std::cout <<
"[NodeGraph::FromJson] Parsing complete: " <<
graph.m_Nodes.size() <<
" nodes loaded" << std::endl;
428 }
catch (
const std::exception&
e) {
429 std::cerr <<
"[NodeGraph::FromJson] EXCEPTION: " <<
e.what() << std::endl;
440 for (
int childId :
node.childIds)
444 errorMsg =
"Node " + std::to_string(
node.id) +
" has invalid child " + std::to_string(childId);
451 errorMsg =
"Node " + std::to_string(
node.id) +
" has invalid decorator child";
471 return static_cast<int>(
i);
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;
483 std::cout <<
"[NodeGraph] Calculating hierarchical positions for " <<
m_Nodes.size() <<
" nodes\n";
489 if (!
node.childIds.empty())
498 std::cerr <<
"[NodeGraph] No root node ID, cannot calculate positions\n";
502 std::queue<std::pair<int, int>>
queue;
508 while (!
queue.empty())
511 int nodeId =
front.first;
512 int depth =
front.second;
515 if (
visited.count(nodeId))
continue;
527 std::cout <<
"[NodeGraph] Node " << nodeId <<
" positioned at ("
538 queue.push({childId, depth + 1});
544 std::cout <<
"[NodeGraph] Position calculation complete\n";
569 std::cout <<
"[NodeGraphManager] Initializing...\n";
578 std::cout <<
"[NodeGraphManager] Shutting down...\n";
586 auto graph = std::make_unique<NodeGraph>();
596 std::cout <<
"[NodeGraphManager] Created graph " << graphId <<
" (" << name <<
")\n";
648 std::cout <<
"[NodeGraphManager] Closed graph " << graphId <<
"\n";
657 return it->second.get();
665 return it->second.get();
723 auto now = std::chrono::system_clock::now();
724 auto time = std::chrono::system_clock::to_time_t(
now);
725 std::stringstream
ss;
730 ss << std::put_time(&
timeinfo,
"%Y-%m-%dT%H:%M:%S");
735 ss << std::put_time(&
timeinfo,
"%Y-%m-%dT%H:%M:%S");
738 graph->editorMetadata.lastModified =
ss.str();
742 std::ofstream
file(filepath);
750 graph->SetFilepath(filepath);
753 std::cout <<
"[NodeGraphManager] Saved graph " << graphId <<
" to " << filepath <<
"\n";
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;
766 std::cout <<
"[NodeGraphManager] Step 1: Checking file exists..." << std::endl;
770 std::cerr <<
"[NodeGraphManager] ERROR: File not found: " << filepath << std::endl;
771 std::cout <<
"========================================+n" << std::endl;
775 std::cout <<
"[NodeGraphManager] File exists: OK" << std::endl;
778 std::cout <<
"[NodeGraphManager] Step 2: Loading file content..." << std::endl;
779 std::ifstream
file(filepath);
782 std::cerr <<
"[NodeGraphManager] ERROR: Cannot open file: " << filepath << std::endl;
783 std::cout <<
"========================================+n" << std::endl;
787 std::string
content((std::istreambuf_iterator<char>(
file)), std::istreambuf_iterator<char>());
789 std::cout <<
"[NodeGraphManager] File loaded: " <<
content.size() <<
" bytes" << std::endl;
793 std::cerr <<
"[NodeGraphManager] ERROR: File is empty" << std::endl;
794 std::cout <<
"========================================+n" << std::endl;
799 std::cout <<
"[NodeGraphManager] Step 3: Parsing JSON..." << std::endl;
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;
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"));
815 std::cout <<
"[NodeGraphManager] Version: " << (
isV2 ?
"v2" : (
isV1 ?
"v1" :
"Unknown")) << std::endl;
819 std::cerr <<
"[NodeGraphManager] ERROR: Invalid blueprint format (neither v1 nor v2)" << std::endl;
820 std::cout <<
"========================================+n" << std::endl;
825 std::cout <<
"[NodeGraphManager] Step 5: Parsing graph with FromJson..." << std::endl;
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;
839 std::cout <<
"[NodeGraphManager] Step 6: Detected v1 format, migrating to v2..." << std::endl;
843 v2Json[
"schema_version"] = 2;
846 v2Json[
"description"] =
"";
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;
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;
869 std::cout <<
"[NodeGraphManager] Saving migrated v2 file..." << std::endl;
872 std::string
backupPath = filepath +
".v1.backup";
873 std::ifstream
src(filepath, std::ios::binary);
878 std::cout <<
"[NodeGraphManager] Original backed up to: " <<
backupPath << std::endl;
881 std::ofstream
outFile(filepath);
886 std::cout <<
"[NodeGraphManager] Migrated file saved: " << filepath << std::endl;
888 }
catch (
const std::exception&
e) {
889 std::cerr <<
"[NodeGraphManager] WARNING: Could not save migrated file: " <<
e.what() << std::endl;
894 std::cout <<
"[NodeGraphManager] Step 7: Creating graph in manager..." << std::endl;
896 auto graphPtr = std::make_unique<NodeGraph>(std::move(
graph));
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;
913 std::cout <<
"\n========================================" << std::endl;
914 std::cout <<
"[NodeGraphManager::LoadGraph] SUCCESS ->" << std::endl;
915 std::cout <<
"========================================+n" << std::endl;
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;
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;
944 if (
pair.second &&
pair.second->IsDirty())
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)
ComponentTypeID GetComponentTypeID_Static()
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
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 ¶mName, 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 ¶mName) 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.
const char * NodeTypeToString(NodeType type)
NodeType StringToNodeType(const std::string &str)
std::vector< int > childIds