Add BehaviorKnowledgeSelector for Behavior Knowledge unified data access

This commit is contained in:
Wojtek Figat
2023-08-19 19:50:17 +02:00
parent dee2f11ae4
commit eee53dfbdc
12 changed files with 775 additions and 43 deletions

View File

@@ -5,41 +5,6 @@
#include "BehaviorTreeNodes.h"
#include "Engine/Engine/Time.h"
BehaviorKnowledge::~BehaviorKnowledge()
{
FreeMemory();
}
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);
}
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 && node.Instance->_executionIndex != -1 && RelevantNodes[node.Instance->_executionIndex])
node.Instance->ReleaseState(Behavior, Memory);
}
Allocator::Free(Memory);
Memory = nullptr;
}
RelevantNodes.Clear();
Blackboard.DeleteValue();
Tree = nullptr;
}
Behavior::Behavior(const SpawnParams& params)
: Script(params)
{

View File

@@ -0,0 +1,168 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "BehaviorKnowledge.h"
#include "BehaviorTree.h"
#include "BehaviorTreeNodes.h"
#include "BehaviorKnowledgeSelector.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
CHECK_RETURN(instance.Type == value.Type, false);
if (set)
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);
if (const auto mField = mClass->GetField(member.Get()))
{
bool failed;
if (set)
mField->SetValue(instanceObject, MUtils::VariantToManagedArgPtr(value, mField->GetType(), failed));
else
value = MUtils::UnboxVariant(mField->GetValueBoxed(instanceObject));
return true;
}
else if (const auto mProperty = mClass->GetProperty(member.Get()))
{
if (set)
mProperty->SetValue(instanceObject, MUtils::BoxVariant(value), nullptr);
else
value = MUtils::UnboxVariant(mProperty->GetValue(instanceObject, nullptr));
return true;
}
}
#endif
else
{
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);
}
// TODO: goals and sensors data access from BehaviorKnowledge via Selector
return false;
}
bool BehaviorKnowledgeSelectorAny::Set(BehaviorKnowledge* knowledge, const Variant& value)
{
return knowledge && knowledge->Set(Path, value);
}
Variant BehaviorKnowledgeSelectorAny::Get(BehaviorKnowledge* knowledge)
{
Variant value;
if (knowledge)
knowledge->Get(Path, value);
return value;
}
bool BehaviorKnowledgeSelectorAny::TryGet(BehaviorKnowledge* knowledge, Variant& value)
{
return knowledge && knowledge->Get(Path, value);
}
BehaviorKnowledge::~BehaviorKnowledge()
{
FreeMemory();
}
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);
}
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 && node.Instance->_executionIndex != -1 && RelevantNodes[node.Instance->_executionIndex])
node.Instance->ReleaseState(Behavior, Memory);
}
Allocator::Free(Memory);
Memory = nullptr;
}
RelevantNodes.Clear();
Blackboard.DeleteValue();
Tree = nullptr;
}
bool BehaviorKnowledge::Get(const StringAnsiView& path, Variant& value)
{
return AccessBehaviorKnowledge(this, path, value, false);
}
bool BehaviorKnowledge::Set(const StringAnsiView& path, const Variant& value)
{
return AccessBehaviorKnowledge(this, path, const_cast<Variant&>(value), true);
}

View File

@@ -51,4 +51,22 @@ API_CLASS() class FLAXENGINE_API BehaviorKnowledge : public ScriptingObject
/// Releases the memory of the knowledge.
/// </summary>
void FreeMemory();
/// <summary>
/// Gets the knowledge item value via selector path.
/// </summary>
/// <seealso cref="BehaviorKnowledgeSelector{T}"/>
/// <param name="path">Selector path.</param>
/// <param name="value">Result value (valid only when returned true).</param>
/// <returns>True if got value, otherwise false.</returns>
API_FUNCTION() bool Get(const StringAnsiView& path, API_PARAM(Out) Variant& value);
/// <summary>
/// Sets the knowledge item value via selector path.
/// </summary>
/// <seealso cref="BehaviorKnowledgeSelector{T}"/>
/// <param name="path">Selector path.</param>
/// <param name="value">Value to set.</param>
/// <returns>True if set value, otherwise false.</returns>
API_FUNCTION() bool Set(const StringAnsiView& path, const Variant& value);
};

View File

