Add BehaviorKnowledgeSelector for Behavior Knowledge unified data access
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Utilities;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="BehaviorKnowledgeSelector{T}"/> and <see cref="BehaviorKnowledgeSelectorAny"/>.
|
||||
/// </summary>
|
||||
public sealed class BehaviorKnowledgeSelectorEditor : CustomEditor
|
||||
{
|
||||
private ClickableLabel _label;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_label = layout.ClickableLabel(Path).CustomControl;
|
||||
_label.RightClick += ShowPicker;
|
||||
var button = new Button
|
||||
{
|
||||
Size = new Float2(16.0f),
|
||||
Text = "...",
|
||||
TooltipText = "Edit...",
|
||||
Parent = _label,
|
||||
};
|
||||
button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
|
||||
button.Clicked += ShowPicker;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
// Update label
|
||||
_label.Text = Path;
|
||||
}
|
||||
|
||||
private string Path
|
||||
{
|
||||
get
|
||||
{
|
||||
var v = Values[0];
|
||||
if (v is BehaviorKnowledgeSelectorAny any)
|
||||
return any.Path;
|
||||
if (v is string str)
|
||||
return str;
|
||||
var pathField = v.GetType().GetField("Path");
|
||||
return pathField.GetValue(v) as string;
|
||||
}
|
||||
set
|
||||
{
|
||||
var v = Values[0];
|
||||
if (v is BehaviorKnowledgeSelectorAny)
|
||||
v = new BehaviorKnowledgeSelectorAny(value);
|
||||
else if (v is string)
|
||||
v = value;
|
||||
else
|
||||
{
|
||||
var pathField = v.GetType().GetField("Path");
|
||||
pathField.SetValue(v, value);
|
||||
}
|
||||
SetValue(v);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowPicker()
|
||||
{
|
||||
// Get Behavior Knowledge to select from
|
||||
var behaviorTreeWindow = Presenter.Owner as Windows.Assets.BehaviorTreeWindow;
|
||||
var blackboard = behaviorTreeWindow?.Blackboard;
|
||||
if (blackboard == null)
|
||||
return;
|
||||
var typed = ScriptType.Null;
|
||||
var valueType = Values[0].GetType();
|
||||
if (valueType.Name == "BehaviorKnowledgeSelector`1")
|
||||
{
|
||||
// Get typed selector type to show only assignable items
|
||||
typed = new ScriptType(valueType.GenericTypeArguments[0]);
|
||||
}
|
||||
// TODO: add support for selecting goals and sensors
|
||||
|
||||
// Create menu with tree-like structure and search box
|
||||
var menu = Utilities.Utils.CreateSearchPopup(out var searchBox, out var tree, 0, true);
|
||||
var blackboardType = TypeUtils.GetObjectType(blackboard);
|
||||
var items = GenericEditor.GetItemsForType(blackboardType, blackboardType.IsClass, true);
|
||||
var selected = Path;
|
||||
var noneNode = new TreeNode
|
||||
{
|
||||
Text = "<none>",
|
||||
TooltipText = "Deselect value",
|
||||
Parent = tree,
|
||||
};
|
||||
if (string.IsNullOrEmpty(selected))
|
||||
tree.Select(noneNode);
|
||||
var blackboardNode = new TreeNode
|
||||
{
|
||||
Text = "Blackboard",
|
||||
TooltipText = blackboardType.TypeName,
|
||||
Tag = "Blackboard/", // Ability to select whole blackboard data
|
||||
Parent = tree,
|
||||
};
|
||||
if (typed && !typed.IsAssignableFrom(blackboardType))
|
||||
blackboardNode.Tag = null;
|
||||
if (string.Equals(selected, (string)blackboardNode.Tag, StringComparison.Ordinal))
|
||||
tree.Select(blackboardNode);
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (typed && !typed.IsAssignableFrom(item.Info.ValueType))
|
||||
continue;
|
||||
var path = "Blackboard/" + item.Info.Name;
|
||||
var node = new TreeNode
|
||||
{
|
||||
Text = item.DisplayName,
|
||||
TooltipText = item.TooltipText,
|
||||
Tag = path,
|
||||
Parent = blackboardNode,
|
||||
};
|
||||
if (string.Equals(selected, path, StringComparison.Ordinal))
|
||||
tree.Select(node);
|
||||
// TODO: add support for nested items (eg. field from blackboard structure field)
|
||||
}
|
||||
blackboardNode.Expand(true);
|
||||
tree.SelectedChanged += delegate(List<TreeNode> before, List<TreeNode> after)
|
||||
{
|
||||
if (after.Count == 1)
|
||||
{
|
||||
menu.Hide();
|
||||
Path = after[0].Tag as string;
|
||||
}
|
||||
};
|
||||
menu.Show(_label, new Float2(0, _label.Height));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// Describes object property/field information for custom editors pipeline.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.IComparable" />
|
||||
protected class ItemInfo : IComparable
|
||||
public class ItemInfo : IComparable
|
||||
{
|
||||
private Options.GeneralOptions.MembersOrder _membersOrder;
|
||||
|
||||
@@ -248,7 +248,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// <param name="useProperties">True if use type properties.</param>
|
||||
/// <param name="useFields">True if use type fields.</param>
|
||||
/// <returns>The items.</returns>
|
||||
protected List<ItemInfo> GetItemsForType(ScriptType type, bool useProperties, bool useFields)
|
||||
public static List<ItemInfo> GetItemsForType(ScriptType type, bool useProperties, bool useFields)
|
||||
{
|
||||
var items = new List<ItemInfo>();
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -1060,8 +1061,9 @@ namespace FlaxEditor.Utilities
|
||||
/// <param name="searchBox">The search box.</param>
|
||||
/// <param name="tree">The tree control.</param>
|
||||
/// <param name="headerHeight">Amount of additional space above the search box to put custom UI.</param>
|
||||
/// <param name="autoSearch">Plug automatic tree search delegate.</param>
|
||||
/// <returns>The created menu to setup and show.</returns>
|
||||
public static ContextMenuBase CreateSearchPopup(out TextBox searchBox, out Tree tree, float headerHeight = 0)
|
||||
public static ContextMenuBase CreateSearchPopup(out TextBox searchBox, out Tree tree, float headerHeight = 0, bool autoSearch = false)
|
||||
{
|
||||
var menu = new ContextMenuBase
|
||||
{
|
||||
@@ -1087,9 +1089,35 @@ namespace FlaxEditor.Utilities
|
||||
{
|
||||
Parent = panel2,
|
||||
};
|
||||
if (autoSearch)
|
||||
{
|
||||
var s = searchBox;
|
||||
var t = tree;
|
||||
searchBox.TextChanged += delegate
|
||||
{
|
||||
if (t.IsLayoutLocked)
|
||||
return;
|
||||
t.LockChildrenRecursive();
|
||||
UpdateSearchPopupFilter(t, s.Text);
|
||||
t.UnlockChildrenRecursive();
|
||||
menu.PerformLayout();
|
||||
};
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates (recursively) search popup tree structures based on the filter text.
|
||||
/// </summary>
|
||||
public static void UpdateSearchPopupFilter(Tree tree, string filterText)
|
||||
{
|
||||
for (int i = 0; i < tree.Children.Count; i++)
|
||||
{
|
||||
if (tree.Children[i] is TreeNode child)
|
||||
UpdateSearchPopupFilter(child, filterText);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates (recursively) search popup tree structures based on the filter text.
|
||||
/// </summary>
|
||||
|
||||
@@ -6,8 +6,10 @@ using System.Xml;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.CustomEditors;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface;
|
||||
using FlaxEditor.Viewport;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Utilities;
|
||||
@@ -19,7 +21,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
/// </summary>
|
||||
/// <seealso cref="BehaviorTree" />
|
||||
/// <seealso cref="BehaviorTreeSurface" />
|
||||
public sealed class BehaviorTreeWindow : AssetEditorWindowBase<BehaviorTree>, IVisjectSurfaceWindow
|
||||
public sealed class BehaviorTreeWindow : AssetEditorWindowBase<BehaviorTree>, IVisjectSurfaceWindow, IPresenterOwner
|
||||
{
|
||||
private readonly SplitPanel _split1;
|
||||
private readonly SplitPanel _split2;
|
||||
@@ -44,6 +46,11 @@ namespace FlaxEditor.Windows.Assets
|
||||
/// </summary>
|
||||
public Undo Undo => _undo;
|
||||
|
||||
/// <summary>
|
||||
/// Current instance of the Behavior Knowledge's blackboard type or null.
|
||||
/// </summary>
|
||||
public object Blackboard => _knowledgePropertiesEditor.Selection.Count != 0 ? _knowledgePropertiesEditor.Selection[0] : null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public BehaviorTreeWindow(Editor editor, BinaryAssetItem item)
|
||||
: base(editor, item)
|
||||
@@ -81,11 +88,11 @@ namespace FlaxEditor.Windows.Assets
|
||||
_surface.SelectionChanged += OnNodeSelectionChanged;
|
||||
|
||||
// Properties editors
|
||||
_nodePropertiesEditor = new CustomEditorPresenter(null); // Surface handles undo for nodes editing
|
||||
_nodePropertiesEditor = new CustomEditorPresenter(null, null, this); // Surface handles undo for nodes editing
|
||||
_nodePropertiesEditor.Features = FeatureFlags.UseDefault;
|
||||
_nodePropertiesEditor.Panel.Parent = _split2.Panel1;
|
||||
_nodePropertiesEditor.Modified += OnNodePropertyEdited;
|
||||
_knowledgePropertiesEditor = new CustomEditorPresenter(null, "No blackboard type assigned"); // No undo for knowledge editing
|
||||
_knowledgePropertiesEditor = new CustomEditorPresenter(null, "No blackboard type assigned", this); // No undo for knowledge editing
|
||||
_knowledgePropertiesEditor.Features = FeatureFlags.None;
|
||||
_knowledgePropertiesEditor.Panel.Parent = _split2.Panel2;
|
||||
|
||||
@@ -429,5 +436,13 @@ namespace FlaxEditor.Windows.Assets
|
||||
|
||||
/// <inheritdoc />
|
||||
public VisjectSurface VisjectSurface => _surface;
|
||||
|
||||
/// <inheritdoc />
|
||||
public EditorViewport PresenterViewport => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Select(List<SceneGraphNode> nodes)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
168
Source/Engine/AI/BehaviorKnowledge.cpp
Normal file
168
Source/Engine/AI/BehaviorKnowledge.cpp
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
238
Source/Engine/AI/BehaviorKnowledgeSelector.cs
Normal file
238
Source/Engine/AI/BehaviorKnowledgeSelector.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
127
Source/Engine/AI/BehaviorKnowledgeSelector.h
Normal file
127
Source/Engine/AI/BehaviorKnowledgeSelector.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -128,7 +128,6 @@ namespace FlaxEngine.Json
|
||||
var result = new SoftTypeReference();
|
||||
if (reader.TokenType == JsonToken.String)
|
||||
result.TypeName = (string)reader.Value;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -139,6 +138,34 @@ namespace FlaxEngine.Json
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize <see cref="BehaviorKnowledgeSelectorAny"/> as path string in internal format.
|
||||
/// </summary>
|
||||
/// <seealso cref="Newtonsoft.Json.JsonConverter" />
|
||||
internal class BehaviorKnowledgeSelectorAnyConverter : JsonConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(((BehaviorKnowledgeSelectorAny)value).Path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
|
||||
{
|
||||
var result = new BehaviorKnowledgeSelectorAny();
|
||||
if (reader.TokenType == JsonToken.String)
|
||||
result.Path = (string)reader.Value;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(BehaviorKnowledgeSelectorAny);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize <see cref="Margin"/> as Guid in internal format.
|
||||
/// </summary>
|
||||
|
||||
@@ -124,6 +124,7 @@ namespace FlaxEngine.Json
|
||||
settings.Converters.Add(new SceneReferenceConverter());
|
||||
settings.Converters.Add(new SoftObjectReferenceConverter());
|
||||
settings.Converters.Add(new SoftTypeReferenceConverter());
|
||||
settings.Converters.Add(new BehaviorKnowledgeSelectorAnyConverter());
|
||||
settings.Converters.Add(new MarginConverter());
|
||||
settings.Converters.Add(new VersionConverter());
|
||||
settings.Converters.Add(new LocalizedStringConverter());
|
||||
|
||||
Reference in New Issue
Block a user