diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp index a8eb761a8..f496c887d 100644 --- a/Source/Engine/AI/Behavior.cpp +++ b/Source/Engine/AI/Behavior.cpp @@ -79,6 +79,7 @@ void Behavior::OnLateUpdate() context.Behavior = this; context.Knowledge = &_knowledge; context.Memory = _knowledge.Memory; + context.RelevantNodes = &_knowledge.RelevantNodes; context.DeltaTime = updateDeltaTime; const BehaviorUpdateResult result = tree->Graph.Root->InvokeUpdate(context); if (result != BehaviorUpdateResult::Running) diff --git a/Source/Engine/AI/BehaviorTreeNode.h b/Source/Engine/AI/BehaviorTreeNode.h index 69e3bcb46..80126902d 100644 --- a/Source/Engine/AI/BehaviorTreeNode.h +++ b/Source/Engine/AI/BehaviorTreeNode.h @@ -13,6 +13,7 @@ API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeNode : public SerializableS DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeNode, SerializableScriptingObject); friend class BehaviorTreeGraph; friend class BehaviorKnowledge; + friend class BehaviorTreeSubTreeNode; protected: // Raw memory byte offset from the start of the behavior memory block. diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index fa40dd8e1..d4d3ce02f 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -4,15 +4,49 @@ #include "Behavior.h" #include "BehaviorKnowledge.h" #include "Engine/Core/Random.h" +#include "Engine/Scripting/Scripting.h" +#if USE_CSHARP +#include "Engine/Scripting/ManagedCLR/MClass.h" +#endif #include "Engine/Serialization/Serialization.h" +bool IsAssignableFrom(const StringAnsiView& to, const StringAnsiView& from) +{ + // Special case of null + if (to.IsEmpty()) + return from.IsEmpty(); + if (from.IsEmpty()) + return false; + + // Exact typename math + if (to == from) + return true; + + // Scripting Type match + const ScriptingTypeHandle typeHandleTo = Scripting::FindScriptingType(to); + const ScriptingTypeHandle typeHandleFrom = Scripting::FindScriptingType(from); + if (typeHandleTo && typeHandleFrom) + return typeHandleTo.IsAssignableFrom(typeHandleFrom); + +#if USE_CSHARP + // MClass match + const auto mclassTo = Scripting::FindClass(to); + const auto mclassFrom = Scripting::FindClass(from); + if (mclassTo && mclassFrom) + return mclassTo == mclassFrom || mclassFrom->IsSubClassOf(mclassTo); +#endif + + return false; +} + BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext& context) { ASSERT_LOW_LAYER(_executionIndex != -1); - if (context.Knowledge->RelevantNodes.Get(_executionIndex) == false) + BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes; + if (relevantNodes.Get(_executionIndex) == false) { // Node becomes relevant so initialize it's state - context.Knowledge->RelevantNodes.Set(_executionIndex, true); + relevantNodes.Set(_executionIndex, true); InitState(context.Behavior, context.Memory); } @@ -22,7 +56,7 @@ BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext& // Check if node is not relevant anymore if (result != BehaviorUpdateResult::Running) { - context.Knowledge->RelevantNodes.Set(_executionIndex, false); + relevantNodes.Set(_executionIndex, false); ReleaseState(context.Behavior, context.Memory); } @@ -152,3 +186,63 @@ BehaviorUpdateResult BehaviorTreeDelayNode::Update(BehaviorUpdateContext context state->TimeLeft -= context.DeltaTime; return state->TimeLeft <= 0.0f ? BehaviorUpdateResult::Success : BehaviorUpdateResult::Running; } + +int32 BehaviorTreeSubTreeNode::GetStateSize() const +{ + return sizeof(State); +} + +void BehaviorTreeSubTreeNode::InitState(Behavior* behavior, void* memory) +{ + auto state = GetState(memory); + new(state)State(); + const BehaviorTree* tree = Tree.Get(); + if (!tree || tree->WaitForLoaded()) + return; + state->Memory.Resize(tree->Graph.NodesStatesSize); + state->RelevantNodes.Resize(tree->Graph.NodesCount, false); + state->RelevantNodes.SetAll(false); +} + +void BehaviorTreeSubTreeNode::ReleaseState(Behavior* behavior, void* memory) +{ + auto state = GetState(memory); + const BehaviorTree* tree = Tree.Get(); + if (tree && tree->IsLoaded()) + { + for (const auto& node : tree->Graph.Nodes) + { + if (node.Instance && node.Instance->_executionIndex != -1 && state->RelevantNodes[node.Instance->_executionIndex]) + node.Instance->ReleaseState(behavior, state->Memory.Get()); + } + } + state->~State(); +} + +BehaviorUpdateResult BehaviorTreeSubTreeNode::Update(BehaviorUpdateContext context) +{ + const BehaviorTree* tree = Tree.Get(); + if (!tree || !tree->Graph.Root) + return BehaviorUpdateResult::Failed; + const StringAnsiView treeBlackboardType = tree->Graph.Root->BlackboardType; + if (treeBlackboardType.HasChars()) + { + // Validate if nested tree blackboard data matches (the same type or base type) + const VariantType& blackboardType = context.Knowledge->Blackboard.Type; + if (IsAssignableFrom(treeBlackboardType, StringAnsiView(blackboardType.GetTypeName()))) + { + LOG(Error, "Cannot use nested '{}' with Blackboard of type '{}' inside '{}' with Blackboard of type '{}'", + tree->ToString(), String(treeBlackboardType), + context.Knowledge->Tree->ToString(), blackboardType.ToString()); + return BehaviorUpdateResult::Failed; + } + } + + // Override memory with custom one for the subtree + auto state = GetState(context.Memory); + context.Memory = state->Memory.Get(); + context.RelevantNodes = &state->RelevantNodes; + + // Run nested tree + return tree->Graph.Root->InvokeUpdate(context); +} diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index 9d6683d74..e28d0b81b 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -6,6 +6,7 @@ #include "BehaviorTreeNode.h" #include "BehaviorKnowledgeSelector.h" #include "Engine/Core/Collections/Array.h" +#include "Engine/Content/AssetReference.h" /// /// Base class for compound Behavior Tree nodes that composite child nodes. @@ -114,3 +115,29 @@ private: float TimeLeft; }; }; + +/// +/// Sub-tree node runs a nested Behavior Tree within this tree. +/// +API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeSubTreeNode : public BehaviorTreeNode +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeSubTreeNode, BehaviorTreeNode); + API_AUTO_SERIALIZATION(); + + // Nested behavior tree to execute within this node. + API_FIELD(Attributes="EditorOrder(10), Limit(0)") + AssetReference Tree; + +public: + // [BehaviorTreeNode] + int32 GetStateSize() const override; + void InitState(Behavior* behavior, void* memory) override; + void ReleaseState(Behavior* behavior, void* memory) override; + BehaviorUpdateResult Update(BehaviorUpdateContext context) override; + + struct State + { + Array Memory; + BitArray<> RelevantNodes; + }; +}; diff --git a/Source/Engine/AI/BehaviorTypes.h b/Source/Engine/AI/BehaviorTypes.h index 554cdf57a..bfbde2c26 100644 --- a/Source/Engine/AI/BehaviorTypes.h +++ b/Source/Engine/AI/BehaviorTypes.h @@ -12,7 +12,7 @@ class BehaviorKnowledge; /// /// Behavior update context state. /// -API_STRUCT() struct FLAXENGINE_API BehaviorUpdateContext +API_STRUCT(NoDefault) struct FLAXENGINE_API BehaviorUpdateContext { DECLARE_SCRIPTING_TYPE_MINIMAL(BehaviorUpdateContext); @@ -31,6 +31,11 @@ API_STRUCT() struct FLAXENGINE_API BehaviorUpdateContext /// API_FIELD() void* Memory; + /// + /// Pointer to array with per-node bit indicating whether node is relevant (active in graph with state created). + /// + API_FIELD() void* RelevantNodes; + /// /// Simulation time delta (in seconds) since the last update. ///