diff --git a/Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs b/Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs new file mode 100644 index 000000000..0da87c7ec --- /dev/null +++ b/Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs @@ -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 +{ + /// + /// Custom editor for and . + /// + public sealed class BehaviorKnowledgeSelectorEditor : CustomEditor + { + private ClickableLabel _label; + + /// + public override DisplayStyle Style => DisplayStyle.Inline; + + /// + 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; + } + + /// + 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 = "", + 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 before, List after) + { + if (after.Count == 1) + { + menu.Hide(); + Path = after[0].Tag as string; + } + }; + menu.Show(_label, new Float2(0, _label.Height)); + } + } +} diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index cba2b5a5a..55ac453a9 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -26,7 +26,7 @@ namespace FlaxEditor.CustomEditors.Editors /// Describes object property/field information for custom editors pipeline. /// /// - protected class ItemInfo : IComparable + public class ItemInfo : IComparable { private Options.GeneralOptions.MembersOrder _membersOrder; @@ -248,7 +248,7 @@ namespace FlaxEditor.CustomEditors.Editors /// True if use type properties. /// True if use type fields. /// The items. - protected List GetItemsForType(ScriptType type, bool useProperties, bool useFields) + public static List GetItemsForType(ScriptType type, bool useProperties, bool useFields) { var items = new List(); diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index fe801bbaf..e2022d03c 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -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 /// The search box. /// The tree control. /// Amount of additional space above the search box to put custom UI. + /// Plug automatic tree search delegate. /// The created menu to setup and show. - 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; } + /// + /// Updates (recursively) search popup tree structures based on the filter text. + /// + 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); + } + } + /// /// Updates (recursively) search popup tree structures based on the filter text. /// diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs index f2dde24a8..7c5ff57a4 100644 --- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs +++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs @@ -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 /// /// /// - public sealed class BehaviorTreeWindow : AssetEditorWindowBase, IVisjectSurfaceWindow + public sealed class BehaviorTreeWindow : AssetEditorWindowBase, IVisjectSurfaceWindow, IPresenterOwner { private readonly SplitPanel _split1; private readonly SplitPanel _split2; @@ -44,6 +46,11 @@ namespace FlaxEditor.Windows.Assets /// public Undo Undo => _undo; + /// + /// Current instance of the Behavior Knowledge's blackboard type or null. + /// + public object Blackboard => _knowledgePropertiesEditor.Selection.Count != 0 ? _knowledgePropertiesEditor.Selection[0] : null; + /// 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 /// public VisjectSurface VisjectSurface => _surface; + + /// + public EditorViewport PresenterViewport => null; + + /// + public void Select(List nodes) + { + } } } diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp index f08b733ad..a8eb761a8 100644 --- a/Source/Engine/AI/Behavior.cpp +++ b/Source/Engine/AI/Behavior.cpp @@ -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) { diff --git a/Source/Engine/AI/BehaviorKnowledge.cpp b/Source/Engine/AI/BehaviorKnowledge.cpp new file mode 100644 index 000000000..c08a09ffc --- /dev/null +++ b/Source/Engine/AI/BehaviorKnowledge.cpp @@ -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(value), true); +} diff --git a/Source/Engine/AI/BehaviorKnowledge.h b/Source/Engine/AI/BehaviorKnowledge.h index a39e70eea..252f85764 100644 --- a/Source/Engine/AI/BehaviorKnowledge.h +++ b/Source/Engine/AI/BehaviorKnowledge.h @@ -51,4 +51,22 @@ API_CLASS() class FLAXENGINE_API BehaviorKnowledge : public ScriptingObject /// Releases the memory of the knowledge. /// void FreeMemory(); + + /// + /// Gets the knowledge item value via selector path. + /// + /// + /// Selector path. + /// Result value (valid only when returned true). + /// True if got value, otherwise false. + API_FUNCTION() bool Get(const StringAnsiView& path, API_PARAM(Out) Variant& value); + + /// + /// Sets the knowledge item value via selector path. + /// + /// + /// Selector path. + /// Value to set. + /// True if set value, otherwise false. + API_FUNCTION() bool Set(const StringAnsiView& path, const Variant& value); }; diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.cs b/Source/Engine/AI/BehaviorKnowledgeSelector.cs new file mode 100644 index 000000000..7d5698b6e --- /dev/null +++ b/Source/Engine/AI/BehaviorKnowledgeSelector.cs @@ -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 + { + /// + /// Initializes a new instance of the structure. + /// + /// The selector path. + public BehaviorKnowledgeSelectorAny(string path) + { + Path = path; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The other selector. + public BehaviorKnowledgeSelectorAny(BehaviorKnowledgeSelectorAny other) + { + Path = other.Path; + } + + /// + /// Implicit cast operator from selector to string. + /// + /// Selector + /// Path + public static implicit operator string(BehaviorKnowledgeSelectorAny value) + { + return value.Path; + } + + /// + /// Implicit cast operator from string to selector. + /// + /// Path + /// Selector + public static implicit operator BehaviorKnowledgeSelectorAny(string value) + { + return new BehaviorKnowledgeSelectorAny(value); + } + + /// + /// Sets the selected knowledge value. + /// + /// The knowledge container to access. + /// The value to set. + /// True if set value value, otherwise false. + public bool Set(BehaviorKnowledge knowledge, object value) + { + return knowledge != null && knowledge.Set(Path, value); + } + + /// + /// Gets the selected knowledge value. + /// + /// The knowledge container to access. + /// The output value or null (if cannot read it - eg. missing goal or no blackboard entry of that name). + public object Get(BehaviorKnowledge knowledge) + { + object value = null; + if (knowledge != null) + knowledge.Get(Path, out value); + return value; + } + + /// + /// Tries to get the selected knowledge value. Returns true if got value, otherwise false. + /// + /// The knowledge container to access. + /// The output value. + /// True if got value, otherwise false. + public bool TryGet(BehaviorKnowledge knowledge, out object value) + { + value = null; + return knowledge != null && knowledge.Get(Path, out value); + } + + /// + public override string ToString() + { + return Path; + } + + /// + public override int GetHashCode() + { + return Path?.GetHashCode() ?? 0; + } + + /// + public int CompareTo(object obj) + { + if (obj is BehaviorKnowledgeSelectorAny other) + return CompareTo(other); + return 0; + } + + /// + public int CompareTo(BehaviorKnowledgeSelectorAny other) + { + return string.Compare(Path, other.Path, StringComparison.Ordinal); + } + } + + /// + /// Behavior knowledge value selector that can reference blackboard item, behavior goal or sensor values. + /// +#if FLAX_EDITOR + [CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.BehaviorKnowledgeSelectorEditor))] +#endif + public struct BehaviorKnowledgeSelector : IComparable, IComparable, IComparable> + { + /// + /// Selector path that redirects to the specific knowledge value. + /// + public string Path; + + /// + /// Initializes a new instance of the structure. + /// + /// The selector path. + public BehaviorKnowledgeSelector(string path) + { + Path = path; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The other selector. + public BehaviorKnowledgeSelector(BehaviorKnowledgeSelectorAny other) + { + Path = other.Path; + } + + /// + /// Implicit cast operator from selector to string. + /// + /// Selector + /// Path + public static implicit operator string(BehaviorKnowledgeSelector value) + { + return value.Path; + } + + /// + /// Implicit cast operator from string to selector. + /// + /// Path + /// Selector + public static implicit operator BehaviorKnowledgeSelector(string value) + { + return new BehaviorKnowledgeSelector(value); + } + + /// + /// Sets the selected knowledge value. + /// + /// The knowledge container to access. + /// The value to set. + /// True if set value value, otherwise false. + public bool Set(BehaviorKnowledge knowledge, T value) + { + return knowledge != null && knowledge.Set(Path, value); + } + + /// + /// Gets the selected knowledge value. + /// + /// The knowledge container to access. + /// The output value or null (if cannot read it - eg. missing goal or no blackboard entry of that name). + public T Get(BehaviorKnowledge knowledge) + { + object value = null; + if (knowledge != null) + knowledge.Get(Path, out value); + return (T)value; + } + + /// + /// Tries to get the selected knowledge value. Returns true if got value, otherwise false. + /// + /// The knowledge container to access. + /// The output value. + /// True if got value, otherwise false. + 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; + } + + /// + public override string ToString() + { + return Path; + } + + /// + public override int GetHashCode() + { + return Path?.GetHashCode() ?? 0; + } + + /// + public int CompareTo(object obj) + { + if (obj is BehaviorKnowledgeSelectorAny otherAny) + return CompareTo(otherAny); + if (obj is BehaviorKnowledgeSelector other) + return CompareTo(other); + return 0; + } + + /// + public int CompareTo(BehaviorKnowledgeSelectorAny other) + { + return string.Compare(Path, other.Path, StringComparison.Ordinal); + } + + /// + public int CompareTo(BehaviorKnowledgeSelector other) + { + return string.Compare(Path, other.Path, StringComparison.Ordinal); + } + } +} diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.h b/Source/Engine/AI/BehaviorKnowledgeSelector.h new file mode 100644 index 000000000..96241a9d3 --- /dev/null +++ b/Source/Engine/AI/BehaviorKnowledgeSelector.h @@ -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; + +/// +/// Behavior knowledge value selector that can reference blackboard item, behavior goal or sensor values. +/// +API_STRUCT(NoDefault, MarshalAs=StringAnsi) struct FLAXENGINE_API BehaviorKnowledgeSelectorAny +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(BehaviorKnowledgeSelectorAny); + + /// + /// Selector path that redirects to the specific knowledge value. + /// + 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; + } +}; + +/// +/// Behavior knowledge value selector that can reference blackboard item, behavior goal or sensor values. +/// +template +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 diff --git a/Source/Engine/AI/BehaviorTree.cs b/Source/Engine/AI/BehaviorTree.cs index bd342f3d2..d35cf2802 100644 --- a/Source/Engine/AI/BehaviorTree.cs +++ b/Source/Engine/AI/BehaviorTree.cs @@ -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; diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs index 40f9cb5a2..5843f7ec6 100644 --- a/Source/Engine/Serialization/JsonConverters.cs +++ b/Source/Engine/Serialization/JsonConverters.cs @@ -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 } } + /// + /// Serialize as path string in internal format. + /// + /// + internal class BehaviorKnowledgeSelectorAnyConverter : JsonConverter + { + /// + public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + writer.WriteValue(((BehaviorKnowledgeSelectorAny)value).Path); + } + + /// + 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; + } + + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(BehaviorKnowledgeSelectorAny); + } + } + /// /// Serialize as Guid in internal format. /// diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index 619862b1f..013a52391 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -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());