From a4a99cf32091690aa69fe6006b8a4dfc77db4a23 Mon Sep 17 00:00:00 2001 From: Saas Date: Mon, 23 Mar 2026 12:47:18 +0100 Subject: [PATCH 01/35] add shortcut to show editor windows content (Ctrl + 0) --- Source/Editor/Options/InputOptions.cs | 4 ++++ Source/Editor/Surface/SurfaceUtils.cs | 3 ++- Source/Editor/Windows/Assets/PreviewsCacheWindow.cs | 2 +- Source/Editor/Windows/Assets/SpriteAtlasWindow.cs | 4 +++- Source/Editor/Windows/Assets/TextureWindow.cs | 4 +++- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index 9b15c0c09..3eca3677b 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -123,6 +123,10 @@ namespace FlaxEditor.Options [EditorDisplay("Common"), EditorOrder(200)] public InputBinding LockFocusSelection = new InputBinding(KeyboardKeys.F, KeyboardKeys.Shift); + [DefaultValue(typeof(InputBinding), "Ctrl+Alpha0")] + [EditorDisplay("Common"), EditorOrder(201)] + public InputBinding ShowEditorContents = new InputBinding(KeyboardKeys.Alpha0, KeyboardKeys.Control); + [DefaultValue(typeof(InputBinding), "Ctrl+F")] [EditorDisplay("Common"), EditorOrder(210)] public InputBinding Search = new InputBinding(KeyboardKeys.F, KeyboardKeys.Control); diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs index 7a19567fa..71315695b 100644 --- a/Source/Editor/Surface/SurfaceUtils.cs +++ b/Source/Editor/Surface/SurfaceUtils.cs @@ -579,7 +579,7 @@ namespace FlaxEditor.Surface redoButton = toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip("Redo", ref inputOptions.Redo); toolStrip.AddSeparator(); toolStrip.AddButton(editor.Icons.Search64, showSearch).LinkTooltip("Open content search tool", ref inputOptions.Search); - toolStrip.AddButton(editor.Icons.CenterView64, surface.ShowWholeGraph).LinkTooltip("Show whole graph"); + toolStrip.AddButton(editor.Icons.CenterView64, surface.ShowWholeGraph).LinkTooltip("Show whole graph", ref inputOptions.ShowEditorContents); var gridSnapButton = toolStrip.AddButton(editor.Icons.Grid32, surface.ToggleGridSnapping); gridSnapButton.LinkTooltip("Toggle grid snapping for nodes."); gridSnapButton.AutoCheck = true; @@ -590,6 +590,7 @@ namespace FlaxEditor.Surface window.InputActions.Add(options => options.Undo, undo.PerformUndo); window.InputActions.Add(options => options.Redo, undo.PerformRedo); window.InputActions.Add(options => options.Search, showSearch); + window.InputActions.Add(options => options.ShowEditorContents, surface.ShowWholeGraph); } } } diff --git a/Source/Editor/Windows/Assets/PreviewsCacheWindow.cs b/Source/Editor/Windows/Assets/PreviewsCacheWindow.cs index 1e6e3e863..8e5db9e08 100644 --- a/Source/Editor/Windows/Assets/PreviewsCacheWindow.cs +++ b/Source/Editor/Windows/Assets/PreviewsCacheWindow.cs @@ -28,7 +28,7 @@ namespace FlaxEditor.Windows.Assets }; // Toolstrip - _toolstrip.AddButton(editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view"); + _toolstrip.AddButton(editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view", ref editor.Options.Options.Input.ShowEditorContents); } /// diff --git a/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs b/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs index 94deb18c0..b7cf16219 100644 --- a/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs +++ b/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs @@ -326,7 +326,9 @@ namespace FlaxEditor.Windows.Assets _propertiesEditor.BuildLayout(); }).LinkTooltip("Add a new sprite"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view"); + _toolstrip.AddButton(editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view", ref editor.Options.Options.Input.ShowEditorContents); + + InputActions.Add(options => options.ShowEditorContents, _preview.CenterView); } /// diff --git a/Source/Editor/Windows/Assets/TextureWindow.cs b/Source/Editor/Windows/Assets/TextureWindow.cs index 865099aea..a77c5a742 100644 --- a/Source/Editor/Windows/Assets/TextureWindow.cs +++ b/Source/Editor/Windows/Assets/TextureWindow.cs @@ -260,9 +260,11 @@ namespace FlaxEditor.Windows.Assets _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save", ref inputOptions.Save); _toolstrip.AddButton(Editor.Icons.Import64, () => Editor.ContentImporting.Reimport((BinaryAssetItem)Item)).LinkTooltip("Reimport"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(Editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view"); + _toolstrip.AddButton(Editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view", ref editor.Options.Options.Input.ShowEditorContents); _toolstrip.AddSeparator(); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/textures/index.html")).LinkTooltip("See documentation to learn more"); + + InputActions.Add(options => options.ShowEditorContents, _preview.CenterView); } /// From 2c91cca1b481b6904179df426136a54a90c6a4d7 Mon Sep 17 00:00:00 2001 From: Saas Date: Thu, 14 May 2026 19:51:23 +0200 Subject: [PATCH 02/35] auto scroll the content panel navigation bar to the current folder --- Source/Editor/Windows/ContentWindow.Navigation.cs | 11 +++++++++++ Source/Editor/Windows/ContentWindow.cs | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/Source/Editor/Windows/ContentWindow.Navigation.cs b/Source/Editor/Windows/ContentWindow.Navigation.cs index 4c7373aa6..265f9d524 100644 --- a/Source/Editor/Windows/ContentWindow.Navigation.cs +++ b/Source/Editor/Windows/ContentWindow.Navigation.cs @@ -257,6 +257,17 @@ namespace FlaxEditor.Windows _navigationBar.IsLayoutLocked = wasLayoutLocked; _navigationBar.PerformLayout(); UpdateNavigationBarBounds(); + + ScrollNavigationBarToCurrentFolder(); + } + + private void ScrollNavigationBarToCurrentFolder() + { + if (_navigationBar != null && _navigationBar.ChildrenCount != 0 && _navigationBar.HScrollBar.Visible) + { + _navigationBar.HScrollBar.TargetValue = _navigationBar.HScrollBar.Maximum; + _navigationBar.HScrollBar.FastScroll(); + } } /// diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 5a73530bc..7014732ac 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -1774,6 +1774,14 @@ namespace FlaxEditor.Windows return base.OnMouseUp(location, button); } + /// + protected override void OnSizeChanged() + { + base.OnSizeChanged(); + + ScrollNavigationBarToCurrentFolder(); + } + /// protected override void PerformLayoutBeforeChildren() { From e6d981036dd75290754242252455c9743a2d25c8 Mon Sep 17 00:00:00 2001 From: Saas Date: Thu, 14 May 2026 23:41:41 +0200 Subject: [PATCH 03/35] scroll on editor start --- Source/Editor/Windows/ContentWindow.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 7014732ac..42bbb8f27 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -559,7 +559,7 @@ namespace FlaxEditor.Windows } /// - /// Enables or disables vertical and horizontal scrolling on the content tree panel + /// Enables or disables vertical and horizontal scrolling on the content tree panel /// /// The state to set scrolling to public void ScrollingOnTreeView(bool enabled) @@ -571,7 +571,7 @@ namespace FlaxEditor.Windows } /// - /// Enables or disables vertical and horizontal scrolling on the content view panel + /// Enables or disables vertical and horizontal scrolling on the content view panel /// /// The state to set scrolling to public void ScrollingOnContentView(bool enabled) @@ -1555,6 +1555,7 @@ namespace FlaxEditor.Windows PerformLayout(); } UpdateViewDropdownBounds(); + ScrollNavigationBarToCurrentFolder(); } } From f8d436ca7522baaac035a994905b4956851ebdfb Mon Sep 17 00:00:00 2001 From: Andrei Gagua Date: Sun, 24 May 2026 10:12:31 +0300 Subject: [PATCH 04/35] 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 05/35] 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 06/35] 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 07/35] 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 41520bb37724e72f4ae6938f626bcc7fa20bcc49 Mon Sep 17 00:00:00 2001 From: Andrei Gagua Date: Tue, 7 Jul 2026 11:50:34 +0300 Subject: [PATCH 08/35] Fix floating window ordering and focus restoration Keep minimizable macOS regular windows as independent top-level windows instead of Cocoa child windows while preserving their logical parent relationship. --- Source/Engine/Platform/Mac/MacWindow.cpp | 97 +++++++++++++++---- .../Engine/Platform/Windows/WindowsWindow.cpp | 21 ++-- 2 files changed, 91 insertions(+), 27 deletions(-) diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp index b98fb46ec..514a4ca67 100644 --- a/Source/Engine/Platform/Mac/MacWindow.cpp +++ b/Source/Engine/Platform/Mac/MacWindow.cpp @@ -39,6 +39,62 @@ inline bool IsWindowInvalid(Window* win) return !hasWindow || !win; } +inline Window* GetEngineWindow(NSWindow* nativeWindow) +{ + WindowsManager::WindowsLocker.Lock(); + for (auto* window : WindowsManager::Windows) + { + if (window && window->GetNativePtr() == nativeWindow) + { + WindowsManager::WindowsLocker.Unlock(); + return window; + } + } + WindowsManager::WindowsLocker.Unlock(); + return nullptr; +} + +inline bool ShouldAttachToParent(const CreateWindowSettings& settings) +{ + // Cocoa child windows inherit parent z-order and minimize behavior. + // Minimizable regular windows need independent top-level window semantics. + return settings.Parent && (settings.Type != WindowType::Regular || !settings.AllowMinimize); +} + +inline bool CanMakeKeyWindow(NSWindow* window) +{ + return window && [window isVisible] && ![window isMiniaturized] && [window canBecomeKeyWindow]; +} + +inline bool IsRegularWindowWithParent(Window* window, Window* parent) +{ + return window && + !window->IsClosed() && + window->GetSettings().Type == WindowType::Regular && + window->GetSettings().Parent == parent; +} + +inline void MakeNextParentedWindowKey(NSWindow* hiddenWindow, Window* fallbackParent) +{ + if (!fallbackParent || IsWindowInvalid(fallbackParent)) + return; + + for (NSWindow* candidate in [NSApp orderedWindows]) + { + if (candidate == hiddenWindow || !CanMakeKeyWindow(candidate)) + continue; + Window* candidateWindow = GetEngineWindow(candidate); + if (!IsRegularWindowWithParent(candidateWindow, fallbackParent)) + continue; + [candidate makeKeyAndOrderFront:nil]; + return; + } + + NSWindow* parent = (NSWindow*)fallbackParent->GetNativePtr(); + if (parent != hiddenWindow && CanMakeKeyWindow(parent)) + [parent makeKeyAndOrderFront:nil]; +} + KeyboardKeys GetKey(NSEvent* event) { switch ([event keyCode]) @@ -963,7 +1019,7 @@ void MacWindow::Show() // Show NSWindow* window = (NSWindow*)_window; - if (_settings.Parent) + if (ShouldAttachToParent(_settings)) { NSWindow* parent = (NSWindow*)_settings.Parent->GetNativePtr(); [parent addChildWindow:window ordered:NSWindowAbove]; @@ -989,18 +1045,17 @@ void MacWindow::Hide() // Hide (order out doesn't work for miniaturized windows) NSWindow* window = (NSWindow*)_window; - const BOOL wasKey = [window isKeyWindow]; + const BOOL wasKey = [window isKeyWindow] || [NSApp keyWindow] == window; if ([window isMiniaturized]) [window close]; else [window orderOut:nil]; - - // Transfer focus back to the parent when hiding popup - if (_settings.Parent && wasKey && _settings.Type != WindowType::Popup && _settings.Type != WindowType::Tooltip) - { - NSWindow* parent = (NSWindow*)_settings.Parent->GetNativePtr(); - [parent makeKeyAndOrderFront:nil]; - } + const bool shouldRestoreFocus = _settings.Parent && + _settings.Type == WindowType::Regular && + [NSApp isActive] && + (wasKey || [NSApp keyWindow] == nil); + if (!IsClosed() && shouldRestoreFocus) + MakeNextParentedWindowKey(window, _settings.Parent); // Base WindowBase::Hide(); @@ -1009,23 +1064,27 @@ void MacWindow::Hide() void MacWindow::Close(ClosingReason reason) { - const BOOL wasKey = _window && [(NSWindow*)_window isKeyWindow]; + NSWindow* window = (NSWindow*)_window; + const BOOL shouldRestoreFocus = window && + _settings.Parent && + _settings.Type == WindowType::Regular && + [NSApp isActive] && + ([window isKeyWindow] || [NSApp keyWindow] == window); + WindowBase::Close(reason); // Closing can be cancelled by managed Window.Closing handlers. if (!IsClosed()) return; - if (NSWindow* window = (NSWindow*)_window) - { + if (window) [window close]; - } - - if (_settings.Parent && wasKey && _settings.Type != WindowType::Popup && _settings.Type != WindowType::Tooltip) - { - NSWindow* parent = (NSWindow*)_settings.Parent->GetNativePtr(); - [parent makeKeyAndOrderFront:nil]; - } + const bool hasNoKeyWindow = _settings.Parent && + _settings.Type == WindowType::Regular && + [NSApp isActive] && + [NSApp keyWindow] == nil; + if (shouldRestoreFocus || hasNoKeyWindow) + MakeNextParentedWindowKey(window, _settings.Parent); } void MacWindow::Minimize() diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index 3a1cf0dd3..902950133 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -362,6 +362,13 @@ void WindowsWindow::BringToFront(bool force) { ASSERT(HasHWND()); + HWND hWndInsertAfter = HWND_TOP; + uint32 flags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER; + if (_settings.IsTopmost) + { + hWndInsertAfter = HWND_TOPMOST; + } + if (_settings.Type == WindowType::Regular) { if (IsIconic(_handle)) @@ -372,22 +379,15 @@ void WindowsWindow::BringToFront(bool force) { SetActiveWindow(_handle); } + SetWindowPos(_handle, hWndInsertAfter, 0, 0, 0, 0, flags); } else { - HWND hWndInsertAfter = HWND_TOP; - uint32 flags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER; - if (!force) { flags |= SWP_NOACTIVATE; } - if (_settings.IsTopmost) - { - hWndInsertAfter = HWND_TOPMOST; - } - SetWindowPos(_handle, hWndInsertAfter, 0, 0, 0, 0, flags); } } @@ -614,6 +614,7 @@ void WindowsWindow::SetOpacity(const float opacity) void WindowsWindow::Focus() { ASSERT(HasHWND()); + BringToFront(); if (GetFocus() != _handle) { SetFocus(_handle); @@ -1130,6 +1131,10 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) } } break; + case WM_MOUSEACTIVATE: + if (_settings.Type == WindowType::Regular) + BringToFront(); + break; case WM_CREATE: return 0; case WM_SIZE: From e9707d86076c406a36fa57d0f484e1b494c6be4c Mon Sep 17 00:00:00 2001 From: Jake Young Date: Sun, 26 Jul 2026 03:42:48 -0400 Subject: [PATCH 09/35] Update various actor bounds naming and deprecate deprecated methods. --- Source/Engine/Level/Actor.cpp | 25 +++++++++++++++---- Source/Engine/Level/Actor.h | 46 +++++++++++++++++++++++++++++++---- 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 8ce79fea4..161fbd53c 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1429,9 +1429,14 @@ void Actor::OnLayerChanged() BoundingBox Actor::GetBoxWithChildren() const { - BoundingBox result = GetBox(); + return GetBoundingBoxWithChildren(); +} + +BoundingBox Actor::GetBoundingBoxWithChildren() const +{ + BoundingBox result = GetBoundingBox(); for (int32 i = 0; i < Children.Count(); i++) - BoundingBox::Merge(result, Children.Get()[i]->GetBoxWithChildren(), result); + BoundingBox::Merge(result, Children.Get()[i]->GetBoundingBoxWithChildren(), result); return result; } @@ -1439,14 +1444,24 @@ BoundingBox Actor::GetBoxWithChildren() const BoundingBox Actor::GetEditorBox() const { - return GetBox(); + return GetEditorBoundingBox(); +} + +BoundingBox Actor::GetEditorBoundingBox() const +{ + return GetBoundingBox(); } BoundingBox Actor::GetEditorBoxChildren() const { - BoundingBox result = GetEditorBox(); + return GetEditorBoundingBoxWithChildren(); +} + +BoundingBox Actor::GetEditorBoundingBoxWithChildren() const +{ + BoundingBox result = GetEditorBoundingBox(); for (int32 i = 0; i < Children.Count(); i++) - BoundingBox::Merge(result, Children.Get()[i]->GetEditorBoxChildren(), result); + BoundingBox::Merge(result, Children.Get()[i]->GetEditorBoundingBoxWithChildren(), result); return result; } diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index a1ca01cc6..072077fe4 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -689,35 +689,71 @@ public: public: /// /// Gets actor bounding sphere that defines 3D space intersecting with the actor (for determination of the visibility for actor). + /// [DEPRECATED IN 1.13] /// - API_PROPERTY() FORCE_INLINE const BoundingSphere& GetSphere() const + API_PROPERTY() DEPRECATED("Use GetBoundingSphere instead.") FORCE_INLINE const BoundingSphere& GetSphere() const + { + return _sphere; + } + + /// + /// Gets actor bounding sphere that defines 3D space intersecting with the actor (for determination of the visibility for actor). + /// + API_PROPERTY() FORCE_INLINE const BoundingSphere& GetBoundingSphere() const { return _sphere; } /// /// Gets actor bounding box that defines 3D space intersecting with the actor (for determination of the visibility for actor). + /// [DEPRECATED IN 1.13] /// - API_PROPERTY() FORCE_INLINE const BoundingBox& GetBox() const + API_PROPERTY() DEPRECATED("Use GetBoundingBox instead.") FORCE_INLINE const BoundingBox& GetBox() const + { + return _box; + } + + /// + /// Gets actor bounding box that defines 3D space intersecting with the actor (for determination of the visibility for actor). + /// + API_PROPERTY() FORCE_INLINE const BoundingBox& GetBoundingBox() const { return _box; } /// /// Gets actor bounding box of the actor including all child actors (children included in recursive way) + /// [DEPRECATED IN 1.13] /// - API_PROPERTY() BoundingBox GetBoxWithChildren() const; + API_PROPERTY() DEPRECATED("Use GetBoundingBoxWithChildren instead.") BoundingBox GetBoxWithChildren() const; + + /// + /// Gets actor bounding box of the actor including all child actors (children included in recursive way) + /// + API_PROPERTY() BoundingBox GetBoundingBoxWithChildren() const; #if USE_EDITOR /// /// Gets actor bounding box (single actor, no children included) for editor tools. + /// [DEPRECATED IN 1.13] /// - API_PROPERTY() virtual BoundingBox GetEditorBox() const; + API_PROPERTY() DEPRECATED("Use GetEditorBoundingBox instead.") virtual BoundingBox GetEditorBox() const; + + /// + /// Gets actor bounding box (single actor, no children included) for editor tools. + /// + API_PROPERTY() virtual BoundingBox GetEditorBoundingBox() const; /// /// Gets actor bounding box of the actor including all child actors for editor tools. + /// [DEPRECATED IN 1.13] /// - API_PROPERTY() BoundingBox GetEditorBoxChildren() const; + API_PROPERTY() DEPRECATED("Use GetEditorBoundingBoxWithChildren instead.") BoundingBox GetEditorBoxChildren() const; + + /// + /// Gets actor bounding box of the actor including all child actors for editor tools. + /// + API_PROPERTY() BoundingBox GetEditorBoundingBoxWithChildren() const; #endif /// From 8d95979846eb250c3079c280349bc5ff42283cef Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Tue, 18 Aug 2026 00:50:51 +0300 Subject: [PATCH 10/35] Hide tooltips when mouse buttons are pressed --- Source/Engine/UI/GUI/Tooltip.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index ea3c27658..31429ec3c 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -242,6 +242,12 @@ namespace FlaxEngine.GUI if (_window) _window.Position = mousePos + TooltipOffset; } + + if (Input.Mouse.IsAnyButtonDown || Math.Abs(Input.Mouse.ScrollDelta) > Mathf.Epsilon) + { + // Hide popup when buttons are pressed + Hide(); + } base.Update(deltaTime); } From 9a3623420cdf329695bdb480561c308fd0c30515 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 30 Aug 2026 22:30:32 +0200 Subject: [PATCH 11/35] Revert mistake change from d99631ddbf1a2ced518e2cff98ba80f483e36261 --- Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index 54dbcc250..de0fa755c 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -136,7 +136,7 @@ namespace Flax.Build /// /// The maximum SDK version. /// - public static Version MaximumVersion => new Version(8, 0); + public static Version MaximumVersion => new Version(10, 0); /// public override TargetPlatform[] Platforms From 5e32e40c73853fa197393966191525a2136a1fdc Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Wed, 2 Sep 2026 21:04:22 +0300 Subject: [PATCH 12/35] Improve Rider projects C++ preprocessor definitions All known preprocessor definitions from the compiler are now exposed to Rider language server through MSBuild .props files in order to fix most error squiggles caused by missing or invalid definitions. This should fix some common errors like unable to resolve C++ and C standard library types and macros like `assert`. --- .../VisualStudio/VCProjectGenerator.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs index a4d772e0d..53a6ab243 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs @@ -386,7 +386,9 @@ namespace Flax.Build.Projects.VisualStudio // Override MSBuild .targets file with one that runs NMake commands (workaround for Rider not finding "Microsoft.Cpp.Default.props" file) var cppTargetsFileContent = new StringBuilder(); + cppTargetsFileContent.AppendLine(""); cppTargetsFileContent.AppendLine(""); + cppTargetsFileContent.AppendLine(" "); cppTargetsFileContent.AppendLine(" "); cppTargetsFileContent.AppendLine(" "); cppTargetsFileContent.AppendLine(" "); @@ -408,10 +410,48 @@ namespace Flax.Build.Projects.VisualStudio cppTargetsFileContent.AppendLine(string.Format(" {0}", debuggerWorkingDirectory)); cppTargetsFileContent.AppendLine(" "); cppTargetsFileContent.AppendLine(""); + + // Workarounds for Rider language server to not show errors over well known symbols + Dictionary preprocessor = new Dictionary() + { + {"__attribute__(x)", ""}, + {"__null", "0"}, + {"__extension__", ""}, + {"__asm__", "__asm"}, + {"_SIZE_T", "1"}, + }; + // Undefine hardcoded MSVC definitions + HashSet undefine = new HashSet() + { + "_MSC_VER", + "_MSC_EXTENSIONS", + "_M_IX86", + "_WIN32", + }; + + // Expose all known preprocessor definitions known by the compiler to language server + var clangDefinitions = Utilities.ReadProcessOutput("clang++", "-dM -E -x c++ /dev/null"); + foreach (var (def, val) in clangDefinitions.Split('\n').Select(x => (x.Split(' ')[1], x.Split(' ')[2]))) + preprocessor.Add(def, val); + + var workaroundsFileContent = new StringBuilder(); + workaroundsFileContent.AppendLine($""); + workaroundsFileContent.AppendLine($""); + workaroundsFileContent.AppendLine($" "); + workaroundsFileContent.AppendLine($" "); + workaroundsFileContent.AppendLine($" {string.Join(' ', undefine.Select(x => $"/U {x}"))} %(AdditionalOptions)"); + workaroundsFileContent.AppendLine($" "); + workaroundsFileContent.AppendLine($" "); + workaroundsFileContent.AppendLine($" "); + workaroundsFileContent.AppendLine($" /usr/include/c++/16/aarch64-redhat-linux/;$(NMakeIncludeSearchPath)"); + workaroundsFileContent.AppendLine($" {string.Join(';', preprocessor.Select(x => $"{x.Key}={x.Value}"))};$(NMakePreprocessorDefinitions)"); + workaroundsFileContent.AppendLine($" "); + workaroundsFileContent.AppendLine($""); Utilities.WriteFileIfChanged(Path.Combine(projectDirectory, "Microsoft.Cpp.targets"), cppTargetsFileContent.ToString()); Utilities.WriteFileIfChanged(Path.Combine(projectDirectory, "Microsoft.Cpp.Default.props"), vcUserFileContent.ToString()); Utilities.WriteFileIfChanged(Path.Combine(projectDirectory, "Microsoft.Cpp.props"), vcUserFileContent.ToString()); + Utilities.WriteFileIfChanged(Path.Combine(projectDirectory, "Rider.Workarounds.Cpp.props"), workaroundsFileContent.ToString()); } // Save the files From a630575ab6e1c3ea19fefef8cf6bbaebb0e69e09 Mon Sep 17 00:00:00 2001 From: Andrei Gagua Date: Thu, 10 Sep 2026 06:25:26 +0200 Subject: [PATCH 13/35] Move out `ManagedDictionary` impl from header file #4120 --- .../Scripting/Internal/ManagedDictionary.cpp | 163 ++++++++++++++++++ .../Scripting/Internal/ManagedDictionary.h | 162 +---------------- Source/Engine/Scripting/ManagedCLR/MUtils.cpp | 3 + 3 files changed, 172 insertions(+), 156 deletions(-) diff --git a/Source/Engine/Scripting/Internal/ManagedDictionary.cpp b/Source/Engine/Scripting/Internal/ManagedDictionary.cpp index d2f74e054..b3de77e85 100644 --- a/Source/Engine/Scripting/Internal/ManagedDictionary.cpp +++ b/Source/Engine/Scripting/Internal/ManagedDictionary.cpp @@ -3,6 +3,12 @@ #include "ManagedDictionary.h" #if USE_CSHARP +#include "Engine/Scripting/BinaryModule.h" +#include "Engine/Scripting/ManagedCLR/MClass.h" +#include "Engine/Scripting/ManagedCLR/MMethod.h" +#include "Engine/Scripting/ManagedCLR/MAssembly.h" +#include "Engine/Scripting/ManagedCLR/MException.h" +#include "Engine/Scripting/Internal/StdTypesContainer.h" Dictionary ManagedDictionary::CachedTypes; #if !USE_MONO_AOT ManagedDictionary::MakeGenericTypeThunk ManagedDictionary::MakeGenericType; @@ -15,4 +21,161 @@ 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..1707aa6d4 100644 --- a/Source/Engine/Scripting/Internal/ManagedDictionary.h +++ b/Source/Engine/Scripting/Internal/ManagedDictionary.h @@ -5,13 +5,7 @@ #include "Engine/Core/Log.h" #include "Engine/Scripting/Scripting.h" #if USE_CSHARP -#include "Engine/Scripting/BinaryModule.h" #include "Engine/Scripting/ManagedCLR/MUtils.h" -#include "Engine/Scripting/ManagedCLR/MClass.h" -#include "Engine/Scripting/ManagedCLR/MMethod.h" -#include "Engine/Scripting/ManagedCLR/MAssembly.h" -#include "Engine/Scripting/ManagedCLR/MException.h" -#include "Engine/Scripting/Internal/StdTypesContainer.h" #include "Engine/Core/Collections/Dictionary.h" /// @@ -57,53 +51,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 +102,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/Engine/Scripting/ManagedCLR/MUtils.cpp b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp index 5c0ad1676..67efbcb97 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp @@ -3,6 +3,8 @@ #include "MUtils.h" #include "MClass.h" #include "MCore.h" +#include "MMethod.h" +#include "MAssembly.h" #include "Engine/Core/Log.h" #include "Engine/Core/Types/DataContainer.h" #include "Engine/Core/Types/Version.h" @@ -19,6 +21,7 @@ #include "Engine/Core/Math/Ray.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/ScriptingObject.h" +#include "Engine/Scripting/BinaryModule.h" #include "Engine/Scripting/Internal/StdTypesContainer.h" #include "Engine/Scripting/Internal/ManagedDictionary.h" #include "Engine/Utilities/StringConverter.h" From 769694ff9b2fede919e5b9325fc8a21893eafd5b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 10 Sep 2026 13:34:26 +0200 Subject: [PATCH 14/35] Fix flickering on multi-windows on macOS regression from #4141 --- .../GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp index b259ed688..3bc38a765 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp @@ -135,6 +135,7 @@ GPUTextureView* GPUSwapChainVulkan::GetBackBufferView() PROFILE_CPU(); auto context = _device->MainContext; auto cmdBufferManager = context->GetCmdBufferManager(); + // Keep commands recorded before acquire independent from the image-acquired semaphore wait below. if (cmdBufferManager->HasPendingActiveCmdBuffer()) context->Flush(); @@ -152,7 +153,6 @@ GPUTextureView* GPUSwapChainVulkan::GetBackBufferView() acquiredBackBuffer.WaitForSubmit(); const auto backBuffer = &_backBuffers[_acquiredImageIndex].Handle; - auto cmdBuffer = cmdBufferManager->GetCmdBuffer(); // Transition to render target (typical usage in most cases when calling backbuffer getter) @@ -171,6 +171,17 @@ GPUTextureView* GPUSwapChainVulkan::GetBackBufferView() void GPUSwapChainVulkan::Begin(RenderTask* task) { GPUSwapChain::Begin(task); + + // Wait for the backbuffer to be available + if (_currentImageIndex != -1) + { + auto& backBuffer = _backBuffers[_currentImageIndex]; + if (backBuffer.SubmitCmdBuffer) + { + backBuffer.SubmitCmdBuffer->Wait(); + backBuffer.SubmitCmdBuffer = nullptr; + } + } } bool GPUSwapChainVulkan::Resize(int32 width, int32 height) From a370aa29e9cfd9ba45804c5464ad05435dc4d33e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 11 Sep 2026 12:13:57 +0200 Subject: [PATCH 15/35] Fix interface or abstract property editing of read-only or scripting object types --- Source/Editor/CustomEditors/Editors/GenericEditor.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index f108136d4..4e78603a9 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -716,19 +716,18 @@ namespace FlaxEditor.CustomEditors.Editors if (value == null) { // Check if it's an object type that can be created in editor - if (type != ScriptMemberInfo.Null) + if (type != ScriptMemberInfo.Null && CanEditValue) { ScriptType[] types = null; if (type.IsAbstract || type.IsInterface) { // Show picker with all types that implement specific class/interface but are not abstract - types = Editor.Instance.CodeEditing.All.Get().Where(x => !x.IsAbstract && x.CanCreateInstance && type.IsAssignableFrom(x)).ToArray(); + types = Editor.Instance.CodeEditing.All.Get().Where(x => !x.IsAbstract && !x.IsScriptingObject && x.CanCreateInstance && type.IsAssignableFrom(x)).ToArray(); } else if (type.CanCreateInstance) { types = [type]; } - if (types != null && types.Length != 0) { layout = layout.Space(20); @@ -759,7 +758,7 @@ namespace FlaxEditor.CustomEditors.Editors layout.Label(""); return; } - if (!type.IsArray && !type.IsStructure && !type.IsScriptingObject && (type.IsAbstract || type.IsInterface) && value.GetType() != type.Type && layout is GroupElement group) + if (!type.IsArray && !type.IsStructure && !type.IsScriptingObject && (type.IsAbstract || type.IsInterface) && value.GetType() != type.Type && layout is GroupElement group && CanEditValue) { // Add button to unset the value to null (eg. to edit it to different type) var button = group.AddHeaderButton("Reset value to null", 0, FlaxEngine.GUI.Style.Current.Cross); From 48f23399fc27a6a65e52f326f0ac988d8cd59bc0 Mon Sep 17 00:00:00 2001 From: Jake Young Date: Fri, 11 Sep 2026 14:05:35 -0400 Subject: [PATCH 16/35] Update comment for simple move to include Time.DeltaTime --- Source/Engine/Physics/Colliders/CharacterController.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index 367b87df1..1a8847819 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -209,9 +209,9 @@ public: public: /// - /// Moves the character with the given speed. Gravity is automatically applied. It will slide along colliders. Result collision flags is the summary of collisions that occurred during the Move. + /// Moves the character with the given speed. Gravity is automatically applied. The speed is multipled by Time.DeltaTime internally. It will slide along colliders. Result collision flags is the summary of collisions that occurred during the Move. /// - /// The movement speed (in units/s). + /// The movement speed (in units/s). The speed is multipled by Time.DeltaTime internally. /// The collision flags. It can be used to trigger various character animations. API_FUNCTION() CollisionFlags SimpleMove(const Vector3& speed); From 76c6c8d038bf629a9d7b1f8abd4a7380f7065963 Mon Sep 17 00:00:00 2001 From: luchu1993 Date: Sat, 12 Sep 2026 22:57:55 +0800 Subject: [PATCH 17/35] Fix mesh collider updates after recooking collision data --- .../Engine/Physics/Colliders/MeshCollider.cpp | 19 +++++++++++++++---- .../Engine/Physics/Colliders/MeshCollider.h | 2 ++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Physics/Colliders/MeshCollider.cpp b/Source/Engine/Physics/Colliders/MeshCollider.cpp index 91731e7de..5e7343a8a 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.cpp +++ b/Source/Engine/Physics/Colliders/MeshCollider.cpp @@ -3,6 +3,7 @@ #include "MeshCollider.h" #include "Engine/Core/Math/Matrix.h" #include "Engine/Core/Math/Ray.h" +#include "Engine/Core/ScopeExit.h" #include "Engine/Physics/Physics.h" #include "Engine/Physics/PhysicsScene.h" #if USE_EDITOR || !BUILD_RELEASE @@ -22,19 +23,29 @@ void MeshCollider::OnCollisionDataChanged() if (CollisionData) { + _isChangingCollisionData = true; + SCOPE_EXIT { _isChangingCollisionData = false; }; + // Ensure that collision asset is loaded (otherwise objects might fall though collider that is not yet loaded on play begin) - CollisionData->WaitForLoaded(); + // OnSet sends Loaded after Changed returns, so let that notification update the collider once. + if (!CollisionData->WaitForLoaded()) + return; } + // Clearing the reference or failing to load won't send Loaded, so clear the old geometry here. UpdateGeometry(); UpdateBounds(); } void MeshCollider::OnCollisionDataLoaded() { - // Not needed as OnCollisionDataChanged waits for it to be loaded - //UpdateGeometry(); - //UpdateBounds(); + // WaitForLoaded can dispatch Loaded while Changed is still waiting. OnSet will send it again afterwards. + if (_isChangingCollisionData) + return; + + // Virtual collision data can be recooked without changing the asset reference. + UpdateGeometry(); + UpdateBounds(); } bool MeshCollider::CanAttach(RigidBody* rigidBody) const diff --git a/Source/Engine/Physics/Colliders/MeshCollider.h b/Source/Engine/Physics/Colliders/MeshCollider.h index 2e1ce533a..bc04575b0 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.h +++ b/Source/Engine/Physics/Colliders/MeshCollider.h @@ -23,6 +23,8 @@ public: AssetReference CollisionData; private: + bool _isChangingCollisionData = false; + void OnCollisionDataChanged(); void OnCollisionDataLoaded(); From 796bdae4eaea6fb25edadde0e20da05e7ee8fb8b Mon Sep 17 00:00:00 2001 From: killerdevildog Date: Sun, 13 Sep 2026 13:48:01 -0600 Subject: [PATCH 18/35] Fix ParseFloat rejecting a '+' sign in the exponent --- Source/Engine/Platform/Base/StringUtilsBase.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Base/StringUtilsBase.cpp b/Source/Engine/Platform/Base/StringUtilsBase.cpp index 750758b33..fbf77a1f9 100644 --- a/Source/Engine/Platform/Base/StringUtilsBase.cpp +++ b/Source/Engine/Platform/Base/StringUtilsBase.cpp @@ -458,7 +458,16 @@ bool ParseFloat(const C* str, T* ret) if (*str == 'e' || *str == 'E') { str++; - T powerer = *str == '-' ? str++, (T)0.1 : (T)10; + T powerer = (T)10; + if (*str == '-') + { + powerer = (T)0.1; + str++; + } + else if (*str == '+') + { + str++; + } T power = 0; while (*str >= '0' && *str <= '9') { From 0fc3972ceacfe816447feca02213c95d60b0d22a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Sep 2026 06:58:40 +0200 Subject: [PATCH 19/35] 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 20/35] 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 21/35] 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 22/35] 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 23/35] 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(); From 7bcdbef4caf63d7887014a6023d60c8feea56530 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Sep 2026 08:14:42 +0200 Subject: [PATCH 24/35] Revert #4178 changes for Windows due to flickering bug --- .../Engine/Platform/Windows/WindowsWindow.cpp | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index e3ca893a2..ec0b9962a 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -147,7 +147,7 @@ WindowsWindow::WindowsWindow(const CreateWindowSettings& settings) const HMODULE user32Dll = LoadLibraryW(L"user32.dll"); if (user32Dll) { - typedef UINT (STDAPICALLTYPE*GetDpiForWindowProc)(HWND hwnd); + typedef UINT(STDAPICALLTYPE* GetDpiForWindowProc)(HWND hwnd); const GetDpiForWindowProc getDpiForWindowProc = (GetDpiForWindowProc)GetProcAddress(user32Dll, "GetDpiForWindow"); if (getDpiForWindowProc) { @@ -386,13 +386,6 @@ void WindowsWindow::BringToFront(bool force) { ASSERT(HasHWND()); - HWND hWndInsertAfter = HWND_TOP; - uint32 flags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER; - if (_settings.IsTopmost) - { - hWndInsertAfter = HWND_TOPMOST; - } - if (_settings.Type == WindowType::Regular) { if (IsIconic(_handle)) @@ -403,15 +396,22 @@ void WindowsWindow::BringToFront(bool force) { SetActiveWindow(_handle); } - SetWindowPos(_handle, hWndInsertAfter, 0, 0, 0, 0, flags); } else { + HWND hWndInsertAfter = HWND_TOP; + uint32 flags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER; + if (!force) { flags |= SWP_NOACTIVATE; } + if (_settings.IsTopmost) + { + hWndInsertAfter = HWND_TOPMOST; + } + SetWindowPos(_handle, hWndInsertAfter, 0, 0, 0, 0, flags); } } @@ -649,7 +649,6 @@ void WindowsWindow::SetOpacity(const float opacity) void WindowsWindow::Focus() { ASSERT(HasHWND()); - BringToFront(); if (GetFocus() != _handle) { SetFocus(_handle); @@ -782,7 +781,7 @@ void WindowsWindow::UpdateCursor() if (!_lastCursorHidden) { _lastCursorHidden = true; - while(::ShowCursor(FALSE) >= 0) + while (::ShowCursor(FALSE) >= 0) { if (_cursorHiddenSafetyCount >= 100) { @@ -799,7 +798,7 @@ void WindowsWindow::UpdateCursor() else if (_lastCursorHidden) { _lastCursorHidden = false; - while(::ShowCursor(TRUE) < 0) + while (::ShowCursor(TRUE) < 0) { if (_cursorHiddenSafetyCount >= 100) { @@ -1174,10 +1173,6 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) } } break; - case WM_MOUSEACTIVATE: - if (_settings.Type == WindowType::Regular) - BringToFront(); - break; case WM_CREATE: return 0; case WM_SIZE: From e669e9ca65ad92f53b7005fd88fa178fd7b5b6f3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Sep 2026 08:30:51 +0200 Subject: [PATCH 25/35] Fix old api usage for #4185 --- Source/Editor/Content/Proxy/PrefabProxy.cs | 4 ++-- .../Dedicated/NavMeshBoundsVolumeEditor.cs | 2 +- Source/Editor/CustomEditors/Dedicated/TerrainEditor.cs | 2 +- Source/Editor/Gizmo/TransformGizmo.cs | 4 ++-- Source/Editor/Gizmo/ViewportRubberBandSelector.cs | 2 +- Source/Editor/Managed/ManagedEditor.Internal.cpp | 2 +- Source/Editor/Modules/SceneEditingModule.cs | 2 +- Source/Editor/SceneGraph/ActorNode.cs | 2 +- Source/Editor/SceneGraph/Actors/SplineNode.cs | 4 ++-- Source/Editor/SceneGraph/Actors/StaticModelNode.cs | 6 +++--- Source/Editor/Tools/ClothPainting.cs | 2 +- Source/Editor/Tools/Foliage/FoliageTools.cpp | 2 +- Source/Editor/Tools/VertexPainting.cs | 4 ++-- Source/Editor/Undo/Actions/DeleteActorsAction.cs | 2 +- Source/Editor/Utilities/Utils.cs | 2 +- .../Editor/Viewport/Previews/AnimatedModelPreview.cs | 6 +++--- Source/Editor/Viewport/Previews/ModelBasePreview.cs | 2 +- Source/Editor/Viewport/Previews/ModelPreview.cs | 8 ++++---- .../Editor/Viewport/Previews/ParticleSystemPreview.cs | 6 +++--- Source/Editor/Viewport/Previews/SkinnedModelPreview.cs | 2 +- Source/Editor/Viewport/ViewportDraggingHelper.cs | 2 +- Source/Editor/ViewportDebugDrawData.cs | 2 +- Source/Editor/Windows/Assets/AnimationWindow.cs | 2 +- Source/Editor/Windows/EditGameWindow.cs | 2 +- Source/Engine/AI/BehaviorTreeNodes.cpp | 4 ++-- .../Animations/SceneAnimations/SceneAnimationPlayer.h | 2 +- Source/Engine/Audio/AudioListener.h | 2 +- Source/Engine/Audio/AudioSource.h | 2 +- Source/Engine/Level/Actor.h | 10 +++++----- Source/Engine/Level/Actors/AnimatedModel.cpp | 2 +- Source/Engine/Level/Actors/AnimatedModel.h | 2 +- Source/Engine/Level/Actors/Camera.cpp | 2 +- Source/Engine/Level/Actors/Camera.h | 2 +- Source/Engine/Level/Actors/Decal.cpp | 2 +- Source/Engine/Level/Actors/Decal.h | 2 +- Source/Engine/Level/Actors/EmptyActor.cpp | 2 +- Source/Engine/Level/Actors/EmptyActor.h | 2 +- Source/Engine/Level/Actors/EnvironmentProbe.h | 2 +- Source/Engine/Level/Actors/ExponentialHeightFog.h | 2 +- Source/Engine/Level/Actors/Light.h | 2 +- Source/Engine/Level/Actors/Sky.h | 2 +- Source/Engine/Level/Actors/Skybox.h | 2 +- Source/Engine/Level/Scene/Scene.cpp | 6 +++--- Source/Engine/Level/Scene/SceneRendering.cpp | 4 ++-- Source/Engine/Navigation/NavMeshBuilder.cpp | 10 +++++----- Source/Engine/Particles/ParticleEffect.h | 2 +- Source/Engine/Particles/Particles.cpp | 2 +- Source/Engine/Physics/Colliders/BoxCollider.cpp | 4 ++-- Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp | 4 ++-- Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp | 6 +++--- Source/Engine/Renderer/ProbesRenderer.cpp | 2 +- Source/Engine/Renderer/ShadowsPass.cpp | 8 ++++---- Source/Engine/UI/UICanvas.cpp | 2 +- Source/Engine/UI/UICanvas.h | 2 +- Source/Engine/UI/UIControl.cpp | 2 +- Source/Engine/UI/UIControl.h | 2 +- Source/Engine/Video/VideoPlayer.h | 3 +-- 57 files changed, 89 insertions(+), 90 deletions(-) diff --git a/Source/Editor/Content/Proxy/PrefabProxy.cs b/Source/Editor/Content/Proxy/PrefabProxy.cs index 410df23b0..e5cf053d6 100644 --- a/Source/Editor/Content/Proxy/PrefabProxy.cs +++ b/Source/Editor/Content/Proxy/PrefabProxy.cs @@ -140,7 +140,7 @@ namespace FlaxEditor.Content actor is SpriteRender || actor is TextRender) { - actorBounds = actor.EditorBox; + actorBounds = actor.EditorBoundingBox; } if (actorBounds != BoundingBox.Empty) { @@ -182,7 +182,7 @@ namespace FlaxEditor.Content // Update some actors data (some actor types update bounds/data later but its required to be done before rendering) var bounds = BoundingBox.Empty; Prepare(_preview.Instance, ref bounds); - //bounds = _preview.Instance.EditorBoxChildren; + //bounds = _preview.Instance.EditorBoundingBoxWithChildren; // Auto fit actor to camera if (bounds != BoundingBox.Empty) diff --git a/Source/Editor/CustomEditors/Dedicated/NavMeshBoundsVolumeEditor.cs b/Source/Editor/CustomEditors/Dedicated/NavMeshBoundsVolumeEditor.cs index 2cbf01e41..3c101453f 100644 --- a/Source/Editor/CustomEditors/Dedicated/NavMeshBoundsVolumeEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/NavMeshBoundsVolumeEditor.cs @@ -29,7 +29,7 @@ namespace FlaxEditor.CustomEditors.Dedicated { if (value is NavMeshBoundsVolume volume) { - Navigation.BuildNavMesh(volume.Box, volume.Scene); + Navigation.BuildNavMesh(volume.BoundingBox, volume.Scene); Editor.Instance.Scene.MarkSceneEdited(volume.Scene); } } diff --git a/Source/Editor/CustomEditors/Dedicated/TerrainEditor.cs b/Source/Editor/CustomEditors/Dedicated/TerrainEditor.cs index 448c55443..bfcb8bc57 100644 --- a/Source/Editor/CustomEditors/Dedicated/TerrainEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/TerrainEditor.cs @@ -24,7 +24,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var patchesCount = terrain.PatchesCount; var chunkSize = terrain.ChunkSize; var resolution = terrain.Scale; - var totalSize = terrain.Box.Size; + var totalSize = terrain.BoundingBox.Size; string text = string.Format("Patches: {0}\nTotal Chunks: {1}\nChunk Size: {2}\nResolution: {3}m x {4}m\nTotal size: {5}km x {6}km", patchesCount, patchesCount * 16, diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs index 91e37ca25..4b40caecd 100644 --- a/Source/Editor/Gizmo/TransformGizmo.cs +++ b/Source/Editor/Gizmo/TransformGizmo.cs @@ -128,7 +128,7 @@ namespace FlaxEditor.Gizmo { if (_selectionParents[i] is ActorNode actorNode) { - var b = actorNode.Actor.EditorBoxChildren; + var b = actorNode.Actor.EditorBoundingBoxWithChildren; BoundingBox.Merge(ref editorBounds, ref b, out editorBounds); bottomToCenter = Mathf.Min(bottomToCenter, actorNode.Actor.Position.Y - editorBounds.Minimum.Y); } @@ -299,7 +299,7 @@ namespace FlaxEditor.Gizmo { if (_selectionParents[i] is ActorNode actorNode) { - bounds = BoundingBox.Merge(bounds, actorNode.Actor.BoxWithChildren); + bounds = BoundingBox.Merge(bounds, actorNode.Actor.BoundingBoxWithChildren); navigationDirty |= actorNode.AffectsNavigationWithChildren; } } diff --git a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs index 66e835fac..1d85fcf77 100644 --- a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs +++ b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs @@ -182,7 +182,7 @@ public sealed class ViewportRubberBandSelector var a = node.Actor; // Skip actor if outside of view frustum - var actorBox = a.EditorBox; + var actorBox = a.EditorBoundingBox; if (projection.FrustumCull(ref actorBox) == ContainmentType.Disjoint) continue; diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 2220fb0f3..78217e6be 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -333,7 +333,7 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_GetCollisionWires(CollisionData* colli DEFINE_INTERNAL_CALL(void) EditorInternal_GetEditorBoxWithChildren(Actor* obj, BoundingBox* result) { INTERNAL_CALL_CHECK(obj); - *result = obj->GetEditorBoxChildren(); + *result = obj->GetEditorBoundingBoxWithChildren(); } DEFINE_INTERNAL_CALL(void) EditorInternal_SetOptions(ManagedEditor::InternalOptions* options) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index f6643f4f2..9697a099f 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -244,7 +244,7 @@ namespace FlaxEditor.Modules // Auto NavMesh rebuild if (!isPlayMode && options.General.AutoRebuildNavMesh && actor.Scene && node.AffectsNavigationWithChildren) { - var bounds = actor.BoxWithChildren; + var bounds = actor.BoundingBoxWithChildren; Navigation.BuildNavMesh(bounds, options.General.AutoRebuildNavMeshTimeoutMs); } } diff --git a/Source/Editor/SceneGraph/ActorNode.cs b/Source/Editor/SceneGraph/ActorNode.cs index f25659303..67f671756 100644 --- a/Source/Editor/SceneGraph/ActorNode.cs +++ b/Source/Editor/SceneGraph/ActorNode.cs @@ -233,7 +233,7 @@ namespace FlaxEditor.SceneGraph /// The points to use if the actor can be selected. public virtual Vector3[] GetActorSelectionPoints() { - return Actor.EditorBox.GetCorners(); + return Actor.EditorBoundingBox.GetCorners(); } /// diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 515939526..73ef65f7c 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -555,7 +555,7 @@ namespace FlaxEditor.SceneGraph.Actors var options = Editor.Instance.Options.Options.General; if (options.AutoRebuildNavMesh) { - Navigation.BuildNavMesh(collider.Box, options.AutoRebuildNavMeshTimeoutMs); + Navigation.BuildNavMesh(collider.BoundingBox, options.AutoRebuildNavMeshTimeoutMs); } } } @@ -572,7 +572,7 @@ namespace FlaxEditor.SceneGraph.Actors var viewBounds = sceneContext.Viewport.ViewFrustum; foreach (var s in splines) { - var contains = viewBounds.Contains(s.EditorBox); + var contains = viewBounds.Contains(s.EditorBoundingBox); if (contains == ContainmentType.Contains || contains == ContainmentType.Intersects) result.Add(s); } diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs index 4cd63f05d..391222b6a 100644 --- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs +++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs @@ -319,7 +319,7 @@ namespace FlaxEditor.SceneGraph.Actors private void CreateSphere(StaticModel actor, Spawner spawner, bool singleNode) { - var bounds = actor.Sphere; + var bounds = actor.BoundingSphere; var collider = new SphereCollider { Transform = actor.Transform, @@ -336,10 +336,10 @@ namespace FlaxEditor.SceneGraph.Actors var collider = new CapsuleCollider { Transform = actor.Transform, - Position = actor.Box.Center, + Position = actor.BoundingBox.Center, // Size the capsule to best fit the actor - Radius = (float)actor.Sphere.Radius / Mathf.Max((float)actor.Scale.MaxValue, 0.0001f) * 0.707f, + Radius = (float)actor.BoundingSphere.Radius / Mathf.Max((float)actor.Scale.MaxValue, 0.0001f) * 0.707f, Height = 100f, }; spawner(collider); diff --git a/Source/Editor/Tools/ClothPainting.cs b/Source/Editor/Tools/ClothPainting.cs index e8ab67039..caaa64d93 100644 --- a/Source/Editor/Tools/ClothPainting.cs +++ b/Source/Editor/Tools/ClothPainting.cs @@ -231,7 +231,7 @@ namespace FlaxEngine.Tools public override bool IsControllingMouse => IsPainting; - public override BoundingSphere FocusBounds => _cloth?.Sphere ?? base.FocusBounds; + public override BoundingSphere FocusBounds => _cloth?.BoundingSphere ?? base.FocusBounds; public override void Update(float dt) { diff --git a/Source/Editor/Tools/Foliage/FoliageTools.cpp b/Source/Editor/Tools/Foliage/FoliageTools.cpp index 4a545e9d5..19a3330a8 100644 --- a/Source/Editor/Tools/Foliage/FoliageTools.cpp +++ b/Source/Editor/Tools/Foliage/FoliageTools.cpp @@ -67,7 +67,7 @@ struct GeometryLookup static bool Search(Actor* actor, GeometryLookup& lookup) { // Early out if object is not intersecting with the foliage brush bounds - if (!actor->GetIsActive() || !actor->GetBox().Intersects(lookup.Brush)) + if (!actor->GetIsActive() || !actor->GetBoundingBox().Intersects(lookup.Brush)) return true; const auto brush = lookup.Brush; diff --git a/Source/Editor/Tools/VertexPainting.cs b/Source/Editor/Tools/VertexPainting.cs index dea5247f4..b0a9c6fb6 100644 --- a/Source/Editor/Tools/VertexPainting.cs +++ b/Source/Editor/Tools/VertexPainting.cs @@ -487,7 +487,7 @@ namespace FlaxEditor.Tools public override bool IsControllingMouse => IsPainting; /// - public override BoundingSphere FocusBounds => _selectedModel != null ? _selectedModel.Sphere : base.FocusBounds; + public override BoundingSphere FocusBounds => _selectedModel != null ? _selectedModel.BoundingSphere : base.FocusBounds; /// public override void Update(float dt) @@ -598,7 +598,7 @@ namespace FlaxEditor.Tools var vertexScale = Mathf.Lerp(0.005f, 0.01f, Mathf.Saturate(distanceScale)); var modelScaleMatrix = Matrix.Scaling(_gizmoMode.PreviewVertexSize * vertexScale); var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize * 0.5f); - var lodIndex = _gizmoMode.ModelLOD == -1 ? RenderTools.ComputeModelLOD(_selectedModel.Model, ref renderContext.View.Position, (float)_selectedModel.Sphere.Radius, ref renderContext) : _gizmoMode.ModelLOD; + var lodIndex = _gizmoMode.ModelLOD == -1 ? RenderTools.ComputeModelLOD(_selectedModel.Model, ref renderContext.View.Position, (float)_selectedModel.BoundingSphere.Radius, ref renderContext) : _gizmoMode.ModelLOD; lodIndex = Mathf.Clamp(lodIndex, 0, meshDatas.Length - 1); var lodData = meshDatas[lodIndex]; if (lodData != null) diff --git a/Source/Editor/Undo/Actions/DeleteActorsAction.cs b/Source/Editor/Undo/Actions/DeleteActorsAction.cs index 19ffb1e3f..06729d5fb 100644 --- a/Source/Editor/Undo/Actions/DeleteActorsAction.cs +++ b/Source/Editor/Undo/Actions/DeleteActorsAction.cs @@ -302,7 +302,7 @@ namespace FlaxEditor.Actions { if (_nodeParents[i] is ActorNode node && node.Actor && node.Actor.Scene && node.AffectsNavigationWithChildren) { - var bounds = node.Actor.BoxWithChildren; + var bounds = node.Actor.BoundingBoxWithChildren; Navigation.BuildNavMesh(bounds, options.General.AutoRebuildNavMeshTimeoutMs); } } diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 8f5df866d..21794412c 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -387,7 +387,7 @@ namespace FlaxEditor.Utilities transforms[i] = nodes[i].Transform; if (nodes[i] is ActorNode actorNode) { - bounds = BoundingBox.Merge(bounds, actorNode.Actor.BoxWithChildren); + bounds = BoundingBox.Merge(bounds, actorNode.Actor.BoundingBoxWithChildren); } } return transforms; diff --git a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs index 721b8504d..83e8917fb 100644 --- a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs +++ b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs @@ -361,7 +361,7 @@ namespace FlaxEditor.Viewport.Previews if (_showNodes) { // Draw bounding box at the node locations - var boxSize = Mathf.Min(1.0f, _previewModel.Sphere.Radius / 100.0f); + var boxSize = Mathf.Min(1.0f, _previewModel.BoundingSphere.Radius / 100.0f); var localBox = new OrientedBoundingBox(new Vector3(-boxSize), new Vector3(boxSize)); for (int nodeIndex = 0; nodeIndex < pose.Length; nodeIndex++) { @@ -405,7 +405,7 @@ namespace FlaxEditor.Viewport.Previews // Draw bounds if (_showBounds) { - DebugDrawBounds(_previewModel.Box); + DebugDrawBounds(_previewModel.BoundingBox); } } @@ -433,7 +433,7 @@ namespace FlaxEditor.Viewport.Previews /// public void ResetCamera() { - ViewportCamera.SetArcBallView(_previewModel.Box); + ViewportCamera.SetArcBallView(_previewModel.BoundingBox); } /// diff --git a/Source/Editor/Viewport/Previews/ModelBasePreview.cs b/Source/Editor/Viewport/Previews/ModelBasePreview.cs index 47a2aec97..d4fab6f02 100644 --- a/Source/Editor/Viewport/Previews/ModelBasePreview.cs +++ b/Source/Editor/Viewport/Previews/ModelBasePreview.cs @@ -62,7 +62,7 @@ namespace FlaxEditor.Viewport.Previews /// public void ResetCamera() { - ViewportCamera.SetArcBallView(StaticModel.Model != null ? StaticModel.Box : AnimatedModel.Box); + ViewportCamera.SetArcBallView(StaticModel.Model != null ? StaticModel.BoundingBox : AnimatedModel.BoundingBox); } private void OnBegin(RenderTask task, GPUContext context) diff --git a/Source/Editor/Viewport/Previews/ModelPreview.cs b/Source/Editor/Viewport/Previews/ModelPreview.cs index b6306b905..026cd94ed 100644 --- a/Source/Editor/Viewport/Previews/ModelPreview.cs +++ b/Source/Editor/Viewport/Previews/ModelPreview.cs @@ -302,7 +302,7 @@ namespace FlaxEditor.Viewport.Previews // Draw bounds if (_showBounds) { - DebugDrawBounds(_previewModel.Box); + DebugDrawBounds(_previewModel.BoundingBox); } // Draw normals @@ -395,7 +395,7 @@ namespace FlaxEditor.Viewport.Previews // Based on RenderTools::ComputeModelLOD CreateProjectionMatrix(out var projectionMatrix); float screenMultiple = 0.5f * Mathf.Max(projectionMatrix.M11, projectionMatrix.M22); - var sphere = PreviewActor.Sphere; + var sphere = PreviewActor.BoundingSphere; var viewOrigin = ViewPosition; var distSqr = Vector3.DistanceSquared(ref sphere.Center, ref viewOrigin); var screenRadiusSquared = Mathf.Square(screenMultiple * sphere.Radius) / Mathf.Max(1.0f, distSqr); @@ -461,11 +461,11 @@ namespace FlaxEditor.Viewport.Previews } /// - /// Resets the camera to focus on a object. + /// Resets the camera to focus on an object. /// public void ResetCamera() { - ViewportCamera.SetArcBallView(_previewModel.Box); + ViewportCamera.SetArcBallView(_previewModel.BoundingBox); } /// diff --git a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs index 74c7c435b..13987157e 100644 --- a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs +++ b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs @@ -207,7 +207,7 @@ namespace FlaxEditor.Viewport.Previews private void UpdateBoundsModel() { - var bounds = _previewEffect.Box; + var bounds = _previewEffect.BoundingBox; Transform t = Transform.Identity; t.Translation = bounds.Center; t.Scale = bounds.Size; @@ -221,7 +221,7 @@ namespace FlaxEditor.Viewport.Previews public void FitIntoView(float targetSize = 300.0f) { _previewEffect.Scale = Float3.One; - float maxSize = Mathf.Max(0.001f, (float)_previewEffect.Box.Size.MaxValue); + float maxSize = Mathf.Max(0.001f, (float)_previewEffect.BoundingBox.Size.MaxValue); _previewEffect.Scale = new Float3(targetSize / maxSize); } @@ -278,7 +278,7 @@ namespace FlaxEditor.Viewport.Previews switch (key) { case KeyboardKeys.F: - ViewportCamera.SetArcBallView(_previewEffect.Box); + ViewportCamera.SetArcBallView(_previewEffect.BoundingBox); return true; case KeyboardKeys.Spacebar: PlaySimulation = !PlaySimulation; diff --git a/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs b/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs index d394190ce..903326ab6 100644 --- a/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs +++ b/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs @@ -93,7 +93,7 @@ namespace FlaxEditor.Viewport.Previews // Based on RenderTools::ComputeModelLOD CreateProjectionMatrix(out var projectionMatrix); float screenMultiple = 0.5f * Mathf.Max(projectionMatrix.M11, projectionMatrix.M22); - var sphere = PreviewActor.Sphere; + var sphere = PreviewActor.BoundingSphere; var viewOrigin = ViewPosition; var distSqr = Vector3.DistanceSquared(ref sphere.Center, ref viewOrigin); var screenRadiusSquared = Mathf.Square(screenMultiple * sphere.Radius) / Mathf.Max(1.0f, distSqr); diff --git a/Source/Editor/Viewport/ViewportDraggingHelper.cs b/Source/Editor/Viewport/ViewportDraggingHelper.cs index 5c68b399a..bdd85024a 100644 --- a/Source/Editor/Viewport/ViewportDraggingHelper.cs +++ b/Source/Editor/Viewport/ViewportDraggingHelper.cs @@ -185,7 +185,7 @@ namespace FlaxEditor.Viewport // Place the object //var location = hitLocation - (box.Size.Length * 0.5f) * ViewDirection; - var editorBounds = actor.EditorBoxChildren; + var editorBounds = actor.EditorBoundingBoxWithChildren; var bottomToCenter = actor.Position.Y - editorBounds.Minimum.Y; var location = hitLocation + new Vector3(0, bottomToCenter, 0); diff --git a/Source/Editor/ViewportDebugDrawData.cs b/Source/Editor/ViewportDebugDrawData.cs index d70ec9cfb..073a41844 100644 --- a/Source/Editor/ViewportDebugDrawData.cs +++ b/Source/Editor/ViewportDebugDrawData.cs @@ -125,7 +125,7 @@ namespace FlaxEditor if (model == null) continue; staticModel.Transform.GetWorld(out world); - var bounds = BoundingSphere.FromBox(staticModel.Box); + var bounds = BoundingSphere.FromBox(staticModel.BoundingBox); // Pick a proper LOD Float3 center = bounds.Center - renderContext.View.Origin; diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs index 69da1f9e9..9ab94bcb4 100644 --- a/Source/Editor/Windows/Assets/AnimationWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationWindow.cs @@ -137,7 +137,7 @@ namespace FlaxEditor.Windows.Assets { // Focus model value.WaitForLoaded(500); - Window._preview.ViewportCamera.SetArcBallView(Window._preview.PreviewActor.Sphere); + Window._preview.ViewportCamera.SetArcBallView(Window._preview.PreviewActor.BoundingSphere); } if (EnablePreviewModelCache) diff --git a/Source/Editor/Windows/EditGameWindow.cs b/Source/Editor/Windows/EditGameWindow.cs index 8d0c146d9..44c07be21 100644 --- a/Source/Editor/Windows/EditGameWindow.cs +++ b/Source/Editor/Windows/EditGameWindow.cs @@ -189,7 +189,7 @@ namespace FlaxEditor.Windows _pilotActor = actor; _pilotStart = actor.Transform; - _pilotBounds = actor.BoxWithChildren; + _pilotBounds = actor.BoundingBoxWithChildren; Viewport.ViewTransform = _pilotStart; if (_pilotWidget == null) { diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index d62210072..21cfe4c95 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -394,8 +394,8 @@ void BehaviorTreeMoveToNode::GetAgentSize(Actor* agent, float& outRadius, float& } // Estimate actor bounds to extract capsule information - const BoundingBox box = agent->GetBox(); - const BoundingSphere sphere = agent->GetSphere(); + const BoundingBox box = agent->GetBoundingBox(); + const BoundingSphere sphere = agent->GetBoundingSphere(); outRadius = (float)sphere.Radius; outHeight = (float)box.GetSize().Y; } diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h index df649f299..6a15eb4cb 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h @@ -221,7 +221,7 @@ public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Audio/AudioListener.h b/Source/Engine/Audio/AudioListener.h index 7bb4299a5..89f4ba5af 100644 --- a/Source/Engine/Audio/AudioListener.h +++ b/Source/Engine/Audio/AudioListener.h @@ -30,7 +30,7 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Audio/AudioSource.h b/Source/Engine/Audio/AudioSource.h index 07762be2f..db698f3ba 100644 --- a/Source/Engine/Audio/AudioSource.h +++ b/Source/Engine/Audio/AudioSource.h @@ -318,7 +318,7 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 072077fe4..4271393ce 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -689,7 +689,7 @@ public: public: /// /// Gets actor bounding sphere that defines 3D space intersecting with the actor (for determination of the visibility for actor). - /// [DEPRECATED IN 1.13] + /// [Deprecated in 1.13] /// API_PROPERTY() DEPRECATED("Use GetBoundingSphere instead.") FORCE_INLINE const BoundingSphere& GetSphere() const { @@ -706,7 +706,7 @@ public: /// /// Gets actor bounding box that defines 3D space intersecting with the actor (for determination of the visibility for actor). - /// [DEPRECATED IN 1.13] + /// [Deprecated in 1.13] /// API_PROPERTY() DEPRECATED("Use GetBoundingBox instead.") FORCE_INLINE const BoundingBox& GetBox() const { @@ -723,7 +723,7 @@ public: /// /// Gets actor bounding box of the actor including all child actors (children included in recursive way) - /// [DEPRECATED IN 1.13] + /// [Deprecated in 1.13] /// API_PROPERTY() DEPRECATED("Use GetBoundingBoxWithChildren instead.") BoundingBox GetBoxWithChildren() const; @@ -735,7 +735,7 @@ public: #if USE_EDITOR /// /// Gets actor bounding box (single actor, no children included) for editor tools. - /// [DEPRECATED IN 1.13] + /// [Deprecated in 1.13] /// API_PROPERTY() DEPRECATED("Use GetEditorBoundingBox instead.") virtual BoundingBox GetEditorBox() const; @@ -746,7 +746,7 @@ public: /// /// Gets actor bounding box of the actor including all child actors for editor tools. - /// [DEPRECATED IN 1.13] + /// [Deprecated in 1.13] /// API_PROPERTY() DEPRECATED("Use GetEditorBoundingBoxWithChildren instead.") BoundingBox GetEditorBoxChildren() const; diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 2f878f3d6..90d978af0 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -1216,7 +1216,7 @@ void AnimatedModel::OnDebugDraw() ModelInstanceActor::OnDebugDraw(); } -BoundingBox AnimatedModel::GetEditorBox() const +BoundingBox AnimatedModel::GetEditorBoundingBox() const { if (SkinnedModel) SkinnedModel->WaitForLoaded(100); diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index bbfa6f0ea..806e0fcdf 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -479,7 +479,7 @@ public: #if USE_EDITOR void OnDebugDrawSelected() override; void OnDebugDraw() override; - BoundingBox GetEditorBox() const override; + BoundingBox GetEditorBoundingBox() const override; #endif bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override; void Serialize(SerializeStream& stream, const void* otherObj) override; diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp index fa4c2abfb..5ac227c2f 100644 --- a/Source/Engine/Level/Actors/Camera.cpp +++ b/Source/Engine/Level/Actors/Camera.cpp @@ -333,7 +333,7 @@ void Camera::BeginPlay(SceneBeginData* data) Actor::BeginPlay(data); } -BoundingBox Camera::GetEditorBox() const +BoundingBox Camera::GetEditorBoundingBox() const { const Vector3 size(100); const Vector3 pos = _transform.Translation + _transform.Orientation * Vector3::Forward * 30.0f; diff --git a/Source/Engine/Level/Actors/Camera.h b/Source/Engine/Level/Actors/Camera.h index 81531bd37..1f17f8575 100644 --- a/Source/Engine/Level/Actors/Camera.h +++ b/Source/Engine/Level/Actors/Camera.h @@ -257,7 +257,7 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override; + BoundingBox GetEditorBoundingBox() const override; bool HasContentLoaded() const override; void Draw(RenderContext& renderContext) override; void OnDebugDrawSelected() override; diff --git a/Source/Engine/Level/Actors/Decal.cpp b/Source/Engine/Level/Actors/Decal.cpp index 78066133c..f29063a9b 100644 --- a/Source/Engine/Level/Actors/Decal.cpp +++ b/Source/Engine/Level/Actors/Decal.cpp @@ -53,7 +53,7 @@ void Decal::OnDebugDrawSelected() Actor::OnDebugDrawSelected(); } -BoundingBox Decal::GetEditorBox() const +BoundingBox Decal::GetEditorBoundingBox() const { const Vector3 size(10.0f); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Level/Actors/Decal.h b/Source/Engine/Level/Actors/Decal.h index 068a4447e..838f31af6 100644 --- a/Source/Engine/Level/Actors/Decal.h +++ b/Source/Engine/Level/Actors/Decal.h @@ -71,7 +71,7 @@ public: // [Actor] #if USE_EDITOR void OnDebugDrawSelected() override; - BoundingBox GetEditorBox() const override; + BoundingBox GetEditorBoundingBox() const override; #endif void OnLayerChanged() override; void Draw(RenderContext& renderContext) override; diff --git a/Source/Engine/Level/Actors/EmptyActor.cpp b/Source/Engine/Level/Actors/EmptyActor.cpp index fde7531f4..477e04d0a 100644 --- a/Source/Engine/Level/Actors/EmptyActor.cpp +++ b/Source/Engine/Level/Actors/EmptyActor.cpp @@ -9,7 +9,7 @@ EmptyActor::EmptyActor(const SpawnParams& params) #if USE_EDITOR -BoundingBox EmptyActor::GetEditorBox() const +BoundingBox EmptyActor::GetEditorBoundingBox() const { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Level/Actors/EmptyActor.h b/Source/Engine/Level/Actors/EmptyActor.h index 9149b698e..6515a70e4 100644 --- a/Source/Engine/Level/Actors/EmptyActor.h +++ b/Source/Engine/Level/Actors/EmptyActor.h @@ -14,7 +14,7 @@ class FLAXENGINE_API EmptyActor : public Actor public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override; + BoundingBox GetEditorBoundingBox() const override; #endif protected: diff --git a/Source/Engine/Level/Actors/EnvironmentProbe.h b/Source/Engine/Level/Actors/EnvironmentProbe.h index bf65222ee..c5f06c573 100644 --- a/Source/Engine/Level/Actors/EnvironmentProbe.h +++ b/Source/Engine/Level/Actors/EnvironmentProbe.h @@ -152,7 +152,7 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.h b/Source/Engine/Level/Actors/ExponentialHeightFog.h index c0e5407d2..bf89c6e60 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.h +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.h @@ -141,7 +141,7 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Level/Actors/Light.h b/Source/Engine/Level/Actors/Light.h index 0e2a441db..3b7527095 100644 --- a/Source/Engine/Level/Actors/Light.h +++ b/Source/Engine/Level/Actors/Light.h @@ -75,7 +75,7 @@ public: void OnEnable() override; void OnDisable() override; #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Level/Actors/Sky.h b/Source/Engine/Level/Actors/Sky.h index 61bb048ef..c52651666 100644 --- a/Source/Engine/Level/Actors/Sky.h +++ b/Source/Engine/Level/Actors/Sky.h @@ -63,7 +63,7 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Level/Actors/Skybox.h b/Source/Engine/Level/Actors/Skybox.h index fb831df44..d18b49f7a 100644 --- a/Source/Engine/Level/Actors/Skybox.h +++ b/Source/Engine/Level/Actors/Skybox.h @@ -57,7 +57,7 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Level/Scene/Scene.cpp b/Source/Engine/Level/Scene/Scene.cpp index b45424a3d..98f1bc76c 100644 --- a/Source/Engine/Level/Scene/Scene.cpp +++ b/Source/Engine/Level/Scene/Scene.cpp @@ -44,9 +44,9 @@ BoundingBox SceneNavigation::GetNavigationBounds() if (Volumes.IsEmpty()) return BoundingBox::Empty; PROFILE_CPU_NAMED("GetNavigationBounds"); - auto box = Volumes[0]->GetBox(); + auto box = Volumes[0]->GetBoundingBox(); for (int32 i = 1; i < Volumes.Count(); i++) - BoundingBox::Merge(box, Volumes[i]->GetBox(), box); + BoundingBox::Merge(box, Volumes[i]->GetBoundingBox(), box); return box; } @@ -55,7 +55,7 @@ NavMeshBoundsVolume* SceneNavigation::FindNavigationBoundsOverlap(const Bounding NavMeshBoundsVolume* result = nullptr; for (int32 i = 0; i < Volumes.Count(); i++) { - if (Volumes[i]->GetBox().Intersects(bounds)) + if (Volumes[i]->GetBoundingBox().Intersects(bounds)) { result = Volumes[i]; break; diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index d87e5580c..6227d3517 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -184,7 +184,7 @@ void SceneRendering::AddActor(Actor* a, int32& key) auto& e = list[key]; e.Actor = a; e.LayerMask = a->GetLayerMask(); - e.Bounds = a->GetSphere(); + e.Bounds = a->GetBoundingSphere(); e.NoCulling = a->_drawNoCulling; for (auto* listener : _listeners) listener->OnSceneRenderingAddActor(a); @@ -207,7 +207,7 @@ void SceneRendering::UpdateActor(Actor* a, int32& key, ISceneRenderingListener:: if (flags & ISceneRenderingListener::Layer) e.LayerMask = a->GetLayerMask(); if (flags & ISceneRenderingListener::Bounds) - e.Bounds = a->GetSphere(); + e.Bounds = a->GetBoundingSphere(); } } if (lock) diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index 896cf4217..0c4d10589 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -251,7 +251,7 @@ struct NavSceneRasterizer return; PROFILE_CPU_NAMED("SphereCollider"); - const BoundingSphere sphere = sphereCollider->GetSphere(); + const BoundingSphere sphere = sphereCollider->GetBoundingSphere(); TriangulateSphere(VertexBuffer, IndexBuffer, sphere); RasterizeTriangles(); } @@ -261,7 +261,7 @@ struct NavSceneRasterizer return; PROFILE_CPU_NAMED("CapsuleCollider"); - const BoundingBox box = capsuleCollider->GetBox(); + const BoundingBox box = capsuleCollider->GetBoundingBox(); TriangulateBox(VertexBuffer, IndexBuffer, box); RasterizeTriangles(); } @@ -411,7 +411,7 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B for (Actor* actor : scene->Navigation.Actors) { BoundingBox actorBoxNavMesh; - BoundingBox::Transform(actor->GetBox(), rasterizer.WorldToNavMesh, actorBoxNavMesh); + BoundingBox::Transform(actor->GetBoundingBox(), rasterizer.WorldToNavMesh, actorBoxNavMesh); if (actorBoxNavMesh.Intersects(rasterizer.TileBoundsNavMesh) && actor->IsActiveInHierarchy() && EnumHasAllFlags(actor->GetStaticFlags(), StaticFlags::Navigation)) @@ -963,10 +963,10 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo for (const NavMeshBoundsVolume* volume : scene->Navigation.Volumes) { if (!volume->AgentsMask.IsNavMeshSupported(navMesh->Properties) || - !volume->GetBox().Intersects(dirtyBoundsNavMesh)) + !volume->GetBoundingBox().Intersects(dirtyBoundsNavMesh)) continue; auto& bounds = volumes.AddOne(); - BoundingBox::Transform(volume->GetBox(), worldToNavMesh, bounds); + BoundingBox::Transform(volume->GetBoundingBox(), worldToNavMesh, bounds); } Array unusedTiles; diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h index 5ae56603a..491a23b0c 100644 --- a/Source/Engine/Particles/ParticleEffect.h +++ b/Source/Engine/Particles/ParticleEffect.h @@ -424,7 +424,7 @@ public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index 3c41cd243..b1e28190b 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -1141,7 +1141,7 @@ void Particles::DrawParticles(RenderContextBatch& renderContextBatch, ParticleEf // Drawing assumes that all views within a batch have the same Origin const Vector3& viewOrigin = renderContextBatch.GetMainContext().View.Origin; - BoundingSphere bounds = effect->GetSphere(); + BoundingSphere bounds = effect->GetBoundingSphere(); bounds.Center -= viewOrigin; // Cull particles against all views diff --git a/Source/Engine/Physics/Colliders/BoxCollider.cpp b/Source/Engine/Physics/Colliders/BoxCollider.cpp index 47e551b37..95890e268 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.cpp +++ b/Source/Engine/Physics/Colliders/BoxCollider.cpp @@ -34,13 +34,13 @@ void BoxCollider::AutoResize(bool globalOrientation = true) // Hacky way to get unrotated bounded box of parent const Quaternion parentOrientation = parent->GetOrientation(); parent->SetOrientation(Quaternion::Identity); - BoundingBox parentBox = parent->GetBox(); + BoundingBox parentBox = parent->GetBoundingBox(); parent->SetOrientation(parentOrientation); for (const Actor* sibling : parent->Children) { if (sibling != this) - BoundingBox::Merge(parentBox, sibling->GetBoxWithChildren(), parentBox); + BoundingBox::Merge(parentBox, sibling->GetBoundingBoxWithChildren(), parentBox); } const Vector3 parentSize = parentBox.GetSize(); const Vector3 parentCenter = parentBox.GetCenter() - parent->GetPosition(); diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 18ed6effd..f2f6d3968 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -216,8 +216,8 @@ struct ClothSettings clothTrans.LocalToWorld(c, c); // Setup bounds - BoundingBox::FromPoints(boundsCorners, 8, const_cast(Actor->GetBox())); - BoundingSphere::FromBox(Actor->GetBox(), const_cast(Actor->GetSphere())); + BoundingBox::FromPoints(boundsCorners, 8, const_cast(Actor->GetBoundingBox())); + BoundingSphere::FromBox(Actor->GetBoundingBox(), const_cast(Actor->GetBoundingSphere())); return false; } }; diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index ae6e8a9a8..7c46aa268 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -399,7 +399,7 @@ public: if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) { ScopeWriteLock lock(Locker); - OnSceneRenderingDirty(a->GetBox()); + OnSceneRenderingDirty(a->GetBoundingBox()); } } @@ -410,7 +410,7 @@ public: ScopeWriteLock lock(Locker); if (flags != DrawModes && flags != Layer && flags != StaticFlags) OnSceneRenderingDirty(BoundingBox::FromSphere(prevBounds)); - OnSceneRenderingDirty(a->GetBox()); + OnSceneRenderingDirty(a->GetBoundingBox()); } } @@ -419,7 +419,7 @@ public: if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) { ScopeWriteLock lock(Locker); - OnSceneRenderingDirty(a->GetBox()); + OnSceneRenderingDirty(a->GetBoundingBox()); } } diff --git a/Source/Engine/Renderer/ProbesRenderer.cpp b/Source/Engine/Renderer/ProbesRenderer.cpp index 5ed2af5d9..40b0a4496 100644 --- a/Source/Engine/Renderer/ProbesRenderer.cpp +++ b/Source/Engine/Renderer/ProbesRenderer.cpp @@ -450,7 +450,7 @@ void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context) { auto envProbe = (EnvironmentProbe*)_current.Actor.Get(); Vector3 position = envProbe->GetTransform().LocalToWorld(envProbe->CaptureOffset); - float radius = (float)envProbe->GetSphere().Radius; + float radius = (float)envProbe->GetBoundingSphere().Radius; float nearPlane = Math::Max(METERS_TO_UNITS(0.001f), envProbe->CaptureNearPlane); // Adjust far plane distance diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 12848ed47..21bbd138b 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -396,7 +396,7 @@ public: void OnSceneRenderingAddActor(Actor* a) override { if (a->HasStaticFlag(StaticFlags::Shadow)) - DirtyStaticBounds(a->GetSphere()); + DirtyStaticBounds(a->GetBoundingSphere()); } void OnSceneRenderingUpdateActor(Actor* a, const BoundingSphere& prevBounds, UpdateFlags flags) override @@ -405,7 +405,7 @@ public: if (a->HasStaticFlag(StaticFlags::Shadow)) { // TODO: skip actors that don't cast shadows (eg. particles) - BoundingSphere bounds = a->GetSphere(); + BoundingSphere bounds = a->GetBoundingSphere(); if (bounds != prevBounds) { // Avoid dirtying twice when bounds are close to each other @@ -418,14 +418,14 @@ public: } else if (flags & StaticFlags) { - DirtyStaticBounds(a->GetSphere()); + DirtyStaticBounds(a->GetBoundingSphere()); } } void OnSceneRenderingRemoveActor(Actor* a) override { if (a->HasStaticFlag(StaticFlags::Shadow)) - DirtyStaticBounds(a->GetSphere()); + DirtyStaticBounds(a->GetBoundingSphere()); } void OnSceneRenderingClear(SceneRendering* scene) override diff --git a/Source/Engine/UI/UICanvas.cpp b/Source/Engine/UI/UICanvas.cpp index bf87fd676..be54260da 100644 --- a/Source/Engine/UI/UICanvas.cpp +++ b/Source/Engine/UI/UICanvas.cpp @@ -63,7 +63,7 @@ UICanvas::UICanvas(const SpawnParams& params) #if USE_EDITOR -BoundingBox UICanvas::GetEditorBox() const +BoundingBox UICanvas::GetEditorBoundingBox() const { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/UI/UICanvas.h b/Source/Engine/UI/UICanvas.h index d654991bb..e8c757a3e 100644 --- a/Source/Engine/UI/UICanvas.h +++ b/Source/Engine/UI/UICanvas.h @@ -14,7 +14,7 @@ class FLAXENGINE_API UICanvas : public Actor public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override; + BoundingBox GetEditorBoundingBox() const override; #endif void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; diff --git a/Source/Engine/UI/UIControl.cpp b/Source/Engine/UI/UIControl.cpp index 06554542b..aace1ce52 100644 --- a/Source/Engine/UI/UIControl.cpp +++ b/Source/Engine/UI/UIControl.cpp @@ -60,7 +60,7 @@ UIControl::UIControl(const SpawnParams& params) #if USE_EDITOR -BoundingBox UIControl::GetEditorBox() const +BoundingBox UIControl::GetEditorBoundingBox() const { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/UI/UIControl.h b/Source/Engine/UI/UIControl.h index e7739781d..141753104 100644 --- a/Source/Engine/UI/UIControl.h +++ b/Source/Engine/UI/UIControl.h @@ -18,7 +18,7 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override; + BoundingBox GetEditorBoundingBox() const override; #endif void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; diff --git a/Source/Engine/Video/VideoPlayer.h b/Source/Engine/Video/VideoPlayer.h index ecae37e61..b754969eb 100644 --- a/Source/Engine/Video/VideoPlayer.h +++ b/Source/Engine/Video/VideoPlayer.h @@ -241,12 +241,11 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); } - void OnDebugDrawSelected() override; #endif bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override; From 411c94efcc20a6a52845a3000a10f82304ddf0ce Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Sep 2026 08:48:22 +0200 Subject: [PATCH 26/35] Fix typo --- Source/Editor/Gizmo/ViewportRubberBandSelector.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs index 1d85fcf77..bb174d224 100644 --- a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs +++ b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs @@ -13,7 +13,7 @@ namespace FlaxEngine.Gizmo; /// public sealed class ViewportRubberBandSelector { - private bool _isMosueCaptured; + private bool _isMouseCaptured; private bool _isRubberBandSpanning; private bool _tryStartRubberBand; private Float2 _cachedStartingMousePosition; @@ -53,9 +53,9 @@ public sealed class ViewportRubberBandSelector /// Returns true if rubber band is currently spanning public bool ReleaseRubberBandSelection() { - if (_isMosueCaptured) + if (_isMouseCaptured) { - _isMosueCaptured = false; + _isMouseCaptured = false; _owner.Viewport.EndMouseCapture(); } if (_tryStartRubberBand) @@ -99,9 +99,9 @@ public sealed class ViewportRubberBandSelector _rubberBandRect.Height = mousePosition.Y - _cachedStartingMousePosition.Y; if (_lastRubberBandRect != _rubberBandRect) { - if (!_isMosueCaptured) + if (!_isMouseCaptured) { - _isMosueCaptured = true; + _isMouseCaptured = true; _owner.Viewport.StartMouseCapture(); } UpdateRubberBand(); @@ -263,9 +263,9 @@ public sealed class ViewportRubberBandSelector /// True if rubber band was active before stopping. public bool StopRubberBand() { - if (_isMosueCaptured) + if (_isMouseCaptured) { - _isMosueCaptured = false; + _isMouseCaptured = false; _owner.Viewport.EndMouseCapture(); } var result = _tryStartRubberBand; From d7f52ee832a208b0010aa6d2c84a87e4244138d5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 15 Sep 2026 06:47:11 +0200 Subject: [PATCH 27/35] Fix using `NuGet` package with different dependencies #4182 --- Source/Tools/Flax.Build/Build/Builder.cs | 6 +++++- .../Build/NativeCpp/BuildOptions.cs | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Build/Builder.cs b/Source/Tools/Flax.Build/Build/Builder.cs index 775bef1dc..7e84e9d89 100644 --- a/Source/Tools/Flax.Build/Build/Builder.cs +++ b/Source/Tools/Flax.Build/Build/Builder.cs @@ -520,7 +520,7 @@ namespace Flax.Build { var doc = System.Xml.Linq.XDocument.Load(nuspecFile); var root = (System.Xml.Linq.XElement)doc.FirstNode; - var metadataNode = root.Descendants().First(x => x.Name.LocalName== "metadata"); + var metadataNode = root.Descendants().First(x => x.Name.LocalName == "metadata"); var dependenciesNode = metadataNode.Descendants().First(x => x.Name.LocalName == "dependencies"); var groupNode = dependenciesNode.Descendants().FirstOrDefault(x => x.Attribute("targetFramework")?.Value == package.Framework); if (groupNode == null) @@ -539,6 +539,10 @@ namespace Flax.Build DeployNuGetPackage(nugetPath, targetBuildOptions, nugetFiles, new NugetPackage { Name = dependencyId, Version = dependencyVersion, Framework = package.Framework } ); } } + else + { + Log.Verbose($"Missing nuspec file '{nuspecFile}' for NuGet package '{package}'"); + } } } } diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs index f158078f5..9369578b5 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs @@ -130,6 +130,21 @@ namespace Flax.Build.NativeCpp } } + // Fallback to netcore or netstandard if original directory + var fallbacks = new[] + { + "netcoreapp3.1", + "netcoreapp3.0", + "netstandard2.1", + "netstandard2.0", + }; + foreach (var fallback in fallbacks) + { + libFolder = Path.Combine(nugetPath, Name, Version, "lib", fallback); + if (Directory.Exists(libFolder)) + return libFolder; + } + Log.Error($"Missing NuGet package \"{Name}, {Version}, {Framework}\" (nuget: {nugetPath})"); return string.Empty; } @@ -157,6 +172,11 @@ namespace Flax.Build.NativeCpp } return dlls[0]; } + + public override string ToString() + { + return $"{Name}, {Version}, {Framework}"; + } } /// From 3ff1f825a75a22b0a09a59841e12d9beee05b01f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 15 Sep 2026 12:54:56 +0200 Subject: [PATCH 28/35] Revert partially #2147 to fix #4091 when imported as `fbx` --- Source/Engine/ContentImporters/ImportModel.cpp | 3 +++ Source/Engine/Tools/ModelTool/ModelTool.cpp | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index b2a9434c9..315a5fcfe 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -794,7 +794,9 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M // Setup node in hierarchy nodeToActor.Add(nodeIndex, nodeActor); nodeActor->SetName(node.Name); + nodeActor->SetLocalTransform(node.LocalTransform); +#if 0 // When use local origin is checked, it shifts everything over the same amount, including the root. This tries to work around that. if (!(nodeIndex == 0 && options.UseLocalOrigin)) { @@ -829,6 +831,7 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M nodeActor->SetLocalTransform(positionOffset); } +#endif if (nodeIndex == 0) { diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index e06e1aa03..715a3fcc7 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1631,6 +1631,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option } } } +#if 0 if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Geometry) && options.Type == ModelType::Prefab) { // Apply just the scale and rotations. @@ -1663,6 +1664,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option } } } +#endif if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Animations)) { for (auto& animation : data.Animations) From bd5edcc4d2d2ea7ecfd462d722b4d1a590679511 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 15 Sep 2026 12:55:13 +0200 Subject: [PATCH 29/35] Fix minor Editor UI issues --- Source/Editor/Windows/Assets/ModelBaseWindow.cs | 9 ++++++--- Source/Engine/UI/GUI/ContainerControl.cs | 6 ++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Windows/Assets/ModelBaseWindow.cs b/Source/Editor/Windows/Assets/ModelBaseWindow.cs index 3046ea43b..89ff1ff96 100644 --- a/Source/Editor/Windows/Assets/ModelBaseWindow.cs +++ b/Source/Editor/Windows/Assets/ModelBaseWindow.cs @@ -117,9 +117,12 @@ namespace FlaxEditor.Windows.Assets public override void OnClean() { - Window._isolateIndex = -1; - Window._highlightIndex = -1; - Window._meshProxy = null; + if (Window != null) + { + Window._isolateIndex = -1; + Window._highlightIndex = -1; + Window._meshProxy = null; + } base.OnClean(); } diff --git a/Source/Engine/UI/GUI/ContainerControl.cs b/Source/Engine/UI/GUI/ContainerControl.cs index aa0078c19..0caca6509 100644 --- a/Source/Engine/UI/GUI/ContainerControl.cs +++ b/Source/Engine/UI/GUI/ContainerControl.cs @@ -171,6 +171,12 @@ namespace FlaxEngine.GUI /// public virtual void DisposeChildren() { + // Steal focus from children to maintain it in a hierarchy + if (ContainsFocus) + { + Focus(); + } + bool wasLayoutLocked = _isLayoutLocked; _isLayoutLocked = true; From 4df2f3b4c18af815fedc328ff3b4f677d9176ce7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 15 Sep 2026 13:13:27 +0200 Subject: [PATCH 30/35] Reduce assertions in dock panel in favor of pain error --- Source/Editor/GUI/Docking/DockPanel.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Editor/GUI/Docking/DockPanel.cs b/Source/Editor/GUI/Docking/DockPanel.cs index ae162c714..20ecad605 100644 --- a/Source/Editor/GUI/Docking/DockPanel.cs +++ b/Source/Editor/GUI/Docking/DockPanel.cs @@ -501,8 +501,10 @@ namespace FlaxEditor.GUI.Docking { scrPanel.GetChild(i).Parent = splitterParent; } - Assert.IsTrue(scrPanel.ChildrenCount == 0); - Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount); + if (scrPanel.ChildrenCount != 0 || splitterParent.ChildrenCount == srcPanelChildrenCount) + { + Editor.LogError("Broken dock panel layout!"); + } // Delete splitter.Dispose(); From fe6da846168ab13ca41986ed6b64fdbf55e573a5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 15 Sep 2026 13:15:04 +0200 Subject: [PATCH 31/35] Tweak #4226 --- Source/Engine/UI/GUI/Tooltip.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index 31429ec3c..1882045ba 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -242,8 +242,9 @@ namespace FlaxEngine.GUI if (_window) _window.Position = mousePos + TooltipOffset; } - - if (Input.Mouse.IsAnyButtonDown || Math.Abs(Input.Mouse.ScrollDelta) > Mathf.Epsilon) + + var mouse = Input.Mouse; + if (mouse.IsAnyButtonDown || Math.Abs(mouse.ScrollDelta) > Mathf.Epsilon) { // Hide popup when buttons are pressed Hide(); From d04d48490abaa6b4d40ecdf01f71bf9e269a2cfe Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 15 Sep 2026 13:39:06 +0200 Subject: [PATCH 32/35] Rename `EditorViewport.HasLoadedAssets` to `HasContentLoaded` and fix it for prefab auto-fit Ref #4137 --- Source/Editor/Content/Proxy/CubeTextureProxy.cs | 2 +- .../Content/Proxy/MaterialInstanceProxy.cs | 2 +- Source/Editor/Content/Proxy/MaterialProxy.cs | 2 +- Source/Editor/Content/Proxy/ModelProxy.cs | 2 +- .../Editor/Content/Proxy/ParticleEmitterProxy.cs | 2 +- .../Editor/Content/Proxy/ParticleSystemProxy.cs | 2 +- Source/Editor/Content/Proxy/PrefabProxy.cs | 2 +- Source/Editor/Content/Proxy/SkinnedModelProxy.cs | 2 +- Source/Editor/Viewport/EditorViewport.cs | 2 +- Source/Editor/Viewport/PrefabWindowViewport.cs | 16 ++++++++++++++++ Source/Editor/Viewport/Previews/AssetPreview.cs | 2 +- .../Viewport/Previews/CubeTexturePreview.cs | 2 +- .../Editor/Viewport/Previews/MaterialPreview.cs | 4 ++-- .../Viewport/Previews/ParticleEmitterPreview.cs | 2 +- .../Viewport/Previews/ParticleSystemPreview.cs | 2 +- Source/Editor/Windows/Assets/PrefabWindow.cs | 2 +- 16 files changed, 32 insertions(+), 16 deletions(-) diff --git a/Source/Editor/Content/Proxy/CubeTextureProxy.cs b/Source/Editor/Content/Proxy/CubeTextureProxy.cs index efccfdbe5..bf7116647 100644 --- a/Source/Editor/Content/Proxy/CubeTextureProxy.cs +++ b/Source/Editor/Content/Proxy/CubeTextureProxy.cs @@ -52,7 +52,7 @@ namespace FlaxEditor.Content /// public override bool CanDrawThumbnail(ThumbnailRequest request) { - return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((CubeTexture)request.Asset); + return _preview.HasContentLoaded && ThumbnailsModule.HasMinimumQuality((CubeTexture)request.Asset); } /// diff --git a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs index 212417e9f..45de48f56 100644 --- a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs @@ -39,7 +39,7 @@ namespace FlaxEditor.Content /// public override bool CanDrawThumbnail(ThumbnailRequest request) { - return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((MaterialInstance)request.Asset); + return _preview.HasContentLoaded && ThumbnailsModule.HasMinimumQuality((MaterialInstance)request.Asset); } } } diff --git a/Source/Editor/Content/Proxy/MaterialProxy.cs b/Source/Editor/Content/Proxy/MaterialProxy.cs index 58d34299c..0943c2b6f 100644 --- a/Source/Editor/Content/Proxy/MaterialProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialProxy.cs @@ -39,7 +39,7 @@ namespace FlaxEditor.Content /// public override bool CanDrawThumbnail(ThumbnailRequest request) { - return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Material)request.Asset); + return _preview.HasContentLoaded && ThumbnailsModule.HasMinimumQuality((Material)request.Asset); } } } diff --git a/Source/Editor/Content/Proxy/ModelProxy.cs b/Source/Editor/Content/Proxy/ModelProxy.cs index a57ddbf61..7c3f8ae1c 100644 --- a/Source/Editor/Content/Proxy/ModelProxy.cs +++ b/Source/Editor/Content/Proxy/ModelProxy.cs @@ -83,7 +83,7 @@ namespace FlaxEditor.Content /// public override bool CanDrawThumbnail(ThumbnailRequest request) { - return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Model)request.Asset); + return _preview.HasContentLoaded && ThumbnailsModule.HasMinimumQuality((Model)request.Asset); } /// diff --git a/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs b/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs index 9ef533b76..6c6f0c24f 100644 --- a/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs +++ b/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs @@ -133,7 +133,7 @@ namespace FlaxEditor.Content return false; // Ensure assets are ready to be used - if (!_preview.HasLoadedAssets) + if (!_preview.HasContentLoaded) return false; var asset = (ParticleEmitter)request.Asset; if (!asset.IsLoaded) diff --git a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs index ccea01998..97ca17f25 100644 --- a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs +++ b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs @@ -104,7 +104,7 @@ namespace FlaxEditor.Content return false; // Ensure assets are ready to be used - if (!_preview.HasLoadedAssets) + if (!_preview.HasContentLoaded) return false; var asset = (ParticleSystem)request.Asset; if (!asset.IsLoaded) diff --git a/Source/Editor/Content/Proxy/PrefabProxy.cs b/Source/Editor/Content/Proxy/PrefabProxy.cs index e5cf053d6..32e3b83f3 100644 --- a/Source/Editor/Content/Proxy/PrefabProxy.cs +++ b/Source/Editor/Content/Proxy/PrefabProxy.cs @@ -118,7 +118,7 @@ namespace FlaxEditor.Content /// public override bool CanDrawThumbnail(ThumbnailRequest request) { - if (!_preview.HasLoadedAssets) + if (!_preview.HasContentLoaded) return false; // Check if asset is streamed enough diff --git a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs index 95f2f7510..9db9a152d 100644 --- a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs +++ b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs @@ -106,7 +106,7 @@ namespace FlaxEditor.Content /// public override bool CanDrawThumbnail(ThumbnailRequest request) { - return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((SkinnedModel)request.Asset); + return _preview.HasContentLoaded && ThumbnailsModule.HasMinimumQuality((SkinnedModel)request.Asset); } /// diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 763f710c8..ea36f558a 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -423,7 +423,7 @@ namespace FlaxEditor.Viewport /// /// Gets a value indicating whether this viewport has loaded dependant assets. /// - public virtual bool HasLoadedAssets => true; + public virtual bool HasContentLoaded => true; /// /// The 'View' widget button context menu. diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index 0a3b4b4e5..ba0fbc2c4 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -396,6 +396,22 @@ namespace FlaxEditor.Viewport ViewportCamera.ShowActors(TransformGizmo.SelectedParents, ref orient); } + private static bool ActorHasContentLoaded(Actor a) + { + if (!a.HasContentLoaded) + return false; + var children = a.ChildrenCount; + for (int i = 0; i < children; i++) + { + if (!ActorHasContentLoaded(a.GetChild(i))) + return false; + } + return true; + } + + /// + public override bool HasContentLoaded => base.HasContentLoaded && (Instance == null || ActorHasContentLoaded(Instance)); + /// public EditorViewport Viewport => this; diff --git a/Source/Editor/Viewport/Previews/AssetPreview.cs b/Source/Editor/Viewport/Previews/AssetPreview.cs index b6610c880..73709248f 100644 --- a/Source/Editor/Viewport/Previews/AssetPreview.cs +++ b/Source/Editor/Viewport/Previews/AssetPreview.cs @@ -245,7 +245,7 @@ namespace FlaxEditor.Viewport.Previews } /// - public override bool HasLoadedAssets => base.HasLoadedAssets && Sky.HasContentLoaded && EnvProbe.HasContentLoaded && PostFxVolume.HasContentLoaded; + public override bool HasContentLoaded => base.HasContentLoaded && Sky.HasContentLoaded && EnvProbe.HasContentLoaded && PostFxVolume.HasContentLoaded; /// public override void OnDestroy() diff --git a/Source/Editor/Viewport/Previews/CubeTexturePreview.cs b/Source/Editor/Viewport/Previews/CubeTexturePreview.cs index f0b05c8e6..0a52b8e48 100644 --- a/Source/Editor/Viewport/Previews/CubeTexturePreview.cs +++ b/Source/Editor/Viewport/Previews/CubeTexturePreview.cs @@ -237,7 +237,7 @@ namespace FlaxEditor.Viewport.Previews } /// - public override bool HasLoadedAssets => base.HasLoadedAssets && _previewMaterial.IsLoaded && _previewMaterial.BaseMaterial.IsLoaded; + public override bool HasContentLoaded => base.HasContentLoaded && _previewMaterial.IsLoaded && _previewMaterial.BaseMaterial.IsLoaded; /// public override void OnDestroy() diff --git a/Source/Editor/Viewport/Previews/MaterialPreview.cs b/Source/Editor/Viewport/Previews/MaterialPreview.cs index b64a4fdba..67267708d 100644 --- a/Source/Editor/Viewport/Previews/MaterialPreview.cs +++ b/Source/Editor/Viewport/Previews/MaterialPreview.cs @@ -175,11 +175,11 @@ namespace FlaxEditor.Viewport.Previews } /// - public override bool HasLoadedAssets + public override bool HasContentLoaded { get { - if (!base.HasLoadedAssets) + if (!base.HasContentLoaded) return false; UpdateMaterial(); return true; diff --git a/Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs b/Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs index 023bd121f..1bcaa015e 100644 --- a/Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs +++ b/Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs @@ -78,7 +78,7 @@ namespace FlaxEditor.Viewport.Previews } /// - public override bool HasLoadedAssets => (_emitter == null || _emitter.IsLoaded) && base.HasLoadedAssets; + public override bool HasContentLoaded => (_emitter == null || _emitter.IsLoaded) && base.HasContentLoaded; /// public override void OnDestroy() diff --git a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs index 13987157e..9bfbaabab 100644 --- a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs +++ b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs @@ -226,7 +226,7 @@ namespace FlaxEditor.Viewport.Previews } /// - public override bool HasLoadedAssets => _previewEffect.HasContentLoaded && base.HasLoadedAssets; + public override bool HasContentLoaded => _previewEffect.HasContentLoaded && base.HasContentLoaded; /// public override void Update(float deltaTime) diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index da41b88c5..91db9f0e0 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -536,7 +536,7 @@ namespace FlaxEditor.Windows.Assets } // Auto fit - if (_focusCamera && _viewport.Task.FrameCount > 1) + if (_focusCamera && _viewport.Task.FrameCount > 1 && _viewport.HasContentLoaded) { _focusCamera = false; Editor.GetActorEditorSphere(_viewport.Instance, out BoundingSphere bounds); From 1394adb6eb5c6395a7b64d22af293393c4d58c1b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 15 Sep 2026 13:48:23 +0200 Subject: [PATCH 33/35] Fix mesh tangents parameter documentation #4247 --- Source/Engine/Graphics/Models/Mesh.cs | 22 ++++++++++---------- Source/Engine/Graphics/Models/Mesh.h | 4 ++-- Source/Engine/Graphics/Models/SkinnedMesh.cs | 12 +++++------ Source/Engine/Graphics/Models/SkinnedMesh.h | 4 ++-- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Source/Engine/Graphics/Models/Mesh.cs b/Source/Engine/Graphics/Models/Mesh.cs index dfca198af..8b19e90a1 100644 --- a/Source/Engine/Graphics/Models/Mesh.cs +++ b/Source/Engine/Graphics/Models/Mesh.cs @@ -117,7 +117,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(Float3[] vertices, int[] triangles, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null) @@ -153,7 +153,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(List vertices, List triangles, List normals = null, List tangents = null, List uv = null, List colors = null) @@ -189,7 +189,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(Float3[] vertices, uint[] triangles, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null) @@ -225,7 +225,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(List vertices, List triangles, List normals = null, List tangents = null, List uv = null, List colors = null) @@ -261,7 +261,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 16-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The tangent vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(Float3[] vertices, ushort[] triangles, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null) @@ -297,7 +297,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 16-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The tangent vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(List vertices, List triangles, List normals = null, List tangents = null, List uv = null, List colors = null) @@ -334,7 +334,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). [Obsolete("Use UpdateMesh with Float3 and Float2 parameters instead")] @@ -352,7 +352,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). [Obsolete("Use UpdateMesh with Float3 and Float2 parameters instead")] @@ -388,7 +388,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). [Obsolete("Use UpdateMesh with Float3 and Float2 parameters instead")] @@ -406,7 +406,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 16-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The tangent vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). [Obsolete("Use UpdateMesh with Float3 and Float2 parameters instead")] @@ -424,7 +424,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 16-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The tangent vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). [Obsolete("Use UpdateMesh with Float3 and Float2 parameters instead")] diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index 8e273a3d5..9d7579610 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -93,7 +93,7 @@ public: /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). /// True if failed, otherwise false. @@ -109,7 +109,7 @@ public: /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). /// True if failed, otherwise false. diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cs b/Source/Engine/Graphics/Models/SkinnedMesh.cs index eb391dd76..cc132435a 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cs +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cs @@ -102,7 +102,7 @@ namespace FlaxEngine /// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null. /// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(Float3[] vertices, int[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null) @@ -140,7 +140,7 @@ namespace FlaxEngine /// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null. /// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(Float3[] vertices, uint[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null) @@ -178,7 +178,7 @@ namespace FlaxEngine /// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null. /// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null. /// The normal vectors (per vertex). - /// The tangent vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(Float3[] vertices, ushort[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null) @@ -217,7 +217,7 @@ namespace FlaxEngine /// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null. /// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). [Obsolete("Use UpdateMesh with Float3 and Float2 parameters instead")] public void UpdateMesh(Vector3[] vertices, int[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null) @@ -236,7 +236,7 @@ namespace FlaxEngine /// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null. /// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). [Obsolete("Use UpdateMesh with Float3 and Float2 parameters instead")] public void UpdateMesh(Vector3[] vertices, uint[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null) @@ -255,7 +255,7 @@ namespace FlaxEngine /// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null. /// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null. /// The normal vectors (per vertex). - /// The tangent vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). [Obsolete("Use UpdateMesh with Float3 and Float2 parameters instead")] public void UpdateMesh(Vector3[] vertices, ushort[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null) diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.h b/Source/Engine/Graphics/Models/SkinnedMesh.h index ce77c81d7..36eff0313 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.h +++ b/Source/Engine/Graphics/Models/SkinnedMesh.h @@ -99,7 +99,7 @@ public: /// The skeletal bones indices to use for skinning. /// The skeletal bones weights to use for skinning (matches blendIndices). /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). /// True if failed, otherwise false. @@ -117,7 +117,7 @@ public: /// The skeletal bones indices to use for skinning. /// The skeletal bones weights to use for skinning (matches blendIndices). /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). /// True if failed, otherwise false. From 82ee8713f509e52fbc006a25fb3f0f1361a79583 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 15 Sep 2026 13:52:36 +0200 Subject: [PATCH 34/35] Fix missing mutex lock in Game Cooker thread #4170 #4181 --- Source/Editor/Cooker/GameCooker.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Editor/Cooker/GameCooker.cpp b/Source/Editor/Cooker/GameCooker.cpp index 969e71b71..3c2dad82d 100644 --- a/Source/Editor/Cooker/GameCooker.cpp +++ b/Source/Editor/Cooker/GameCooker.cpp @@ -802,7 +802,9 @@ int32 GameCookerImpl::ThreadFunction() Build(); } + mutex.Lock(); ThreadCond.Wait(mutex); + mutex.Unlock(); } IsThreadRunning = false; From 7a6190944c976b6b838e5afdee89294855d02ad9 Mon Sep 17 00:00:00 2001 From: Roman Zhu Date: Wed, 2 Sep 2026 01:53:26 +0200 Subject: [PATCH 35/35] RMB look doesn't pitch up sometimes on weird scaling/dpi combo # Conflicts: # Source/Editor/Viewport/EditorViewport.cs --- Source/Editor/Viewport/EditorViewport.cs | 15 ++++++++---- Source/Engine/Tests/TestEditorViewport.cs | 28 +++++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 Source/Engine/Tests/TestEditorViewport.cs diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index ea36f558a..6711b3606 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -1560,8 +1560,9 @@ namespace FlaxEditor.Viewport var center = Float2.Round(size * 0.5f); if (Mathf.Abs(_viewMousePos.X - center.X) > center.X * 0.8f || Mathf.Abs(_viewMousePos.Y - center.Y) > center.Y * 0.8f) { - _viewMousePos = center; - win.MousePosition = PointToWindow(_viewMousePos); + var windowPosition = SnapMousePositionToDevicePixels(PointToWindow(center), win.DpiScale); + _viewMousePos = PointFromWindow(windowPosition); + win.MousePosition = windowPosition * win.DpiScale; } #endif } @@ -1578,6 +1579,11 @@ namespace FlaxEditor.Viewport win.MouseMoveRelative -= OnMouseMoveRelative; } + internal static Float2 SnapMousePositionToDevicePixels(Float2 position, float dpiScale) + { + return Float2.Round(position * dpiScale) / dpiScale; + } + /// /// Called when left mouse button goes down (on press). /// @@ -1858,8 +1864,9 @@ namespace FlaxEditor.Viewport // Move mouse back to the root position if (centerMouse && (_input.IsMouseRightDown || _input.IsMouseLeftDown || _input.IsMouseMiddleDown || _isVirtualMouseRightDown)) { - var center = PointToWindow(_startPos); - win.MousePosition = center; + var windowPosition = SnapMousePositionToDevicePixels(PointToWindow(_startPos), win.Window.DpiScale); + _startPos = PointFromWindow(windowPosition); + win.MousePosition = windowPosition; } #endif diff --git a/Source/Engine/Tests/TestEditorViewport.cs b/Source/Engine/Tests/TestEditorViewport.cs new file mode 100644 index 000000000..9123cfd20 --- /dev/null +++ b/Source/Engine/Tests/TestEditorViewport.cs @@ -0,0 +1,28 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#if FLAX_TESTS +using FlaxEditor.Viewport; +using FlaxEngine; +using NUnit.Framework; + +namespace FlaxEditor.Tests +{ + [TestFixture] + public class TestEditorViewport + { + [Test] + public void TestMouseAnchorSnapsToDevicePixels() + { + const float dpiScale = 1.25f; + var logicalPosition = new Float2(984.7999f, 580.7999f); + + var snapped = EditorViewport.SnapMousePositionToDevicePixels(logicalPosition, dpiScale); + var physicalPosition = snapped * dpiScale; + + Assert.AreEqual(1231.0f, physicalPosition.X, 0.0001f); + Assert.AreEqual(726.0f, physicalPosition.Y, 0.0001f); + Assert.AreEqual(snapped, EditorViewport.SnapMousePositionToDevicePixels(snapped, dpiScale)); + } + } +} +#endif