From f8d436ca7522baaac035a994905b4956851ebdfb Mon Sep 17 00:00:00 2001 From: Andrei Gagua Date: Sun, 24 May 2026 10:12:31 +0300 Subject: [PATCH 1/9] New: Add interface-filtered object references Add native hard and soft reference types for scene objects that implement a scripting interface: - ScriptingObjectInterfaceReference - SoftObjectInterfaceReference --- .../Editor/CustomEditors/CustomEditorsUtil.cs | 10 +- .../Editors/FlaxObjectRefEditor.cs | 20 +- .../GUI/Popups/SceneObjectSearchPopup.cs | 146 ++++++++++ ...iptingObjectInterfaceReferenceAttribute.cs | 14 + .../SoftObjectInterfaceReferenceAttribute.cs | 14 + .../Engine/Scripting/Internal/InternalCalls.h | 2 +- Source/Engine/Scripting/ManagedCLR/MUtils.h | 48 ++++ .../ScriptingObjectInterfaceReference.h | 195 +++++++++++++ .../ScriptingObjectInterfaceReferenceUtils.h | 30 ++ .../Scripting/SoftObjectInterfaceReference.h | 258 ++++++++++++++++++ Source/Engine/Serialization/ReadStream.h | 16 ++ Source/Engine/Serialization/Serialization.h | 53 +++- Source/Engine/Serialization/Stream.h | 4 + Source/Engine/Serialization/WriteStream.h | 18 ++ Source/Engine/Tests/TestScripting.h | 6 + .../Bindings/BindingsGenerator.CSharp.cs | 29 +- .../Bindings/BindingsGenerator.Cpp.cs | 17 ++ Source/Tools/Flax.Build/Bindings/ClassInfo.cs | 2 + Source/Tools/Flax.Build/Bindings/TypeInfo.cs | 10 +- 19 files changed, 876 insertions(+), 16 deletions(-) create mode 100644 Source/Editor/GUI/Popups/SceneObjectSearchPopup.cs create mode 100644 Source/Engine/Scripting/Attributes/Editor/ScriptingObjectInterfaceReferenceAttribute.cs create mode 100644 Source/Engine/Scripting/Attributes/Editor/SoftObjectInterfaceReferenceAttribute.cs create mode 100644 Source/Engine/Scripting/ScriptingObjectInterfaceReference.h create mode 100644 Source/Engine/Scripting/ScriptingObjectInterfaceReferenceUtils.h create mode 100644 Source/Engine/Scripting/SoftObjectInterfaceReference.h diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cs b/Source/Editor/CustomEditors/CustomEditorsUtil.cs index 476219960..2323333e2 100644 --- a/Source/Editor/CustomEditors/CustomEditorsUtil.cs +++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cs @@ -58,11 +58,13 @@ namespace FlaxEditor.CustomEditors if (targetType.Type == typeof(object) && values.Count > 0 && values[0] != null && !values.HasDifferentTypes) return CreateEditor(TypeUtils.GetObjectType(values[0]), canUseRefPicker); - // Special case if property is interface but the value is implemented as Scripting Object that should use reference picker - if (targetType.IsInterface && canUseRefPicker && values.Count > 0 && values[0] is FlaxEngine.Object) - return new DummyEditor(); - // Use editor for the property type + if (canUseRefPicker && + targetType.IsInterface && + values.GetAttributes().Any(x => x is ScriptingObjectInterfaceReferenceAttribute || x is SoftObjectInterfaceReferenceAttribute)) + { + return new FlaxObjectRefEditor(); + } return CreateEditor(targetType, canUseRefPicker); } diff --git a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs index d7ab6d12b..289c7ec90 100644 --- a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs @@ -48,7 +48,7 @@ namespace FlaxEditor.CustomEditors.Editors public IPresenterOwner PresenterContext; /// - /// Gets or sets the allowed objects type (given type and all subclasses). Must be type of any subclass. + /// Gets or sets the allowed objects type (given type and all subclasses). Must be type of any subclass or a scripting interface. /// public ScriptType Type { @@ -57,11 +57,12 @@ namespace FlaxEditor.CustomEditors.Editors { if (_type == value) return; - if (value == ScriptType.Null || (value.Type != typeof(Object) && !value.IsSubclassOf(ScriptType.Object))) + if (value == ScriptType.Null || (!value.IsInterface && value.Type != typeof(Object) && !value.IsSubclassOf(ScriptType.Object))) throw new ArgumentException(string.Format("Invalid type for FlaxObjectRefEditor. Input type: {0}", value != ScriptType.Null ? value.TypeName : "null")); _type = value; - _supportsPickDropDown = new ScriptType(typeof(Actor)).IsAssignableFrom(value) || + _supportsPickDropDown = value.IsInterface || + new ScriptType(typeof(Actor)).IsAssignableFrom(value) || new ScriptType(typeof(Script)).IsAssignableFrom(value); // Deselect value if it's not valid now @@ -149,13 +150,22 @@ namespace FlaxEditor.CustomEditors.Editors protected virtual bool IsValid(Object obj) { var type = TypeUtils.GetObjectType(obj); - return obj == null || _type.IsAssignableFrom(type) && (CheckValid == null || CheckValid(obj, type)); + return obj == null || (!_type.IsInterface || obj is SceneObject) && _type.IsAssignableFrom(type) && (CheckValid == null || CheckValid(obj, type)); } private void ShowDropDownMenu() { Focus(); - if (new ScriptType(typeof(Actor)).IsAssignableFrom(_type)) + if (_type.IsInterface) + { + SceneObjectSearchPopup.Show(this, new Float2(0, Height), IsValid, obj => + { + Value = obj; + RootWindow.Focus(); + Focus(); + }, PresenterContext); + } + else if (new ScriptType(typeof(Actor)).IsAssignableFrom(_type)) { ActorSearchPopup.Show(this, new Float2(0, Height), IsValid, actor => { diff --git a/Source/Editor/GUI/Popups/SceneObjectSearchPopup.cs b/Source/Editor/GUI/Popups/SceneObjectSearchPopup.cs new file mode 100644 index 000000000..4868dd049 --- /dev/null +++ b/Source/Editor/GUI/Popups/SceneObjectSearchPopup.cs @@ -0,0 +1,146 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +using System; +using FlaxEditor.Windows; +using FlaxEditor.Windows.Assets; +using FlaxEngine; +using FlaxEngine.GUI; +using FlaxEngine.Utilities; + +namespace FlaxEditor.GUI +{ + /// + /// Popup that shows the list of scene objects to pick. Supports searching and basic type filtering. + /// + /// + public class SceneObjectSearchPopup : ItemsListContextMenu + { + /// + /// The scene object item. + /// + /// + public class SceneObjectItemView : Item + { + private SceneObject _object; + + /// + /// Gets the scene object. + /// + public SceneObject Object => _object; + + /// + /// Initializes a new instance of the class. + /// + /// The object. + public SceneObjectItemView(SceneObject obj) + { + _object = obj; + Category = obj is Actor ? "Actors" : "Scripts"; + if (obj is Script script) + { + var type = TypeUtils.GetObjectType(script); + Name = script.Actor ? $"{type.Name} ({script.Actor.Name})" : type.Name; + } + else if (obj is Actor actor) + { + Name = actor.Name; + } + else + { + Name = obj.ToString(); + } + TooltipText = Utilities.Utils.GetTooltip(obj); + } + + /// + public override void OnDestroy() + { + _object = null; + base.OnDestroy(); + } + } + + /// + /// Validates if the given scene object item can be used to pick it. + /// + /// The scene object. + /// True if is valid. + public delegate bool IsValidDelegate(SceneObject obj); + + private IsValidDelegate _isValid; + private Action _selected; + + private SceneObjectSearchPopup(IsValidDelegate isValid, Action selected, CustomEditors.IPresenterOwner context) + { + _isValid = isValid; + _selected = selected; + + ItemClicked += OnItemClicked; + + if (context is PropertiesWindow || context == null) + { + // TODO: use async thread to search scenes + for (int i = 0; i < Level.ScenesCount; i++) + { + Find(Level.GetScene(i)); + } + } + else if (context is PrefabWindow prefabWindow) + { + Find(prefabWindow.Graph.MainActor); + } + + SortItems(); + } + + private void OnItemClicked(Item item) + { + _selected(((SceneObjectItemView)item).Object); + } + + private void Find(Actor actor) + { + if (!actor) + return; + + if (_isValid(actor)) + AddItem(new SceneObjectItemView(actor)); + + for (int i = 0; i < actor.ScriptsCount; i++) + { + var script = actor.GetScript(i); + if (_isValid(script)) + AddItem(new SceneObjectItemView(script)); + } + + for (int i = 0; i < actor.ChildrenCount; i++) + { + Find(actor.GetChild(i)); + } + } + + /// + /// Shows the popup. + /// + /// The show target. + /// The show target location. + /// Event called to check if a given scene object item is valid to be used. + /// Event called on scene object item pick. + /// The presenter owner context (i.e. PrefabWindow, PropertiesWindow). + /// The dialog. + public static SceneObjectSearchPopup Show(Control showTarget, Float2 showTargetLocation, IsValidDelegate isValid, Action selected, CustomEditors.IPresenterOwner context) + { + var popup = new SceneObjectSearchPopup(isValid, selected, context); + popup.Show(showTarget, showTargetLocation); + return popup; + } + + /// + public override void OnDestroy() + { + _isValid = null; + _selected = null; + base.OnDestroy(); + } + } +} diff --git a/Source/Engine/Scripting/Attributes/Editor/ScriptingObjectInterfaceReferenceAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/ScriptingObjectInterfaceReferenceAttribute.cs new file mode 100644 index 000000000..720497b2c --- /dev/null +++ b/Source/Engine/Scripting/Attributes/Editor/ScriptingObjectInterfaceReferenceAttribute.cs @@ -0,0 +1,14 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ + /// + /// Marks a generated interface property as a native scripting object interface reference. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class ScriptingObjectInterfaceReferenceAttribute : Attribute + { + } +} diff --git a/Source/Engine/Scripting/Attributes/Editor/SoftObjectInterfaceReferenceAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/SoftObjectInterfaceReferenceAttribute.cs new file mode 100644 index 000000000..ec106412f --- /dev/null +++ b/Source/Engine/Scripting/Attributes/Editor/SoftObjectInterfaceReferenceAttribute.cs @@ -0,0 +1,14 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ + /// + /// Marks a generated interface property as a native soft object interface reference. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class SoftObjectInterfaceReferenceAttribute : Attribute + { + } +} diff --git a/Source/Engine/Scripting/Internal/InternalCalls.h b/Source/Engine/Scripting/Internal/InternalCalls.h index 4535952ea..d179fd8fd 100644 --- a/Source/Engine/Scripting/Internal/InternalCalls.h +++ b/Source/Engine/Scripting/Internal/InternalCalls.h @@ -37,7 +37,7 @@ struct FLAXENGINE_API VTableFunctionInjector #if USE_NETCORE #define ADD_INTERNAL_CALL(fullName, method) -#define DEFINE_INTERNAL_CALL(returnType) extern "C" DLLEXPORT returnType +#define DEFINE_INTERNAL_CALL(returnType) extern "C" DLLEXPORT USED returnType #else extern "C" FLAXENGINE_API void mono_add_internal_call(const char* name, const void* method); #define ADD_INTERNAL_CALL(fullName, method) mono_add_internal_call(fullName, (const void*)method) diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.h b/Source/Engine/Scripting/ManagedCLR/MUtils.h index 5598dbee0..aed0c48c5 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.h +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.h @@ -278,6 +278,10 @@ struct MConverter::Val // Converter for ScriptingObject References. template class ScriptingObjectReference; +template +class ScriptingObjectInterfaceReference; +template +class SoftObjectInterfaceReference; template struct MConverter> @@ -311,6 +315,50 @@ struct MConverter> } }; +template +struct MInterfaceReferenceConverter +{ + MObject* Box(const TReference& data, const MClass* klass) + { + return data.GetManagedInstance(); + } + + void Unbox(TReference& result, MObject* data) + { + result = ScriptingObject::ToInterface(ScriptingObject::ToNative(data)); + } + + void ToManagedArray(MArray* result, const Span& data) + { + if (data.Length() == 0) + return; + MObject** objects = (MObject**)Allocator::Allocate(data.Length() * sizeof(MObject*)); + for (int32 i = 0; i < data.Length(); i++) + objects[i] = data[i].GetManagedInstance(); + MCore::GC::WriteArrayRef(result, Span(objects, data.Length())); + Allocator::Free(objects); + } + + void ToNativeArray(Span& result, const MArray* data) + { + MObject** dataPtr = MCore::Array::GetAddress(data); + for (int32 i = 0; i < result.Length(); i++) + result.Get()[i] = ScriptingObject::ToInterface(ScriptingObject::ToNative(dataPtr[i])); + } +}; + +// Converter for Scripting Interface References. +template +struct MConverter> : MInterfaceReferenceConverter, T> +{ +}; + +// Converter for Soft Object Interface References. +template +struct MConverter> : MInterfaceReferenceConverter, T> +{ +}; + // Converter for Asset References. template class AssetReference; diff --git a/Source/Engine/Scripting/ScriptingObjectInterfaceReference.h b/Source/Engine/Scripting/ScriptingObjectInterfaceReference.h new file mode 100644 index 000000000..65c479075 --- /dev/null +++ b/Source/Engine/Scripting/ScriptingObjectInterfaceReference.h @@ -0,0 +1,195 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/ScriptingObjectInterfaceReferenceUtils.h" + +/// +/// The scene object interface reference. +/// +/// The type of the scripting interface. +template +API_CLASS(InBuild) class ScriptingObjectInterfaceReference : public ScriptingObjectReferenceBase +{ + typedef ScriptingObjectInterfaceReferenceHelper Helper; + +public: + typedef ScriptingObjectInterfaceReference Type; + +public: + /// + /// Initializes a new instance of the class. + /// + ScriptingObjectInterfaceReference() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The object to link. + ScriptingObjectInterfaceReference(SceneObject* obj) + : ScriptingObjectReferenceBase(Helper::IsValidObject(obj) ? obj : nullptr) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The interface object to link. + ScriptingObjectInterfaceReference(T* interfaceObj) + : ScriptingObjectReferenceBase(Helper::GetSceneObject(interfaceObj)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The other property. + ScriptingObjectInterfaceReference(const ScriptingObjectInterfaceReference& other) + : ScriptingObjectReferenceBase(other._object) + { + } + + ScriptingObjectInterfaceReference(ScriptingObjectInterfaceReference&& other) noexcept + : ScriptingObjectReferenceBase(MoveTemp(other)) + { + } + + /// + /// Finalizes an instance of the class. + /// + ~ScriptingObjectInterfaceReference() + { + } + +public: + FORCE_INLINE bool operator==(SceneObject* other) const + { + return _object == other; + } + + FORCE_INLINE bool operator!=(SceneObject* other) const + { + return _object != other; + } + + FORCE_INLINE bool operator==(T* other) const + { + return Get() == other; + } + + FORCE_INLINE bool operator!=(T* other) const + { + return Get() != other; + } + + FORCE_INLINE bool operator==(const ScriptingObjectInterfaceReference& other) const + { + return _object == other._object; + } + + FORCE_INLINE bool operator!=(const ScriptingObjectInterfaceReference& other) const + { + return _object != other._object; + } + + FORCE_INLINE ScriptingObjectInterfaceReference& operator=(SceneObject* other) + { + OnSet(Helper::IsValidObject(other) ? other : nullptr); + return *this; + } + + FORCE_INLINE ScriptingObjectInterfaceReference& operator=(T* other) + { + OnSet(Helper::GetSceneObject(other)); + return *this; + } + + ScriptingObjectInterfaceReference& operator=(const ScriptingObjectInterfaceReference& other) + { + OnSet(other._object); + return *this; + } + + ScriptingObjectInterfaceReference& operator=(ScriptingObjectInterfaceReference&& other) noexcept + { + ScriptingObjectReferenceBase::operator=(MoveTemp(other)); + return *this; + } + + FORCE_INLINE ScriptingObjectInterfaceReference& operator=(const Guid& id) + { + OnSet(Helper::FindSceneObject(id)); + return *this; + } + + /// + /// Implicit conversion to the interface. + /// + FORCE_INLINE operator T*() const + { + return Get(); + } + + /// + /// Implicit conversion to boolean value. + /// + FORCE_INLINE operator bool() const + { + return _object != nullptr; + } + + /// + /// Interface accessor. + /// + FORCE_INLINE T* operator->() const + { + return Get(); + } + + /// + /// Gets the interface pointer. + /// + FORCE_INLINE T* Get() const + { + return ScriptingObject::ToInterface(_object); + } + + /// + /// Gets the referenced object. + /// + FORCE_INLINE SceneObject* GetObject() const + { + return static_cast(_object); + } + + /// + /// Copies the object ID into the raw storage. + /// + FORCE_INLINE void CopyID(uint32 id[4]) const + { + memset(id, 0, sizeof(uint32) * 4); + if (_object) + { + const Guid value = GetID(); + memcpy(id, &value, sizeof(uint32) * 4); + } + } + + /// + /// Gets the object as a given type (static cast). + /// + template + FORCE_INLINE U* As() const + { + return static_cast(_object); + } + +}; + +template +uint32 GetHash(const ScriptingObjectInterfaceReference& key) +{ + return GetHash(key.GetID()); +} diff --git a/Source/Engine/Scripting/ScriptingObjectInterfaceReferenceUtils.h b/Source/Engine/Scripting/ScriptingObjectInterfaceReferenceUtils.h new file mode 100644 index 000000000..d18df3d80 --- /dev/null +++ b/Source/Engine/Scripting/ScriptingObjectInterfaceReferenceUtils.h @@ -0,0 +1,30 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/ScriptingObjectReference.h" +#include "Engine/Level/SceneObject.h" + +/// +/// Utility methods for scene object interface references. +/// +/// The type of the scripting interface. +template +struct ScriptingObjectInterfaceReferenceHelper +{ + FORCE_INLINE static bool IsValidObject(const SceneObject* obj) + { + return !obj || obj->GetType().GetInterface(T::TypeInitializer) != nullptr; + } + + FORCE_INLINE static SceneObject* GetSceneObject(T* interfaceObj) + { + return ScriptingObject::Cast(ScriptingObject::FromInterface(interfaceObj)); + } + + FORCE_INLINE static SceneObject* FindSceneObject(const Guid& id) + { + SceneObject* obj = static_cast(FindObject(id, SceneObject::GetStaticClass())); + return IsValidObject(obj) ? obj : nullptr; + } +}; diff --git a/Source/Engine/Scripting/SoftObjectInterfaceReference.h b/Source/Engine/Scripting/SoftObjectInterfaceReference.h new file mode 100644 index 000000000..0814f4927 --- /dev/null +++ b/Source/Engine/Scripting/SoftObjectInterfaceReference.h @@ -0,0 +1,258 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/SoftObjectReference.h" +#include "Engine/Scripting/ScriptingObjectInterfaceReferenceUtils.h" + +/// +/// The scene object soft interface reference. Objects gets referenced on use (ID reference is resolving it). +/// +/// The type of the scripting interface. +template +API_CLASS(InBuild) class SoftObjectInterfaceReference : public SoftObjectReferenceBase +{ + typedef ScriptingObjectInterfaceReferenceHelper Helper; + +public: + typedef SoftObjectInterfaceReference Type; + +public: + /// + /// Initializes a new instance of the class. + /// + SoftObjectInterfaceReference() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The object to link. + SoftObjectInterfaceReference(SceneObject* obj) + { + OnSet(Helper::IsValidObject(obj) ? obj : nullptr); + } + + /// + /// Initializes a new instance of the class. + /// + /// The interface object to link. + SoftObjectInterfaceReference(T* interfaceObj) + { + OnSet(Helper::GetSceneObject(interfaceObj)); + } + + /// + /// Initializes a new instance of the class. + /// + /// The other property. + SoftObjectInterfaceReference(const SoftObjectInterfaceReference& other) + { + OnSet(other.GetID()); + } + + /// + /// Initializes a new instance of the class. + /// + /// The other property. + SoftObjectInterfaceReference(SoftObjectInterfaceReference&& other) + { + OnSet(other.GetID()); + other.OnSet(nullptr); + } + + /// + /// Finalizes an instance of the class. + /// + ~SoftObjectInterfaceReference() + { + } + +public: + FORCE_INLINE bool operator==(SceneObject* other) + { + return GetObject() == other; + } + + FORCE_INLINE bool operator!=(SceneObject* other) + { + return GetObject() != other; + } + + FORCE_INLINE bool operator==(T* other) + { + return Get() == other; + } + + FORCE_INLINE bool operator!=(T* other) + { + return Get() != other; + } + + FORCE_INLINE bool operator==(const SoftObjectInterfaceReference& other) + { + return GetID() == other.GetID(); + } + + FORCE_INLINE bool operator!=(const SoftObjectInterfaceReference& other) + { + return GetID() != other.GetID(); + } + + SoftObjectInterfaceReference& operator=(const SoftObjectInterfaceReference& other) + { + if (this != &other) + OnSet(other.GetID()); + return *this; + } + + SoftObjectInterfaceReference& operator=(SoftObjectInterfaceReference&& other) + { + if (this != &other) + { + OnSet(other.GetID()); + other.OnSet(nullptr); + } + return *this; + } + + FORCE_INLINE SoftObjectInterfaceReference& operator=(SceneObject* other) + { + OnSet(Helper::IsValidObject(other) ? other : nullptr); + return *this; + } + + FORCE_INLINE SoftObjectInterfaceReference& operator=(T* other) + { + OnSet(Helper::GetSceneObject(other)); + return *this; + } + + FORCE_INLINE SoftObjectInterfaceReference& operator=(const Guid& id) + { + OnSet(id); + return *this; + } + + /// + /// Implicit conversion to the interface. + /// + FORCE_INLINE operator T*() const + { + return Get(); + } + + /// + /// Implicit conversion to boolean value. + /// + FORCE_INLINE operator bool() const + { + return Get() != nullptr; + } + + /// + /// Interface accessor. + /// + FORCE_INLINE T* operator->() const + { + return Get(); + } + + /// + /// Gets the object as a given type (static cast). + /// + template + FORCE_INLINE U* As() const + { + return static_cast(GetObject()); + } + +public: + /// + /// Gets the interface pointer. + /// + FORCE_INLINE T* Get() const + { + return ScriptingObject::ToInterface(GetObject()); + } + + /// + /// Gets the referenced object. + /// + SceneObject* GetObject() const + { + if (!_object) + const_cast(this)->OnResolve(SceneObject::GetStaticClass()); + return Helper::IsValidObject(static_cast(_object)) ? static_cast(_object) : nullptr; + } + + /// + /// Gets managed instance object (or null if no object linked). + /// + MObject* GetManagedInstance() const + { + auto object = GetObject(); + return object ? object->GetOrCreateManagedInstance() : nullptr; + } + + /// + /// Determines whether object is assigned and managed instance of the object is alive. + /// + bool HasManagedInstance() const + { + auto object = GetObject(); + return object && object->HasManagedInstance(); + } + + /// + /// Gets the managed instance object or creates it if missing or null if not assigned. + /// + MObject* GetOrCreateManagedInstance() const + { + auto object = GetObject(); + return object ? object->GetOrCreateManagedInstance() : nullptr; + } + + /// + /// Copies the object ID into the raw storage. + /// + FORCE_INLINE void CopyID(uint32 id[4]) const + { + const Guid value = GetID(); + memcpy(id, &value, sizeof(uint32) * 4); + } + + /// + /// Sets the object. + /// + /// The object ID. Uses Scripting to find the registered object of the given ID. + FORCE_INLINE void Set(const Guid& id) + { + OnSet(id); + } + + /// + /// Sets the object. + /// + /// The object. + FORCE_INLINE void Set(SceneObject* object) + { + OnSet(Helper::IsValidObject(object) ? object : nullptr); + } + + /// + /// Sets the object. + /// + /// The interface object. + FORCE_INLINE void Set(T* interfaceObj) + { + OnSet(Helper::GetSceneObject(interfaceObj)); + } +}; + +template +uint32 GetHash(const SoftObjectInterfaceReference& key) +{ + return GetHash(key.GetID()); +} diff --git a/Source/Engine/Serialization/ReadStream.h b/Source/Engine/Serialization/ReadStream.h index cc2b7e73b..d8868c67e 100644 --- a/Source/Engine/Serialization/ReadStream.h +++ b/Source/Engine/Serialization/ReadStream.h @@ -133,6 +133,14 @@ public: v = ptr; } + template + FORCE_INLINE void Read(ScriptingObjectInterfaceReference& v) + { + uint32 id[4]; + ReadBytes(id, sizeof(id)); + v = *(Guid*)id; + } + template FORCE_INLINE void Read(SoftObjectReference& v) { @@ -141,6 +149,14 @@ public: v.Set(*(Guid*)id); } + template + FORCE_INLINE void Read(SoftObjectInterfaceReference& v) + { + uint32 id[4]; + ReadBytes(id, sizeof(id)); + v.Set(*(Guid*)id); + } + template FORCE_INLINE void Read(AssetReference& v) { diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index 9af6d7be1..4031ba2ba 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -14,8 +14,12 @@ struct VariantType; template class ScriptingObjectReference; template +class ScriptingObjectInterfaceReference; +template class SoftObjectReference; template +class SoftObjectInterfaceReference; +template class AssetReference; template class WeakAssetReference; @@ -454,7 +458,6 @@ namespace Serialization } FLAXENGINE_API bool ShouldSerializeRef(const SceneObject* v, const SceneObject* other); - template inline typename TEnableIf, TNot>>::Value, bool>::Type ShouldSerialize(const T* v, const void* otherObj) { @@ -470,7 +473,7 @@ namespace Serialization { Guid id; Deserialize(stream, id, modifier); - modifier->IdsMapping.TryGet(id, id); + modifier->IdsMapping.TryGet(id, id); v = (T*)::FindObject(id, T::GetStaticClass()); } @@ -497,7 +500,28 @@ namespace Serialization { Guid id; Deserialize(stream, id, modifier); - modifier->IdsMapping.TryGet(id, id); + modifier->IdsMapping.TryGet(id, id); + v = id; + } + + // Scripting Interface Reference + + template + inline bool ShouldSerialize(const ScriptingObjectInterfaceReference& v, const void* otherObj) + { + return !otherObj || ShouldSerializeRef(v.GetObject(), ((ScriptingObjectInterfaceReference*)otherObj)->GetObject()); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const ScriptingObjectInterfaceReference& v, const void* otherObj) + { + stream.Guid(v.GetID()); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, ScriptingObjectInterfaceReference& v, ISerializeModifier* modifier) + { + Guid id; + Deserialize(stream, id, modifier); + modifier->IdsMapping.TryGet(id, id); v = id; } @@ -518,7 +542,28 @@ namespace Serialization { Guid id; Deserialize(stream, id, modifier); - modifier->IdsMapping.TryGet(id, id); + modifier->IdsMapping.TryGet(id, id); + v = id; + } + + // Soft Object Interface Reference + + template + inline bool ShouldSerialize(const SoftObjectInterfaceReference& v, const void* otherObj) + { + return !otherObj || ShouldSerializeRef(v.GetObject(), ((SoftObjectInterfaceReference*)otherObj)->GetObject()); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const SoftObjectInterfaceReference& v, const void* otherObj) + { + stream.Guid(v.GetID()); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, SoftObjectInterfaceReference& v, ISerializeModifier* modifier) + { + Guid id; + Deserialize(stream, id, modifier); + modifier->IdsMapping.TryGet(id, id); v = id; } diff --git a/Source/Engine/Serialization/Stream.h b/Source/Engine/Serialization/Stream.h index 7e82467f5..e6dd3a42b 100644 --- a/Source/Engine/Serialization/Stream.h +++ b/Source/Engine/Serialization/Stream.h @@ -17,8 +17,12 @@ class ScriptingObject; template class ScriptingObjectReference; template +class ScriptingObjectInterfaceReference; +template class SoftObjectReference; template +class SoftObjectInterfaceReference; +template class AssetReference; template class WeakAssetReference; diff --git a/Source/Engine/Serialization/WriteStream.h b/Source/Engine/Serialization/WriteStream.h index f027deabb..6c610b66f 100644 --- a/Source/Engine/Serialization/WriteStream.h +++ b/Source/Engine/Serialization/WriteStream.h @@ -156,11 +156,29 @@ public: { Write(v.Get()); } + + template + FORCE_INLINE void Write(const ScriptingObjectInterfaceReference& v) + { + uint32 id[4]; + v.CopyID(id); + WriteBytes(id, sizeof(id)); + } + template FORCE_INLINE void Write(const SoftObjectReference& v) { Write(v.Get()); } + + template + FORCE_INLINE void Write(const SoftObjectInterfaceReference& v) + { + uint32 id[4]; + v.CopyID(id); + WriteBytes(id, sizeof(id)); + } + template FORCE_INLINE void Write(const AssetReference& v) { diff --git a/Source/Engine/Tests/TestScripting.h b/Source/Engine/Tests/TestScripting.h index ca13ac307..0a764c89f 100644 --- a/Source/Engine/Tests/TestScripting.h +++ b/Source/Engine/Tests/TestScripting.h @@ -6,6 +6,8 @@ #include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Scripting/ScriptingObject.h" +#include "Engine/Scripting/ScriptingObjectInterfaceReference.h" +#include "Engine/Scripting/SoftObjectInterfaceReference.h" #include "Engine/Scripting/SerializableScriptingObject.h" #include "Engine/Scripting/SoftTypeReference.h" #include "Engine/Content/SceneReference.h" @@ -177,6 +179,10 @@ public: // Test struct API_FIELD() TestStruct SimpleStruct; + // Test interface reference + API_FIELD() ScriptingObjectInterfaceReference InterfaceRef; + // Test soft interface reference + API_FIELD() SoftObjectInterfaceReference SoftInterfaceRef; // Test event API_EVENT() Delegate&, Array&> SimpleEvent; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 174c6de2c..433dbdda6 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -108,7 +108,7 @@ namespace Flax.Build.Bindings if (attribute && valueType != null && !valueType.IsArray) { //if (valueType.Type == "") - //ScriptingObjectReference + //ScriptingObjectReference, ScriptingObjectInterfaceReference, SoftObjectInterfaceReference apiType = FindApiTypeInfo(buildData, valueType, caller); // Object reference @@ -350,6 +350,10 @@ namespace Flax.Build.Bindings if (CSharpNativeToManagedDefault.TryGetValue(typeInfo.Type, out result)) return result; + // Interface reference property + if (typeInfo.IsInterfaceRef) + return marshalling ? "IntPtr" : GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling); + // Object reference property if (typeInfo.IsObjectRef) return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling); @@ -556,6 +560,10 @@ namespace Flax.Build.Bindings } return string.Empty; default: + // Interface reference property + if (typeInfo.IsInterfaceRef) + return string.Format("FlaxEngine.Object.GetUnmanagedInterface({{0}}, typeof({0}))", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller)); + var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) { @@ -778,9 +786,17 @@ namespace Flax.Build.Bindings } } #endif + const string interfaceResultName = "__interfaceResult"; + + var returnInterfaceRef = !functionInfo.Glue.UseReferenceForResult && functionInfo.ReturnType.IsInterfaceRef; + if (functionInfo.Glue.UseReferenceForResult) { } + else if (returnInterfaceRef) + { + contents.Append("var ").Append(interfaceResultName).Append(" = "); + } else if (!functionInfo.ReturnType.IsVoid) { contents.Append("return "); @@ -851,6 +867,11 @@ namespace Flax.Build.Bindings } contents.Append(')'); + if (returnInterfaceRef) + { + var managedType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType.GenericArgs[0], caller); + contents.Append("; return ").Append(interfaceResultName).Append(" != IntPtr.Zero ? Unsafe.As<").Append(managedType).Append(">(ManagedHandle.FromIntPtr(").Append(interfaceResultName).Append(").Target) : null"); + } if ((functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer") && functionInfo.ReturnType.GenericArgs != null) { // Convert array that uses different type for marshalling @@ -987,6 +1008,12 @@ namespace Flax.Build.Bindings private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, MemberInfo memberInfo, bool useUnmanaged, string defaultValue = null, TypeInfo defaultValueType = null) { GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo, memberInfo.Attributes, memberInfo.Comment, true, useUnmanaged, defaultValue, memberInfo.DeprecatedMessage, defaultValueType); + var memberType = (memberInfo as FieldInfo)?.Type ?? (memberInfo as PropertyInfo)?.Type; + if (memberType != null && memberType.IsInterfaceRef) + { + var attribute = memberType.Type == "SoftObjectInterfaceReference" ? "SoftObjectInterfaceReference" : "ScriptingObjectInterfaceReference"; + contents.Append(indent).Append("[FlaxEngine.").Append(attribute).AppendLine("]"); + } } private static bool GenerateCSharpStructureUseDefaultInitialize(BuildData buildData, StructureInfo structureInfo) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 6dd2aba6f..e67bde3af 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -167,6 +167,8 @@ namespace Flax.Build.Bindings return $"Variant(StringView({value}))"; if (typeInfo.Type == "StringAnsi") return $"Variant(StringAnsiView({value}))"; + if (typeInfo.IsInterfaceRef) + return $"Variant({value}.GetObject())"; if (typeInfo.IsObjectRef) return $"Variant({value}.Get())"; if (typeInfo.Type == "SoftTypeReference") @@ -307,6 +309,8 @@ namespace Flax.Build.Bindings return $"((StringView){value}).GetText()"; // (StringView)Variant, if not empty, is guaranteed to point to a null-terminated buffer. if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; + if (typeInfo.IsInterfaceRef) + return $"ScriptingObject::ToInterface<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; if (typeInfo.IsObjectRef) return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; if (typeInfo.Type == "SoftTypeReference") @@ -797,6 +801,19 @@ namespace Flax.Build.Bindings type = "MObject*"; return "MUtils::ToNative({0})"; default: + // Interface reference property + if (typeInfo.IsInterfaceRef) + { + if (CppNonPodTypesConvertingGeneration) + { + type = "MObject*"; + return "ScriptingObject::ToInterface<" + typeInfo.GenericArgs[0].Type + ">(ScriptingObject::ToNative({0}))"; + } + + type = typeInfo.GenericArgs[0].Type + '*'; + return string.Empty; + } + // Object reference property if (typeInfo.IsObjectRef) { diff --git a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs index f2d82cc6e..46097a218 100644 --- a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs @@ -18,6 +18,8 @@ namespace Flax.Build.Bindings "ManagedScriptingObject", "PersistentScriptingObject", "ScriptingObjectReference", + "ScriptingObjectInterfaceReference", + "SoftObjectInterfaceReference", "AssetReference", "BinaryAsset", "SceneObject", diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs index 04836c636..79ade2bfa 100644 --- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs @@ -38,10 +38,18 @@ namespace Flax.Build.Bindings /// Gets a value indicating whether this type is a reference to another object. /// public bool IsObjectRef => (Type == "ScriptingObjectReference" || + Type == "ScriptingObjectInterfaceReference" || Type == "AssetReference" || Type == "WeakAssetReference" || Type == "SoftAssetReference" || - Type == "SoftObjectReference") && GenericArgs != null; + Type == "SoftObjectReference" || + Type == "SoftObjectInterfaceReference") && GenericArgs != null; + + /// + /// Gets a value indicating whether this type is a reference to another object filtered by interface. + /// + public bool IsInterfaceRef => (Type == "ScriptingObjectInterfaceReference" || + Type == "SoftObjectInterfaceReference") && GenericArgs != null; public TypeInfo() { From 54a103d840992beca873288e6d12b4d00d049936 Mon Sep 17 00:00:00 2001 From: Andrei Gagua Date: Sun, 24 May 2026 13:27:46 +0300 Subject: [PATCH 2/9] Upd: Array & Dictionary support for the ObjectInterfaceReferences --- .../CustomEditors/Editors/DictionaryEditor.cs | 2 +- .../Values/DictionaryValueContainer.cs | 13 +- .../Scripting/CodeEditors/RiderCodeEditor.cpp | 33 +++- .../Scripting/Internal/ManagedDictionary.cpp | 156 ++++++++++++++++++ .../Scripting/Internal/ManagedDictionary.h | 156 +----------------- .../Bindings/BindingsGenerator.CSharp.cs | 72 +++++++- .../Bindings/BindingsGenerator.Cpp.cs | 8 +- 7 files changed, 272 insertions(+), 168 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs index 1fe849f09..7dfce6055 100644 --- a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs +++ b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs @@ -260,7 +260,7 @@ namespace FlaxEditor.CustomEditors.Editors var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; var property = panel.AddPropertyItem(new DictionaryItemLabel(this, key)); var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel(); - itemLayout.Object(new DictionaryValueContainer(valuesType, key, Values), overrideEditor); + itemLayout.Object(new DictionaryValueContainer(valuesType, key, Values, attributes), overrideEditor); if (_readOnly && itemLayout.Children.Count > 0) GenericEditor.OnReadOnlyProperty(itemLayout); } diff --git a/Source/Editor/CustomEditors/Values/DictionaryValueContainer.cs b/Source/Editor/CustomEditors/Values/DictionaryValueContainer.cs index b143b44b1..435e5a515 100644 --- a/Source/Editor/CustomEditors/Values/DictionaryValueContainer.cs +++ b/Source/Editor/CustomEditors/Values/DictionaryValueContainer.cs @@ -14,6 +14,8 @@ namespace FlaxEditor.CustomEditors [HideInEditor] public class DictionaryValueContainer : ValueContainer { + private readonly object[] _attributes; + /// /// The key in the collection. /// @@ -36,9 +38,12 @@ namespace FlaxEditor.CustomEditors /// Type of the collection elements. /// The key. /// The collection values. - public DictionaryValueContainer(ScriptType elementType, object key, ValueContainer values) + /// The dictionary property attributes to inherit. + public DictionaryValueContainer(ScriptType elementType, object key, ValueContainer values, object[] attributes = null) : this(elementType, key) { + _attributes = attributes; + Capacity = values.Count; for (int i = 0; i < values.Count; i++) { @@ -123,5 +128,11 @@ namespace FlaxEditor.CustomEditors _hasReferenceValue = true; } } + + /// + public override object[] GetAttributes() + { + return _attributes ?? base.GetAttributes(); + } } } diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp index b63815dce..de6001efd 100644 --- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp @@ -14,6 +14,9 @@ #if PLATFORM_WINDOWS #include "Engine/Platform/Win32/IncludeWindowsHeaders.h" +#elif PLATFORM_MAC +#include "Engine/Platform/Apple/AppleUtils.h" +#include #endif namespace @@ -68,10 +71,14 @@ namespace if (!launcherPath.HasChars() || !FileSystem::FileExists(exePath)) return; - if (launchOverridePath != String::Empty) - installations->Add(New(launchOverridePath, versionMember->value.GetText())); - else - installations->Add(New(exePath, versionMember->value.GetText())); + String installPath = launchOverridePath != String::Empty ? launchOverridePath : exePath; + StringUtils::PathRemoveRelativeParts(installPath); + for (RiderInstallation* installation : *installations) + { + if (installation->path == installPath) + return; + } + installations->Add(New(installPath, versionMember->value.GetText())); } #if PLATFORM_WINDOWS @@ -221,17 +228,29 @@ void RiderCodeEditor::FindEditors(Array* output) String applicationSupportFolder; FileSystem::GetSpecialFolderPath(SpecialFolder::ProgramData, applicationSupportFolder); + NSURL* appURL = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:@"com.jetbrains.rider"]; + if (appURL != nullptr) + { + const String appPath = AppleUtils::ToString((CFStringRef)[appURL path]); + SearchDirectory(&installations, appPath / TEXT("Contents/Resources"), appPath); + } + Array subMacDirectories; FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/Rider/ch-0/")); FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/Rider/ch-1/")); for (const String& directory : subMacDirectories) { - String riderAppDirectory = directory / TEXT("Rider.app/Contents/Resources"); - SearchDirectory(&installations, riderAppDirectory); + String riderAppPath = directory / TEXT("Rider.app"); + SearchDirectory(&installations, riderAppPath / TEXT("Contents/Resources"), riderAppPath); } // Check the local installer version - SearchDirectory(&installations, TEXT("/Applications/Rider.app/Contents/Resources")); + SearchDirectory(&installations, TEXT("/Applications/Rider.app/Contents/Resources"), TEXT("/Applications/Rider.app")); + + String userFolder; + FileSystem::GetSpecialFolderPath(SpecialFolder::Documents, userFolder); + String riderAppPath = userFolder / TEXT("../Applications/Rider.app"); + SearchDirectory(&installations, riderAppPath / TEXT("Contents/Resources"), riderAppPath); #endif for (const String& directory : subDirectories) diff --git a/Source/Engine/Scripting/Internal/ManagedDictionary.cpp b/Source/Engine/Scripting/Internal/ManagedDictionary.cpp index d2f74e054..3cd4bfd8b 100644 --- a/Source/Engine/Scripting/Internal/ManagedDictionary.cpp +++ b/Source/Engine/Scripting/Internal/ManagedDictionary.cpp @@ -15,4 +15,160 @@ MMethod* ManagedDictionary::CreateInstance; MMethod* ManagedDictionary::AddDictionaryItem; MMethod* ManagedDictionary::GetDictionaryKeys; #endif + +ManagedDictionary::ManagedDictionary(MObject* instance) +{ + Instance = instance; + +#if !USE_MONO_AOT + // Cache the thunks of the dictionary helper methods + if (MakeGenericType == nullptr) + { + MClass* scriptingClass = Scripting::GetStaticClass(); + CHECK(scriptingClass); + + MMethod* makeGenericTypeMethod = scriptingClass->GetMethod("MakeGenericType", 2); + CHECK(makeGenericTypeMethod); + MakeGenericType = (MakeGenericTypeThunk)makeGenericTypeMethod->GetThunk(); + + MMethod* createInstanceMethod = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2); + CHECK(createInstanceMethod); + CreateInstance = (CreateInstanceThunk)createInstanceMethod->GetThunk(); + + MMethod* addDictionaryItemMethod = scriptingClass->GetMethod("AddDictionaryItem", 3); + CHECK(addDictionaryItemMethod); + AddDictionaryItem = (AddDictionaryItemThunk)addDictionaryItemMethod->GetThunk(); + + MMethod* getDictionaryKeysItemMethod = scriptingClass->GetMethod("GetDictionaryKeys", 1); + CHECK(getDictionaryKeysItemMethod); + GetDictionaryKeys = (GetDictionaryKeysThunk)getDictionaryKeysItemMethod->GetThunk(); + } +#else + if (MakeGenericType == nullptr) + { + MClass* scriptingClass = Scripting::GetStaticClass(); + CHECK(scriptingClass); + + MakeGenericType = scriptingClass->GetMethod("MakeGenericType", 2); + CHECK(MakeGenericType); + + CreateInstance = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2); + CHECK(CreateInstance); + + AddDictionaryItem = scriptingClass->GetMethod("AddDictionaryItem", 3); + CHECK(AddDictionaryItem); + + GetDictionaryKeys = scriptingClass->GetMethod("GetDictionaryKeys", 1); + CHECK(GetDictionaryKeys); + } +#endif +} + +MTypeObject* ManagedDictionary::GetClass(MType* keyType, MType* valueType) +{ + // Check if the generic type was generated earlier + KeyValueType cacheKey = { keyType, valueType }; + MTypeObject* dictionaryType; + if (CachedTypes.TryGet(cacheKey, dictionaryType)) + return dictionaryType; + + MTypeObject* genericType = MUtils::GetType(StdTypesContainer::Instance()->DictionaryClass); +#if USE_NETCORE + MArray* genericArgs = MCore::Array::New(MCore::TypeCache::IntPtr, 2); +#else + MArray* genericArgs = MCore::Array::New(MCore::TypeCache::Object, 2); +#endif + MTypeObject** genericArgsPtr = MCore::Array::GetAddress(genericArgs); + genericArgsPtr[0] = INTERNAL_TYPE_GET_OBJECT(keyType); + genericArgsPtr[1] = INTERNAL_TYPE_GET_OBJECT(valueType); + + MObject* exception = nullptr; +#if !USE_MONO_AOT + dictionaryType = MakeGenericType(nullptr, genericType, genericArgs, &exception); +#else + void* params[2]; + params[0] = genericType; + params[1] = genericArgs; + dictionaryType = (MTypeObject*)MakeGenericType->Invoke(nullptr, params, &exception); +#endif + if (exception) + { + MException ex(exception); + ex.Log(LogType::Error, TEXT("")); + return nullptr; + } + CachedTypes.Add(cacheKey, dictionaryType); + return dictionaryType; +} + +ManagedDictionary ManagedDictionary::New(MType* keyType, MType* valueType) +{ + ManagedDictionary result; + MTypeObject* dictionaryType = GetClass(keyType, valueType); + if (!dictionaryType) + return result; + + MObject* exception = nullptr; +#if !USE_MONO_AOT + MObject* instance = CreateInstance(nullptr, dictionaryType, nullptr, &exception); +#else + void* params[2]; + params[0] = dictionaryType; + params[1] = nullptr; + MObject* instance = CreateInstance->Invoke(nullptr, params, &exception); +#endif + if (exception) + { + MException ex(exception); + ex.Log(LogType::Error, TEXT("")); + return result; + } + + result.Instance = instance; + return result; +} + +void ManagedDictionary::Add(MObject* key, MObject* value) +{ + CHECK(Instance); + + MObject* exception = nullptr; +#if !USE_MONO_AOT + AddDictionaryItem(nullptr, Instance, key, value, &exception); +#else + void* params[3]; + params[0] = Instance; + params[1] = key; + params[2] = value; + AddDictionaryItem->Invoke(Instance, params, &exception); +#endif + if (exception) + { + MException ex(exception); + ex.Log(LogType::Error, TEXT("")); + } +} + +MArray* ManagedDictionary::GetKeys() const +{ + CHECK_RETURN(Instance, nullptr); +#if !USE_MONO_AOT + return GetDictionaryKeys(nullptr, Instance, nullptr); +#else + void* params[1]; + params[0] = Instance; + return (MArray*)GetDictionaryKeys->Invoke(nullptr, params, nullptr); +#endif +} + +MObject* ManagedDictionary::GetValue(MObject* key) const +{ + CHECK_RETURN(Instance, nullptr); + MClass* klass = MCore::Object::GetClass(Instance); + MMethod* getItemMethod = klass->GetMethod("System.Collections.IDictionary.get_Item", 1); + CHECK_RETURN(getItemMethod, nullptr); + void* params[1]; + params[0] = key; + return getItemMethod->Invoke(Instance, params, nullptr); +} #endif diff --git a/Source/Engine/Scripting/Internal/ManagedDictionary.h b/Source/Engine/Scripting/Internal/ManagedDictionary.h index 5e2638af7..f4663ac58 100644 --- a/Source/Engine/Scripting/Internal/ManagedDictionary.h +++ b/Source/Engine/Scripting/Internal/ManagedDictionary.h @@ -57,53 +57,7 @@ private: public: MObject* Instance; - ManagedDictionary(MObject* instance = nullptr) - { - Instance = instance; - -#if !USE_MONO_AOT - // Cache the thunks of the dictionary helper methods - if (MakeGenericType == nullptr) - { - MClass* scriptingClass = Scripting::GetStaticClass(); - CHECK(scriptingClass); - - MMethod* makeGenericTypeMethod = scriptingClass->GetMethod("MakeGenericType", 2); - CHECK(makeGenericTypeMethod); - MakeGenericType = (MakeGenericTypeThunk)makeGenericTypeMethod->GetThunk(); - - MMethod* createInstanceMethod = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2); - CHECK(createInstanceMethod); - CreateInstance = (CreateInstanceThunk)createInstanceMethod->GetThunk(); - - MMethod* addDictionaryItemMethod = scriptingClass->GetMethod("AddDictionaryItem", 3); - CHECK(addDictionaryItemMethod); - AddDictionaryItem = (AddDictionaryItemThunk)addDictionaryItemMethod->GetThunk(); - - MMethod* getDictionaryKeysItemMethod = scriptingClass->GetMethod("GetDictionaryKeys", 1); - CHECK(getDictionaryKeysItemMethod); - GetDictionaryKeys = (GetDictionaryKeysThunk)getDictionaryKeysItemMethod->GetThunk(); - } -#else - if (MakeGenericType == nullptr) - { - MClass* scriptingClass = Scripting::GetStaticClass(); - CHECK(scriptingClass); - - MakeGenericType = scriptingClass->GetMethod("MakeGenericType", 2); - CHECK(MakeGenericType); - - CreateInstance = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2); - CHECK(CreateInstance); - - AddDictionaryItem = scriptingClass->GetMethod("AddDictionaryItem", 3); - CHECK(AddDictionaryItem); - - GetDictionaryKeys = scriptingClass->GetMethod("GetDictionaryKeys", 1); - CHECK(GetDictionaryKeys); - } -#endif - } + ManagedDictionary(MObject* instance = nullptr); template static MObject* ToManaged(const Dictionary& data, MType* keyType, MType* valueType) @@ -154,113 +108,15 @@ public: return result; } - static MTypeObject* GetClass(MType* keyType, MType* valueType) - { - // Check if the generic type was generated earlier - KeyValueType cacheKey = { keyType, valueType }; - MTypeObject* dictionaryType; - if (CachedTypes.TryGet(cacheKey, dictionaryType)) - return dictionaryType; + static MTypeObject* GetClass(MType* keyType, MType* valueType); - MTypeObject* genericType = MUtils::GetType(StdTypesContainer::Instance()->DictionaryClass); -#if USE_NETCORE - MArray* genericArgs = MCore::Array::New(MCore::TypeCache::IntPtr, 2); -#else - MArray* genericArgs = MCore::Array::New(MCore::TypeCache::Object, 2); -#endif - MTypeObject** genericArgsPtr = MCore::Array::GetAddress(genericArgs); - genericArgsPtr[0] = INTERNAL_TYPE_GET_OBJECT(keyType); - genericArgsPtr[1] = INTERNAL_TYPE_GET_OBJECT(valueType); + static ManagedDictionary New(MType* keyType, MType* valueType); - MObject* exception = nullptr; -#if !USE_MONO_AOT - dictionaryType = MakeGenericType(nullptr, genericType, genericArgs, &exception); -#else - void* params[2]; - params[0] = genericType; - params[1] = genericArgs; - dictionaryType = (MTypeObject*)MakeGenericType->Invoke(nullptr, params, &exception); -#endif - if (exception) - { - MException ex(exception); - ex.Log(LogType::Error, TEXT("")); - return nullptr; - } - CachedTypes.Add(cacheKey, dictionaryType); - return dictionaryType; - } + void Add(MObject* key, MObject* value); - static ManagedDictionary New(MType* keyType, MType* valueType) - { - ManagedDictionary result; - MTypeObject* dictionaryType = GetClass(keyType, valueType); - if (!dictionaryType) - return result; + MArray* GetKeys() const; - MObject* exception = nullptr; -#if !USE_MONO_AOT - MObject* instance = CreateInstance(nullptr, dictionaryType, nullptr, &exception); -#else - void* params[2]; - params[0] = dictionaryType; - params[1] = nullptr; - MObject* instance = CreateInstance->Invoke(nullptr, params, &exception); -#endif - if (exception) - { - MException ex(exception); - ex.Log(LogType::Error, TEXT("")); - return result; - } - - result.Instance = instance; - return result; - } - - void Add(MObject* key, MObject* value) - { - CHECK(Instance); - - MObject* exception = nullptr; -#if !USE_MONO_AOT - AddDictionaryItem(nullptr, Instance, key, value, &exception); -#else - void* params[3]; - params[0] = Instance; - params[1] = key; - params[2] = value; - AddDictionaryItem->Invoke(Instance, params, &exception); -#endif - if (exception) - { - MException ex(exception); - ex.Log(LogType::Error, TEXT("")); - } - } - - MArray* GetKeys() const - { - CHECK_RETURN(Instance, nullptr); -#if !USE_MONO_AOT - return GetDictionaryKeys(nullptr, Instance, nullptr); -#else - void* params[1]; - params[0] = Instance; - return (MArray*)GetDictionaryKeys->Invoke(nullptr, params, nullptr); -#endif - } - - MObject* GetValue(MObject* key) const - { - CHECK_RETURN(Instance, nullptr); - MClass* klass = MCore::Object::GetClass(Instance); - MMethod* getItemMethod = klass->GetMethod("System.Collections.IDictionary.get_Item", 1); - CHECK_RETURN(getItemMethod, nullptr); - void* params[1]; - params[0] = key; - return getItemMethod->Invoke(Instance, params, nullptr); - } + MObject* GetValue(MObject* key) const; }; inline uint32 GetHash(const ManagedDictionary::KeyValueType& other) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 433dbdda6..604d501ab 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -375,12 +375,16 @@ namespace Flax.Build.Bindings if (arrayApiType != null && arrayApiType.MarshalAs != null) arrayTypeInfo = arrayApiType.MarshalAs; } - return GenerateCSharpNativeToManaged(buildData, arrayTypeInfo, caller) + "[]"; + return GenerateCSharpNativeToManaged(buildData, arrayTypeInfo, caller, marshalling) + "[]"; } // Dictionary if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) - return string.Format("System.Collections.Generic.Dictionary<{0}, {1}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling), GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[1], caller, marshalling)); + { + var keyType = marshalling && typeInfo.GenericArgs[0].IsInterfaceRef ? "object" : GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling); + var valueType = marshalling && typeInfo.GenericArgs[1].IsInterfaceRef ? "object" : GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[1], caller, marshalling); + return string.Format("System.Collections.Generic.Dictionary<{0}, {1}>", keyType, valueType); + } // HashSet if (typeInfo.Type == "HashSet" && typeInfo.GenericArgs != null) @@ -554,11 +558,21 @@ namespace Flax.Build.Bindings { // Convert array that uses different type for marshalling var arrayTypeInfo = typeInfo.GenericArgs[0]; + if (arrayTypeInfo.IsInterfaceRef) + return "{0} != null ? FlaxEngine.Interop.NativeInterop.ManagedArrayToGCHandleArray({0}) : null"; var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); if (arrayApiType != null && arrayApiType.MarshalAs != null) return $"{{0}}.ConvertArray(x => ({GenerateCSharpNativeToManaged(buildData, arrayApiType.MarshalAs, caller)})x)"; } return string.Empty; + case "Dictionary": + if (typeInfo.GenericArgs != null && typeInfo.GenericArgs.Count == 2 && (typeInfo.GenericArgs[0].IsInterfaceRef || typeInfo.GenericArgs[1].IsInterfaceRef)) + { + var keyConverter = typeInfo.GenericArgs[0].IsInterfaceRef ? "(object)x.Key" : "x.Key"; + var valueConverter = typeInfo.GenericArgs[1].IsInterfaceRef ? "(object)x.Value" : "x.Value"; + return $"{{0}} != null ? System.Linq.Enumerable.ToDictionary({{0}}, x => {keyConverter}, x => {valueConverter}) : null"; + } + return string.Empty; default: // Interface reference property if (typeInfo.IsInterfaceRef) @@ -787,8 +801,20 @@ namespace Flax.Build.Bindings } #endif const string interfaceResultName = "__interfaceResult"; + const string interfaceArrayResultName = "__interfaceArrayResult"; + const string interfaceDictionaryResultName = "__interfaceDictionaryResult"; var returnInterfaceRef = !functionInfo.Glue.UseReferenceForResult && functionInfo.ReturnType.IsInterfaceRef; + var returnInterfaceRefArray = !functionInfo.Glue.UseReferenceForResult && + (functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer") && + functionInfo.ReturnType.GenericArgs != null && + functionInfo.ReturnType.GenericArgs.Count != 0 && + functionInfo.ReturnType.GenericArgs[0].IsInterfaceRef; + var returnInterfaceRefDictionary = !functionInfo.Glue.UseReferenceForResult && + functionInfo.ReturnType.Type == "Dictionary" && + functionInfo.ReturnType.GenericArgs != null && + functionInfo.ReturnType.GenericArgs.Count == 2 && + (functionInfo.ReturnType.GenericArgs[0].IsInterfaceRef || functionInfo.ReturnType.GenericArgs[1].IsInterfaceRef); if (functionInfo.Glue.UseReferenceForResult) { @@ -797,6 +823,14 @@ namespace Flax.Build.Bindings { contents.Append("var ").Append(interfaceResultName).Append(" = "); } + else if (returnInterfaceRefArray) + { + contents.Append("var ").Append(interfaceArrayResultName).Append(" = "); + } + else if (returnInterfaceRefDictionary) + { + contents.Append("var ").Append(interfaceDictionaryResultName).Append(" = "); + } else if (!functionInfo.ReturnType.IsVoid) { contents.Append("return "); @@ -872,7 +906,20 @@ namespace Flax.Build.Bindings var managedType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType.GenericArgs[0], caller); contents.Append("; return ").Append(interfaceResultName).Append(" != IntPtr.Zero ? Unsafe.As<").Append(managedType).Append(">(ManagedHandle.FromIntPtr(").Append(interfaceResultName).Append(").Target) : null"); } - if ((functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer") && functionInfo.ReturnType.GenericArgs != null) + else if (returnInterfaceRefArray) + { + var managedType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType.GenericArgs[0].GenericArgs[0], caller); + contents.Append("; return ").Append(interfaceArrayResultName).Append("?.ConvertArray(x => x != IntPtr.Zero ? Unsafe.As<").Append(managedType).Append(">(ManagedHandle.FromIntPtr(x).Target) : null)"); + } + else if (returnInterfaceRefDictionary) + { + var keyTypeInfo = functionInfo.ReturnType.GenericArgs[0]; + var valueTypeInfo = functionInfo.ReturnType.GenericArgs[1]; + var keyConverter = keyTypeInfo.IsInterfaceRef ? $"x.Key != null ? Unsafe.As<{GenerateCSharpNativeToManaged(buildData, keyTypeInfo.GenericArgs[0], caller)}>(x.Key) : null" : "x.Key"; + var valueConverter = valueTypeInfo.IsInterfaceRef ? $"x.Value != null ? Unsafe.As<{GenerateCSharpNativeToManaged(buildData, valueTypeInfo.GenericArgs[0], caller)}>(x.Value) : null" : "x.Value"; + contents.Append("; return ").Append(interfaceDictionaryResultName).Append(" != null ? System.Linq.Enumerable.ToDictionary(").Append(interfaceDictionaryResultName).Append(", x => ").Append(keyConverter).Append(", x => ").Append(valueConverter).Append(") : null"); + } + else if ((functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer") && functionInfo.ReturnType.GenericArgs != null) { // Convert array that uses different type for marshalling var arrayTypeInfo = functionInfo.ReturnType.GenericArgs[0]; @@ -1009,9 +1056,24 @@ namespace Flax.Build.Bindings { GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo, memberInfo.Attributes, memberInfo.Comment, true, useUnmanaged, defaultValue, memberInfo.DeprecatedMessage, defaultValueType); var memberType = (memberInfo as FieldInfo)?.Type ?? (memberInfo as PropertyInfo)?.Type; - if (memberType != null && memberType.IsInterfaceRef) + var interfaceRefType = memberType; + if ((memberType?.Type == "Array" || memberType?.Type == "Span" || memberType?.Type == "DataContainer") && + memberType.GenericArgs != null && + memberType.GenericArgs.Count != 0 && + memberType.GenericArgs[0].IsInterfaceRef) { - var attribute = memberType.Type == "SoftObjectInterfaceReference" ? "SoftObjectInterfaceReference" : "ScriptingObjectInterfaceReference"; + interfaceRefType = memberType.GenericArgs[0]; + } + else if (memberType?.Type == "Dictionary" && + memberType.GenericArgs != null && + memberType.GenericArgs.Count == 2 && + (memberType.GenericArgs[0].IsInterfaceRef || memberType.GenericArgs[1].IsInterfaceRef)) + { + interfaceRefType = memberType.GenericArgs[1].IsInterfaceRef ? memberType.GenericArgs[1] : memberType.GenericArgs[0]; + } + if (interfaceRefType != null && interfaceRefType.IsInterfaceRef) + { + var attribute = interfaceRefType.Type == "SoftObjectInterfaceReference" ? "SoftObjectInterfaceReference" : "ScriptingObjectInterfaceReference"; contents.Append(indent).Append("[FlaxEngine.").Append(attribute).AppendLine("]"); } } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index e67bde3af..da53fa78b 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -656,8 +656,8 @@ namespace Flax.Build.Bindings { CppIncludeFiles.Add("Engine/Scripting/Internal/ManagedDictionary.h"); type = "MObject*"; - var keyClass = GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[0], caller, functionInfo); - var valueClass = GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[1], caller, functionInfo); + var keyClass = typeInfo.GenericArgs[0].IsInterfaceRef ? "MCore::TypeCache::Object->GetType()" : GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[0], caller, functionInfo); + var valueClass = typeInfo.GenericArgs[1].IsInterfaceRef ? "MCore::TypeCache::Object->GetType()" : GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[1], caller, functionInfo); return "ManagedDictionary::ToManaged({0}, " + keyClass + ", " + valueClass + ")"; } @@ -1023,8 +1023,8 @@ namespace Flax.Build.Bindings if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) { CppIncludeFiles.Add("Engine/Scripting/Internal/ManagedDictionary.h"); - var keyClass = GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[0], caller); - var valueClass = GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[1], caller); + var keyClass = typeInfo.GenericArgs[0].IsInterfaceRef ? "MCore::TypeCache::Object->GetType()" : GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[0], caller); + var valueClass = typeInfo.GenericArgs[1].IsInterfaceRef ? "MCore::TypeCache::Object->GetType()" : GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[1], caller); return $"ManagedDictionary::ToManaged({value}, {keyClass}, {valueClass})"; } From d6fb11cca3dfa7c41aba6499e084f11ed721694d Mon Sep 17 00:00:00 2001 From: Andrei Gagua Date: Sun, 24 May 2026 19:45:22 +0300 Subject: [PATCH 3/9] Upd: Fixed C# script resolution bug, fixed object lookup for interfaces, fixed serialization bug. --- Source/Engine/Scripting/ScriptingObject.cpp | 10 +- .../ExtendedDefaultContractResolver.cs | 107 +++++++++++-- Source/Engine/Serialization/JsonSerializer.cs | 102 ++++++------ .../Bindings/BindingsGenerator.CSharp.cs | 147 +++++++++++------- 4 files changed, 242 insertions(+), 124 deletions(-) diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index dee97a849..dd530c50f 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -717,6 +717,12 @@ DEFINE_INTERNAL_CALL(MString*) ObjectInternal_GetTypeName(ScriptingObject* obj) return MUtils::ToString(obj->GetType().Fullname); } +FORCE_INLINE bool ObjectInternal_MatchesType(ScriptingObject* obj, MClass* klass) +{ + return !klass || + (klass->IsInterface() ? obj->GetClass()->HasInterface(klass) : obj->Is(klass)); +} + DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_FindObject(Guid* id, MTypeObject* type, bool skipLog = false) { if (!id->IsValid()) @@ -732,7 +738,7 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_FindObject(Guid* id, MTypeObject* } if (obj) { - if (klass && !obj->Is(klass)) + if (!ObjectInternal_MatchesType(obj, klass)) { if (!skipLog) { @@ -762,7 +768,7 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_FindObject(Guid* id, MTypeObject* DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_TryFindObject(Guid* id, MTypeObject* type) { ScriptingObject* obj = Scripting::TryFindObject(*id); - if (obj && !obj->Is(MUtils::GetClass(type))) + if (obj && !ObjectInternal_MatchesType(obj, MUtils::GetClass(type))) obj = nullptr; return obj ? obj->GetOrCreateManagedInstance() : nullptr; } diff --git a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs index b8e07e448..7c6bac2a3 100644 --- a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs +++ b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs @@ -12,6 +12,7 @@ namespace FlaxEngine.Json.JsonCustomSerializers internal class ExtendedDefaultContractResolver : DefaultContractResolver { private readonly Type _flaxType = typeof(Object); + private static readonly JsonConverter InterfaceObjectReferenceConverterInstance = new InterfaceObjectReferenceConverter(); private readonly Type[] AttributesIgnoreList = { @@ -33,6 +34,88 @@ namespace FlaxEngine.Json.JsonCustomSerializers _attributesIgnoreList = isManagedOnly ? AttributesIgnoreListManaged : AttributesIgnoreList; } + private static bool HasObjectInterfaceReferenceAttribute(IEnumerable attributes) + { + return attributes.Any(x => x is ScriptingObjectInterfaceReferenceAttribute || x is SoftObjectInterfaceReferenceAttribute); + } + + private static Type GetCollectionItemType(Type type) + { + if (type.IsArray) + return type.GetElementType(); + if (!type.IsGenericType || type == typeof(string)) + return null; + + var types = type.GetInterfaces().Concat(new[] { type }); + var dictionaryType = types.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>)); + if (dictionaryType != null) + return dictionaryType.GetGenericArguments()[1]; + var enumerableType = types.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>)); + return enumerableType?.GetGenericArguments()[0]; + } + + private static void SetupInterfaceObjectReferenceItems(JsonContainerContract contract, Type itemType) + { + if (itemType?.IsInterface == true) + { + contract.ItemReferenceLoopHandling = ReferenceLoopHandling.Serialize; + contract.ItemConverter = InterfaceObjectReferenceConverterInstance; + } + } + + private void SetupObjectReferenceProperty(JsonProperty jsonProperty, Type type, IEnumerable attributes) + { + var hasObjectInterfaceReferenceAttribute = HasObjectInterfaceReferenceAttribute(attributes); + if (_flaxType.IsAssignableFrom(type) || (type.IsInterface && hasObjectInterfaceReferenceAttribute)) + { + jsonProperty.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; + jsonProperty.Converter = JsonSerializer.ObjectConverter; + } + if (hasObjectInterfaceReferenceAttribute && GetCollectionItemType(type)?.IsInterface == true) + { + jsonProperty.ItemReferenceLoopHandling = ReferenceLoopHandling.Serialize; + jsonProperty.ItemConverter = JsonSerializer.ObjectConverter; + } + } + + private sealed class InterfaceObjectReferenceConverter : JsonConverter + { + public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + if (value is Object obj) + { + var id = obj.ID; + writer.WriteValue(JsonSerializer.GetStringID(&id)); + } + else if (value == null) + { + writer.WriteNull(); + } + else + { + serializer.Serialize(writer, value, value.GetType()); + } + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.String && JsonSerializer.TryParseID((string)reader.Value, out var id)) + { + return Object.Find(ref id, objectType, true); + } + if (reader.TokenType == JsonToken.Null) + return null; + // objectType is the same interface item type that selected this converter. Passing it back to + // Newtonsoft can cause this converter to be chosen again and recurse until the stack overflows. + return Newtonsoft.Json.Linq.JToken.Load(reader).ToObject(serializer); + } + + public override bool CanConvert(Type objectType) + { + return objectType.IsInterface; + } + } + /// protected override JsonContract CreateContract(Type objectType) { @@ -47,11 +130,23 @@ namespace FlaxEngine.Json.JsonCustomSerializers return contract; } + /// + protected override JsonArrayContract CreateArrayContract(Type objectType) + { + var contract = base.CreateArrayContract(objectType); + + SetupInterfaceObjectReferenceItems(contract, contract.CollectionItemType); + + return contract; + } + /// protected override JsonDictionaryContract CreateDictionaryContract(Type objectType) { var contract = base.CreateDictionaryContract(objectType); + SetupInterfaceObjectReferenceItems(contract, contract.DictionaryValueType); + // Override contract to save enums keys as integer if (contract.DictionaryKeyType?.IsEnum ?? false) { @@ -108,11 +203,7 @@ namespace FlaxEngine.Json.JsonCustomSerializers jsonProperty.Writable = true; jsonProperty.Readable = true; - if (_flaxType.IsAssignableFrom(f.FieldType)) - { - jsonProperty.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; - jsonProperty.Converter = JsonSerializer.ObjectConverter; - } + SetupObjectReferenceProperty(jsonProperty, f.FieldType, attributes); result.Add(jsonProperty); } @@ -151,11 +242,7 @@ namespace FlaxEngine.Json.JsonCustomSerializers jsonProperty.Writable = true; jsonProperty.Readable = !isObsolete; - if (_flaxType.IsAssignableFrom(p.PropertyType)) - { - jsonProperty.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; - jsonProperty.Converter = JsonSerializer.ObjectConverter; - } + SetupObjectReferenceProperty(jsonProperty, p.PropertyType, attributes); result.Add(jsonProperty); } diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index c8d00567a..758ebf1ff 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -618,6 +618,31 @@ namespace FlaxEngine.Json return id; } + /// + /// Tries to parse the given object identifier represented in the internal serialization format. + /// + /// The ID string. + /// The identifier. + /// True if parsing succeeded, otherwise false. + public static unsafe bool TryParseID(string str, out Guid id) + { + id = Guid.Empty; + if (str == null || str.Length != 32) + return false; + + GuidInterop g; + if (!TryParseHex(str, 0, 8, out g.A) || + !TryParseHex(str, 8, 8, out g.B) || + !TryParseHex(str, 16, 8, out g.C) || + !TryParseHex(str, 24, 8, out g.D)) + { + return false; + } + + id = *(Guid*)&g; + return true; + } + /// /// Parses the given object identifier represented in the internal serialization format. /// @@ -625,76 +650,40 @@ namespace FlaxEngine.Json /// The identifier. public static unsafe void ParseID(string str, out Guid id) { - GuidInterop g; - - // Broken after VS 15.5 - /*fixed (char* a = str) - { - char* b = a + 8; - char* c = b + 8; - char* d = c + 8; - - ParseHex(a, 8, out g.A); - ParseHex(b, 8, out g.B); - ParseHex(c, 8, out g.C); - ParseHex(d, 8, out g.D); - }*/ - - // Temporary fix (not using raw char* pointer) - ParseHex(str, 0, 8, out g.A); - ParseHex(str, 8, 8, out g.B); - ParseHex(str, 16, 8, out g.C); - ParseHex(str, 24, 8, out g.D); - - id = *(Guid*)&g; + TryParseID(str, out id); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static unsafe void ParseHex(char* str, int length, out uint result) { - uint sum = 0; - char* p = str; - char* end = str + length; - - if (*p == '0' && *(p + 1) == 'x') - p += 2; - - while (p < end && *p != 0) - { - int c = *p - '0'; - - if (c < 0 || c > 9) - { - c = char.ToLower(*p) - 'a' + 10; - if (c < 10 || c > 15) - { - result = 0; - return; - } - } - - sum = 16 * sum + (uint)c; - - p++; - } - - result = sum; + TryParseHex(new ReadOnlySpan(str, length), out result); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void ParseHex(string str, int start, int length, out uint result) { - uint sum = 0; - int p = start; - int end = start + length; + TryParseHex(str, start, length, out result); + } - if (str.Length < end) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool TryParseHex(string str, int start, int length, out uint result) + { + if (str.Length < start + length) { result = 0; - return; + return false; } + return TryParseHex(str.AsSpan(start, length), out result); + } - if (str[p] == '0' && str[p + 1] == 'x') + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool TryParseHex(ReadOnlySpan str, out uint result) + { + uint sum = 0; + int p = 0; + int end = str.Length; + + if (p + 1 < end && str[p] == '0' && str[p + 1] == 'x') p += 2; while (p < end && str[p] != 0) @@ -707,7 +696,7 @@ namespace FlaxEngine.Json if (c < 10 || c > 15) { result = 0; - return; + return false; } } @@ -717,6 +706,7 @@ namespace FlaxEngine.Json } result = sum; + return p == end; } } } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 604d501ab..81fb9d873 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -315,6 +315,83 @@ namespace Flax.Build.Bindings return value; } + private static bool IsInterfaceRefArrayLike(TypeInfo typeInfo) + { + return typeInfo != null && + (typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && + typeInfo.GenericArgs != null && + typeInfo.GenericArgs.Count != 0 && + typeInfo.GenericArgs[0].IsInterfaceRef; + } + + private static bool IsInterfaceRefDictionary(TypeInfo typeInfo) + { + return typeInfo != null && + typeInfo.Type == "Dictionary" && + typeInfo.GenericArgs != null && + typeInfo.GenericArgs.Count == 2 && + (typeInfo.GenericArgs[0].IsInterfaceRef || typeInfo.GenericArgs[1].IsInterfaceRef); + } + + private static bool IsInterfaceRefContainer(TypeInfo typeInfo) + { + return IsInterfaceRefArrayLike(typeInfo) || IsInterfaceRefDictionary(typeInfo); + } + + private static TypeInfo GetInterfaceRefElementType(TypeInfo typeInfo) + { + if (typeInfo == null) + return null; + if (typeInfo.IsInterfaceRef) + return typeInfo; + if (IsInterfaceRefArrayLike(typeInfo)) + return typeInfo.GenericArgs[0]; + if (IsInterfaceRefDictionary(typeInfo)) + return typeInfo.GenericArgs[1].IsInterfaceRef ? typeInfo.GenericArgs[1] : typeInfo.GenericArgs[0]; + return null; + } + + private static string GenerateInterfaceRefToNative(BuildData buildData, TypeInfo interfaceRefType, ApiTypeInfo caller, string value) + { + return $"FlaxEngine.Object.GetUnmanagedInterface({value}, typeof({GenerateCSharpNativeToManaged(buildData, interfaceRefType.GenericArgs[0], caller)}))"; + } + + private static string GenerateInterfaceRefToManaged(BuildData buildData, TypeInfo interfaceRefType, ApiTypeInfo caller, string value, bool fromHandle) + { + var managedType = GenerateCSharpNativeToManaged(buildData, interfaceRefType.GenericArgs[0], caller); + return fromHandle + ? $"{value} != IntPtr.Zero ? Unsafe.As<{managedType}>(ManagedHandle.FromIntPtr({value}).Target) : null" + : $"{value} != null ? Unsafe.As<{managedType}>({value}) : null"; + } + + private static string GenerateInterfaceRefContainerToNative(TypeInfo typeInfo) + { + if (IsInterfaceRefArrayLike(typeInfo)) + return "{0} != null ? FlaxEngine.Interop.NativeInterop.ManagedArrayToGCHandleArray({0}) : null"; + if (IsInterfaceRefDictionary(typeInfo)) + { + var keyConverter = typeInfo.GenericArgs[0].IsInterfaceRef ? "(object)x.Key" : "x.Key"; + var valueConverter = typeInfo.GenericArgs[1].IsInterfaceRef ? "(object)x.Value" : "x.Value"; + return $"{{0}} != null ? System.Linq.Enumerable.ToDictionary({{0}}, x => {keyConverter}, x => {valueConverter}) : null"; + } + return string.Empty; + } + + private static string GenerateInterfaceRefContainerToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, string value) + { + if (IsInterfaceRefArrayLike(typeInfo)) + return $"{value}?.ConvertArray(x => {GenerateInterfaceRefToManaged(buildData, typeInfo.GenericArgs[0], caller, "x", true)})"; + if (IsInterfaceRefDictionary(typeInfo)) + { + var keyTypeInfo = typeInfo.GenericArgs[0]; + var valueTypeInfo = typeInfo.GenericArgs[1]; + var keyConverter = keyTypeInfo.IsInterfaceRef ? GenerateInterfaceRefToManaged(buildData, keyTypeInfo, caller, "x.Key", false) : "x.Key"; + var valueConverter = valueTypeInfo.IsInterfaceRef ? GenerateInterfaceRefToManaged(buildData, valueTypeInfo, caller, "x.Value", false) : "x.Value"; + return $"{value} != null ? System.Linq.Enumerable.ToDictionary({value}, x => {keyConverter}, x => {valueConverter}) : null"; + } + return value; + } + private static string GenerateCSharpNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, bool marshalling = false) { string result; @@ -554,29 +631,25 @@ namespace Flax.Build.Bindings case "Array": case "Span": case "DataContainer": + if (IsInterfaceRefArrayLike(typeInfo)) + return GenerateInterfaceRefContainerToNative(typeInfo); if (typeInfo.GenericArgs != null) { // Convert array that uses different type for marshalling var arrayTypeInfo = typeInfo.GenericArgs[0]; - if (arrayTypeInfo.IsInterfaceRef) - return "{0} != null ? FlaxEngine.Interop.NativeInterop.ManagedArrayToGCHandleArray({0}) : null"; var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); if (arrayApiType != null && arrayApiType.MarshalAs != null) return $"{{0}}.ConvertArray(x => ({GenerateCSharpNativeToManaged(buildData, arrayApiType.MarshalAs, caller)})x)"; } return string.Empty; case "Dictionary": - if (typeInfo.GenericArgs != null && typeInfo.GenericArgs.Count == 2 && (typeInfo.GenericArgs[0].IsInterfaceRef || typeInfo.GenericArgs[1].IsInterfaceRef)) - { - var keyConverter = typeInfo.GenericArgs[0].IsInterfaceRef ? "(object)x.Key" : "x.Key"; - var valueConverter = typeInfo.GenericArgs[1].IsInterfaceRef ? "(object)x.Value" : "x.Value"; - return $"{{0}} != null ? System.Linq.Enumerable.ToDictionary({{0}}, x => {keyConverter}, x => {valueConverter}) : null"; - } + if (IsInterfaceRefDictionary(typeInfo)) + return GenerateInterfaceRefContainerToNative(typeInfo); return string.Empty; default: // Interface reference property if (typeInfo.IsInterfaceRef) - return string.Format("FlaxEngine.Object.GetUnmanagedInterface({{0}}, typeof({0}))", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller)); + return GenerateInterfaceRefToNative(buildData, typeInfo, caller, "{0}"); var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) @@ -801,20 +874,10 @@ namespace Flax.Build.Bindings } #endif const string interfaceResultName = "__interfaceResult"; - const string interfaceArrayResultName = "__interfaceArrayResult"; - const string interfaceDictionaryResultName = "__interfaceDictionaryResult"; + const string interfaceContainerResultName = "__interfaceContainerResult"; var returnInterfaceRef = !functionInfo.Glue.UseReferenceForResult && functionInfo.ReturnType.IsInterfaceRef; - var returnInterfaceRefArray = !functionInfo.Glue.UseReferenceForResult && - (functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer") && - functionInfo.ReturnType.GenericArgs != null && - functionInfo.ReturnType.GenericArgs.Count != 0 && - functionInfo.ReturnType.GenericArgs[0].IsInterfaceRef; - var returnInterfaceRefDictionary = !functionInfo.Glue.UseReferenceForResult && - functionInfo.ReturnType.Type == "Dictionary" && - functionInfo.ReturnType.GenericArgs != null && - functionInfo.ReturnType.GenericArgs.Count == 2 && - (functionInfo.ReturnType.GenericArgs[0].IsInterfaceRef || functionInfo.ReturnType.GenericArgs[1].IsInterfaceRef); + var returnInterfaceRefContainer = !functionInfo.Glue.UseReferenceForResult && IsInterfaceRefContainer(functionInfo.ReturnType); if (functionInfo.Glue.UseReferenceForResult) { @@ -823,13 +886,9 @@ namespace Flax.Build.Bindings { contents.Append("var ").Append(interfaceResultName).Append(" = "); } - else if (returnInterfaceRefArray) + else if (returnInterfaceRefContainer) { - contents.Append("var ").Append(interfaceArrayResultName).Append(" = "); - } - else if (returnInterfaceRefDictionary) - { - contents.Append("var ").Append(interfaceDictionaryResultName).Append(" = "); + contents.Append("var ").Append(interfaceContainerResultName).Append(" = "); } else if (!functionInfo.ReturnType.IsVoid) { @@ -903,21 +962,11 @@ namespace Flax.Build.Bindings contents.Append(')'); if (returnInterfaceRef) { - var managedType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType.GenericArgs[0], caller); - contents.Append("; return ").Append(interfaceResultName).Append(" != IntPtr.Zero ? Unsafe.As<").Append(managedType).Append(">(ManagedHandle.FromIntPtr(").Append(interfaceResultName).Append(").Target) : null"); + contents.Append("; return ").Append(GenerateInterfaceRefToManaged(buildData, functionInfo.ReturnType, caller, interfaceResultName, true)); } - else if (returnInterfaceRefArray) + else if (returnInterfaceRefContainer) { - var managedType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType.GenericArgs[0].GenericArgs[0], caller); - contents.Append("; return ").Append(interfaceArrayResultName).Append("?.ConvertArray(x => x != IntPtr.Zero ? Unsafe.As<").Append(managedType).Append(">(ManagedHandle.FromIntPtr(x).Target) : null)"); - } - else if (returnInterfaceRefDictionary) - { - var keyTypeInfo = functionInfo.ReturnType.GenericArgs[0]; - var valueTypeInfo = functionInfo.ReturnType.GenericArgs[1]; - var keyConverter = keyTypeInfo.IsInterfaceRef ? $"x.Key != null ? Unsafe.As<{GenerateCSharpNativeToManaged(buildData, keyTypeInfo.GenericArgs[0], caller)}>(x.Key) : null" : "x.Key"; - var valueConverter = valueTypeInfo.IsInterfaceRef ? $"x.Value != null ? Unsafe.As<{GenerateCSharpNativeToManaged(buildData, valueTypeInfo.GenericArgs[0], caller)}>(x.Value) : null" : "x.Value"; - contents.Append("; return ").Append(interfaceDictionaryResultName).Append(" != null ? System.Linq.Enumerable.ToDictionary(").Append(interfaceDictionaryResultName).Append(", x => ").Append(keyConverter).Append(", x => ").Append(valueConverter).Append(") : null"); + contents.Append("; return ").Append(GenerateInterfaceRefContainerToManaged(buildData, functionInfo.ReturnType, caller, interfaceContainerResultName)); } else if ((functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer") && functionInfo.ReturnType.GenericArgs != null) { @@ -1056,22 +1105,8 @@ namespace Flax.Build.Bindings { GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo, memberInfo.Attributes, memberInfo.Comment, true, useUnmanaged, defaultValue, memberInfo.DeprecatedMessage, defaultValueType); var memberType = (memberInfo as FieldInfo)?.Type ?? (memberInfo as PropertyInfo)?.Type; - var interfaceRefType = memberType; - if ((memberType?.Type == "Array" || memberType?.Type == "Span" || memberType?.Type == "DataContainer") && - memberType.GenericArgs != null && - memberType.GenericArgs.Count != 0 && - memberType.GenericArgs[0].IsInterfaceRef) - { - interfaceRefType = memberType.GenericArgs[0]; - } - else if (memberType?.Type == "Dictionary" && - memberType.GenericArgs != null && - memberType.GenericArgs.Count == 2 && - (memberType.GenericArgs[0].IsInterfaceRef || memberType.GenericArgs[1].IsInterfaceRef)) - { - interfaceRefType = memberType.GenericArgs[1].IsInterfaceRef ? memberType.GenericArgs[1] : memberType.GenericArgs[0]; - } - if (interfaceRefType != null && interfaceRefType.IsInterfaceRef) + var interfaceRefType = GetInterfaceRefElementType(memberType); + if (interfaceRefType != null) { var attribute = interfaceRefType.Type == "SoftObjectInterfaceReference" ? "SoftObjectInterfaceReference" : "ScriptingObjectInterfaceReference"; contents.Append(indent).Append("[FlaxEngine.").Append(attribute).AppendLine("]"); From 82ec6902d42b2cacada3419d3f551a3c3830438f Mon Sep 17 00:00:00 2001 From: Andrei Gagua Date: Sun, 24 May 2026 19:57:06 +0300 Subject: [PATCH 4/9] Upd: Revert Rider PR. --- .../Scripting/CodeEditors/RiderCodeEditor.cpp | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp index de6001efd..b63815dce 100644 --- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp @@ -14,9 +14,6 @@ #if PLATFORM_WINDOWS #include "Engine/Platform/Win32/IncludeWindowsHeaders.h" -#elif PLATFORM_MAC -#include "Engine/Platform/Apple/AppleUtils.h" -#include #endif namespace @@ -71,14 +68,10 @@ namespace if (!launcherPath.HasChars() || !FileSystem::FileExists(exePath)) return; - String installPath = launchOverridePath != String::Empty ? launchOverridePath : exePath; - StringUtils::PathRemoveRelativeParts(installPath); - for (RiderInstallation* installation : *installations) - { - if (installation->path == installPath) - return; - } - installations->Add(New(installPath, versionMember->value.GetText())); + if (launchOverridePath != String::Empty) + installations->Add(New(launchOverridePath, versionMember->value.GetText())); + else + installations->Add(New(exePath, versionMember->value.GetText())); } #if PLATFORM_WINDOWS @@ -228,29 +221,17 @@ void RiderCodeEditor::FindEditors(Array* output) String applicationSupportFolder; FileSystem::GetSpecialFolderPath(SpecialFolder::ProgramData, applicationSupportFolder); - NSURL* appURL = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:@"com.jetbrains.rider"]; - if (appURL != nullptr) - { - const String appPath = AppleUtils::ToString((CFStringRef)[appURL path]); - SearchDirectory(&installations, appPath / TEXT("Contents/Resources"), appPath); - } - Array subMacDirectories; FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/Rider/ch-0/")); FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/Rider/ch-1/")); for (const String& directory : subMacDirectories) { - String riderAppPath = directory / TEXT("Rider.app"); - SearchDirectory(&installations, riderAppPath / TEXT("Contents/Resources"), riderAppPath); + String riderAppDirectory = directory / TEXT("Rider.app/Contents/Resources"); + SearchDirectory(&installations, riderAppDirectory); } // Check the local installer version - SearchDirectory(&installations, TEXT("/Applications/Rider.app/Contents/Resources"), TEXT("/Applications/Rider.app")); - - String userFolder; - FileSystem::GetSpecialFolderPath(SpecialFolder::Documents, userFolder); - String riderAppPath = userFolder / TEXT("../Applications/Rider.app"); - SearchDirectory(&installations, riderAppPath / TEXT("Contents/Resources"), riderAppPath); + SearchDirectory(&installations, TEXT("/Applications/Rider.app/Contents/Resources")); #endif for (const String& directory : subDirectories) From 0fc3972ceacfe816447feca02213c95d60b0d22a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Sep 2026 06:58:40 +0200 Subject: [PATCH 5/9] Refactor #2746 to use `ScriptingObjectInterfaceReference` for C# too instead of attribute Use `MarshalAs=ScriptingObject*` for more universal way of marshaling data between C++ and C# --- .../Editor/CustomEditors/CustomEditorsUtil.cs | 10 +- .../Editors/FlaxObjectRefEditor.cs | 95 +++++-- .../Values/DictionaryValueContainer.cs | 8 +- Source/Editor/Scripting/ScriptType.cs | 55 +++- Source/Editor/Surface/SurfaceStyle.cs | 2 +- Source/Editor/Utilities/Utils.cs | 18 ++ Source/Engine/AI/BehaviorKnowledgeSelector.cs | 2 +- Source/Engine/Engine/NativeInterop.cs | 26 +- ...iptingObjectInterfaceReferenceAttribute.cs | 14 - .../SoftObjectInterfaceReferenceAttribute.cs | 14 - Source/Engine/Scripting/ManagedCLR/MUtils.h | 111 ++------ .../ScriptingObjectInterfaceReference.cs | 188 +++++++++++++ .../ScriptingObjectInterfaceReference.h | 66 +++-- .../ScriptingObjectInterfaceReferenceUtils.h | 30 -- .../Scripting/ScriptingObjectReference.h | 2 +- .../Scripting/SoftObjectInterfaceReference.h | 258 ------------------ Source/Engine/Scripting/SoftObjectReference.h | 2 +- Source/Engine/Serialization/JsonConverters.cs | 48 +++- .../ExtendedDefaultContractResolver.cs | 94 +------ Source/Engine/Serialization/JsonSerializer.cs | 71 ++--- Source/Engine/Serialization/WriteStream.h | 4 +- .../Bindings/BindingsGenerator.CSharp.cs | 158 +++-------- .../Bindings/BindingsGenerator.Cpp.cs | 33 +-- Source/Tools/Flax.Build/Bindings/TypeInfo.cs | 10 +- 24 files changed, 527 insertions(+), 792 deletions(-) delete mode 100644 Source/Engine/Scripting/Attributes/Editor/ScriptingObjectInterfaceReferenceAttribute.cs delete mode 100644 Source/Engine/Scripting/Attributes/Editor/SoftObjectInterfaceReferenceAttribute.cs create mode 100644 Source/Engine/Scripting/ScriptingObjectInterfaceReference.cs delete mode 100644 Source/Engine/Scripting/ScriptingObjectInterfaceReferenceUtils.h delete mode 100644 Source/Engine/Scripting/SoftObjectInterfaceReference.h diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cs b/Source/Editor/CustomEditors/CustomEditorsUtil.cs index 2323333e2..d70557047 100644 --- a/Source/Editor/CustomEditors/CustomEditorsUtil.cs +++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cs @@ -58,13 +58,11 @@ namespace FlaxEditor.CustomEditors if (targetType.Type == typeof(object) && values.Count > 0 && values[0] != null && !values.HasDifferentTypes) return CreateEditor(TypeUtils.GetObjectType(values[0]), canUseRefPicker); - // Use editor for the property type - if (canUseRefPicker && - targetType.IsInterface && - values.GetAttributes().Any(x => x is ScriptingObjectInterfaceReferenceAttribute || x is SoftObjectInterfaceReferenceAttribute)) - { + // Special case if property is interface but the value is implemented as Scripting Object that should use reference picker (or all interface impl are by Scripting Objects) + if (canUseRefPicker && targetType.IsInterface && values.Count > 0 && values[0] is FlaxEngine.Object) return new FlaxObjectRefEditor(); - } + + // Use editor for the property type return CreateEditor(targetType, canUseRefPicker); } diff --git a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs index 7ce707a77..f522310db 100644 --- a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs @@ -2,6 +2,7 @@ using System; using System.Linq; +using System.Reflection; using FlaxEditor.Content; using FlaxEditor.CustomEditors.Elements; using FlaxEditor.GUI; @@ -156,44 +157,32 @@ namespace FlaxEditor.CustomEditors.Editors private void ShowDropDownMenu() { Focus(); + var pos = new Float2(0, Height); if (_type.IsInterface) { - SceneObjectSearchPopup.Show(this, new Float2(0, Height), IsValid, obj => - { - Value = obj; - RootWindow.Focus(); - Focus(); - }, PresenterContext); + SceneObjectSearchPopup.Show(this, pos, IsValid, SetDropDownResult, PresenterContext); } else if (new ScriptType(typeof(Actor)).IsAssignableFrom(_type)) { - ActorSearchPopup.Show(this, new Float2(0, Height), IsValid, actor => - { - Value = actor; - RootWindow.Focus(); - Focus(); - }, PresenterContext); + ActorSearchPopup.Show(this, pos, IsValid, SetDropDownResult, PresenterContext); } else if (new ScriptType(typeof(Control)).IsAssignableFrom(_type)) { - ActorSearchPopup.Show(this, new Float2(0, Height), IsValid, actor => - { - Value = actor as UIControl; - RootWindow.Focus(); - Focus(); - }, PresenterContext); + ActorSearchPopup.Show(this, pos, IsValid, actor => { SetDropDownResult(actor as UIControl); }, PresenterContext); } else { - ScriptSearchPopup.Show(this, new Float2(0, Height), IsValid, script => - { - Value = script; - RootWindow.Focus(); - Focus(); - }, PresenterContext); + ScriptSearchPopup.Show(this, pos, IsValid, SetDropDownResult, PresenterContext); } } + private void SetDropDownResult(Object value) + { + Value = value; + RootWindow.Focus(); + Focus(); + } + /// /// Called when value gets changed. /// @@ -228,7 +217,7 @@ namespace FlaxEditor.CustomEditors.Editors { // Draw info Render2D.PushClip(nameRect); - Render2D.DrawText(style.FontMedium, Type != null ? $"Multiple Values ({Utilities.Utils.GetPropertyNameUI(Type.ToString())})" : "-", nameRect, isEnabled ? style.ForegroundGrey : style.ForegroundGrey.AlphaMultiplied(0.75f), TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, Type != null ? $"Multiple Values ({Utilities.Utils.GetTypeNameUI(_type)})" : "-", nameRect, isEnabled ? style.ForegroundGrey : style.ForegroundGrey.AlphaMultiplied(0.75f), TextAlignment.Near, TextAlignment.Center); Render2D.PopClip(); } else if (isSelected) @@ -245,7 +234,7 @@ namespace FlaxEditor.CustomEditors.Editors { // Draw info Render2D.PushClip(nameRect); - Render2D.DrawText(style.FontMedium, Type != null ? $"None ({Utilities.Utils.GetPropertyNameUI(Type.ToString())})" : "-", nameRect, isEnabled ? style.ForegroundGrey : style.ForegroundGrey.AlphaMultiplied(0.75f), TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, Type != null ? $"None ({Utilities.Utils.GetTypeNameUI(_type)})" : "-", nameRect, isEnabled ? style.ForegroundGrey : style.ForegroundGrey.AlphaMultiplied(0.75f), TextAlignment.Near, TextAlignment.Center); Render2D.PopClip(); } @@ -670,4 +659,58 @@ namespace FlaxEditor.CustomEditors.Editors } } } + + /// + /// Default implementation of the inspector used to edit reference to the . + /// + internal sealed class ScriptingObjectInterfaceReferenceEditor : CustomEditor + { + private CustomElement _element; + + /// + public override DisplayStyle Style => DisplayStyle.Inline; + + /// + public override void Initialize(LayoutElementsContainer layout) + { + if (!HasDifferentTypes) + { + _element = layout.Custom(); + _element.CustomControl.PresenterContext = Presenter.Owner; + _element.CustomControl.Type = new ScriptType(Values.Type.GetGenericArguments()[0]); + _element.CustomControl.ValueChanged += OnValueChanged; + } + } + + private void OnValueChanged() + { + // Set value + var obj = _element.CustomControl.Value; + var v = Values.Type.CreateInstance(); + var objectField = v.GetType().GetField("_object", BindingFlags.Instance | BindingFlags.NonPublic); + objectField.SetValue(v, obj); + SetValue(v); + } + + /// + public override void Refresh() + { + base.Refresh(); + + var differentValues = HasDifferentValues; + _element.CustomControl.DifferentValues = differentValues; + if (!differentValues) + { + // Get value + var v = Values[0]; + var obj = v as Object; + if (v != null && obj == null) + { + var objectField = v.GetType().GetField("_object", BindingFlags.Instance | BindingFlags.NonPublic); + obj = objectField.GetValue(v) as Object; + } + _element.CustomControl.Value = obj; + } + } + } } diff --git a/Source/Editor/CustomEditors/Values/DictionaryValueContainer.cs b/Source/Editor/CustomEditors/Values/DictionaryValueContainer.cs index 435e5a515..b08d39beb 100644 --- a/Source/Editor/CustomEditors/Values/DictionaryValueContainer.cs +++ b/Source/Editor/CustomEditors/Values/DictionaryValueContainer.cs @@ -26,9 +26,11 @@ namespace FlaxEditor.CustomEditors /// /// Type of the collection elements. /// The key. - public DictionaryValueContainer(ScriptType elementType, object key) + /// The dictionary property attributes to inherit. + public DictionaryValueContainer(ScriptType elementType, object key, object[] attributes = null) : base(ScriptMemberInfo.Null, elementType) { + _attributes = attributes; Key = key; } @@ -40,10 +42,8 @@ namespace FlaxEditor.CustomEditors /// The collection values. /// The dictionary property attributes to inherit. public DictionaryValueContainer(ScriptType elementType, object key, ValueContainer values, object[] attributes = null) - : this(elementType, key) + : this(elementType, key, attributes) { - _attributes = attributes; - Capacity = values.Count; for (int i = 0; i < values.Count; i++) { diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs index 6db13e07a..1d26fc162 100644 --- a/Source/Editor/Scripting/ScriptType.cs +++ b/Source/Editor/Scripting/ScriptType.cs @@ -1,15 +1,62 @@ // Copyright (c) Wojciech Figat. All rights reserved. +using FlaxEditor.Content; +using FlaxEngine; +using FlaxEngine.TypeConverters; +using FlaxEngine.Utilities; using System; using System.Collections.Generic; using System.ComponentModel; +using System.ComponentModel.Design.Serialization; +using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; -using FlaxEditor.Content; -using FlaxEngine; -using FlaxEngine.Utilities; + +namespace FlaxEngine.TypeConverters +{ + /// + /// Internal ITypeDescriptorContext used to pass some context for custom TypeConvert implementations. + /// Allows passing CurrentType to TypeConverter.ConvertFrom method to support custom type conversion for FlaxEngine types (simpler than PropertyDescriptor.PropertyType). + /// + internal sealed class DummyTypeDescriptorContext : ITypeDescriptorContext + { + private static DummyTypeDescriptorContext _cached; + + public Type CurrentType; + + public static object ConvertFrom(TypeConverter converter, object value, Type type) + { + if (_cached == null) + _cached = new DummyTypeDescriptorContext(); + _cached.CurrentType = type; + var result = converter.ConvertFrom(_cached, CultureInfo.CurrentUICulture, value); + _cached.CurrentType = null; + return result; + } + + public object GetService(Type serviceType) + { + return null; + } + + public void OnComponentChanged() + { + } + + public bool OnComponentChanging() + { + return false; + } + + public IContainer Container => null; + + public object Instance => null; + + public PropertyDescriptor PropertyDescriptor => null; + } +} namespace FlaxEditor.Scripting { @@ -681,7 +728,7 @@ namespace FlaxEditor.Scripting if (converter.CanConvertTo(type)) value = converter.ConvertTo(value, type); else if (converter.CanConvertFrom(valueType)) - value = converter.ConvertFrom(null, null, value); + value = DummyTypeDescriptorContext.ConvertFrom(converter, value, type); } if (_managed is PropertyInfo propertyInfo) diff --git a/Source/Editor/Surface/SurfaceStyle.cs b/Source/Editor/Surface/SurfaceStyle.cs index e6bc82fe3..0738c6dd5 100644 --- a/Source/Editor/Surface/SurfaceStyle.cs +++ b/Source/Editor/Surface/SurfaceStyle.cs @@ -212,7 +212,7 @@ namespace FlaxEditor.Surface color = Colors.Enum; else if (type.IsValueType) color = Colors.Structures; - else if (type.IsScriptingObject || type.IsInterface) + else if (type.IsScriptingObject || type.IsInterface || type.Name.StartsWith("ScriptingObjectInterfaceReference")) color = Colors.Object; else if (hint == ConnectionsHint.Vector) color = Colors.Vector; diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 5955ccf60..8f5df866d 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -833,6 +833,24 @@ namespace FlaxEditor.Utilities } } + /// + /// Gets the type name for UI. Removes unnecessary characters and filters text. Makes it more user-friendly. + /// + /// The type. + /// The result. + public static string GetTypeNameUI(Scripting.ScriptType type) + { + var name = type.ToString(); + + // Don't format interfaces to maintain code-name (eg. prefix 'I') + if (type.IsInterface) + return name; + if (type.IsGenericType && name.StartsWith("ScriptingObjectInterfaceReference", StringComparison.Ordinal)) + return type.GetGenericArguments()[0].GetTypeDisplayName(); + + return GetPropertyNameUI(name); + } + /// /// Gets the property name for UI. Removes unnecessary characters and filters text. Makes it more user-friendly. /// diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.cs b/Source/Engine/AI/BehaviorKnowledgeSelector.cs index 67d8bbf0c..580646b17 100644 --- a/Source/Engine/AI/BehaviorKnowledgeSelector.cs +++ b/Source/Engine/AI/BehaviorKnowledgeSelector.cs @@ -188,7 +188,7 @@ namespace FlaxEngine /// /// The knowledge container to access. /// The value to set. - /// True if set value value, otherwise false. + /// True if set value, otherwise false. public bool Set(BehaviorKnowledge knowledge, T value) { return knowledge != null && knowledge.Set(Path, value); diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 304270b34..9648c204f 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -263,12 +263,36 @@ namespace FlaxEngine.Interop /// The output array. public static TDst[] ConvertArray(this TSrc[] src, Func convertFunc) { - TDst[] dst = new TDst[src.Length]; + if (src == null) + return null; + var dst = new TDst[src.Length]; for (int i = 0; i < src.Length; i++) dst[i] = convertFunc(src[i]); return dst; } + /// + /// Converts dictionary with a custom converter function for each pair of keys and values. + /// + /// Input dictionary key type. + /// Input dictionary value type. + /// Output dictionary key type. + /// Output dictionary value type. + /// The input dictionary. + /// Converter callback for keys. + /// Converter callback for values. + /// The output dictionary. + public static Dictionary ConvertDictionary(this Dictionary src, Func convertFuncKey, Func convertFuncValue) + { + if (src == null) + return null; + var dst = new Dictionary(); + dst.EnsureCapacity(src.Count); + foreach (var e in src) + dst.Add(convertFuncKey(e.Key), convertFuncValue(e.Value)); + return dst; + } + /// Find among the scripting assemblies. /// The name to find /// If true, partial names should be allowed to be resolved. diff --git a/Source/Engine/Scripting/Attributes/Editor/ScriptingObjectInterfaceReferenceAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/ScriptingObjectInterfaceReferenceAttribute.cs deleted file mode 100644 index 720497b2c..000000000 --- a/Source/Engine/Scripting/Attributes/Editor/ScriptingObjectInterfaceReferenceAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Wojciech Figat. All rights reserved. - -using System; - -namespace FlaxEngine -{ - /// - /// Marks a generated interface property as a native scripting object interface reference. - /// - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] - public sealed class ScriptingObjectInterfaceReferenceAttribute : Attribute - { - } -} diff --git a/Source/Engine/Scripting/Attributes/Editor/SoftObjectInterfaceReferenceAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/SoftObjectInterfaceReferenceAttribute.cs deleted file mode 100644 index ec106412f..000000000 --- a/Source/Engine/Scripting/Attributes/Editor/SoftObjectInterfaceReferenceAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Wojciech Figat. All rights reserved. - -using System; - -namespace FlaxEngine -{ - /// - /// Marks a generated interface property as a native soft object interface reference. - /// - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] - public sealed class SoftObjectInterfaceReferenceAttribute : Attribute - { - } -} diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.h b/Source/Engine/Scripting/ManagedCLR/MUtils.h index aed0c48c5..6becee4f7 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.h +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.h @@ -281,102 +281,22 @@ class ScriptingObjectReference; template class ScriptingObjectInterfaceReference; template -class SoftObjectInterfaceReference; - -template -struct MConverter> -{ - MObject* Box(const ScriptingObjectReference& data, const MClass* klass) - { - return data.GetManagedInstance(); - } - - void Unbox(ScriptingObjectReference& result, MObject* data) - { - result = (T*)ScriptingObject::ToNative(data); - } - - void ToManagedArray(MArray* result, const Span>& data) - { - if (data.Length() == 0) - return; - MObject** objects = (MObject**)Allocator::Allocate(data.Length() * sizeof(MObject*)); - for (int32 i = 0; i < data.Length(); i++) - objects[i] = data[i].GetManagedInstance(); - MCore::GC::WriteArrayRef(result, Span(objects, data.Length())); - Allocator::Free(objects); - } - - void ToNativeArray(Span>& result, const MArray* data) - { - MObject** dataPtr = MCore::Array::GetAddress(data); - for (int32 i = 0; i < result.Length(); i++) - result.Get()[i] = (T*)ScriptingObject::ToNative(dataPtr[i]); - } -}; - -template -struct MInterfaceReferenceConverter -{ - MObject* Box(const TReference& data, const MClass* klass) - { - return data.GetManagedInstance(); - } - - void Unbox(TReference& result, MObject* data) - { - result = ScriptingObject::ToInterface(ScriptingObject::ToNative(data)); - } - - void ToManagedArray(MArray* result, const Span& data) - { - if (data.Length() == 0) - return; - MObject** objects = (MObject**)Allocator::Allocate(data.Length() * sizeof(MObject*)); - for (int32 i = 0; i < data.Length(); i++) - objects[i] = data[i].GetManagedInstance(); - MCore::GC::WriteArrayRef(result, Span(objects, data.Length())); - Allocator::Free(objects); - } - - void ToNativeArray(Span& result, const MArray* data) - { - MObject** dataPtr = MCore::Array::GetAddress(data); - for (int32 i = 0; i < result.Length(); i++) - result.Get()[i] = ScriptingObject::ToInterface(ScriptingObject::ToNative(dataPtr[i])); - } -}; - -// Converter for Scripting Interface References. -template -struct MConverter> : MInterfaceReferenceConverter, T> -{ -}; - -// Converter for Soft Object Interface References. -template -struct MConverter> : MInterfaceReferenceConverter, T> -{ -}; - -// Converter for Asset References. -template class AssetReference; -template -struct MConverter> +template +struct MObjectReferenceConverter { - MObject* Box(const AssetReference& data, const MClass* klass) + MObject* Box(const Reference& data, const MClass* klass) { return data.GetManagedInstance(); } - void Unbox(AssetReference& result, MObject* data) + void Unbox(Reference& result, MObject* data) { - result = (T*)ScriptingObject::ToNative(data); + result = (Object*)ScriptingObject::ToNative(data); } - void ToManagedArray(MArray* result, const Span>& data) + void ToManagedArray(MArray* result, const Span& data) { if (data.Length() == 0) return; @@ -387,14 +307,29 @@ struct MConverter> Allocator::Free(objects); } - void ToNativeArray(Span>& result, const MArray* data) + void ToNativeArray(Span& result, const MArray* data) { MObject** dataPtr = MCore::Array::GetAddress(data); for (int32 i = 0; i < result.Length(); i++) - result.Get()[i] = (T*)ScriptingObject::ToNative(dataPtr[i]); + result.Get()[i] = (Object*)ScriptingObject::ToNative(dataPtr[i]); } }; +template +struct MConverter> : MObjectReferenceConverter, T> +{ +}; + +template +struct MConverter> : MObjectReferenceConverter, ScriptingObject> +{ +}; + +template +struct MConverter> : MObjectReferenceConverter, T> +{ +}; + // TODO: use MarshalAs=Guid on SoftAssetReference to pass guid over bindings and not load asset in glue code template class SoftAssetReference; diff --git a/Source/Engine/Scripting/ScriptingObjectInterfaceReference.cs b/Source/Engine/Scripting/ScriptingObjectInterfaceReference.cs new file mode 100644 index 000000000..46cbbef24 --- /dev/null +++ b/Source/Engine/Scripting/ScriptingObjectInterfaceReference.cs @@ -0,0 +1,188 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +using System; +#if FLAX_EDITOR +using System.Globalization; +using System.ComponentModel; +#endif + +namespace FlaxEngine +{ + /// + /// The scripting object reference with interface. + /// + /// The type of the scripting interface. +#if FLAX_EDITOR + [CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.ScriptingObjectInterfaceReferenceEditor))] + [TypeConverter(typeof(TypeConverters.ScriptingObjectInterfaceReferenceConverter))] +#endif + public struct ScriptingObjectInterfaceReference : IComparable, IComparable> where T : class + { + private Object _object; + + /// + /// Gets or sets the referenced object that implements the interface. + /// + public Object Object + { + get => _object; + set => _object = value != null && value is T ? value : null; + } + + /// + /// Gets or sets the referenced object that implements the interface. + /// + [NoSerialize] + public T Interface + { + get => _object as T; + set + { + var obj = value as Object; + if (value == null || obj != null) + _object = obj; + else + throw new InvalidCastException($"Cannot use object of type {value.GetType().FullName} for ScriptingObjectInterfaceReference<{typeof(T).FullName}>. It needs to inherit from {typeof(Object).FullName}."); + } + } + + /// + /// Initializes a new instance of the structure. + /// + /// The object to link. + public ScriptingObjectInterfaceReference(Object obj) + { + Object = obj; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The interface object to link. + public ScriptingObjectInterfaceReference(T interfaceObj) + { + Interface = interfaceObj; + } + + /// + /// Implicit cast operator to typed interface. + /// + /// Reference + /// Interface + public static explicit operator T(ScriptingObjectInterfaceReference value) + { + return value._object as T; + } + + /// + /// Implicit cast operator from object to reference. + /// + /// The object to link. + /// Reference + public static explicit operator ScriptingObjectInterfaceReference(T obj) + { + return new ScriptingObjectInterfaceReference(obj); + } + + /// + /// Implicit cast operator to object. + /// + /// Reference + /// Object + public static implicit operator Object(ScriptingObjectInterfaceReference value) + { + return value._object; + } + + /// + /// Implicit cast operator from object to reference. + /// + /// Object + /// Reference + public static implicit operator ScriptingObjectInterfaceReference(Object obj) + { + return new ScriptingObjectInterfaceReference(obj); + } + + /// + public override string ToString() + { + return _object?.ToString() ?? ""; + } + + /// + public override int GetHashCode() + { + return Object.GetUnmanagedPtr(_object).GetHashCode(); + } + + /// + public int CompareTo(object obj) + { + if (obj is ScriptingObjectInterfaceReference other) + return CompareTo(other); + return 0; + } + + /// + public int CompareTo(ScriptingObjectInterfaceReference other) + { + return Object.GetUnmanagedPtr(_object).CompareTo(Object.GetUnmanagedPtr(other._object)); + } + } +} + +#if FLAX_EDITOR +namespace FlaxEngine.TypeConverters +{ + internal class ScriptingObjectInterfaceReferenceConverter : TypeConverter + { + /// + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + return true; + return base.CanConvertFrom(context, sourceType); + } + + /// + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + if (destinationType == typeof(string)) + return false; + return base.CanConvertTo(context, destinationType); + } + + /// + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string str && context is DummyTypeDescriptorContext internalContext) + { + var type = internalContext.CurrentType; + Json.JsonSerializer.ParseID(str, out var id); + var obj = Object.Find(ref id, type.GetGenericArguments()[0]); + var objectField = type.GetField("_object", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + value = Activator.CreateInstance(type); + objectField.SetValue(value, obj); + return value; + } + return base.ConvertFrom(context, culture, value); + } + + /// + public override unsafe object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (destinationType == typeof(string)) + { + var objectField = value.GetType().GetField("_object", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + var obj = objectField.GetValue(value) as Object; + if (obj == null) + return string.Empty; + var id = obj.ID; + return Json.JsonSerializer.GetStringID(&id); + } + return base.ConvertTo(context, culture, value, destinationType); + } + } +} +#endif diff --git a/Source/Engine/Scripting/ScriptingObjectInterfaceReference.h b/Source/Engine/Scripting/ScriptingObjectInterfaceReference.h index 65c479075..46687074a 100644 --- a/Source/Engine/Scripting/ScriptingObjectInterfaceReference.h +++ b/Source/Engine/Scripting/ScriptingObjectInterfaceReference.h @@ -2,17 +2,15 @@ #pragma once -#include "Engine/Scripting/ScriptingObjectInterfaceReferenceUtils.h" +#include "ScriptingObjectReference.h" /// -/// The scene object interface reference. +/// The scripting object reference with interface. /// /// The type of the scripting interface. template -API_CLASS(InBuild) class ScriptingObjectInterfaceReference : public ScriptingObjectReferenceBase +API_CLASS(Template, MarshalAs=ScriptingObject*) class ScriptingObjectInterfaceReference : public ScriptingObjectReferenceBase { - typedef ScriptingObjectInterfaceReferenceHelper Helper; - public: typedef ScriptingObjectInterfaceReference Type; @@ -28,8 +26,8 @@ public: /// Initializes a new instance of the class. /// /// The object to link. - ScriptingObjectInterfaceReference(SceneObject* obj) - : ScriptingObjectReferenceBase(Helper::IsValidObject(obj) ? obj : nullptr) + ScriptingObjectInterfaceReference(ScriptingObject* obj) + : ScriptingObjectReferenceBase(IsValid(obj) ? obj : nullptr) { } @@ -38,7 +36,7 @@ public: /// /// The interface object to link. ScriptingObjectInterfaceReference(T* interfaceObj) - : ScriptingObjectReferenceBase(Helper::GetSceneObject(interfaceObj)) + : ScriptingObjectReferenceBase(ScriptingObject::FromInterface(interfaceObj)) { } @@ -64,12 +62,12 @@ public: } public: - FORCE_INLINE bool operator==(SceneObject* other) const + FORCE_INLINE bool operator==(ScriptingObject* other) const { return _object == other; } - FORCE_INLINE bool operator!=(SceneObject* other) const + FORCE_INLINE bool operator!=(ScriptingObject* other) const { return _object != other; } @@ -94,33 +92,34 @@ public: return _object != other._object; } - FORCE_INLINE ScriptingObjectInterfaceReference& operator=(SceneObject* other) + FORCE_INLINE ScriptingObjectInterfaceReference& operator=(ScriptingObject* other) { - OnSet(Helper::IsValidObject(other) ? other : nullptr); + OnSet(IsValid(other) ? other : nullptr); return *this; } FORCE_INLINE ScriptingObjectInterfaceReference& operator=(T* other) { - OnSet(Helper::GetSceneObject(other)); + OnSet(ScriptingObject::FromInterface(other)); return *this; } - ScriptingObjectInterfaceReference& operator=(const ScriptingObjectInterfaceReference& other) + FORCE_INLINE ScriptingObjectInterfaceReference& operator=(const ScriptingObjectInterfaceReference& other) { OnSet(other._object); return *this; } - ScriptingObjectInterfaceReference& operator=(ScriptingObjectInterfaceReference&& other) noexcept + FORCE_INLINE ScriptingObjectInterfaceReference& operator=(ScriptingObjectInterfaceReference&& other) noexcept { ScriptingObjectReferenceBase::operator=(MoveTemp(other)); return *this; } - FORCE_INLINE ScriptingObjectInterfaceReference& operator=(const Guid& id) + ScriptingObjectInterfaceReference& operator=(const Guid& id) { - OnSet(Helper::FindSceneObject(id)); + ScriptingObject* obj = FindObject(id, ScriptingObject::GetStaticClass()); + OnSet(IsValid(obj) ? obj : nullptr); return *this; } @@ -132,6 +131,14 @@ public: return Get(); } + /// + /// Implicit conversion to the object. + /// + FORCE_INLINE operator ScriptingObject*() const + { + return _object; + } + /// /// Implicit conversion to boolean value. /// @@ -159,33 +166,24 @@ public: /// /// Gets the referenced object. /// - FORCE_INLINE SceneObject* GetObject() const + FORCE_INLINE ScriptingObject* GetObject() const { - return static_cast(_object); + return _object; } /// - /// Copies the object ID into the raw storage. + /// Gets managed instance object. /// - FORCE_INLINE void CopyID(uint32 id[4]) const + FORCE_INLINE MObject* GetManagedInstance() const { - memset(id, 0, sizeof(uint32) * 4); - if (_object) - { - const Guid value = GetID(); - memcpy(id, &value, sizeof(uint32) * 4); - } + return _object ? _object->GetOrCreateManagedInstance() : nullptr; } - /// - /// Gets the object as a given type (static cast). - /// - template - FORCE_INLINE U* As() const +private: + FORCE_INLINE static bool IsValid(const ScriptingObject* obj) { - return static_cast(_object); + return !obj || obj->GetType().GetInterface(T::TypeInitializer); } - }; template diff --git a/Source/Engine/Scripting/ScriptingObjectInterfaceReferenceUtils.h b/Source/Engine/Scripting/ScriptingObjectInterfaceReferenceUtils.h deleted file mode 100644 index d18df3d80..000000000 --- a/Source/Engine/Scripting/ScriptingObjectInterfaceReferenceUtils.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Wojciech Figat. All rights reserved. - -#pragma once - -#include "Engine/Scripting/ScriptingObjectReference.h" -#include "Engine/Level/SceneObject.h" - -/// -/// Utility methods for scene object interface references. -/// -/// The type of the scripting interface. -template -struct ScriptingObjectInterfaceReferenceHelper -{ - FORCE_INLINE static bool IsValidObject(const SceneObject* obj) - { - return !obj || obj->GetType().GetInterface(T::TypeInitializer) != nullptr; - } - - FORCE_INLINE static SceneObject* GetSceneObject(T* interfaceObj) - { - return ScriptingObject::Cast(ScriptingObject::FromInterface(interfaceObj)); - } - - FORCE_INLINE static SceneObject* FindSceneObject(const Guid& id) - { - SceneObject* obj = static_cast(FindObject(id, SceneObject::GetStaticClass())); - return IsValidObject(obj) ? obj : nullptr; - } -}; diff --git a/Source/Engine/Scripting/ScriptingObjectReference.h b/Source/Engine/Scripting/ScriptingObjectReference.h index 58fed7668..21e4ecd66 100644 --- a/Source/Engine/Scripting/ScriptingObjectReference.h +++ b/Source/Engine/Scripting/ScriptingObjectReference.h @@ -72,7 +72,7 @@ public: } /// - /// Gets managed instance object (or null if no object linked). + /// Gets managed instance object. /// FORCE_INLINE MObject* GetManagedInstance() const { diff --git a/Source/Engine/Scripting/SoftObjectInterfaceReference.h b/Source/Engine/Scripting/SoftObjectInterfaceReference.h deleted file mode 100644 index 0814f4927..000000000 --- a/Source/Engine/Scripting/SoftObjectInterfaceReference.h +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright (c) Wojciech Figat. All rights reserved. - -#pragma once - -#include "Engine/Scripting/SoftObjectReference.h" -#include "Engine/Scripting/ScriptingObjectInterfaceReferenceUtils.h" - -/// -/// The scene object soft interface reference. Objects gets referenced on use (ID reference is resolving it). -/// -/// The type of the scripting interface. -template -API_CLASS(InBuild) class SoftObjectInterfaceReference : public SoftObjectReferenceBase -{ - typedef ScriptingObjectInterfaceReferenceHelper Helper; - -public: - typedef SoftObjectInterfaceReference Type; - -public: - /// - /// Initializes a new instance of the class. - /// - SoftObjectInterfaceReference() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The object to link. - SoftObjectInterfaceReference(SceneObject* obj) - { - OnSet(Helper::IsValidObject(obj) ? obj : nullptr); - } - - /// - /// Initializes a new instance of the class. - /// - /// The interface object to link. - SoftObjectInterfaceReference(T* interfaceObj) - { - OnSet(Helper::GetSceneObject(interfaceObj)); - } - - /// - /// Initializes a new instance of the class. - /// - /// The other property. - SoftObjectInterfaceReference(const SoftObjectInterfaceReference& other) - { - OnSet(other.GetID()); - } - - /// - /// Initializes a new instance of the class. - /// - /// The other property. - SoftObjectInterfaceReference(SoftObjectInterfaceReference&& other) - { - OnSet(other.GetID()); - other.OnSet(nullptr); - } - - /// - /// Finalizes an instance of the class. - /// - ~SoftObjectInterfaceReference() - { - } - -public: - FORCE_INLINE bool operator==(SceneObject* other) - { - return GetObject() == other; - } - - FORCE_INLINE bool operator!=(SceneObject* other) - { - return GetObject() != other; - } - - FORCE_INLINE bool operator==(T* other) - { - return Get() == other; - } - - FORCE_INLINE bool operator!=(T* other) - { - return Get() != other; - } - - FORCE_INLINE bool operator==(const SoftObjectInterfaceReference& other) - { - return GetID() == other.GetID(); - } - - FORCE_INLINE bool operator!=(const SoftObjectInterfaceReference& other) - { - return GetID() != other.GetID(); - } - - SoftObjectInterfaceReference& operator=(const SoftObjectInterfaceReference& other) - { - if (this != &other) - OnSet(other.GetID()); - return *this; - } - - SoftObjectInterfaceReference& operator=(SoftObjectInterfaceReference&& other) - { - if (this != &other) - { - OnSet(other.GetID()); - other.OnSet(nullptr); - } - return *this; - } - - FORCE_INLINE SoftObjectInterfaceReference& operator=(SceneObject* other) - { - OnSet(Helper::IsValidObject(other) ? other : nullptr); - return *this; - } - - FORCE_INLINE SoftObjectInterfaceReference& operator=(T* other) - { - OnSet(Helper::GetSceneObject(other)); - return *this; - } - - FORCE_INLINE SoftObjectInterfaceReference& operator=(const Guid& id) - { - OnSet(id); - return *this; - } - - /// - /// Implicit conversion to the interface. - /// - FORCE_INLINE operator T*() const - { - return Get(); - } - - /// - /// Implicit conversion to boolean value. - /// - FORCE_INLINE operator bool() const - { - return Get() != nullptr; - } - - /// - /// Interface accessor. - /// - FORCE_INLINE T* operator->() const - { - return Get(); - } - - /// - /// Gets the object as a given type (static cast). - /// - template - FORCE_INLINE U* As() const - { - return static_cast(GetObject()); - } - -public: - /// - /// Gets the interface pointer. - /// - FORCE_INLINE T* Get() const - { - return ScriptingObject::ToInterface(GetObject()); - } - - /// - /// Gets the referenced object. - /// - SceneObject* GetObject() const - { - if (!_object) - const_cast(this)->OnResolve(SceneObject::GetStaticClass()); - return Helper::IsValidObject(static_cast(_object)) ? static_cast(_object) : nullptr; - } - - /// - /// Gets managed instance object (or null if no object linked). - /// - MObject* GetManagedInstance() const - { - auto object = GetObject(); - return object ? object->GetOrCreateManagedInstance() : nullptr; - } - - /// - /// Determines whether object is assigned and managed instance of the object is alive. - /// - bool HasManagedInstance() const - { - auto object = GetObject(); - return object && object->HasManagedInstance(); - } - - /// - /// Gets the managed instance object or creates it if missing or null if not assigned. - /// - MObject* GetOrCreateManagedInstance() const - { - auto object = GetObject(); - return object ? object->GetOrCreateManagedInstance() : nullptr; - } - - /// - /// Copies the object ID into the raw storage. - /// - FORCE_INLINE void CopyID(uint32 id[4]) const - { - const Guid value = GetID(); - memcpy(id, &value, sizeof(uint32) * 4); - } - - /// - /// Sets the object. - /// - /// The object ID. Uses Scripting to find the registered object of the given ID. - FORCE_INLINE void Set(const Guid& id) - { - OnSet(id); - } - - /// - /// Sets the object. - /// - /// The object. - FORCE_INLINE void Set(SceneObject* object) - { - OnSet(Helper::IsValidObject(object) ? object : nullptr); - } - - /// - /// Sets the object. - /// - /// The interface object. - FORCE_INLINE void Set(T* interfaceObj) - { - OnSet(Helper::GetSceneObject(interfaceObj)); - } -}; - -template -uint32 GetHash(const SoftObjectInterfaceReference& key) -{ - return GetHash(key.GetID()); -} diff --git a/Source/Engine/Scripting/SoftObjectReference.h b/Source/Engine/Scripting/SoftObjectReference.h index 3fd85200e..b07702e44 100644 --- a/Source/Engine/Scripting/SoftObjectReference.h +++ b/Source/Engine/Scripting/SoftObjectReference.h @@ -233,7 +233,7 @@ public: } /// - /// Gets managed instance object (or null if no object linked). + /// Gets managed instance object. /// MObject* GetManagedInstance() const { diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs index a45ae558a..71094b127 100644 --- a/Source/Engine/Serialization/JsonConverters.cs +++ b/Source/Engine/Serialization/JsonConverters.cs @@ -1,8 +1,8 @@ // Copyright (c) Wojciech Figat. All rights reserved. -using System; using FlaxEngine.GUI; using Newtonsoft.Json; +using System; namespace FlaxEngine.Json { @@ -138,6 +138,52 @@ namespace FlaxEngine.Json } } + /// + /// Serialize as path string in internal format. + /// + /// + internal class ScriptingObjectInterfaceReferenceConverter : JsonConverter + { + /// + public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + if (value == null) + writer.WriteNull(); + else + { + var objectField = value.GetType().GetField("_object", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + var obj = objectField.GetValue(value) as Object; + if (obj == null) + { + writer.WriteNull(); + return; + } + var id = obj.ID; + writer.WriteValue(JsonSerializer.GetStringID(&id)); + } + } + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + var result = existingValue ?? Activator.CreateInstance(objectType); + if (reader.TokenType == JsonToken.String) + { + JsonSerializer.ParseID((string)reader.Value, out var id); + var obj = Object.Find(ref id, objectType.GetGenericArguments()[0]); + var objectField = objectType.GetField("_object", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + objectField.SetValue(result, obj); + } + return result; + } + + /// + public override bool CanConvert(Type objectType) + { + return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(ScriptingObjectInterfaceReference<>); + } + } + /// /// Serialize as path string in internal format. /// diff --git a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs index 84b4729fd..5a1a7c92d 100644 --- a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs +++ b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs @@ -13,7 +13,6 @@ namespace FlaxEngine.Json.JsonCustomSerializers internal class ExtendedDefaultContractResolver : DefaultContractResolver { private readonly Type _flaxType = typeof(Object); - private static readonly JsonConverter InterfaceObjectReferenceConverterInstance = new InterfaceObjectReferenceConverter(); private readonly Type[] AttributesIgnoreList = { @@ -35,86 +34,13 @@ namespace FlaxEngine.Json.JsonCustomSerializers _attributesIgnoreList = isManagedOnly ? AttributesIgnoreListManaged : AttributesIgnoreList; } - private static bool HasObjectInterfaceReferenceAttribute(IEnumerable attributes) + private void SetupProperty(JsonProperty jsonProperty, Type type, IEnumerable attributes) { - return attributes.Any(x => x is ScriptingObjectInterfaceReferenceAttribute || x is SoftObjectInterfaceReferenceAttribute); - } - - private static Type GetCollectionItemType(Type type) - { - if (type.IsArray) - return type.GetElementType(); - if (!type.IsGenericType || type == typeof(string)) - return null; - - var types = type.GetInterfaces().Concat(new[] { type }); - var dictionaryType = types.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>)); - if (dictionaryType != null) - return dictionaryType.GetGenericArguments()[1]; - var enumerableType = types.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>)); - return enumerableType?.GetGenericArguments()[0]; - } - - private static void SetupInterfaceObjectReferenceItems(JsonContainerContract contract, Type itemType) - { - if (itemType?.IsInterface == true) - { - contract.ItemReferenceLoopHandling = ReferenceLoopHandling.Serialize; - contract.ItemConverter = InterfaceObjectReferenceConverterInstance; - } - } - - private void SetupObjectReferenceProperty(JsonProperty jsonProperty, Type type, IEnumerable attributes) - { - var hasObjectInterfaceReferenceAttribute = HasObjectInterfaceReferenceAttribute(attributes); - if (_flaxType.IsAssignableFrom(type) || (type.IsInterface && hasObjectInterfaceReferenceAttribute)) + if (_flaxType.IsAssignableFrom(type)) { jsonProperty.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; jsonProperty.Converter = JsonSerializer.ObjectConverter; } - if (hasObjectInterfaceReferenceAttribute && GetCollectionItemType(type)?.IsInterface == true) - { - jsonProperty.ItemReferenceLoopHandling = ReferenceLoopHandling.Serialize; - jsonProperty.ItemConverter = JsonSerializer.ObjectConverter; - } - } - - private sealed class InterfaceObjectReferenceConverter : JsonConverter - { - public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) - { - if (value is Object obj) - { - var id = obj.ID; - writer.WriteValue(JsonSerializer.GetStringID(&id)); - } - else if (value == null) - { - writer.WriteNull(); - } - else - { - serializer.Serialize(writer, value, value.GetType()); - } - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.String && JsonSerializer.TryParseID((string)reader.Value, out var id)) - { - return Object.Find(ref id, objectType, true); - } - if (reader.TokenType == JsonToken.Null) - return null; - // objectType is the same interface item type that selected this converter. Passing it back to - // Newtonsoft can cause this converter to be chosen again and recurse until the stack overflows. - return Newtonsoft.Json.Linq.JToken.Load(reader).ToObject(serializer); - } - - public override bool CanConvert(Type objectType) - { - return objectType.IsInterface; - } } /// @@ -138,23 +64,11 @@ namespace FlaxEngine.Json.JsonCustomSerializers return contract; } - /// - protected override JsonArrayContract CreateArrayContract(Type objectType) - { - var contract = base.CreateArrayContract(objectType); - - SetupInterfaceObjectReferenceItems(contract, contract.CollectionItemType); - - return contract; - } - /// protected override JsonDictionaryContract CreateDictionaryContract(Type objectType) { var contract = base.CreateDictionaryContract(objectType); - SetupInterfaceObjectReferenceItems(contract, contract.DictionaryValueType); - // Override contract to save enums keys as integer var keyType = contract.DictionaryKeyType; if ((keyType?.IsEnum ?? false) && keyType.GetCustomAttribute() == null) @@ -211,7 +125,7 @@ namespace FlaxEngine.Json.JsonCustomSerializers jsonProperty.Writable = true; jsonProperty.Readable = true; - SetupObjectReferenceProperty(jsonProperty, f.FieldType, attributes); + SetupProperty(jsonProperty, f.FieldType, attributes); result.Add(jsonProperty); } @@ -250,7 +164,7 @@ namespace FlaxEngine.Json.JsonCustomSerializers jsonProperty.Writable = true; jsonProperty.Readable = !isObsolete; - SetupObjectReferenceProperty(jsonProperty, p.PropertyType, attributes); + SetupProperty(jsonProperty, p.PropertyType, attributes); result.Add(jsonProperty); } diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index 758ebf1ff..f08e273ae 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -198,6 +198,7 @@ namespace FlaxEngine.Json settings.Converters.Add(new SceneReferenceConverter()); settings.Converters.Add(new SoftObjectReferenceConverter()); settings.Converters.Add(new SoftTypeReferenceConverter()); + settings.Converters.Add(new ScriptingObjectInterfaceReferenceConverter()); settings.Converters.Add(new BehaviorKnowledgeSelectorAnyConverter()); settings.Converters.Add(new ControlReferenceConverter()); settings.Converters.Add(new MarginConverter()); @@ -623,68 +624,29 @@ namespace FlaxEngine.Json /// /// The ID string. /// The identifier. - /// True if parsing succeeded, otherwise false. - public static unsafe bool TryParseID(string str, out Guid id) + /// True if cannot parse text, otherwise false + public static unsafe bool ParseID(string str, out Guid id) { - id = Guid.Empty; - if (str == null || str.Length != 32) - return false; - + bool result = true; GuidInterop g; - if (!TryParseHex(str, 0, 8, out g.A) || - !TryParseHex(str, 8, 8, out g.B) || - !TryParseHex(str, 16, 8, out g.C) || - !TryParseHex(str, 24, 8, out g.D)) + if (str != null && str.Length == 32) { - return false; + // Matches Flax Guid parsing of FormatType::N + result = ParseHex(str, 0, 8, out g.A) || + ParseHex(str, 8, 8, out g.B) || + ParseHex(str, 16, 8, out g.C) || + ParseHex(str, 24, 8, out g.D); } - id = *(Guid*)&g; - return true; - } - - /// - /// Parses the given object identifier represented in the internal serialization format. - /// - /// The ID string. - /// The identifier. - public static unsafe void ParseID(string str, out Guid id) - { - TryParseID(str, out id); + return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe void ParseHex(char* str, int length, out uint result) - { - TryParseHex(new ReadOnlySpan(str, length), out result); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void ParseHex(string str, int start, int length, out uint result) - { - TryParseHex(str, start, length, out result); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool TryParseHex(string str, int start, int length, out uint result) - { - if (str.Length < start + length) - { - result = 0; - return false; - } - return TryParseHex(str.AsSpan(start, length), out result); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool TryParseHex(ReadOnlySpan str, out uint result) + internal static bool ParseHex(string str, int start, int length, out uint result) { uint sum = 0; - int p = 0; - int end = str.Length; - - if (p + 1 < end && str[p] == '0' && str[p + 1] == 'x') - p += 2; + int p = start; + int end = start + length; while (p < end && str[p] != 0) { @@ -696,17 +658,16 @@ namespace FlaxEngine.Json if (c < 10 || c > 15) { result = 0; - return false; + return true; } } sum = 16 * sum + (uint)c; - p++; } result = sum; - return p == end; + return p != end; } } } diff --git a/Source/Engine/Serialization/WriteStream.h b/Source/Engine/Serialization/WriteStream.h index 6c610b66f..4e31b37cc 100644 --- a/Source/Engine/Serialization/WriteStream.h +++ b/Source/Engine/Serialization/WriteStream.h @@ -160,9 +160,7 @@ public: template FORCE_INLINE void Write(const ScriptingObjectInterfaceReference& v) { - uint32 id[4]; - v.CopyID(id); - WriteBytes(id, sizeof(id)); + Write(v.GetObject()); } template diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 81fb9d873..e56d15149 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -107,8 +107,6 @@ namespace Flax.Build.Bindings { if (attribute && valueType != null && !valueType.IsArray) { - //if (valueType.Type == "") - //ScriptingObjectReference, ScriptingObjectInterfaceReference, SoftObjectInterfaceReference apiType = FindApiTypeInfo(buildData, valueType, caller); // Object reference @@ -315,83 +313,6 @@ namespace Flax.Build.Bindings return value; } - private static bool IsInterfaceRefArrayLike(TypeInfo typeInfo) - { - return typeInfo != null && - (typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && - typeInfo.GenericArgs != null && - typeInfo.GenericArgs.Count != 0 && - typeInfo.GenericArgs[0].IsInterfaceRef; - } - - private static bool IsInterfaceRefDictionary(TypeInfo typeInfo) - { - return typeInfo != null && - typeInfo.Type == "Dictionary" && - typeInfo.GenericArgs != null && - typeInfo.GenericArgs.Count == 2 && - (typeInfo.GenericArgs[0].IsInterfaceRef || typeInfo.GenericArgs[1].IsInterfaceRef); - } - - private static bool IsInterfaceRefContainer(TypeInfo typeInfo) - { - return IsInterfaceRefArrayLike(typeInfo) || IsInterfaceRefDictionary(typeInfo); - } - - private static TypeInfo GetInterfaceRefElementType(TypeInfo typeInfo) - { - if (typeInfo == null) - return null; - if (typeInfo.IsInterfaceRef) - return typeInfo; - if (IsInterfaceRefArrayLike(typeInfo)) - return typeInfo.GenericArgs[0]; - if (IsInterfaceRefDictionary(typeInfo)) - return typeInfo.GenericArgs[1].IsInterfaceRef ? typeInfo.GenericArgs[1] : typeInfo.GenericArgs[0]; - return null; - } - - private static string GenerateInterfaceRefToNative(BuildData buildData, TypeInfo interfaceRefType, ApiTypeInfo caller, string value) - { - return $"FlaxEngine.Object.GetUnmanagedInterface({value}, typeof({GenerateCSharpNativeToManaged(buildData, interfaceRefType.GenericArgs[0], caller)}))"; - } - - private static string GenerateInterfaceRefToManaged(BuildData buildData, TypeInfo interfaceRefType, ApiTypeInfo caller, string value, bool fromHandle) - { - var managedType = GenerateCSharpNativeToManaged(buildData, interfaceRefType.GenericArgs[0], caller); - return fromHandle - ? $"{value} != IntPtr.Zero ? Unsafe.As<{managedType}>(ManagedHandle.FromIntPtr({value}).Target) : null" - : $"{value} != null ? Unsafe.As<{managedType}>({value}) : null"; - } - - private static string GenerateInterfaceRefContainerToNative(TypeInfo typeInfo) - { - if (IsInterfaceRefArrayLike(typeInfo)) - return "{0} != null ? FlaxEngine.Interop.NativeInterop.ManagedArrayToGCHandleArray({0}) : null"; - if (IsInterfaceRefDictionary(typeInfo)) - { - var keyConverter = typeInfo.GenericArgs[0].IsInterfaceRef ? "(object)x.Key" : "x.Key"; - var valueConverter = typeInfo.GenericArgs[1].IsInterfaceRef ? "(object)x.Value" : "x.Value"; - return $"{{0}} != null ? System.Linq.Enumerable.ToDictionary({{0}}, x => {keyConverter}, x => {valueConverter}) : null"; - } - return string.Empty; - } - - private static string GenerateInterfaceRefContainerToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, string value) - { - if (IsInterfaceRefArrayLike(typeInfo)) - return $"{value}?.ConvertArray(x => {GenerateInterfaceRefToManaged(buildData, typeInfo.GenericArgs[0], caller, "x", true)})"; - if (IsInterfaceRefDictionary(typeInfo)) - { - var keyTypeInfo = typeInfo.GenericArgs[0]; - var valueTypeInfo = typeInfo.GenericArgs[1]; - var keyConverter = keyTypeInfo.IsInterfaceRef ? GenerateInterfaceRefToManaged(buildData, keyTypeInfo, caller, "x.Key", false) : "x.Key"; - var valueConverter = valueTypeInfo.IsInterfaceRef ? GenerateInterfaceRefToManaged(buildData, valueTypeInfo, caller, "x.Value", false) : "x.Value"; - return $"{value} != null ? System.Linq.Enumerable.ToDictionary({value}, x => {keyConverter}, x => {valueConverter}) : null"; - } - return value; - } - private static string GenerateCSharpNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, bool marshalling = false) { string result; @@ -427,10 +348,6 @@ namespace Flax.Build.Bindings if (CSharpNativeToManagedDefault.TryGetValue(typeInfo.Type, out result)) return result; - // Interface reference property - if (typeInfo.IsInterfaceRef) - return marshalling ? "IntPtr" : GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling); - // Object reference property if (typeInfo.IsObjectRef) return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling); @@ -457,11 +374,7 @@ namespace Flax.Build.Bindings // Dictionary if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) - { - var keyType = marshalling && typeInfo.GenericArgs[0].IsInterfaceRef ? "object" : GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling); - var valueType = marshalling && typeInfo.GenericArgs[1].IsInterfaceRef ? "object" : GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[1], caller, marshalling); - return string.Format("System.Collections.Generic.Dictionary<{0}, {1}>", keyType, valueType); - } + return string.Format("System.Collections.Generic.Dictionary<{0}, {1}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling), GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[1], caller, marshalling)); // HashSet if (typeInfo.Type == "HashSet" && typeInfo.GenericArgs != null) @@ -631,8 +544,6 @@ namespace Flax.Build.Bindings case "Array": case "Span": case "DataContainer": - if (IsInterfaceRefArrayLike(typeInfo)) - return GenerateInterfaceRefContainerToNative(typeInfo); if (typeInfo.GenericArgs != null) { // Convert array that uses different type for marshalling @@ -643,14 +554,21 @@ namespace Flax.Build.Bindings } return string.Empty; case "Dictionary": - if (IsInterfaceRefDictionary(typeInfo)) - return GenerateInterfaceRefContainerToNative(typeInfo); + if (typeInfo.GenericArgs != null && typeInfo.GenericArgs.Count == 2) + { + // Convert dictionary that uses different type for marshalling + var keyApiType = FindApiTypeInfo(buildData, typeInfo.GenericArgs[0], caller); + var valueApiType = FindApiTypeInfo(buildData, typeInfo.GenericArgs[1], caller); + if ((keyApiType != null && keyApiType.MarshalAs != null) || (valueApiType != null && valueApiType.MarshalAs != null)) + { + var keyConverter = keyApiType != null && keyApiType.MarshalAs != null ? $"({GenerateCSharpNativeToManaged(buildData, keyApiType.MarshalAs, caller)})" : ""; + var valueConverter = valueApiType != null && valueApiType.MarshalAs != null ? $"({GenerateCSharpNativeToManaged(buildData, valueApiType.MarshalAs, caller)})" : ""; + //return $"{{0}} != null ? System.Linq.Enumerable.ToDictionary({{0}}, x => {keyConverter}, x => {valueConverter}) : null"; + return $"{{0}}.ConvertDictionary(key => {keyConverter}key, value => {valueConverter}value)"; + } + } return string.Empty; default: - // Interface reference property - if (typeInfo.IsInterfaceRef) - return GenerateInterfaceRefToNative(buildData, typeInfo, caller, "{0}"); - var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) { @@ -873,24 +791,12 @@ namespace Flax.Build.Bindings } } #endif - const string interfaceResultName = "__interfaceResult"; - const string interfaceContainerResultName = "__interfaceContainerResult"; - - var returnInterfaceRef = !functionInfo.Glue.UseReferenceForResult && functionInfo.ReturnType.IsInterfaceRef; - var returnInterfaceRefContainer = !functionInfo.Glue.UseReferenceForResult && IsInterfaceRefContainer(functionInfo.ReturnType); + var returnType = functionInfo.ReturnType; if (functionInfo.Glue.UseReferenceForResult) { } - else if (returnInterfaceRef) - { - contents.Append("var ").Append(interfaceResultName).Append(" = "); - } - else if (returnInterfaceRefContainer) - { - contents.Append("var ").Append(interfaceContainerResultName).Append(" = "); - } - else if (!functionInfo.ReturnType.IsVoid) + else if (!returnType.IsVoid) { contents.Append("return "); } @@ -960,22 +866,27 @@ namespace Flax.Build.Bindings } contents.Append(')'); - if (returnInterfaceRef) - { - contents.Append("; return ").Append(GenerateInterfaceRefToManaged(buildData, functionInfo.ReturnType, caller, interfaceResultName, true)); - } - else if (returnInterfaceRefContainer) - { - contents.Append("; return ").Append(GenerateInterfaceRefContainerToManaged(buildData, functionInfo.ReturnType, caller, interfaceContainerResultName)); - } - else if ((functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer") && functionInfo.ReturnType.GenericArgs != null) + if ((returnType.Type == "Array" || returnType.Type == "Span" || returnType.Type == "DataContainer") && returnType.GenericArgs != null) { // Convert array that uses different type for marshalling - var arrayTypeInfo = functionInfo.ReturnType.GenericArgs[0]; + var arrayTypeInfo = returnType.GenericArgs[0]; var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); if (arrayApiType != null && arrayApiType.MarshalAs != null) contents.Append($".ConvertArray(x => ({GenerateCSharpNativeToManaged(buildData, arrayTypeInfo, caller)})x)"); } + else if (returnType.Type == "Dictionary" && returnType.GenericArgs != null && returnType.GenericArgs.Count == 2) + { + // Convert dictionary that uses different type for marshalling + var keyApiType = FindApiTypeInfo(buildData, returnType.GenericArgs[0], caller); + var valueApiType = FindApiTypeInfo(buildData, returnType.GenericArgs[1], caller); + if ((keyApiType != null && keyApiType.MarshalAs != null) || (valueApiType != null && valueApiType.MarshalAs != null)) + { + var keyConverter = keyApiType != null && keyApiType.MarshalAs != null ? $"({GenerateCSharpNativeToManaged(buildData, returnType.GenericArgs[0], caller)})" : ""; + var valueConverter = valueApiType != null && valueApiType.MarshalAs != null ? $"({GenerateCSharpNativeToManaged(buildData, returnType.GenericArgs[1], caller)})" : ""; + contents.Append($".ConvertDictionary(key => {keyConverter}key, value => {valueConverter}value)"); + } + } + contents.Append(';'); // Return result @@ -1104,13 +1015,6 @@ namespace Flax.Build.Bindings private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, MemberInfo memberInfo, bool useUnmanaged, string defaultValue = null, TypeInfo defaultValueType = null) { GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo, memberInfo.Attributes, memberInfo.Comment, true, useUnmanaged, defaultValue, memberInfo.DeprecatedMessage, defaultValueType); - var memberType = (memberInfo as FieldInfo)?.Type ?? (memberInfo as PropertyInfo)?.Type; - var interfaceRefType = GetInterfaceRefElementType(memberType); - if (interfaceRefType != null) - { - var attribute = interfaceRefType.Type == "SoftObjectInterfaceReference" ? "SoftObjectInterfaceReference" : "ScriptingObjectInterfaceReference"; - contents.Append(indent).Append("[FlaxEngine.").Append(attribute).AppendLine("]"); - } } private static bool GenerateCSharpStructureUseDefaultInitialize(BuildData buildData, StructureInfo structureInfo) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 56d8d9b27..56798f797 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -167,12 +167,12 @@ namespace Flax.Build.Bindings return $"Variant(StringView({value}))"; if (typeInfo.Type == "StringAnsi") return $"Variant(StringAnsiView({value}))"; - if (typeInfo.IsInterfaceRef) - return $"Variant({value}.GetObject())"; if (typeInfo.IsObjectRef) return $"Variant({value}.Get())"; if (typeInfo.Type == "SoftTypeReference") return $"Variant::Typename(StringAnsiView({value}))"; + if (typeInfo.Type == "ScriptingObjectInterfaceReference") + return $"Variant({value}.GetObject())"; if (typeInfo.IsArray) { var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo); @@ -307,14 +307,12 @@ namespace Flax.Build.Bindings return $"(StringAnsiView){value}"; if (typeInfo.IsPtr && typeInfo.IsConst && typeInfo.Type == "Char") return $"((StringView){value}).GetText()"; // (StringView)Variant, if not empty, is guaranteed to point to a null-terminated buffer. - if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") - return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; - if (typeInfo.IsInterfaceRef) - return $"ScriptingObject::ToInterface<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; if (typeInfo.IsObjectRef) - return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; + return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; if (typeInfo.Type == "SoftTypeReference") return $"(StringAnsiView){value}"; + if (typeInfo.Type == "ScriptingObjectInterfaceReference") + return $"ScriptingObjectInterfaceReference<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; if (typeInfo.IsArray) throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'."); if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) @@ -656,8 +654,8 @@ namespace Flax.Build.Bindings { CppIncludeFiles.Add("Engine/Scripting/Internal/ManagedDictionary.h"); type = "MObject*"; - var keyClass = typeInfo.GenericArgs[0].IsInterfaceRef ? "MCore::TypeCache::Object->GetType()" : GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[0], caller, functionInfo); - var valueClass = typeInfo.GenericArgs[1].IsInterfaceRef ? "MCore::TypeCache::Object->GetType()" : GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[1], caller, functionInfo); + var keyClass = GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[0], caller, functionInfo); + var valueClass = GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[1], caller, functionInfo); return "ManagedDictionary::ToManaged({0}, " + keyClass + ", " + valueClass + ")"; } @@ -801,19 +799,6 @@ namespace Flax.Build.Bindings type = "MObject*"; return "MUtils::ToNative({0})"; default: - // Interface reference property - if (typeInfo.IsInterfaceRef) - { - if (CppNonPodTypesConvertingGeneration) - { - type = "MObject*"; - return "ScriptingObject::ToInterface<" + typeInfo.GenericArgs[0].Type + ">(ScriptingObject::ToNative({0}))"; - } - - type = typeInfo.GenericArgs[0].Type + '*'; - return string.Empty; - } - // Object reference property if (typeInfo.IsObjectRef) { @@ -1023,8 +1008,8 @@ namespace Flax.Build.Bindings if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) { CppIncludeFiles.Add("Engine/Scripting/Internal/ManagedDictionary.h"); - var keyClass = typeInfo.GenericArgs[0].IsInterfaceRef ? "MCore::TypeCache::Object->GetType()" : GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[0], caller); - var valueClass = typeInfo.GenericArgs[1].IsInterfaceRef ? "MCore::TypeCache::Object->GetType()" : GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[1], caller); + var keyClass = GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[0], caller); + var valueClass = GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[1], caller); return $"ManagedDictionary::ToManaged({value}, {keyClass}, {valueClass})"; } diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs index 79ade2bfa..04836c636 100644 --- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs @@ -38,18 +38,10 @@ namespace Flax.Build.Bindings /// Gets a value indicating whether this type is a reference to another object. /// public bool IsObjectRef => (Type == "ScriptingObjectReference" || - Type == "ScriptingObjectInterfaceReference" || Type == "AssetReference" || Type == "WeakAssetReference" || Type == "SoftAssetReference" || - Type == "SoftObjectReference" || - Type == "SoftObjectInterfaceReference") && GenericArgs != null; - - /// - /// Gets a value indicating whether this type is a reference to another object filtered by interface. - /// - public bool IsInterfaceRef => (Type == "ScriptingObjectInterfaceReference" || - Type == "SoftObjectInterfaceReference") && GenericArgs != null; + Type == "SoftObjectReference") && GenericArgs != null; public TypeInfo() { From f83d853e5dd810f6226a697e8e0815db206253cb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Sep 2026 06:59:01 +0200 Subject: [PATCH 6/9] Fix default value in Editor when it's `null` for value-type --- Source/Editor/CustomEditors/Values/ValueContainer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs index c8d44b32a..b9bd66623 100644 --- a/Source/Editor/CustomEditors/Values/ValueContainer.cs +++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs @@ -297,6 +297,11 @@ namespace FlaxEditor.CustomEditors else if (Type.Type == typeof(long)) _defaultValue = Convert.ToInt64(_defaultValue); } + else if (_defaultValue == null && Type.IsValueType) + { + // Use zero value for value-types that have null as default value for some reason + _defaultValue = Type.CreateInstance(); + } } } if (instanceValues._hasReferenceValue) From 09f0477e181a3a87ea0f69b3c7ab42b5cf09ab93 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Sep 2026 06:59:52 +0200 Subject: [PATCH 7/9] Add auto-expand for items list with a single category in use --- Source/Editor/GUI/ItemsListContextMenu.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 5f09342c7..95a9a2164 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -52,7 +52,7 @@ namespace FlaxEditor.GUI public float SortScore; /// - /// Wether the query highlights should be draw. + /// Whether the query highlights should be drawn. /// public bool DrawHighlights = true; @@ -261,6 +261,11 @@ namespace FlaxEditor.GUI /// public readonly VerticalPanel ItemsPanel; + /// + /// Gets a list of panels with item categories. + /// + public IEnumerable CategoryPanels => (IEnumerable)_categoryPanels ?? Array.Empty(); + /// /// Initializes a new instance of the class. /// @@ -504,6 +509,12 @@ namespace FlaxEditor.GUI category.Visible = true; category.Close(false); } + + if (_categoryPanels.Count == 1 && items.Count == 1) + { + // Expand the only category if there are no items outside of it + _categoryPanels[0].Open(false); + } } _searchBox?.Clear(); From e5a6b73caa8f5680dab8d401e616f3d81e39bc96 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Sep 2026 07:00:24 +0200 Subject: [PATCH 8/9] Remove `SoftObjectInterfaceReference` #2746 --- Source/Engine/Serialization/ReadStream.h | 8 ------- Source/Engine/Serialization/Serialization.h | 23 ------------------- Source/Engine/Serialization/Stream.h | 2 -- Source/Engine/Serialization/WriteStream.h | 8 ------- Source/Engine/Tests/TestScripting.h | 4 +--- Source/Tools/Flax.Build/Bindings/ClassInfo.cs | 1 - 6 files changed, 1 insertion(+), 45 deletions(-) diff --git a/Source/Engine/Serialization/ReadStream.h b/Source/Engine/Serialization/ReadStream.h index d8868c67e..06c52b64f 100644 --- a/Source/Engine/Serialization/ReadStream.h +++ b/Source/Engine/Serialization/ReadStream.h @@ -149,14 +149,6 @@ public: v.Set(*(Guid*)id); } - template - FORCE_INLINE void Read(SoftObjectInterfaceReference& v) - { - uint32 id[4]; - ReadBytes(id, sizeof(id)); - v.Set(*(Guid*)id); - } - template FORCE_INLINE void Read(AssetReference& v) { diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index cc27d1726..b588a5d78 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -18,8 +18,6 @@ class ScriptingObjectInterfaceReference; template class SoftObjectReference; template -class SoftObjectInterfaceReference; -template class AssetReference; template class WeakAssetReference; @@ -550,27 +548,6 @@ namespace Serialization v = id; } - // Soft Object Interface Reference - - template - inline bool ShouldSerialize(const SoftObjectInterfaceReference& v, const void* otherObj) - { - return !otherObj || ShouldSerializeRef(v.GetObject(), ((SoftObjectInterfaceReference*)otherObj)->GetObject()); - } - template - inline void Serialize(ISerializable::SerializeStream& stream, const SoftObjectInterfaceReference& v, const void* otherObj) - { - stream.Guid(v.GetID()); - } - template - inline void Deserialize(ISerializable::DeserializeStream& stream, SoftObjectInterfaceReference& v, ISerializeModifier* modifier) - { - Guid id; - Deserialize(stream, id, modifier); - modifier->IdsMapping.TryGet(id, id); - v = id; - } - // Asset Reference template diff --git a/Source/Engine/Serialization/Stream.h b/Source/Engine/Serialization/Stream.h index e6dd3a42b..4e517effe 100644 --- a/Source/Engine/Serialization/Stream.h +++ b/Source/Engine/Serialization/Stream.h @@ -21,8 +21,6 @@ class ScriptingObjectInterfaceReference; template class SoftObjectReference; template -class SoftObjectInterfaceReference; -template class AssetReference; template class WeakAssetReference; diff --git a/Source/Engine/Serialization/WriteStream.h b/Source/Engine/Serialization/WriteStream.h index 4e31b37cc..a5130ed8e 100644 --- a/Source/Engine/Serialization/WriteStream.h +++ b/Source/Engine/Serialization/WriteStream.h @@ -169,14 +169,6 @@ public: Write(v.Get()); } - template - FORCE_INLINE void Write(const SoftObjectInterfaceReference& v) - { - uint32 id[4]; - v.CopyID(id); - WriteBytes(id, sizeof(id)); - } - template FORCE_INLINE void Write(const AssetReference& v) { diff --git a/Source/Engine/Tests/TestScripting.h b/Source/Engine/Tests/TestScripting.h index 0a764c89f..70f443346 100644 --- a/Source/Engine/Tests/TestScripting.h +++ b/Source/Engine/Tests/TestScripting.h @@ -7,7 +7,6 @@ #include "Engine/Core/Collections/Array.h" #include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingObjectInterfaceReference.h" -#include "Engine/Scripting/SoftObjectInterfaceReference.h" #include "Engine/Scripting/SerializableScriptingObject.h" #include "Engine/Scripting/SoftTypeReference.h" #include "Engine/Content/SceneReference.h" @@ -179,10 +178,9 @@ public: // Test struct API_FIELD() TestStruct SimpleStruct; + // Test interface reference API_FIELD() ScriptingObjectInterfaceReference InterfaceRef; - // Test soft interface reference - API_FIELD() SoftObjectInterfaceReference SoftInterfaceRef; // Test event API_EVENT() Delegate&, Array&> SimpleEvent; diff --git a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs index 46097a218..36c47138e 100644 --- a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs @@ -19,7 +19,6 @@ namespace Flax.Build.Bindings "PersistentScriptingObject", "ScriptingObjectReference", "ScriptingObjectInterfaceReference", - "SoftObjectInterfaceReference", "AssetReference", "BinaryAsset", "SceneObject", From c43cfef0a9ec8f74a7101ee9c5f046502b7ca243 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Sep 2026 07:00:55 +0200 Subject: [PATCH 9/9] Add tests for interface reference marshaling #2746 --- Source/Engine/Tests/TestScripting.cpp | 15 +++++++++++--- Source/Engine/Tests/TestScripting.cs | 28 +++++++++++++++++++++++++++ Source/Engine/Tests/TestScripting.h | 19 ++++++++++++++++++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Tests/TestScripting.cpp b/Source/Engine/Tests/TestScripting.cpp index 506291b36..8a3541def 100644 --- a/Source/Engine/Tests/TestScripting.cpp +++ b/Source/Engine/Tests/TestScripting.cpp @@ -37,9 +37,7 @@ TEST_CASE("Scripting") MMethod* method = klass->GetMethod("TestLibraryImports"); CHECK(method); MObject* result = method->Invoke(nullptr, nullptr, nullptr); - CHECK(result); - int32 resultValue = MUtils::Unbox(result); - CHECK(resultValue == 0); + CHECK(MUtils::Unbox(result) == 0); } SECTION("Test Class") @@ -167,4 +165,15 @@ TEST_CASE("Scripting") CHECK(interfaceObject); CHECK(interfaceObject == object); } + + SECTION("Test Interface Reference") + { + // Test native interface implementation + MClass* klass = Scripting::FindClass("FlaxEngine.Tests.TestScripting"); + CHECK(klass); + MMethod* method = klass->GetMethod("TestInterfaceReference"); + CHECK(method); + MObject* result = method->Invoke(nullptr, nullptr, nullptr); + CHECK(MUtils::Unbox(result) == 0); + } } diff --git a/Source/Engine/Tests/TestScripting.cs b/Source/Engine/Tests/TestScripting.cs index edbe18016..5846b569d 100644 --- a/Source/Engine/Tests/TestScripting.cs +++ b/Source/Engine/Tests/TestScripting.cs @@ -2,6 +2,7 @@ #if FLAX_TESTS using System; +using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices; @@ -42,6 +43,33 @@ namespace FlaxEngine.Tests NativeLibrary.Free(library); return result; } + + /// + /// Tests usage with marshalling. + /// + public static int TestInterfaceReference() + { + var native = new TestClassNative(); + native.InterfaceRef = native; + var returned = native.InterfaceRef; + if (returned != native) + return 1; + returned = native.TestPassInterface(native); + if (returned != native) + return 2; + returned = native.TestPassInterfaceArray(new ScriptingObjectInterfaceReference[1] { native })[0]; + if (returned != native) + return 3; + var dic = new Dictionary>(); + dic.Add("key", native); + returned = native.TestPassInterfaceDictionary(dic)["key"]; + if (returned != native) + return 4; + var res = returned.Interface.TestInterfaceMethod("123"); + if (res != 3) + return 5; + return 0; + } } } diff --git a/Source/Engine/Tests/TestScripting.h b/Source/Engine/Tests/TestScripting.h index 70f443346..e9440bb03 100644 --- a/Source/Engine/Tests/TestScripting.h +++ b/Source/Engine/Tests/TestScripting.h @@ -5,6 +5,7 @@ #include "Engine/Core/ISerializable.h" #include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingObjectInterfaceReference.h" #include "Engine/Scripting/SerializableScriptingObject.h" @@ -204,6 +205,24 @@ public: // Test nameless arguments API_FUNCTION() void TestNamelessArguments(int32, float, bool){} + // Test pass interface ref in function + API_FUNCTION() ScriptingObjectInterfaceReference TestPassInterface(ScriptingObjectInterfaceReference param1) const + { + return param1; + } + + // Test pass interface ref array in function + API_FUNCTION() Array> TestPassInterfaceArray(Array> param1) const + { + return param1; + } + + // Test pass interface ref dictionary in function + API_FUNCTION() Dictionary> TestPassInterfaceDictionary(Dictionary> param1) const + { + return param1; + } + int32 TestInterfaceMethod(const String& str) override { return str.Length();