Add relevancy to Behavior Tree nodes

This commit is contained in:
Wojtek Figat
2023-08-17 21:24:19 +02:00
parent 14ffd0aa08
commit 1e3e75cb99
6 changed files with 90 additions and 37 deletions

View File

@@ -15,30 +15,27 @@ void BehaviorKnowledge::InitMemory(BehaviorTree* tree)
ASSERT_LOW_LAYER(!Tree && tree); ASSERT_LOW_LAYER(!Tree && tree);
Tree = tree; Tree = tree;
Blackboard = Variant::NewValue(tree->Graph.Root->BlackboardType); Blackboard = Variant::NewValue(tree->Graph.Root->BlackboardType);
RelevantNodes.Resize(tree->Graph.NodesCount, false);
RelevantNodes.SetAll(false);
if (!Memory && tree->Graph.NodesStatesSize) if (!Memory && tree->Graph.NodesStatesSize)
{
Memory = Allocator::Allocate(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() void BehaviorKnowledge::FreeMemory()
{ {
if (Memory) if (Memory)
{ {
// Release any outstanding nodes state and memory
ASSERT_LOW_LAYER(Tree); ASSERT_LOW_LAYER(Tree);
for (const auto& node : Tree->Graph.Nodes) 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); node.Instance->ReleaseState(Behavior, Memory);
} }
Allocator::Free(Memory); Allocator::Free(Memory);
Memory = nullptr; Memory = nullptr;
} }
RelevantNodes.Clear();
Blackboard.DeleteValue(); Blackboard.DeleteValue();
Tree = nullptr; Tree = nullptr;
} }
@@ -118,7 +115,7 @@ void Behavior::OnLateUpdate()
context.Knowledge = &_knowledge; context.Knowledge = &_knowledge;
context.Memory = _knowledge.Memory; context.Memory = _knowledge.Memory;
context.DeltaTime = updateDeltaTime; context.DeltaTime = updateDeltaTime;
const BehaviorUpdateResult result = tree->Graph.Root->Update(context); const BehaviorUpdateResult result = tree->Graph.Root->InvokeUpdate(context);
if (result != BehaviorUpdateResult::Running) if (result != BehaviorUpdateResult::Running)
{ {
_result = result; _result = result;

View File

@@ -2,6 +2,8 @@
#pragma once #pragma once
#include "Engine/Core/Types/Variant.h"
#include "Engine/Core/Collections/BitArray.h"
#include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingObject.h"
class Behavior; class Behavior;
@@ -28,17 +30,18 @@ API_CLASS() class FLAXENGINE_API BehaviorKnowledge : public ScriptingObject
/// <summary> /// <summary>
/// Raw memory chunk with all Behavior Tree nodes state. /// Raw memory chunk with all Behavior Tree nodes state.
/// </summary> /// </summary>
API_FIELD(ReadOnly) void* Memory = nullptr; void* Memory = nullptr;
/// <summary>
/// Array with per-node bit indicating whether node is relevant (active in graph with state created).
/// </summary>
BitArray<> RelevantNodes;
/// <summary> /// <summary>
/// Instance of the behaviour blackboard (structure or class). /// Instance of the behaviour blackboard (structure or class).
/// </summary> /// </summary>
API_FIELD() Variant Blackboard; API_FIELD() Variant Blackboard;
// TODO: sensors data
// TODO: goals
// TODO: GetGoal/HasGoal
/// <summary> /// <summary>
/// Initializes the knowledge for a certain tree. /// Initializes the knowledge for a certain tree.
/// </summary> /// </summary>

View File

@@ -15,6 +15,7 @@ REGISTER_BINARY_ASSET(BehaviorTree, "FlaxEngine.BehaviorTree", false);
bool SortBehaviorTreeChildren(GraphBox* const& a, GraphBox* const& b) bool SortBehaviorTreeChildren(GraphBox* const& a, GraphBox* const& b)
{ {
// Sort by node X coordinate on surface
auto aNode = (BehaviorTreeGraph::Node*)a->Parent; auto aNode = (BehaviorTreeGraph::Node*)a->Parent;
auto bNode = (BehaviorTreeGraph::Node*)b->Parent; auto bNode = (BehaviorTreeGraph::Node*)b->Parent;
auto aEntry = aNode->Meta.GetEntry(11); auto aEntry = aNode->Meta.GetEntry(11);
@@ -35,31 +36,18 @@ bool BehaviorTreeGraph::Load(ReadStream* stream, bool loadMeta)
return true; return true;
// Build node instances hierarchy // Build node instances hierarchy
Node* root = nullptr;
for (Node& node : Nodes) for (Node& node : Nodes)
{ {
if (auto* nodeCompound = ScriptingObject::Cast<BehaviorTreeCompoundNode>(node.Instance)) if (node.Instance == Root)
{ {
auto& children = node.Boxes[1].Connections; root = &node;
break;
// 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 (root)
if (node.Instance)
{ {
// Count total states memory size LoadRecursive(*root);
node.Instance->_memoryOffset = NodesStatesSize;
NodesStatesSize += node.Instance->GetStateSize();
}
} }
return false; return false;
@@ -70,6 +58,7 @@ void BehaviorTreeGraph::Clear()
VisjectGraph<BehaviorTreeGraphNode>::Clear(); VisjectGraph<BehaviorTreeGraphNode>::Clear();
Root = nullptr; Root = nullptr;
NodesCount = 0;
NodesStatesSize = 0; NodesStatesSize = 0;
} }
@@ -103,6 +92,35 @@ bool BehaviorTreeGraph::onNodeLoaded(Node* n)
return VisjectGraph<BehaviorTreeGraphNode>::onNodeLoaded(n); return VisjectGraph<BehaviorTreeGraphNode>::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<BehaviorTreeCompoundNode>(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) BehaviorTree::BehaviorTree(const SpawnParams& params, const AssetInfo* info)
: BinaryAsset(params, info) : BinaryAsset(params, info)
{ {

View File

@@ -29,6 +29,8 @@ class BehaviorTreeGraph : public VisjectGraph<BehaviorTreeGraphNode>
public: public:
// Instance of the graph root node. // Instance of the graph root node.
BehaviorTreeRootNode* Root = nullptr; BehaviorTreeRootNode* Root = nullptr;
// Total count of used nodes.
int32 NodesCount = 0;
// Total size of the nodes states memory. // Total size of the nodes states memory.
int32 NodesStatesSize = 0; int32 NodesStatesSize = 0;
@@ -36,6 +38,9 @@ public:
bool Load(ReadStream* stream, bool loadMeta) override; bool Load(ReadStream* stream, bool loadMeta) override;
void Clear() override; void Clear() override;
bool onNodeLoaded(Node* n) override; bool onNodeLoaded(Node* n) override;
private:
void LoadRecursive(Node& node);
}; };
/// <summary> /// <summary>

View File

@@ -12,10 +12,13 @@ API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeNode : public SerializableS
{ {
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeNode, SerializableScriptingObject); DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeNode, SerializableScriptingObject);
friend class BehaviorTreeGraph; friend class BehaviorTreeGraph;
friend class BehaviorKnowledge;
protected: protected:
// Raw memory byte offset from the start of the behavior memory block. // Raw memory byte offset from the start of the behavior memory block.
API_FIELD(ReadOnly) int32 _memoryOffset = 0; API_FIELD(ReadOnly) int32 _memoryOffset = 0;
// Execution index of the node within tree.
API_FIELD(ReadOnly) int32 _executionIndex = -1;
public: public:
/// <summary> /// <summary>
@@ -67,6 +70,9 @@ public:
return BehaviorUpdateResult::Success; return BehaviorUpdateResult::Success;
} }
// Helper utility to update node with state creation/cleanup depending on node relevancy.
BehaviorUpdateResult InvokeUpdate(const BehaviorUpdateContext& context);
// [SerializableScriptingObject] // [SerializableScriptingObject]
void Serialize(SerializeStream& stream, const void* otherObj) override; void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;

View File

@@ -1,8 +1,32 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "BehaviorTreeNodes.h" #include "BehaviorTreeNodes.h"
#include "BehaviorKnowledge.h"
#include "Engine/Serialization/Serialization.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) void BehaviorTreeNode::Serialize(SerializeStream& stream, const void* otherObj)
{ {
SerializableScriptingObject::Serialize(stream, 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++) for (int32 i = 0; i < Children.Count() && result == BehaviorUpdateResult::Success; i++)
{ {
BehaviorTreeNode* child = Children[i]; BehaviorTreeNode* child = Children[i];
result = child->Update(context); result = child->InvokeUpdate(context);
} }
return result; return result;
} }
@@ -56,7 +80,7 @@ BehaviorUpdateResult BehaviorTreeSequenceNode::Update(BehaviorUpdateContext cont
if (state->CurrentChildIndex == -1) if (state->CurrentChildIndex == -1)
return BehaviorUpdateResult::Failed; return BehaviorUpdateResult::Failed;
auto result = Children[state->CurrentChildIndex]->Update(context); auto result = Children[state->CurrentChildIndex]->InvokeUpdate(context);
switch (result) switch (result)
{ {