Add relevancy to Behavior Tree nodes
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user