diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp index 71821a57e..f08b733ad 100644 --- a/Source/Engine/AI/Behavior.cpp +++ b/Source/Engine/AI/Behavior.cpp @@ -15,30 +15,27 @@ void BehaviorKnowledge::InitMemory(BehaviorTree* tree) ASSERT_LOW_LAYER(!Tree && tree); Tree = tree; Blackboard = Variant::NewValue(tree->Graph.Root->BlackboardType); + RelevantNodes.Resize(tree->Graph.NodesCount, false); + RelevantNodes.SetAll(false); if (!Memory && tree->Graph.NodesStatesSize) - { Memory = Allocator::Allocate(tree->Graph.NodesStatesSize); - for (const auto& node : tree->Graph.Nodes) - { - if (node.Instance) - node.Instance->InitState(Behavior, Memory); - } - } } void BehaviorKnowledge::FreeMemory() { if (Memory) { + // Release any outstanding nodes state and memory ASSERT_LOW_LAYER(Tree); for (const auto& node : Tree->Graph.Nodes) { - if (node.Instance) + if (node.Instance && node.Instance->_executionIndex != -1 && RelevantNodes[node.Instance->_executionIndex]) node.Instance->ReleaseState(Behavior, Memory); } Allocator::Free(Memory); Memory = nullptr; } + RelevantNodes.Clear(); Blackboard.DeleteValue(); Tree = nullptr; } @@ -118,7 +115,7 @@ void Behavior::OnLateUpdate() context.Knowledge = &_knowledge; context.Memory = _knowledge.Memory; context.DeltaTime = updateDeltaTime; - const BehaviorUpdateResult result = tree->Graph.Root->Update(context); + const BehaviorUpdateResult result = tree->Graph.Root->InvokeUpdate(context); if (result != BehaviorUpdateResult::Running) { _result = result; diff --git a/Source/Engine/AI/BehaviorKnowledge.h b/Source/Engine/AI/BehaviorKnowledge.h index 48f77ffa9..a39e70eea 100644 --- a/Source/Engine/AI/BehaviorKnowledge.h +++ b/Source/Engine/AI/BehaviorKnowledge.h @@ -2,6 +2,8 @@ #pragma once +#include "Engine/Core/Types/Variant.h" +#include "Engine/Core/Collections/BitArray.h" #include "Engine/Scripting/ScriptingObject.h" class Behavior; @@ -28,17 +30,18 @@ API_CLASS() class FLAXENGINE_API BehaviorKnowledge : public ScriptingObject /// /// Raw memory chunk with all Behavior Tree nodes state. /// - API_FIELD(ReadOnly) void* Memory = nullptr; + void* Memory = nullptr; + + /// + /// Array with per-node bit indicating whether node is relevant (active in graph with state created). + /// + BitArray<> RelevantNodes; /// /// Instance of the behaviour blackboard (structure or class). /// API_FIELD() Variant Blackboard; - // TODO: sensors data - // TODO: goals - // TODO: GetGoal/HasGoal - /// /// Initializes the knowledge for a certain tree. /// diff --git a/Source/Engine/AI/BehaviorTree.cpp b/Source/Engine/AI/BehaviorTree.cpp index e2330097e..653e44335 100644 --- a/Source/Engine/AI/BehaviorTree.cpp +++ b/Source/Engine/AI/BehaviorTree.cpp @@ -15,6 +15,7 @@ REGISTER_BINARY_ASSET(BehaviorTree, "FlaxEngine.BehaviorTree", false); bool SortBehaviorTreeChildren(GraphBox* const& a, GraphBox* const& b) { + // Sort by node X coordinate on surface auto aNode = (BehaviorTreeGraph::Node*)a->Parent; auto bNode = (BehaviorTreeGraph::Node*)b->Parent; auto aEntry = aNode->Meta.GetEntry(11); @@ -35,32 +36,19 @@ bool BehaviorTreeGraph::Load(ReadStream* stream, bool loadMeta) return true; // Build node instances hierarchy + Node* root = nullptr; for (Node& node : Nodes) { - if (auto* nodeCompound = ScriptingObject::Cast(node.Instance)) + if (node.Instance == Root) { - auto& children = node.Boxes[1].Connections; - - // Sort children from left to right (based on placement on a graph surface) - Sorting::QuickSort(children.Get(), children.Count(), SortBehaviorTreeChildren); - - // Find all children (of output box) - for (const GraphBox* childBox : children) - { - const Node* child = childBox ? (Node*)childBox->Parent : nullptr; - if (child && child->Instance) - { - nodeCompound->Children.Add(child->Instance); - } - } - } - if (node.Instance) - { - // Count total states memory size - node.Instance->_memoryOffset = NodesStatesSize; - NodesStatesSize += node.Instance->GetStateSize(); + root = &node; + break; } } + if (root) + { + LoadRecursive(*root); + } return false; } @@ -70,6 +58,7 @@ void BehaviorTreeGraph::Clear() VisjectGraph::Clear(); Root = nullptr; + NodesCount = 0; NodesStatesSize = 0; } @@ -103,6 +92,35 @@ bool BehaviorTreeGraph::onNodeLoaded(Node* n) return VisjectGraph::onNodeLoaded(n); } +void BehaviorTreeGraph::LoadRecursive(Node& node) +{ + // Count total states memory size + ASSERT_LOW_LAYER(node.Instance); + node.Instance->_memoryOffset = NodesStatesSize; + node.Instance->_executionIndex = NodesCount; + NodesStatesSize += node.Instance->GetStateSize(); + NodesCount++; + + if (auto* nodeCompound = ScriptingObject::Cast(node.Instance)) + { + auto& children = node.Boxes[1].Connections; + + // Sort children from left to right (based on placement on a graph surface) + Sorting::QuickSort(children.Get(), children.Count(), SortBehaviorTreeChildren); + + // Find all children (of output box) + for (const GraphBox* childBox : children) + { + Node* child = childBox ? (Node*)childBox->Parent : nullptr; + if (child && child->Instance) + { + nodeCompound->Children.Add(child->Instance); + LoadRecursive(*child); + } + } + } +} + BehaviorTree::BehaviorTree(const SpawnParams& params, const AssetInfo* info) : BinaryAsset(params, info) { diff --git a/Source/Engine/AI/BehaviorTree.h b/Source/Engine/AI/BehaviorTree.h index cedd437b2..f81228162 100644 --- a/Source/Engine/AI/BehaviorTree.h +++ b/Source/Engine/AI/BehaviorTree.h @@ -29,6 +29,8 @@ class BehaviorTreeGraph : public VisjectGraph public: // Instance of the graph root node. BehaviorTreeRootNode* Root = nullptr; + // Total count of used nodes. + int32 NodesCount = 0; // Total size of the nodes states memory. int32 NodesStatesSize = 0; @@ -36,6 +38,9 @@ public: bool Load(ReadStream* stream, bool loadMeta) override; void Clear() override; bool onNodeLoaded(Node* n) override; + +private: + void LoadRecursive(Node& node); }; /// diff --git a/Source/Engine/AI/BehaviorTreeNode.h b/Source/Engine/AI/BehaviorTreeNode.h index ac62d7b5f..69e3bcb46 100644 --- a/Source/Engine/AI/BehaviorTreeNode.h +++ b/Source/Engine/AI/BehaviorTreeNode.h @@ -12,10 +12,13 @@ API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeNode : public SerializableS { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeNode, SerializableScriptingObject); friend class BehaviorTreeGraph; + friend class BehaviorKnowledge; protected: // Raw memory byte offset from the start of the behavior memory block. API_FIELD(ReadOnly) int32 _memoryOffset = 0; + // Execution index of the node within tree. + API_FIELD(ReadOnly) int32 _executionIndex = -1; public: /// @@ -67,6 +70,9 @@ public: return BehaviorUpdateResult::Success; } + // Helper utility to update node with state creation/cleanup depending on node relevancy. + BehaviorUpdateResult InvokeUpdate(const BehaviorUpdateContext& context); + // [SerializableScriptingObject] void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index 01a0c7978..783ac9157 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -1,8 +1,32 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "BehaviorTreeNodes.h" +#include "BehaviorKnowledge.h" #include "Engine/Serialization/Serialization.h" +BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext& context) +{ + ASSERT_LOW_LAYER(_executionIndex != -1); + if (context.Knowledge->RelevantNodes.Get(_executionIndex) == false) + { + // Node becomes relevant so initialize it's state + context.Knowledge->RelevantNodes.Set(_executionIndex, true); + InitState(context.Behavior, context.Memory); + } + + // Node-specific update + const BehaviorUpdateResult result = Update(context); + + // Check if node is not relevant anymore + if (result != BehaviorUpdateResult::Running) + { + context.Knowledge->RelevantNodes.Set(_executionIndex, false); + ReleaseState(context.Behavior, context.Memory); + } + + return result; +} + void BehaviorTreeNode::Serialize(SerializeStream& stream, const void* otherObj) { SerializableScriptingObject::Serialize(stream, otherObj); @@ -31,7 +55,7 @@ BehaviorUpdateResult BehaviorTreeCompoundNode::Update(BehaviorUpdateContext cont for (int32 i = 0; i < Children.Count() && result == BehaviorUpdateResult::Success; i++) { BehaviorTreeNode* child = Children[i]; - result = child->Update(context); + result = child->InvokeUpdate(context); } return result; } @@ -56,7 +80,7 @@ BehaviorUpdateResult BehaviorTreeSequenceNode::Update(BehaviorUpdateContext cont if (state->CurrentChildIndex == -1) return BehaviorUpdateResult::Failed; - auto result = Children[state->CurrentChildIndex]->Update(context); + auto result = Children[state->CurrentChildIndex]->InvokeUpdate(context); switch (result) {