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