@@ -0,0 +1,238 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
namespace FlaxEngine
{
#if FLAX_EDITOR
[CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.BehaviorKnowledgeSelectorEditor))]
#endif
partial struct BehaviorKnowledgeSelectorAny : IComparable, IComparable<BehaviorKnowledgeSelectorAny>
{
/// <summary>
/// Initializes a new instance of the <see cref="BehaviorKnowledgeSelectorAny"/> structure.
/// </summary>
/// <param name="path">The selector path.</param>
public BehaviorKnowledgeSelectorAny(string path)
{
Path = path;
}
/// <summary>
/// Initializes a new instance of the <see cref="BehaviorKnowledgeSelectorAny"/> structure.
/// </summary>
/// <param name="other">The other selector.</param>
public BehaviorKnowledgeSelectorAny(BehaviorKnowledgeSelectorAny other)
{
Path = other.Path;
}
/// <summary>
/// Implicit cast operator from selector to string.
/// </summary>
/// <param name="value">Selector</param>
/// <returns>Path</returns>
public static implicit operator string(BehaviorKnowledgeSelectorAny value)
{
return value.Path;
}
/// <summary>
/// Implicit cast operator from string to selector.
/// </summary>
/// <param name="value">Path</param>
/// <returns>Selector</returns>
public static implicit operator BehaviorKnowledgeSelectorAny(string value)
{
return new BehaviorKnowledgeSelectorAny(value);
}
/// <summary>
/// Sets the selected knowledge value.
/// </summary>
/// <param name="knowledge">The knowledge container to access.</param>
/// <param name="value">The value to set.</param>
/// <returns>True if set value value, otherwise false.</returns>
public bool Set(BehaviorKnowledge knowledge, object value)
{
return knowledge != null && knowledge.Set(Path, value);
}
/// <summary>
/// Gets the selected knowledge value.
/// </summary>
/// <param name="knowledge">The knowledge container to access.</param>
/// <returns>The output value or null (if cannot read it - eg. missing goal or no blackboard entry of that name).</returns>
public object Get(BehaviorKnowledge knowledge)
{
object value = null;
if (knowledge != null)
knowledge.Get(Path, out value);
return value;
}
/// <summary>
/// Tries to get the selected knowledge value. Returns true if got value, otherwise false.
/// </summary>
/// <param name="knowledge">The knowledge container to access.</param>
/// <param name="value">The output value.</param>
/// <returns>True if got value, otherwise false.</returns>
public bool TryGet(BehaviorKnowledge knowledge, out object value)
{
value = null;
return knowledge != null && knowledge.Get(Path, out value);
}
/// <inheritdoc />
public override string ToString()
{
return Path;
}
/// <inheritdoc />
public override int GetHashCode()
{
return Path?.GetHashCode() ?? 0;
}
/// <inheritdoc />
public int CompareTo(object obj)
{
if (obj is BehaviorKnowledgeSelectorAny other)
return CompareTo(other);
return 0;
}
/// <inheritdoc />
public int CompareTo(BehaviorKnowledgeSelectorAny other)
{
return string.Compare(Path, other.Path, StringComparison.Ordinal);
}
}
/// <summary>
/// Behavior knowledge value selector that can reference blackboard item, behavior goal or sensor values.
/// </summary>
#if FLAX_EDITOR
[CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.BehaviorKnowledgeSelectorEditor))]
#endif
public struct BehaviorKnowledgeSelector<T> : IComparable, IComparable<BehaviorKnowledgeSelectorAny>, IComparable<BehaviorKnowledgeSelector<T>>
{
/// <summary>
/// Selector path that redirects to the specific knowledge value.
/// </summary>
public string Path;
/// <summary>
/// Initializes a new instance of the <see cref="BehaviorKnowledgeSelectorAny"/> structure.
/// </summary>
/// <param name="path">The selector path.</param>
public BehaviorKnowledgeSelector(string path)
{
Path = path;
}
/// <summary>
/// Initializes a new instance of the <see cref="BehaviorKnowledgeSelectorAny"/> structure.
/// </summary>
/// <param name="other">The other selector.</param>
public BehaviorKnowledgeSelector(BehaviorKnowledgeSelectorAny other)
{
Path = other.Path;
}
/// <summary>
/// Implicit cast operator from selector to string.
/// </summary>
/// <param name="value">Selector</param>
/// <returns>Path</returns>
public static implicit operator string(BehaviorKnowledgeSelector<T> value)
{
return value.Path;
}
/// <summary>
/// Implicit cast operator from string to selector.
/// </summary>
/// <param name="value">Path</param>
/// <returns>Selector</returns>
public static implicit operator BehaviorKnowledgeSelector<T>(string value)
{
return new BehaviorKnowledgeSelector<T>(value);
}
/// <summary>
/// Sets the selected knowledge value.
/// </summary>
/// <param name="knowledge">The knowledge container to access.</param>
/// <param name="value">The value to set.</param>
/// <returns>True if set value value, otherwise false.</returns>
public bool Set(BehaviorKnowledge knowledge, T value)
{
return knowledge != null && knowledge.Set(Path, value);
}
/// <summary>
/// Gets the selected knowledge value.
/// </summary>
/// <param name="knowledge">The knowledge container to access.</param>
/// <returns>The output value or null (if cannot read it - eg. missing goal or no blackboard entry of that name).</returns>
public T Get(BehaviorKnowledge knowledge)
{
object value = null;
if (knowledge != null)
knowledge.Get(Path, out value);
return (T)value;
}
/// <summary>
/// Tries to get the selected knowledge value. Returns true if got value, otherwise false.
/// </summary>
/// <param name="knowledge">The knowledge container to access.</param>
/// <param name="value">The output value.</param>
/// <returns>True if got value, otherwise false.</returns>
public bool TryGet(BehaviorKnowledge knowledge, out T value)
{
value = default;
object tmp = null;
bool result = knowledge != null && knowledge.Get(Path, out tmp);
if (result)
value = (T)tmp;
return result;
}
/// <inheritdoc />
public override string ToString()
{
return Path;
}
/// <inheritdoc />
public override int GetHashCode()
{
return Path?.GetHashCode() ?? 0;
}
/// <inheritdoc />
public int CompareTo(object obj)
{
if (obj is BehaviorKnowledgeSelectorAny otherAny)
return CompareTo(otherAny);
if (obj is BehaviorKnowledgeSelector<T> other)
return CompareTo(other);
return 0;
}
/// <inheritdoc />
public int CompareTo(BehaviorKnowledgeSelectorAny other)
{
return string.Compare(Path, other.Path, StringComparison.Ordinal);
}
/// <inheritdoc />
public int CompareTo(BehaviorKnowledgeSelector<T> other)
{
return string.Compare(Path, other.Path, StringComparison.Ordinal);
}
}
}

