From 57ee884397ef07f90895fad70aa7638b53aff073 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 29 Aug 2023 16:33:41 +0200 Subject: [PATCH] Add hot-reload support for BT to properly reload node instances data --- .../Windows/Assets/BehaviorTreeWindow.cs | 8 ++ Source/Engine/AI/BehaviorTree.cpp | 110 ++++++++++++------ Source/Engine/AI/BehaviorTree.h | 13 ++- 3 files changed, 94 insertions(+), 37 deletions(-) diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs index 5e2869c22..63f801c8f 100644 --- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs +++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs @@ -116,6 +116,7 @@ namespace FlaxEditor.Windows.Assets InputActions.Add(options => options.Search, Editor.ContentFinding.ShowSearch); SetCanEdit(!isPlayMode); + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; } private void OnUndoRedo(IUndoAction action) @@ -170,6 +171,12 @@ namespace FlaxEditor.Windows.Assets } } + private void OnScriptsReloadBegin() + { + // TODO: impl hot-reload for BT to nicely refresh state (save asset, clear undo/properties, reload surface) + Close(); + } + private void UpdateKnowledge() { var rootNode = _surface.FindNode(19, 2) as Surface.Archetypes.BehaviorTree.Node; @@ -398,6 +405,7 @@ namespace FlaxEditor.Windows.Assets { if (IsDisposing) return; + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; _undo.Enabled = false; _nodePropertiesEditor.Deselect(); _knowledgePropertiesEditor.Deselect(); diff --git a/Source/Engine/AI/BehaviorTree.cpp b/Source/Engine/AI/BehaviorTree.cpp index b3fc99ccf..025c2c22d 100644 --- a/Source/Engine/AI/BehaviorTree.cpp +++ b/Source/Engine/AI/BehaviorTree.cpp @@ -11,6 +11,9 @@ #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Threading/Threading.h" #include "FlaxEngine.Gen.h" +#if USE_EDITOR +#include "Engine/Level/Level.h" +#endif REGISTER_BINARY_ASSET(BehaviorTree, "FlaxEngine.BehaviorTree", false); @@ -31,29 +34,6 @@ BehaviorTreeGraphNode::~BehaviorTreeGraphNode() SAFE_DELETE(Instance); } -bool BehaviorTreeGraph::Load(ReadStream* stream, bool loadMeta) -{ - if (VisjectGraph::Load(stream, loadMeta)) - return true; - - // Build node instances hierarchy - Node* root = nullptr; - for (Node& node : Nodes) - { - if (node.Instance == Root) - { - root = &node; - break; - } - } - if (root) - { - LoadRecursive(*root); - } - - return false; -} - void BehaviorTreeGraph::Clear() { VisjectGraph::Clear(); @@ -77,10 +57,6 @@ bool BehaviorTreeGraph::onNodeLoaded(Node* n) const Variant& data = n->Values[1]; if (data.Type == VariantType::Blob) JsonSerializer::LoadFromBytes(n->Instance, Span((byte*)data.AsBlob.Data, data.AsBlob.Length), FLAXENGINE_VERSION_BUILD); - - // Find root node - if (!Root && n->Instance && BehaviorTreeRootNode::TypeInitializer == type) - Root = (BehaviorTreeRootNode*)n->Instance; } else { @@ -93,7 +69,35 @@ bool BehaviorTreeGraph::onNodeLoaded(Node* n) return VisjectGraph::onNodeLoaded(n); } -void BehaviorTreeGraph::LoadRecursive(Node& node) +void BehaviorTreeGraph::Setup(BehaviorTree* tree) +{ + // Find root node + Node* root = nullptr; + Root = nullptr; + for (Node& node : Nodes) + { + if (node.Instance && node.GroupID == 19 && node.TypeID == 2) + { + // Find root node + if (node.Instance->GetTypeHandle() == BehaviorTreeRootNode::TypeInitializer) + Root = (BehaviorTreeRootNode*)node.Instance; + root = &node; + break; + } + } + if (!Root) + return; + + // Setup nodes hierarchy + NodesCount = 0; + NodesStatesSize = 0; + SetupRecursive(*root); + + // Init graph with asset + Root->Init(tree); +} + +void BehaviorTreeGraph::SetupRecursive(Node& node) { // Count total states memory size ASSERT_LOW_LAYER(node.Instance); @@ -116,7 +120,7 @@ void BehaviorTreeGraph::LoadRecursive(Node& node) { node.Instance->_decorators.Add((BehaviorTreeDecorator*)decorator->Instance); decorator->Instance->_parent = node.Instance; - LoadRecursive(*decorator); + SetupRecursive(*decorator); } } } @@ -136,7 +140,7 @@ void BehaviorTreeGraph::LoadRecursive(Node& node) { nodeCompound->Children.Add(child->Instance); child->Instance->_parent = nodeCompound; - LoadRecursive(*child); + SetupRecursive(*child); } } } @@ -195,6 +199,26 @@ bool BehaviorTree::SaveSurface(const BytesContainer& data) return false; } +void BehaviorTree::OnScriptsReloadStart() +{ + // Include all node instances in hot-reload + for (BehaviorTreeGraphNode& n : Graph.Nodes) + { + Level::ScriptsReloadRegisterObject((ScriptingObject*&)n.Instance); + } + + // Clear state + Graph.Root = nullptr; + Graph.NodesCount = 0; + Graph.NodesStatesSize = 0; +} + +void BehaviorTree::OnScriptsReloadEnd() +{ + // Node instances were restored so update the graph cached structure (root, children, decorators, etc.) + Graph.Setup(this); +} + void BehaviorTree::GetReferences(Array& output) const { // Base @@ -215,6 +239,17 @@ void BehaviorTree::GetReferences(Array& output) const #endif +void BehaviorTree::OnScriptingDispose() +{ + // Dispose any node instances to prevent crashes (scripting is released before content) + for (BehaviorTreeGraphNode& n : Graph.Nodes) + { + SAFE_DELETE(n.Instance); + } + + BinaryAsset::OnScriptingDispose(); +} + Asset::LoadResult BehaviorTree::load() { // Load graph @@ -227,18 +262,23 @@ Asset::LoadResult BehaviorTree::load() LOG(Warning, "Failed to load graph \'{0}\'", ToString()); return LoadResult::Failed; } + Graph.Setup(this); - // Init graph - if (Graph.Root) - { - Graph.Root->Init(this); - } +#if USE_EDITOR + Level::ScriptsReloadStart.Bind(this); + Level::ScriptsReloadEnd.Bind(this); +#endif return LoadResult::Ok; } void BehaviorTree::unload(bool isReloading) { +#if USE_EDITOR + Level::ScriptsReloadStart.Unbind(this); + Level::ScriptsReloadEnd.Unbind(this); +#endif + // Clear resources Graph.Clear(); } diff --git a/Source/Engine/AI/BehaviorTree.h b/Source/Engine/AI/BehaviorTree.h index 0888be64d..169210425 100644 --- a/Source/Engine/AI/BehaviorTree.h +++ b/Source/Engine/AI/BehaviorTree.h @@ -6,6 +6,7 @@ #include "Engine/Visject/VisjectGraph.h" class BehaviorKnowledge; +class BehaviorTree; class BehaviorTreeNode; class BehaviorTreeRootNode; @@ -26,6 +27,7 @@ public: /// class BehaviorTreeGraph : public VisjectGraph { + friend BehaviorTree; public: // Instance of the graph root node. BehaviorTreeRootNode* Root = nullptr; @@ -35,12 +37,12 @@ public: int32 NodesStatesSize = 0; // [VisjectGraph] - bool Load(ReadStream* stream, bool loadMeta) override; void Clear() override; bool onNodeLoaded(Node* n) override; private: - void LoadRecursive(Node& node); + void Setup(BehaviorTree* tree); + void SetupRecursive(Node& node); }; /// @@ -79,8 +81,15 @@ public: API_FUNCTION() bool SaveSurface(const BytesContainer& data); #endif +private: +#if USE_EDITOR + void OnScriptsReloadStart(); + void OnScriptsReloadEnd(); +#endif + public: // [BinaryAsset] + void OnScriptingDispose() override; #if USE_EDITOR void GetReferences(Array& output) const override; #endif