Files
FlaxEngine/Source/Engine/AI/BehaviorKnowledge.cpp
2025-12-19 13:14:10 +02:00

274 lines
8.3 KiB
C++

// Copyright (c) Wojciech Figat. All rights reserved.
#include "BehaviorKnowledge.h"
#include "BehaviorTree.h"
#include "BehaviorTreeNodes.h"
#include "BehaviorKnowledgeSelector.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Scripting/ManagedCLR/MProperty.h"
#if USE_CSHARP
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MField.h"
#include "Engine/Scripting/ManagedCLR/MProperty.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
#endif
bool AccessVariant(Variant& instance, const StringAnsiView& member, Variant& value, bool set)
{
if (member.IsEmpty())
{
// Whole blackboard value
if (set)
{
CHECK_RETURN(instance.Type == value.Type, false);
instance = value;
}
else
value = instance;
return true;
}
// TODO: support further path for nested value types (eg. structure field access)
const StringAnsiView typeName(instance.Type.TypeName);
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName);
if (typeHandle)
{
const ScriptingType& type = typeHandle.GetType();
switch (type.Type)
{
case ScriptingTypes::Structure:
{
const String memberStr(member);
// TODO: let SetField/GetField return boolean status of operation maybe?
if (set)
type.Struct.SetField(instance.AsBlob.Data, memberStr, value);
else
type.Struct.GetField(instance.AsBlob.Data, memberStr, value);
return true;
}
default:
{
if (void* field = typeHandle.Module->FindField(typeHandle, member))
{
if (set)
return !typeHandle.Module->SetFieldValue(field, instance, value);
else
return !typeHandle.Module->GetFieldValue(field, instance, value);
}
break;
}
}
}
#if USE_CSHARP
if (const auto mClass = Scripting::FindClass(typeName))
{
MObject* instanceObject = MUtils::BoxVariant(instance);
bool failed = false;
if (const auto mField = mClass->GetField(member.Get()))
{
if (set)
mField->SetValue(instanceObject, MUtils::VariantToManagedArgPtr(value, mField->GetType(), failed));
else
value = MUtils::UnboxVariant(mField->GetValueBoxed(instanceObject), true);
return !failed;
}
else if (const auto mProperty = mClass->GetProperty(member.Get()))
{
if (set)
mProperty->SetValue(instanceObject, MUtils::VariantToManagedArgPtr(value, mProperty->GetType(), failed), nullptr);
else
value = MUtils::UnboxVariant(mProperty->GetValue(instanceObject, nullptr), true);
return !failed;
}
}
#endif
else if (typeName.HasChars())
{
LOG(Warning, "Missing scripting type \'{0}\'", String(typeName));
}
return false;
}
bool AccessBehaviorKnowledge(BehaviorKnowledge* knowledge, const StringAnsiView& path, Variant& value, bool set)
{
const int32 typeEnd = path.Find('/');
if (typeEnd == -1)
return false;
const StringAnsiView type(path.Get(), typeEnd);
if (type == "Blackboard")
{
const StringAnsiView member(path.Get() + typeEnd + 1, path.Length() - typeEnd - 1);
return AccessVariant(knowledge->Blackboard, member, value, set);
}
if (type == "Goal")
{
const StringAnsiView subPath(path.Get() + typeEnd + 1, path.Length() - typeEnd - 1);
const int32 goalTypeEnd = subPath.Find('/');
const StringAnsiView goalType(subPath.Get(), goalTypeEnd);
const StringAnsiView member(subPath.Get() + goalTypeEnd + 1, subPath.Length() - goalTypeEnd - 1);
for (Variant& goal : knowledge->Goals)
{
if (goalType == goal.Type.GetTypeName())
{
return AccessVariant(goal, member, value, set);
}
}
}
return false;
}
bool BehaviorKnowledgeSelectorAny::Set(BehaviorKnowledge* knowledge, const Variant& value) const
{
return knowledge && knowledge->Set(Path, value);
}
Variant BehaviorKnowledgeSelectorAny::Get(const BehaviorKnowledge* knowledge) const
{
Variant value;
if (knowledge)
knowledge->Get(Path, value);
return value;
}
bool BehaviorKnowledgeSelectorAny::TryGet(const BehaviorKnowledge* knowledge, Variant& value) const
{
return knowledge && knowledge->Get(Path, value);
}
BehaviorKnowledge::~BehaviorKnowledge()
{
FreeMemory();
}
void BehaviorKnowledge::InitMemory(BehaviorTree* tree)
{
PROFILE_MEM(AI);
if (Tree)
FreeMemory();
if (!tree)
return;
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);
#if !BUILD_RELEASE
// Clear memory to make it easier to spot missing data issues (eg. zero GCHandle in C# BT node due to missing state init)
Platform::MemoryClear(Memory, tree->Graph.NodesStatesSize);
#endif
}
}
void BehaviorKnowledge::FreeMemory()
{
if (Memory)
{
// Release any outstanding nodes state and memory
ASSERT_LOW_LAYER(Tree);
BehaviorUpdateContext context;
context.Behavior = Behavior;
context.Knowledge = this;
context.Memory = Memory;
context.RelevantNodes = &RelevantNodes;
context.DeltaTime = 0.0f;
context.Time = 0.0f;
for (const auto& node : Tree->Graph.Nodes)
{
if (node.Instance && node.Instance->_executionIndex != -1 && RelevantNodes[node.Instance->_executionIndex])
node.Instance->ReleaseState(context);
}
Allocator::Free(Memory);
Memory = nullptr;
}
RelevantNodes.Clear();
Blackboard.DeleteValue();
for (Variant& goal : Goals)
goal.DeleteValue();
Goals.Resize(0);
Tree = nullptr;
}
bool BehaviorKnowledge::Get(const StringAnsiView& path, Variant& value) const
{
return AccessBehaviorKnowledge(const_cast<BehaviorKnowledge*>(this), path, value, false);
}
bool BehaviorKnowledge::Set(const StringAnsiView& path, const Variant& value)
{
return AccessBehaviorKnowledge(this, path, const_cast<Variant&>(value), true);
}
bool BehaviorKnowledge::HasGoal(ScriptingTypeHandle type) const
{
for (int32 i = 0; i < Goals.Count(); i++)
{
const ScriptingTypeHandle goalType = Scripting::FindScriptingType(Goals[i].Type.GetTypeName());
if (goalType == type)
return true;
}
return false;
}
const Variant& BehaviorKnowledge::GetGoal(ScriptingTypeHandle type) const
{
for (const Variant& goal : Goals)
{
const ScriptingTypeHandle goalType = Scripting::FindScriptingType(goal.Type.GetTypeName());
if (goalType == type)
return goal;
}
return Variant::Null;
}
void BehaviorKnowledge::AddGoal(Variant&& goal)
{
int32 i = 0;
for (; i < Goals.Count(); i++)
{
if (Goals[i].Type == goal.Type)
break;
}
if (i == Goals.Count())
Goals.AddDefault();
Goals.Get()[i] = MoveTemp(goal);
}
void BehaviorKnowledge::RemoveGoal(ScriptingTypeHandle type)
{
for (int32 i = 0; i < Goals.Count(); i++)
{
const ScriptingTypeHandle goalType = Scripting::FindScriptingType(Goals[i].Type.GetTypeName());
if (goalType == type)
{
Goals.RemoveAt(i);
break;
}
}
}
bool BehaviorKnowledge::CompareValues(float a, float b, BehaviorValueComparison comparison)
{
switch (comparison)
{
case BehaviorValueComparison::Equal:
return Math::NearEqual(a, b);
case BehaviorValueComparison::NotEqual:
return Math::NotNearEqual(a, b);
case BehaviorValueComparison::Less:
return a < b;
case BehaviorValueComparison::LessEqual:
return a <= b;
case BehaviorValueComparison::Greater:
return a > b;
case BehaviorValueComparison::GreaterEqual:
return a >= b;
default:
return false;
}
}