View File

@@ -0,0 +1,127 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Types/Variant.h"
#include "Engine/Serialization/SerializationFwd.h"
class BehaviorKnowledge;
/// <summary>
/// Behavior knowledge value selector that can reference blackboard item, behavior goal or sensor values.
/// </summary>
API_STRUCT(NoDefault, MarshalAs=StringAnsi) struct FLAXENGINE_API BehaviorKnowledgeSelectorAny
{
DECLARE_SCRIPTING_TYPE_MINIMAL(BehaviorKnowledgeSelectorAny);
/// <summary>
/// Selector path that redirects to the specific knowledge value.
/// </summary>
API_FIELD() StringAnsi Path;
// Sets the selected knowledge value (as Variant).
bool Set(BehaviorKnowledge* knowledge, const Variant& value);
// Gets the selected knowledge value (as Variant).
Variant Get(BehaviorKnowledge* knowledge);
// Tries to get the selected knowledge value (as Variant). Returns true if got value, otherwise false.
bool TryGet(BehaviorKnowledge* knowledge, Variant& value);
FORCE_INLINE bool operator==(const BehaviorKnowledgeSelectorAny& other) const
{
return Path == other.Path;
}
BehaviorKnowledgeSelectorAny& operator=(const StringAnsiView& other) noexcept
{
Path = other;
return *this;
}
BehaviorKnowledgeSelectorAny& operator=(StringAnsi&& other) noexcept
{
Path = MoveTemp(other);
return *this;
}
operator StringAnsi() const
{
return Path;
}
};
/// <summary>
/// Behavior knowledge value selector that can reference blackboard item, behavior goal or sensor values.
/// </summary>
template<typename T>
API_STRUCT(InBuild, Template, MarshalAs=StringAnsi) struct FLAXENGINE_API BehaviorKnowledgeSelector : BehaviorKnowledgeSelectorAny
{
using BehaviorKnowledgeSelectorAny::Set;
using BehaviorKnowledgeSelectorAny::Get;
using BehaviorKnowledgeSelectorAny::TryGet;
// Sets the selected knowledge value (typed).
FORCE_INLINE void Set(BehaviorKnowledge* knowledge, const T& value)
{
BehaviorKnowledgeSelectorAny::Set(knowledge, Variant(value));
}
// Gets the selected knowledge value (typed).
FORCE_INLINE T Get(BehaviorKnowledge* knowledge)
{
return (T)BehaviorKnowledgeSelectorAny::Get(knowledge);
}
// Tries to get the selected knowledge value (typed). Returns true if got value, otherwise false.
FORCE_INLINE bool TryGet(BehaviorKnowledge* knowledge, T& value)
{
Variant variant;
if (BehaviorKnowledgeSelectorAny::TryGet(knowledge, variant))
{
value = (T)variant;
return true;
}
return false;
}
BehaviorKnowledgeSelector& operator=(const StringAnsiView& other) noexcept
{
Path = other;
return *this;
}
BehaviorKnowledgeSelector& operator=(StringAnsi&& other) noexcept
{
Path = MoveTemp(other);
return *this;
}
operator StringAnsi() const
{
return Path;
}
};
inline uint32 GetHash(const BehaviorKnowledgeSelectorAny& key)
{
return GetHash(key.Path);
}
// @formatter:off
namespace Serialization
{
inline bool ShouldSerialize(const BehaviorKnowledgeSelectorAny& v, const void* otherObj)
{
return !otherObj || v.Path != ((BehaviorKnowledgeSelectorAny*)otherObj)->Path;
}
inline void Serialize(ISerializable::SerializeStream& stream, const BehaviorKnowledgeSelectorAny& v, const void* otherObj)
{
stream.String(v.Path);
}
inline void Deserialize(ISerializable::DeserializeStream& stream, BehaviorKnowledgeSelectorAny& v, ISerializeModifier* modifier)
{
v.Path = stream.GetTextAnsi();
}
}
// @formatter:on

View File

@@ -1,9 +1,9 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if FLAX_EDITOR
using System;
using FlaxEngine.Utilities;
using FlaxEditor.Scripting;
using FlaxEngine.GUI;