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)
{