From a253e01dbd7b925869bd66c3ae482c9a909c96cd Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 31 Dec 2022 00:21:28 +0100 Subject: [PATCH 01/14] Add unit test for scripting event to ensure generated bindings code works fine --- Source/Engine/Tests/TestScripting.cpp | 32 ++++++++++- Source/Engine/Tests/TestScripting.cs | 79 ++++++++++++++++++++++++++- Source/Engine/Tests/TestScripting.h | 22 +++++++- 3 files changed, 128 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Tests/TestScripting.cpp b/Source/Engine/Tests/TestScripting.cpp index ecea80db7..958bb8d8d 100644 --- a/Source/Engine/Tests/TestScripting.cpp +++ b/Source/Engine/Tests/TestScripting.cpp @@ -21,7 +21,9 @@ TEST_CASE("Scripting") CHECK(object->Is()); TestClassNative* testClass = (TestClassNative*)object; CHECK(testClass->SimpleField == 1); - int32 methodResult = testClass->Test(TEXT("123")); + CHECK(testClass->SimpleStruct.Object == nullptr); + CHECK(testClass->SimpleStruct.Vector == Float3::One); + int32 methodResult = testClass->TestMethod(TEXT("123")); CHECK(methodResult == 3); // Test managed class @@ -34,7 +36,33 @@ TEST_CASE("Scripting") MObject* managed = testClass->GetOrCreateManagedInstance(); // Ensure to create C# object and run it's ctor CHECK(managed); CHECK(testClass->SimpleField == 2); - methodResult = testClass->Test(TEXT("123")); + CHECK(testClass->SimpleStruct.Object == testClass); + CHECK(testClass->SimpleStruct.Vector == Float3::UnitX); + methodResult = testClass->TestMethod(TEXT("123")); CHECK(methodResult == 6); } + + SECTION("Test Event") + { + ScriptingTypeHandle type = Scripting::FindScriptingType("FlaxEngine.TestClassManaged"); + CHECK(type); + ScriptingObject* object = Scripting::NewObject(type.GetType().ManagedClass); + CHECK(object); + MObject* managed = object->GetOrCreateManagedInstance(); // Ensure to create C# object and run it's ctor + CHECK(managed); + TestClassNative* testClass = (TestClassNative*)object; + CHECK(testClass->SimpleField == 2); + String str1 = TEXT("1"); + String str2 = TEXT("2"); + Array arr1 = { testClass->SimpleStruct }; + Array arr2 = { testClass->SimpleStruct }; + testClass->SimpleEvent(1, Float3::One, str1, str2, arr1, arr2); + CHECK(testClass->SimpleField == 4); + CHECK(str2 == TEXT("4")); + CHECK(arr2.Count() == 2); + CHECK(arr2[0].Vector == Float3::Half); + CHECK(arr2[0].Object == nullptr); + CHECK(arr2[1].Vector == testClass->SimpleStruct.Vector); + CHECK(arr2[1].Object == testClass); + } } diff --git a/Source/Engine/Tests/TestScripting.cs b/Source/Engine/Tests/TestScripting.cs index 6abc667a1..4f2d9b229 100644 --- a/Source/Engine/Tests/TestScripting.cs +++ b/Source/Engine/Tests/TestScripting.cs @@ -3,6 +3,48 @@ #if FLAX_TESTS namespace FlaxEngine { + partial struct TestStruct : System.IEquatable + { + /// + public static bool operator ==(TestStruct left, TestStruct right) + { + return left.Equals(right); + } + + /// + public static bool operator !=(TestStruct left, TestStruct right) + { + return !left.Equals(right); + } + + /// + public bool Equals(TestStruct other) + { + return Vector.Equals(other.Vector) && Equals(Object, other.Object); + } + + /// + public override bool Equals(object obj) + { + return obj is TestStruct other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (Vector.GetHashCode() * 397) ^ (Object != null ? Object.GetHashCode() : 0); + } + } + + /// + public override string ToString() + { + return $"Vector={Vector}, Object={Object?.ToString() ?? "null"}"; + } + } + /// /// Test class. /// @@ -10,13 +52,46 @@ namespace FlaxEngine { TestClassManaged() { + // Test setting C++ values from C# SimpleField = 2; + SimpleStruct = new TestStruct + { + Vector = Float3.UnitX, + Object = this, + }; + SimpleEvent += OnSimpleEvent; } /// - public override int Test(string str) + public override int TestMethod(string str) { - return str.Length + base.Test(str); + // Test C++ base method invocation + return str.Length + base.TestMethod(str); + } + + private void OnSimpleEvent(int arg1, Float3 arg2, string arg3, ref string arg4, TestStruct[] arg5, ref TestStruct[] arg6) + { + // Verify that C++ passed proper data to C# via event bindings + if (arg1 == 1 && + arg2 == Float3.One && + arg3 == "1" && + arg4 == "2" && + arg5 != null && arg5.Length == 1 && arg5[0] == SimpleStruct && + arg6 != null && arg6.Length == 1 && arg6[0] == SimpleStruct) + { + // Test passing data back from C# to C++ + SimpleField = 4; + arg4 = "4"; + arg6 = new TestStruct[2] + { + new TestStruct + { + Vector = Float3.Half, + Object = null, + }, + SimpleStruct, + }; + } } } } diff --git a/Source/Engine/Tests/TestScripting.h b/Source/Engine/Tests/TestScripting.h index 06b5dddb5..90996de40 100644 --- a/Source/Engine/Tests/TestScripting.h +++ b/Source/Engine/Tests/TestScripting.h @@ -3,8 +3,22 @@ #pragma once #include "Engine/Core/ISerializable.h" +#include "Engine/Core/Math/Vector3.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Scripting/ScriptingObject.h" +// Test structure. +API_STRUCT(NoDefault) struct TestStruct : public ISerializable +{ + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_MINIMAL(TestStruct); + + // Var + API_FIELD() Float3 Vector = Float3::One; + // Ref + API_FIELD() ScriptingObject* Object = nullptr; +}; + // Test class. API_CLASS() class TestClassNative : public ScriptingObject, public ISerializable { @@ -15,8 +29,14 @@ public: // Test value API_FIELD() int32 SimpleField = 1; + // Test struct + API_FIELD() TestStruct SimpleStruct; + + // Test event + API_EVENT() Delegate&, Array&> SimpleEvent; + // Test virtual method - API_FUNCTION() virtual int32 Test(const String& str) + API_FUNCTION() virtual int32 TestMethod(const String& str) { return str.Length(); } From 0ca8fe2f45ad7eae65af42427b2e2c68358c9300 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 31 Dec 2022 00:22:04 +0100 Subject: [PATCH 02/14] Fix passing reference types back to the native code from scripting event --- .../Bindings/BindingsGenerator.Cpp.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 0fd5ead49..fb641eb83 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -99,14 +99,15 @@ namespace Flax.Build.Bindings return sb.ToString(); } - private static string GenerateCppWrapperNativeToManagedParam(BuildData buildData, StringBuilder contents, TypeInfo paramType, string paramName, ApiTypeInfo caller, bool isOut) + private static string GenerateCppWrapperNativeToManagedParam(BuildData buildData, StringBuilder contents, TypeInfo paramType, string paramName, ApiTypeInfo caller, bool isOut, out bool useLocalVar) { + useLocalVar = false; var nativeToManaged = GenerateCppWrapperNativeToManaged(buildData, paramType, caller, out var managedTypeAsNative, null); string result; if (!string.IsNullOrEmpty(nativeToManaged)) { result = string.Format(nativeToManaged, paramName); - if (managedTypeAsNative[managedTypeAsNative.Length - 1] == '*') + if (managedTypeAsNative[managedTypeAsNative.Length - 1] == '*' && !isOut) { // Pass pointer value } @@ -117,6 +118,7 @@ namespace Flax.Build.Bindings result = string.Format(nativeToManaged, '*' + paramName); contents.Append($" auto __param_{paramName} = {result};").AppendLine(); result = $"&__param_{paramName}"; + useLocalVar = true; } } else @@ -1253,7 +1255,7 @@ namespace Flax.Build.Bindings for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; - var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, parameterInfo.Type, parameterInfo.Name, classInfo, parameterInfo.IsOut); + var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, parameterInfo.Type, parameterInfo.Name, classInfo, parameterInfo.IsOut, out _); contents.Append($" params[{i}] = {paramValue};").AppendLine(); } @@ -1645,7 +1647,8 @@ namespace Flax.Build.Bindings { var paramType = eventInfo.Type.GenericArgs[i]; var paramName = "arg" + i; - var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, paramType, paramName, classInfo, false); + var paramIsOut = paramType.IsRef && !paramType.IsConst; + var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, paramType, paramName, classInfo, paramIsOut, out CppParamsThatNeedConversion[i]); contents.Append($" params[{i}] = {paramValue};").AppendLine(); } if (eventInfo.IsStatic) @@ -1658,13 +1661,15 @@ namespace Flax.Build.Bindings for (var i = 0; i < paramsCount; i++) { var paramType = eventInfo.Type.GenericArgs[i]; - if (paramType.IsRef && !paramType.IsConst) + var paramIsOut = paramType.IsRef && !paramType.IsConst; + if (paramIsOut) { // Convert value back from managed to native (could be modified there) paramType.IsRef = false; var managedToNative = GenerateCppWrapperManagedToNative(buildData, paramType, classInfo, out var managedType, null, out _); var passAsParamPtr = managedType.EndsWith("*"); - var paramValue = $"({managedType}{(passAsParamPtr ? "" : "*")})params[{i}]"; + var useLocalVar = CppParamsThatNeedConversion[i]; + var paramValue = useLocalVar ? $"*({managedType}{(passAsParamPtr ? "" : "*")}*)params[{i}]" : $"({managedType}{(passAsParamPtr ? "" : "*")})params[{i}]"; if (!string.IsNullOrEmpty(managedToNative)) { if (!passAsParamPtr) @@ -2379,7 +2384,7 @@ namespace Flax.Build.Bindings if (typeInfo.IsArray) { typeInfo.IsArray = false; - header.Append($"{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}Array(const {typeInfo}* v, const int32 length)").AppendLine(); + header.Append($"{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}Array({(typeInfo.IsConst ? "const " : "")}{typeInfo}* v, const int32 length)").AppendLine(); header.Append('{').AppendLine(); header.Append(" Variant result;").AppendLine(); header.Append(" result.SetType(VariantType(VariantType::Array));").AppendLine(); @@ -2392,7 +2397,7 @@ namespace Flax.Build.Bindings else if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) { var valueType = typeInfo.GenericArgs[0]; - header.Append($"{GenerateCppWrapperNativeToVariantMethodName(valueType)}Array(const {valueType}* v, const int32 length)").AppendLine(); + header.Append($"{GenerateCppWrapperNativeToVariantMethodName(valueType)}Array({(typeInfo.IsConst ? "const " : "")}{valueType}* v, const int32 length)").AppendLine(); header.Append('{').AppendLine(); header.Append(" Variant result;").AppendLine(); header.Append(" result.SetType(VariantType(VariantType::Array));").AppendLine(); From a10fb703fc8c88742d0dee1f8edc3a9683e9f3ec Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 31 Dec 2022 13:10:19 +0100 Subject: [PATCH 03/14] Add unit test for scripting interface to ensure generated bindings code works fine --- Source/Engine/Scripting/ScriptingObject.h | 5 +++ Source/Engine/Tests/TestScripting.cpp | 47 +++++++++++++++++++++++ Source/Engine/Tests/TestScripting.cs | 29 +++++++++++--- Source/Engine/Tests/TestScripting.h | 17 +++++++- 4 files changed, 92 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Scripting/ScriptingObject.h b/Source/Engine/Scripting/ScriptingObject.h index 7db036e17..ff30053c0 100644 --- a/Source/Engine/Scripting/ScriptingObject.h +++ b/Source/Engine/Scripting/ScriptingObject.h @@ -119,6 +119,11 @@ public: // Tries to cast native interface object to scripting object instance. Returns null if fails. static ScriptingObject* FromInterface(void* interfaceObj, const ScriptingTypeHandle& interfaceType); + template + static ScriptingObject* FromInterface(T* interfaceObj) + { + return FromInterface(interfaceObj, T::TypeInitializer); + } static void* ToInterface(ScriptingObject* obj, const ScriptingTypeHandle& interfaceType); template static T* ToInterface(ScriptingObject* obj) diff --git a/Source/Engine/Tests/TestScripting.cpp b/Source/Engine/Tests/TestScripting.cpp index 958bb8d8d..29c7d5eb5 100644 --- a/Source/Engine/Tests/TestScripting.cpp +++ b/Source/Engine/Tests/TestScripting.cpp @@ -65,4 +65,51 @@ TEST_CASE("Scripting") CHECK(arr2[1].Vector == testClass->SimpleStruct.Vector); CHECK(arr2[1].Object == testClass); } + SECTION("Test Interface") + { + // Test native interface implementation + ScriptingTypeHandle type = Scripting::FindScriptingType("FlaxEngine.TestClassNative"); + CHECK(type); + ScriptingObject* object = Scripting::NewObject(type.GetType().ManagedClass); + CHECK(object); + TestClassNative* testClass = (TestClassNative*)object; + int32 methodResult = testClass->TestInterfaceMethod(TEXT("123")); + CHECK(methodResult == 3); + ITestInterface* interface = ScriptingObject::ToInterface(object); + CHECK(interface); + methodResult = interface->TestInterfaceMethod(TEXT("1234")); + CHECK(methodResult == 4); + ScriptingObject* interfaceObject = ScriptingObject::FromInterface(interface); + CHECK(interfaceObject); + CHECK(interfaceObject == object); + + // Test managed interface override + type = Scripting::FindScriptingType("FlaxEngine.TestClassManaged"); + CHECK(type); + object = Scripting::NewObject(type.GetType().ManagedClass); + CHECK(object); + testClass = (TestClassNative*)object; + methodResult = testClass->TestInterfaceMethod(TEXT("123")); + CHECK(methodResult == 6); + interface = ScriptingObject::ToInterface(object); + CHECK(interface); + methodResult = interface->TestInterfaceMethod(TEXT("1234")); + CHECK(methodResult == 8); + interfaceObject = ScriptingObject::FromInterface(interface); + CHECK(interfaceObject); + CHECK(interfaceObject == object); + + // Test managed interface implementation + type = Scripting::FindScriptingType("FlaxEngine.TestInterfaceManaged"); + CHECK(type); + object = Scripting::NewObject(type.GetType().ManagedClass); + CHECK(object); + interface = ScriptingObject::ToInterface(object); + CHECK(interface); + methodResult = interface->TestInterfaceMethod(TEXT("1234")); + CHECK(methodResult == 4); + interfaceObject = ScriptingObject::FromInterface(interface); + CHECK(interfaceObject); + CHECK(interfaceObject == object); + } } diff --git a/Source/Engine/Tests/TestScripting.cs b/Source/Engine/Tests/TestScripting.cs index 4f2d9b229..6203de314 100644 --- a/Source/Engine/Tests/TestScripting.cs +++ b/Source/Engine/Tests/TestScripting.cs @@ -69,14 +69,21 @@ namespace FlaxEngine return str.Length + base.TestMethod(str); } + /// + public override int TestInterfaceMethod(string str) + { + // Test C++ base method invocation + return str.Length + base.TestInterfaceMethod(str); + } + private void OnSimpleEvent(int arg1, Float3 arg2, string arg3, ref string arg4, TestStruct[] arg5, ref TestStruct[] arg6) { // Verify that C++ passed proper data to C# via event bindings - if (arg1 == 1 && - arg2 == Float3.One && - arg3 == "1" && - arg4 == "2" && - arg5 != null && arg5.Length == 1 && arg5[0] == SimpleStruct && + if (arg1 == 1 && + arg2 == Float3.One && + arg3 == "1" && + arg4 == "2" && + arg5 != null && arg5.Length == 1 && arg5[0] == SimpleStruct && arg6 != null && arg6.Length == 1 && arg6[0] == SimpleStruct) { // Test passing data back from C# to C++ @@ -94,5 +101,17 @@ namespace FlaxEngine } } } + + /// + /// Test interface in C#. + /// + public class TestInterfaceManaged : Object, ITestInterface + { + /// + public int TestInterfaceMethod(string str) + { + return str.Length; + } + } } #endif diff --git a/Source/Engine/Tests/TestScripting.h b/Source/Engine/Tests/TestScripting.h index 90996de40..c5ba39b10 100644 --- a/Source/Engine/Tests/TestScripting.h +++ b/Source/Engine/Tests/TestScripting.h @@ -19,8 +19,18 @@ API_STRUCT(NoDefault) struct TestStruct : public ISerializable API_FIELD() ScriptingObject* Object = nullptr; }; +// Test interface. +API_INTERFACE() class ITestInterface +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(ITestInterface); + ~ITestInterface() = default; + + // Test abstract method + API_FUNCTION() virtual int32 TestInterfaceMethod(const String& str) = 0; +}; + // Test class. -API_CLASS() class TestClassNative : public ScriptingObject, public ISerializable +API_CLASS() class TestClassNative : public ScriptingObject, public ISerializable, public ITestInterface { API_AUTO_SERIALIZATION(); DECLARE_SCRIPTING_TYPE(TestClassNative); @@ -40,4 +50,9 @@ public: { return str.Length(); } + + int32 TestInterfaceMethod(const String& str) override + { + return str.Length(); + } }; From 081305fab76ba6643136b44c2befd466584498cc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 31 Dec 2022 13:23:36 +0100 Subject: [PATCH 04/14] Fix regression from 0ca8fe2f45ad7eae65af42427b2e2c68358c9300 for value types --- .../Bindings/BindingsGenerator.Cpp.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index fb641eb83..1bcfc7ff5 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -578,12 +578,12 @@ namespace Flax.Build.Bindings } } - private static string GenerateCppWrapperManagedToNative(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, out string type, FunctionInfo functionInfo, out bool needLocalVariable) + private static string GenerateCppWrapperManagedToNative(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, out string type, out ApiTypeInfo apiType, FunctionInfo functionInfo, out bool needLocalVariable) { needLocalVariable = false; // Register any API types usage - var apiType = FindApiTypeInfo(buildData, typeInfo, caller); + apiType = FindApiTypeInfo(buildData, typeInfo, caller); CppReferencesFiles.Add(apiType?.File); if (typeInfo.GenericArgs != null) { @@ -598,7 +598,7 @@ namespace Flax.Build.Bindings if (typeInfo.IsArray) { var arrayType = new TypeInfo { Type = "Array", GenericArgs = new List { new TypeInfo(typeInfo) { IsArray = false } } }; - var result = GenerateCppWrapperManagedToNative(buildData, arrayType, caller, out type, functionInfo, out needLocalVariable); + var result = GenerateCppWrapperManagedToNative(buildData, arrayType, caller, out type, out _, functionInfo, out needLocalVariable); return result + ".Get()"; } @@ -938,7 +938,7 @@ namespace Flax.Build.Bindings separator = true; CppParamsThatNeedConversion[i] = false; - CppParamsWrappersCache[i] = GenerateCppWrapperManagedToNative(buildData, parameterInfo.Type, caller, out var managedType, functionInfo, out CppParamsThatNeedLocalVariable[i]); + CppParamsWrappersCache[i] = GenerateCppWrapperManagedToNative(buildData, parameterInfo.Type, caller, out var managedType, out var apiType, functionInfo, out CppParamsThatNeedLocalVariable[i]); // Out parameters that need additional converting will be converted at the native side (eg. object reference) var isOutWithManagedConverter = parameterInfo.IsOut && !string.IsNullOrEmpty(GenerateCSharpManagedToNativeConverter(buildData, parameterInfo.Type, caller)); @@ -956,7 +956,6 @@ namespace Flax.Build.Bindings if (parameterInfo.IsOut || isRefOut) { bool convertOutputParameter = false; - var apiType = FindApiTypeInfo(buildData, parameterInfo.Type, caller); if (apiType != null) { // Non-POD structure passed as value (eg. it contains string or array inside) @@ -991,7 +990,7 @@ namespace Flax.Build.Bindings contents.Append(", "); separator = true; - GenerateCppWrapperManagedToNative(buildData, parameterInfo.Type, caller, out var managedType, functionInfo, out _); + GenerateCppWrapperManagedToNative(buildData, parameterInfo.Type, caller, out var managedType, out _, functionInfo, out _); contents.Append(managedType); if (parameterInfo.IsRef || parameterInfo.IsOut || UsePassByReference(buildData, parameterInfo.Type, caller)) contents.Append('*'); @@ -1666,10 +1665,10 @@ namespace Flax.Build.Bindings { // Convert value back from managed to native (could be modified there) paramType.IsRef = false; - var managedToNative = GenerateCppWrapperManagedToNative(buildData, paramType, classInfo, out var managedType, null, out _); + var managedToNative = GenerateCppWrapperManagedToNative(buildData, paramType, classInfo, out var managedType, out var apiType, null, out _); var passAsParamPtr = managedType.EndsWith("*"); - var useLocalVar = CppParamsThatNeedConversion[i]; - var paramValue = useLocalVar ? $"*({managedType}{(passAsParamPtr ? "" : "*")}*)params[{i}]" : $"({managedType}{(passAsParamPtr ? "" : "*")})params[{i}]"; + var useLocalVarPointer = CppParamsThatNeedConversion[i] && !apiType.IsValueType; + var paramValue = useLocalVarPointer ? $"*({managedType}{(passAsParamPtr ? "" : "*")}*)params[{i}]" : $"({managedType}{(passAsParamPtr ? "" : "*")})params[{i}]"; if (!string.IsNullOrEmpty(managedToNative)) { if (!passAsParamPtr) @@ -2562,7 +2561,7 @@ namespace Flax.Build.Bindings continue; CppNonPodTypesConvertingGeneration = true; - var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out _, null, out _); + var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out _, out _, null, out _); CppNonPodTypesConvertingGeneration = false; if (fieldInfo.Type.IsArray) @@ -2646,7 +2645,7 @@ namespace Flax.Build.Bindings continue; CppNonPodTypesConvertingGeneration = true; - var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out _, null, out _); + var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out _, out _, null, out _); CppNonPodTypesConvertingGeneration = false; if (fieldInfo.Type.IsArray) From b73bd519becd1ece63fe6d0476e443b6a6f30670 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 31 Dec 2022 14:43:50 +0100 Subject: [PATCH 05/14] Add navmesh update on terrain sculpt undo/redo and deffer navmesh update when sculpting action ends to prevent stalls --- Source/Editor/Tools/Terrain/Sculpt/Mode.cs | 11 +--- .../Terrain/Undo/EditTerrainMapAction.cs | 62 ++++++++++++++----- 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/Source/Editor/Tools/Terrain/Sculpt/Mode.cs b/Source/Editor/Tools/Terrain/Sculpt/Mode.cs index a8b8cf53a..4297408c8 100644 --- a/Source/Editor/Tools/Terrain/Sculpt/Mode.cs +++ b/Source/Editor/Tools/Terrain/Sculpt/Mode.cs @@ -147,17 +147,8 @@ namespace FlaxEditor.Tools.Terrain.Sculpt Apply(ref p); } - var editorOptions = Editor.Instance.Options.Options; - bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode; - // Auto NavMesh rebuild - if (!isPlayMode && editorOptions.General.AutoRebuildNavMesh) - { - if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation)) - { - Navigation.BuildNavMesh(terrain.Scene, brushBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs); - } - } + gizmo.CurrentEditUndoAction.AddDirtyBounds(ref brushBounds); } /// diff --git a/Source/Editor/Tools/Terrain/Undo/EditTerrainMapAction.cs b/Source/Editor/Tools/Terrain/Undo/EditTerrainMapAction.cs index baede8a73..c82b49b7e 100644 --- a/Source/Editor/Tools/Terrain/Undo/EditTerrainMapAction.cs +++ b/Source/Editor/Tools/Terrain/Undo/EditTerrainMapAction.cs @@ -41,33 +41,24 @@ namespace FlaxEditor.Tools.Terrain.Undo public object Tag; } - /// - /// The terrain (actor Id). - /// [Serialize] protected readonly Guid _terrain; - /// - /// The heightmap length (vertex count). - /// [Serialize] protected readonly int _heightmapLength; - /// - /// The heightmap data size (in bytes). - /// [Serialize] protected readonly int _heightmapDataSize; - /// - /// The terrain patches - /// [Serialize] protected readonly List _patches; - /// - /// Gets a value indicating whether this action has any modification to the terrain (recorded patches changes). - /// + [Serialize] + protected readonly List _navmeshBoundsModifications; + + [Serialize] + protected readonly float _dirtyNavMeshTimeoutMs; + [NoSerialize] public bool HasAnyModification => _patches.Count > 0; @@ -98,6 +89,27 @@ namespace FlaxEditor.Tools.Terrain.Undo var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1; _heightmapLength = heightmapSize * heightmapSize; _heightmapDataSize = _heightmapLength * stride; + + // Auto NavMesh rebuild + var editorOptions = Editor.Instance.Options.Options; + bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode; + if (!isPlayMode && editorOptions.General.AutoRebuildNavMesh) + { + if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation)) + { + _navmeshBoundsModifications = new List(); + _dirtyNavMeshTimeoutMs = editorOptions.General.AutoRebuildNavMeshTimeoutMs; + } + } + } + + /// + /// Adds modified bounds to the undo actor modifications list. + /// + /// The world-space bounds. + public void AddDirtyBounds(ref BoundingBox bounds) + { + _navmeshBoundsModifications?.Add(bounds); } /// @@ -155,7 +167,15 @@ namespace FlaxEditor.Tools.Terrain.Undo _patches[i] = patch; } - Editor.Instance.Scene.MarkSceneEdited(Terrain.Scene); + // Update navmesh + var scene = Terrain.Scene; + if (_navmeshBoundsModifications != null) + { + foreach (var bounds in _navmeshBoundsModifications) + Navigation.BuildNavMesh(scene, bounds, _dirtyNavMeshTimeoutMs); + } + + Editor.Instance.Scene.MarkSceneEdited(scene); } /// @@ -183,10 +203,12 @@ namespace FlaxEditor.Tools.Terrain.Undo Marshal.FreeHGlobal(_patches[i].After); } _patches.Clear(); + _navmeshBoundsModifications?.Clear(); } private void Set(Func dataGetter) { + // Update patches for (int i = 0; i < _patches.Count; i++) { var patch = _patches[i]; @@ -194,6 +216,14 @@ namespace FlaxEditor.Tools.Terrain.Undo SetData(ref patch.PatchCoord, data, patch.Tag); } + // Update navmesh + var scene = Terrain.Scene; + if (_navmeshBoundsModifications != null) + { + foreach (var bounds in _navmeshBoundsModifications) + Navigation.BuildNavMesh(scene, bounds, _dirtyNavMeshTimeoutMs); + } + Editor.Instance.Scene.MarkSceneEdited(Terrain.Scene); } From 18baab0c6437a82fac940767b2958e90bf2b63fb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 1 Jan 2023 15:20:26 +0100 Subject: [PATCH 06/14] Update DefaultLensDirt and DefaultLensStarburst texturs to reduce GPU memory usage --- Content/Engine/Textures/DefaultLensDirt.flax | 4 ++-- Content/Engine/Textures/DefaultLensStarburst.flax | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Content/Engine/Textures/DefaultLensDirt.flax b/Content/Engine/Textures/DefaultLensDirt.flax index f5aab3eaa..a4cc62324 100644 --- a/Content/Engine/Textures/DefaultLensDirt.flax +++ b/Content/Engine/Textures/DefaultLensDirt.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7e30c93ed54ef1ebfd3b8ecb59a73b037c297d058951e616da758cfeca6ee91 -size 16589149 +oid sha256:149b9a2d5d9d5f02e3e1e0a8d58964d53c4432d969ee290dd2be08ce2285d946 +size 8295253 diff --git a/Content/Engine/Textures/DefaultLensStarburst.flax b/Content/Engine/Textures/DefaultLensStarburst.flax index cc3351fed..ffc15ae75 100644 --- a/Content/Engine/Textures/DefaultLensStarburst.flax +++ b/Content/Engine/Textures/DefaultLensStarburst.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6698438109bfd26be8e320986ce3b93b5bf0f44907ad5562e3d4fc555be2c36a -size 11185242 +oid sha256:9b6d662c3f40e7c03da5854bccc34ccf9b81219e38a05141eabefc9d830ff4c3 +size 1399104 From c976d190a5c2c0a2fe88c711666f187ee20043fc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 1 Jan 2023 16:38:50 +0100 Subject: [PATCH 07/14] Add `Tag` to `PhysicalMaterial` for physical surfaces tagging --- Source/Engine/Physics/PhysicalMaterial.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Engine/Physics/PhysicalMaterial.h b/Source/Engine/Physics/PhysicalMaterial.h index b87cb5035..8716833a7 100644 --- a/Source/Engine/Physics/PhysicalMaterial.h +++ b/Source/Engine/Physics/PhysicalMaterial.h @@ -4,6 +4,7 @@ #include "Types.h" #include "Engine/Core/ISerializable.h" +#include "Engine/Level/Tags.h" /// /// Physical materials are used to define the response of a physical object when interacting dynamically with the world. @@ -69,6 +70,12 @@ public: API_FIELD(Attributes="EditorOrder(10), EditorDisplay(\"Physical Material\")") float Density = 1000.0f; + /// + /// Physical material tag used to identify it (eg. `Surface.Wood`). Can be used to play proper footstep sounds when walking over object with that material. + /// + API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Physical Material\")") + Tag Tag; + public: /// /// Gets the PhysX material. From 2df3574cfae9b4fb81c46fe473e9171fcd546245 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 1 Jan 2023 16:39:34 +0100 Subject: [PATCH 08/14] Refactor new tags system to use uint for tag index (0 is invalid tag) --- .../Editor/CustomEditors/Editors/TagEditor.cs | 15 ++-- Source/Engine/Level/Tags.cpp | 17 ++-- Source/Engine/Level/Tags.cs | 81 ++++++++++++++++--- Source/Engine/Level/Tags.h | 10 +-- 4 files changed, 90 insertions(+), 33 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index c5c1ee6b0..0523904e5 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -55,8 +55,8 @@ namespace FlaxEditor.CustomEditors.Editors { if (Values[0] is Tag asTag) return asTag; - if (Values[0] is int asInt) - return new Tag(asInt); + if (Values[0] is uint asUInt) + return new Tag(asUInt); if (Values[0] is string asString) return Tags.Get(asString); return Tag.Default; @@ -65,7 +65,7 @@ namespace FlaxEditor.CustomEditors.Editors { if (Values[0] is Tag) SetValue(value); - if (Values[0] is int) + if (Values[0] is uint) SetValue(value.Index); else if (Values[0] is string) SetValue(value.ToString()); @@ -114,7 +114,7 @@ namespace FlaxEditor.CustomEditors.Editors private static void GetTags(TreeNode n, PickerData pickerData) { if (n is TreeNodeWithAddons a && a.Addons.Count != 0 && a.Addons[0] is CheckBox c && c.Checked) - pickerData.CachedTags.Add(new Tag((int)n.Tag)); + pickerData.CachedTags.Add(new Tag((uint)n.Tag)); foreach (var child in n.Children) { if (child is TreeNode treeNode) @@ -139,7 +139,7 @@ namespace FlaxEditor.CustomEditors.Editors if (pickerData.IsSingle) { UncheckAll(node.ParentTree, node); - var value = new Tag(c.Checked ? (int)node.Tag : -1); + var value = new Tag(c.Checked ? (uint)node.Tag : 0); pickerData.SetValue?.Invoke(value); pickerData.SetValues?.Invoke(new[] { value }); } @@ -283,7 +283,8 @@ namespace FlaxEditor.CustomEditors.Editors for (var i = 0; i < tags.Length; i++) { var tag = tags[i]; - bool isSelected = pickerData.IsSingle ? value.Index == i : values.Contains(new Tag(i)); + var tagValue = new Tag((uint)(i + 1)); + bool isSelected = pickerData.IsSingle ? value == tagValue : values.Contains(tagValue); // Count parent tags count int indentation = 0; @@ -296,7 +297,7 @@ namespace FlaxEditor.CustomEditors.Editors // Create node var node = new TreeNodeWithAddons { - Tag = i, + Tag = tagValue.Index, Text = tag, ChildrenIndent = nodeIndent, CullChildren = false, diff --git a/Source/Engine/Level/Tags.cpp b/Source/Engine/Level/Tags.cpp index 67633df9e..21cd69c08 100644 --- a/Source/Engine/Level/Tags.cpp +++ b/Source/Engine/Level/Tags.cpp @@ -12,7 +12,8 @@ FLAXENGINE_API String* TagsListDebug = nullptr; const String& Tag::ToString() const { - return Index >= 0 && Index < Tags::List.Count() ? Tags::List.Get()[Index] : String::Empty; + const int32 index = (int32)Index - 1; + return index >= 0 && index < Tags::List.Count() ? Tags::List.Get()[index] : String::Empty; } bool Tag::operator==(const StringView& other) const @@ -27,7 +28,7 @@ bool Tag::operator!=(const StringView& other) const void FLAXENGINE_API Serialization::Serialize(ISerializable::SerializeStream& stream, const Tag& v, const void* otherObj) { - if (v.Index != -1) + if (v.Index != 0) stream.String(v.ToString()); else stream.String("", 0); @@ -42,11 +43,11 @@ Tag Tags::Get(const StringView& tagName) { if (tagName.IsEmpty()) return Tag(); - Tag tag = List.Find(tagName); - if (tag.Index == -1 && tagName.HasChars()) + Tag tag(List.Find(tagName) + 1); + if (tag.Index == 0 && tagName.HasChars()) { - tag.Index = List.Count(); List.AddOne() = tagName; + tag.Index = List.Count(); #if !BUILD_RELEASE TagsListDebug = List.Get(); #endif @@ -56,7 +57,7 @@ Tag Tags::Get(const StringView& tagName) bool Tags::HasTag(const Array& list, const Tag& tag) { - if (tag.Index == -1) + if (tag.Index == 0) return false; const String& tagName = tag.ToString(); for (const Tag& e : list) @@ -70,7 +71,7 @@ bool Tags::HasTag(const Array& list, const Tag& tag) bool Tags::HasTagExact(const Array& list, const Tag& tag) { - if (tag.Index == -1) + if (tag.Index == 0) return false; return list.Contains(tag); } @@ -119,7 +120,7 @@ bool Tags::HasAllExact(const Array& list, const Array& tags) return true; } -const String& Tags::GetTagName(int32 tag) +const String& Tags::GetTagName(uint32 tag) { return Tag(tag).ToString(); } diff --git a/Source/Engine/Level/Tags.cs b/Source/Engine/Level/Tags.cs index b4aa3468c..8f46d2f85 100644 --- a/Source/Engine/Level/Tags.cs +++ b/Source/Engine/Level/Tags.cs @@ -1,33 +1,29 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. using System; +using System.ComponentModel; +using System.Globalization; using System.Runtime.CompilerServices; namespace FlaxEngine { + [TypeConverter(typeof(TypeConverters.TagConverter))] partial struct Tag : IEquatable, IEquatable, IComparable, IComparable, IComparable { /// /// The default . /// - public static Tag Default => new Tag(-1); + public static Tag Default => new Tag(0); /// /// Initializes a new instance of the struct. /// /// The tag index. - public Tag(int index) + public Tag(uint index) { Index = index; } - [System.Runtime.Serialization.OnDeserializing] - internal void OnDeserializing(System.Runtime.Serialization.StreamingContext context) - { - // Initialize structure with default values to replicate C++ deserialization behavior - Index = -1; - } - /// /// Compares two tags. /// @@ -50,6 +46,28 @@ namespace FlaxEngine return left.Index == right.Index; } + /// + /// Compares two tags. + /// + /// The lft tag. + /// The right tag name. + /// True if both values are equal, otherwise false. + public static bool operator ==(Tag left, string right) + { + return string.Equals(left.ToString(), right, StringComparison.Ordinal); + } + + /// + /// Compares two tags. + /// + /// The lft tag. + /// The right tag name. + /// True if both values are not equal, otherwise false. + public static bool operator !=(Tag left, string right) + { + return !string.Equals(left.ToString(), right, StringComparison.Ordinal); + } + /// /// Checks if tag is valid. /// @@ -58,7 +76,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator bool(Tag tag) { - return tag.Index != -1; + return tag.Index != 0; } /// @@ -104,7 +122,7 @@ namespace FlaxEngine /// public override int GetHashCode() { - return Index; + return (int)Index; } /// @@ -124,7 +142,7 @@ namespace FlaxEngine /// True if given tag is contained by the list of tags. Returns false for empty list. public static bool HasTag(this Tag[] list, Tag tag) { - if (tag.Index == -1) + if (tag.Index == 0) return false; string tagName = tag.ToString(); foreach (Tag e in list) @@ -144,7 +162,7 @@ namespace FlaxEngine /// True if given tag is contained by the list of tags. Returns false for empty list. public static bool HasTagExact(this Tag[] list, Tag tag) { - if (tag.Index == -1) + if (tag.Index == 0) return false; if (list == null) return false; @@ -233,3 +251,40 @@ namespace FlaxEngine } } } + +namespace FlaxEngine.TypeConverters +{ + internal class TagConverter : TypeConverter + { + /// + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + return true; + return base.CanConvertFrom(context, sourceType); + } + + /// + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string str) + { + if (str.Length == 0) + return Tag.Default; + return Tags.Get(str); + } + return base.ConvertFrom(context, culture, value); + } + + /// + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (destinationType == typeof(string)) + { + var v = (Tag)value; + return v.ToString(); + } + return base.ConvertTo(context, culture, value, destinationType); + } + } +} diff --git a/Source/Engine/Level/Tags.h b/Source/Engine/Level/Tags.h index eda44e224..fac49598d 100644 --- a/Source/Engine/Level/Tags.h +++ b/Source/Engine/Level/Tags.h @@ -14,9 +14,9 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API Tag DECLARE_SCRIPTING_TYPE_MINIMAL(Tag); /// - /// Index of the tag (in global Level.Tags list). + /// Index of the tag (in global Level.Tags list). Index 0 is invalid. 1 is the first index. /// - API_FIELD() int32 Index = -1; + API_FIELD() uint32 Index = 0; /// /// Gets the tag name. @@ -26,14 +26,14 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API Tag public: Tag() = default; - FORCE_INLINE Tag(int32 index) + FORCE_INLINE explicit Tag(uint32 index) : Index(index) { } FORCE_INLINE operator bool() const { - return Index != -1; + return Index != 0; } FORCE_INLINE bool operator==(const Tag& other) const @@ -142,7 +142,7 @@ public: static bool HasAllExact(const Array& list, const Array& tags); private: - API_FUNCTION(NoProxy) static const String& GetTagName(int32 tag); + API_FUNCTION(NoProxy) static const String& GetTagName(uint32 tag); }; #if !BUILD_RELEASE From f246fc018de9e4b0b296c391a003dcd24fd88e01 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 1 Jan 2023 16:54:01 +0100 Subject: [PATCH 09/14] Fixes for 2df3574cfae9b4fb81c46fe473e9171fcd546245 --- Source/Editor/CustomEditors/Editors/TagEditor.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index 0523904e5..54401cf72 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -114,7 +114,7 @@ namespace FlaxEditor.CustomEditors.Editors private static void GetTags(TreeNode n, PickerData pickerData) { if (n is TreeNodeWithAddons a && a.Addons.Count != 0 && a.Addons[0] is CheckBox c && c.Checked) - pickerData.CachedTags.Add(new Tag((uint)n.Tag)); + pickerData.CachedTags.Add((Tag)n.Tag); foreach (var child in n.Children) { if (child is TreeNode treeNode) @@ -139,7 +139,7 @@ namespace FlaxEditor.CustomEditors.Editors if (pickerData.IsSingle) { UncheckAll(node.ParentTree, node); - var value = new Tag(c.Checked ? (uint)node.Tag : 0); + var value = c.Checked ? (Tag)node.Tag : Tag.Default; pickerData.SetValue?.Invoke(value); pickerData.SetValues?.Invoke(new[] { value }); } @@ -166,8 +166,8 @@ namespace FlaxEditor.CustomEditors.Editors if (parentNode.CustomArrowRect.HasValue) { indentation = (int)((parentNode.CustomArrowRect.Value.Location.X - 18) / nodeIndent) + 1; - var parentIndex = (int)parentNode.Tag; - parentTag = Tags.List[parentIndex]; + var parentTagValue = (Tag)parentNode.Tag; + parentTag = parentTagValue.ToString(); } var node = new TreeNodeWithAddons { @@ -244,7 +244,7 @@ namespace FlaxEditor.CustomEditors.Editors // Add tag var tag = Tags.Get(tagName); node.Text = name; - node.Tag = tag.Index; + node.Tag = tag; var settingsAsset = GameSettings.LoadAsset(); if (settingsAsset && !settingsAsset.WaitForLoaded()) { @@ -297,7 +297,7 @@ namespace FlaxEditor.CustomEditors.Editors // Create node var node = new TreeNodeWithAddons { - Tag = tagValue.Index, + Tag = tagValue, Text = tag, ChildrenIndent = nodeIndent, CullChildren = false, From 9554636971c47cb9990d27faad7c760bb2909e76 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 1 Jan 2023 18:30:15 +0100 Subject: [PATCH 10/14] Add bigger input value boxes to Visject Surface --- Source/Editor/Surface/Elements/InputBox.cs | 101 ++++++++++++--------- 1 file changed, 57 insertions(+), 44 deletions(-) diff --git a/Source/Editor/Surface/Elements/InputBox.cs b/Source/Editor/Surface/Elements/InputBox.cs index e97beee2c..87a4aff00 100644 --- a/Source/Editor/Surface/Elements/InputBox.cs +++ b/Source/Editor/Surface/Elements/InputBox.cs @@ -119,7 +119,8 @@ namespace FlaxEditor.Surface.Elements public Control Create(InputBox box, ref Rectangle bounds) { var value = IntegerValue.Get(box.ParentNode, box.Archetype, box.Value); - var control = new IntValueBox(value, bounds.X, bounds.Y, 40, int.MinValue, int.MaxValue, 0.01f) + var width = 40; + var control = new IntValueBox(value, bounds.X, bounds.Y, width + 12, int.MinValue, int.MaxValue, 0.01f) { Height = bounds.Height, Parent = box.Parent, @@ -164,7 +165,8 @@ namespace FlaxEditor.Surface.Elements public Control Create(InputBox box, ref Rectangle bounds) { var value = UnsignedIntegerValue.Get(box.ParentNode, box.Archetype, box.Value); - var control = new UIntValueBox(value, bounds.X, bounds.Y, 40, uint.MinValue, uint.MaxValue, 0.01f) + var width = 40; + var control = new UIntValueBox(value, bounds.X, bounds.Y, width + 12, uint.MinValue, uint.MaxValue, 0.01f) { Height = bounds.Height, Parent = box.Parent, @@ -209,7 +211,8 @@ namespace FlaxEditor.Surface.Elements public Control Create(InputBox box, ref Rectangle bounds) { var value = FloatValue.Get(box.ParentNode, box.Archetype, box.Value); - var control = new FloatValueBox(value, bounds.X, bounds.Y, 40, float.MinValue, float.MaxValue, 0.01f) + var width = 40; + var control = new FloatValueBox(value, bounds.X, bounds.Y, width + 12, float.MinValue, float.MaxValue, 0.01f) { Height = bounds.Height, Parent = box.Parent, @@ -254,7 +257,7 @@ namespace FlaxEditor.Surface.Elements public Control Create(InputBox box, ref Rectangle bounds) { var value = box.Value as string; - var control = new TextBox(false, bounds.X, bounds.Y, 40) + var control = new TextBox(false, bounds.X, bounds.Y, 50) { Text = value, Height = bounds.Height, @@ -299,20 +302,21 @@ namespace FlaxEditor.Surface.Elements public Control Create(InputBox box, ref Rectangle bounds) { var value = GetValue(box); - var control = new ContainerControl(bounds.X, bounds.Y, 22 * 2 - 2, bounds.Height) + var width = 30; + var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 2 - 2, bounds.Height) { ClipChildren = false, AutoFocus = false, Parent = box.Parent, Tag = box, }; - var floatX = new RealValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatX = new RealValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatX.BoxValueChanged += OnValueChanged; - var floatY = new RealValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatY = new RealValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, @@ -372,26 +376,27 @@ namespace FlaxEditor.Surface.Elements public Control Create(InputBox box, ref Rectangle bounds) { var value = GetValue(box); - var control = new ContainerControl(bounds.X, bounds.Y, 22 * 3 - 2, bounds.Height) + var width = 30; + var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 3 - 2, bounds.Height) { ClipChildren = false, AutoFocus = false, Parent = box.Parent, Tag = box, }; - var floatX = new RealValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatX = new RealValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatX.BoxValueChanged += OnValueChanged; - var floatY = new RealValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatY = new RealValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatY.BoxValueChanged += OnValueChanged; - var floatZ = new RealValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatZ = new RealValueBox(value.Z, width * 2 + 4, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, @@ -454,32 +459,33 @@ namespace FlaxEditor.Surface.Elements public Control Create(InputBox box, ref Rectangle bounds) { var value = GetValue(box); - var control = new ContainerControl(bounds.X, bounds.Y, 22 * 4 - 2, bounds.Height) + var width = 20; + var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 4 - 2, bounds.Height) { ClipChildren = false, AutoFocus = false, Parent = box.Parent, Tag = box, }; - var floatX = new RealValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatX = new RealValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatX.BoxValueChanged += OnValueChanged; - var floatY = new RealValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatY = new RealValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatY.BoxValueChanged += OnValueChanged; - var floatZ = new RealValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatZ = new RealValueBox(value.Z, width * 2 + 4, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatZ.BoxValueChanged += OnValueChanged; - var floatW = new RealValueBox(value.W, 66, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatW = new RealValueBox(value.W, width * 3 + 6, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, @@ -546,20 +552,21 @@ namespace FlaxEditor.Surface.Elements public Control Create(InputBox box, ref Rectangle bounds) { var value = GetValue(box); - var control = new ContainerControl(bounds.X, bounds.Y, 22 * 2 - 2, bounds.Height) + var width = 30; + var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 2 - 2, bounds.Height) { ClipChildren = false, AutoFocus = false, Parent = box.Parent, Tag = box, }; - var floatX = new FloatValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatX = new FloatValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatX.BoxValueChanged += OnValueChanged; - var floatY = new FloatValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatY = new FloatValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, @@ -619,26 +626,27 @@ namespace FlaxEditor.Surface.Elements public Control Create(InputBox box, ref Rectangle bounds) { var value = GetValue(box); - var control = new ContainerControl(bounds.X, bounds.Y, 22 * 3 - 2, bounds.Height) + var width = 30; + var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 3 - 2, bounds.Height) { ClipChildren = false, AutoFocus = false, Parent = box.Parent, Tag = box, }; - var floatX = new FloatValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatX = new FloatValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatX.BoxValueChanged += OnValueChanged; - var floatY = new FloatValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatY = new FloatValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatY.BoxValueChanged += OnValueChanged; - var floatZ = new FloatValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatZ = new FloatValueBox(value.Z, width * 2 + 4, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, @@ -701,32 +709,33 @@ namespace FlaxEditor.Surface.Elements public Control Create(InputBox box, ref Rectangle bounds) { var value = GetValue(box); - var control = new ContainerControl(bounds.X, bounds.Y, 22 * 4 - 2, bounds.Height) + var width = 20; + var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 4 - 2, bounds.Height) { ClipChildren = false, AutoFocus = false, Parent = box.Parent, Tag = box, }; - var floatX = new FloatValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatX = new FloatValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatX.BoxValueChanged += OnValueChanged; - var floatY = new FloatValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatY = new FloatValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatY.BoxValueChanged += OnValueChanged; - var floatZ = new FloatValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatZ = new FloatValueBox(value.Z, width * 2 + 4, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatZ.BoxValueChanged += OnValueChanged; - var floatW = new FloatValueBox(value.W, 66, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatW = new FloatValueBox(value.W, width * 3 + 6, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, @@ -793,20 +802,21 @@ namespace FlaxEditor.Surface.Elements public Control Create(InputBox box, ref Rectangle bounds) { var value = GetValue(box); - var control = new ContainerControl(bounds.X, bounds.Y, 22 * 2 - 2, bounds.Height) + var width = 30; + var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 2 - 2, bounds.Height) { ClipChildren = false, AutoFocus = false, Parent = box.Parent, Tag = box, }; - var floatX = new DoubleValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatX = new DoubleValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatX.BoxValueChanged += OnValueChanged; - var floatY = new DoubleValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatY = new DoubleValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, @@ -866,26 +876,27 @@ namespace FlaxEditor.Surface.Elements public Control Create(InputBox box, ref Rectangle bounds) { var value = GetValue(box); - var control = new ContainerControl(bounds.X, bounds.Y, 22 * 3 - 2, bounds.Height) + var width = 30; + var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 3 - 2, bounds.Height) { ClipChildren = false, AutoFocus = false, Parent = box.Parent, Tag = box, }; - var floatX = new DoubleValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatX = new DoubleValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatX.BoxValueChanged += OnValueChanged; - var floatY = new DoubleValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatY = new DoubleValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatY.BoxValueChanged += OnValueChanged; - var floatZ = new DoubleValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatZ = new DoubleValueBox(value.Z, width * 2 + 4, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, @@ -948,32 +959,33 @@ namespace FlaxEditor.Surface.Elements public Control Create(InputBox box, ref Rectangle bounds) { var value = GetValue(box); - var control = new ContainerControl(bounds.X, bounds.Y, 22 * 4 - 2, bounds.Height) + var width = 20; + var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 4 - 2, bounds.Height) { ClipChildren = false, AutoFocus = false, Parent = box.Parent, Tag = box, }; - var floatX = new DoubleValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatX = new DoubleValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatX.BoxValueChanged += OnValueChanged; - var floatY = new DoubleValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatY = new DoubleValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatY.BoxValueChanged += OnValueChanged; - var floatZ = new DoubleValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatZ = new DoubleValueBox(value.Z, width * 2 + 4, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatZ.BoxValueChanged += OnValueChanged; - var floatW = new DoubleValueBox(value.W, 66, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatW = new DoubleValueBox(value.W, width * 3 + 6, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, @@ -1040,26 +1052,27 @@ namespace FlaxEditor.Surface.Elements public Control Create(InputBox box, ref Rectangle bounds) { var value = GetValue(box).EulerAngles; - var control = new ContainerControl(bounds.X, bounds.Y, 22 * 3 - 2, bounds.Height) + var width = 20; + var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 3 - 2, bounds.Height) { ClipChildren = false, AutoFocus = false, Parent = box.Parent, Tag = box, }; - var floatX = new FloatValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatX = new FloatValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatX.BoxValueChanged += OnValueChanged; - var floatY = new FloatValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatY = new FloatValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, }; floatY.BoxValueChanged += OnValueChanged; - var floatZ = new FloatValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f) + var floatZ = new FloatValueBox(value.Z, width * 2 + 4, 0, width, float.MinValue, float.MaxValue, 0.0f) { Height = bounds.Height, Parent = control, From 084fe6f0635475b10ebd2a641d993f6dfa49c31d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 1 Jan 2023 18:55:09 +0100 Subject: [PATCH 11/14] Add `UseAssetImportPathRelative` to Editor options and use it by default to store imported asset path relative to the project folder --- Source/Editor/Editor.cs | 1 + Source/Editor/Managed/ManagedEditor.Internal.cpp | 3 +++ Source/Editor/Managed/ManagedEditor.h | 1 + Source/Editor/Options/GeneralOptions.cs | 7 +++++++ Source/Editor/Options/OptionsModule.cs | 1 + Source/Engine/Content/BinaryAsset.cpp | 9 ++++++++- .../ContentImporters/AssetsImportingManager.cpp | 14 ++++++++++++-- .../ContentImporters/AssetsImportingManager.h | 5 +++++ 8 files changed, 38 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 421512ce0..15dd30471 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -1199,6 +1199,7 @@ namespace FlaxEditor { public byte AutoReloadScriptsOnMainWindowFocus; public byte ForceScriptCompilationOnStartup; + public byte UseAssetImportPathRelative; public byte AutoRebuildCSG; public float AutoRebuildCSGTimeoutMs; public byte AutoRebuildNavMesh; diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 7e81a7f58..79a5bfe61 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -794,6 +794,9 @@ public: static void SetOptions(ManagedEditor::InternalOptions* options) { ManagedEditor::ManagedEditorOptions = *options; + + // Apply options + AssetsImportingManager::UseImportPathRelative = ManagedEditor::ManagedEditorOptions.UseAssetImportPathRelative != 0; } static void DrawNavMesh() diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h index 8d6547377..dd3ebb3bb 100644 --- a/Source/Editor/Managed/ManagedEditor.h +++ b/Source/Editor/Managed/ManagedEditor.h @@ -26,6 +26,7 @@ public: { byte AutoReloadScriptsOnMainWindowFocus = 1; byte ForceScriptCompilationOnStartup = 1; + byte UseAssetImportPathRelative = 1; byte AutoRebuildCSG = 1; float AutoRebuildCSGTimeoutMs = 50; byte AutoRebuildNavMesh = 1; diff --git a/Source/Editor/Options/GeneralOptions.cs b/Source/Editor/Options/GeneralOptions.cs index 5f05cae2c..ea3b0bee9 100644 --- a/Source/Editor/Options/GeneralOptions.cs +++ b/Source/Editor/Options/GeneralOptions.cs @@ -148,6 +148,13 @@ namespace FlaxEditor.Options [EditorDisplay("Scripting", "Auto Save Visual Script On Play Start"), EditorOrder(505), Tooltip("Determines whether automatically save the Visual Script asset editors when starting the play mode in editor.")] public bool AutoSaveVisualScriptOnPlayStart { get; set; } = true; + /// + /// If checked, imported file path will be stored relative to the project folder within imported asset metadata. Otherwise will use absolute path. + /// + [DefaultValue(true)] + [EditorDisplay("Content"), EditorOrder(550)] + public bool UseAssetImportPathRelative { get; set; } = true; + /// /// Gets or sets a value indicating whether perform automatic CSG rebuild on brush change. /// diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 0b4476e0a..7d83f7dba 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -186,6 +186,7 @@ namespace FlaxEditor.Options Editor.InternalOptions internalOptions; internalOptions.AutoReloadScriptsOnMainWindowFocus = (byte)(Options.General.AutoReloadScriptsOnMainWindowFocus ? 1 : 0); internalOptions.ForceScriptCompilationOnStartup = (byte)(Options.General.ForceScriptCompilationOnStartup ? 1 : 0); + internalOptions.UseAssetImportPathRelative = (byte)(Options.General.UseAssetImportPathRelative ? 1 : 0); internalOptions.AutoRebuildCSG = (byte)(Options.General.AutoRebuildCSG ? 1 : 0); internalOptions.AutoRebuildCSGTimeoutMs = Options.General.AutoRebuildCSGTimeoutMs; internalOptions.AutoRebuildNavMesh = (byte)(Options.General.AutoRebuildNavMesh ? 1 : 0); diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp index 99c91cc4a..f247d46d4 100644 --- a/Source/Engine/Content/BinaryAsset.cpp +++ b/Source/Engine/Content/BinaryAsset.cpp @@ -4,15 +4,16 @@ #include "Cache/AssetsCache.h" #include "Storage/ContentStorageManager.h" #include "Loading/Tasks/LoadAssetDataTask.h" +#include "Factories/BinaryAssetFactory.h" #include "Engine/ContentImporters/AssetsImportingManager.h" #include "Engine/Content/Content.h" #include "Engine/Serialization/JsonTools.h" #include "Engine/Debug/Exceptions/JsonParseException.h" -#include "Factories/BinaryAssetFactory.h" #include "Engine/Threading/ThreadPoolTask.h" #if USE_EDITOR #include "Engine/Platform/FileSystem.h" #include "Engine/Threading/Threading.h" +#include "Engine/Engine/Globals.h" #endif REGISTER_BINARY_ASSET_ABSTRACT(BinaryAsset, "FlaxEngine.BinaryAsset"); @@ -123,6 +124,12 @@ void BinaryAsset::GetImportMetadata(String& path, String& username) const { path = JsonTools::GetString(document, "ImportPath"); username = JsonTools::GetString(document, "ImportUsername"); + if (path.HasChars() && FileSystem::IsRelative(path)) + { + // Convert path back to thr absolute (eg. if stored in relative format) + path = Globals::ProjectFolder / path; + StringUtils::PathRemoveRelativeParts(path); + } } else { diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.cpp b/Source/Engine/ContentImporters/AssetsImportingManager.cpp index 9c654aada..2b80d3454 100644 --- a/Source/Engine/ContentImporters/AssetsImportingManager.cpp +++ b/Source/Engine/ContentImporters/AssetsImportingManager.cpp @@ -3,6 +3,7 @@ #if COMPILE_WITH_ASSETS_IMPORTER #include "AssetsImportingManager.h" +#include "Engine/Core/Log.h" #include "Engine/Core/Utilities.h" #include "Engine/Serialization/JsonWriters.h" #include "Engine/Threading/MainThreadTask.h" @@ -10,9 +11,9 @@ #include "Engine/Content/Content.h" #include "Engine/Content/Cache/AssetsCache.h" #include "Engine/Engine/EngineService.h" -#include "Engine/Core/Log.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/Platform.h" +#include "Engine/Engine/Globals.h" #include "ImportTexture.h" #include "ImportModelFile.h" #include "ImportAudio.h" @@ -71,6 +72,7 @@ AssetsImportingManagerService AssetsImportingManagerServiceInstance; Array AssetsImportingManager::Importers; Array AssetsImportingManager::Creators; +bool AssetsImportingManager::UseImportPathRelative = false; CreateAssetContext::CreateAssetContext(const StringView& inputPath, const StringView& outputPath, const Guid& id, void* arg) { @@ -161,7 +163,15 @@ bool CreateAssetContext::AllocateChunk(int32 index) void CreateAssetContext::AddMeta(JsonWriter& writer) const { writer.JKEY("ImportPath"); - writer.String(InputPath); + if (AssetsImportingManager::UseImportPathRelative && !FileSystem::IsRelative(InputPath)) + { + const String relativePath = FileSystem::ConvertAbsolutePathToRelative(Globals::ProjectFolder, InputPath); + writer.String(relativePath); + } + else + { + writer.String(InputPath); + } writer.JKEY("ImportUsername"); writer.String(Platform::GetUserName()); } diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.h b/Source/Engine/ContentImporters/AssetsImportingManager.h index 0ec23d6bb..c329ea10a 100644 --- a/Source/Engine/ContentImporters/AssetsImportingManager.h +++ b/Source/Engine/ContentImporters/AssetsImportingManager.h @@ -22,6 +22,11 @@ public: /// static Array Creators; + /// + /// If true store asset import path relative to the current workspace, otherwise will store absolute path. + /// + static bool UseImportPathRelative; + public: /// /// The create texture tag (using internal import pipeline to crate texture asset from custom image source). From 4afa59a783429a87a55189dc7b0a549e1a0c30ce Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 1 Jan 2023 19:15:59 +0100 Subject: [PATCH 12/14] Fix missing scripting and serialization extensions for `SceneReference` usage in scripts --- Source/Engine/Content/Content.cpp | 12 +++++++++ Source/Engine/Content/SceneReference.h | 36 +++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 1e1f81e09..f894b7417 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -2,6 +2,8 @@ #include "Content.h" #include "JsonAsset.h" +#include "SceneReference.h" +#include "Engine/Serialization/Serialization.h" #include "Cache/AssetsCache.h" #include "Storage/ContentStorageManager.h" #include "Storage/JsonStorageProxy.h" @@ -39,6 +41,16 @@ String AssetInfo::ToString() const return String::Format(TEXT("ID: {0}, TypeName: {1}, Path: \'{2}\'"), ID, TypeName, Path); } +void FLAXENGINE_API Serialization::Serialize(ISerializable::SerializeStream& stream, const SceneReference& v, const void* otherObj) +{ + Serialize(stream, v.ID, otherObj); +} + +void FLAXENGINE_API Serialization::Deserialize(ISerializable::DeserializeStream& stream, SceneReference& v, ISerializeModifier* modifier) +{ + Deserialize(stream, v.ID, modifier); +} + namespace { // Assets diff --git a/Source/Engine/Content/SceneReference.h b/Source/Engine/Content/SceneReference.h index c1e405f4e..50b24955d 100644 --- a/Source/Engine/Content/SceneReference.h +++ b/Source/Engine/Content/SceneReference.h @@ -1,8 +1,9 @@ -// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #pragma once #include "Engine/Core/Types/Guid.h" +#include "Engine/Core/ISerializable.h" /// /// Represents the reference to the scene asset. Stores the unique ID of the scene to reference. Can be used to load the selected scene. @@ -15,4 +16,37 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API SceneReference /// The identifier of the scene asset (and the scene object). /// API_FIELD() Guid ID; + + FORCE_INLINE bool operator==(const SceneReference& other) const + { + return ID == other.ID; + } + + FORCE_INLINE bool operator!=(const SceneReference& other) const + { + return ID != other.ID; + } }; + +template<> +struct TIsPODType +{ + enum { Value = true }; +}; + +inline uint32 GetHash(const SceneReference& key) +{ + return GetHash(key.ID); +} + +// @formatter:off +namespace Serialization +{ + inline bool ShouldSerialize(const SceneReference& v, const void* otherObj) + { + return !otherObj || v != *(SceneReference*)otherObj; + } + void FLAXENGINE_API Serialize(ISerializable::SerializeStream& stream, const SceneReference& v, const void* otherObj); + void FLAXENGINE_API Deserialize(ISerializable::DeserializeStream& stream, SceneReference& v, ISerializeModifier* modifier); +} +// @formatter:on From bc7bf7266091695f2007bf2880f1eecf930db402 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 1 Jan 2023 21:42:35 +0100 Subject: [PATCH 13/14] Fix case when child context menu is already hidden and changed to other from code --- Source/Editor/GUI/ContextMenu/ContextMenuBase.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs index 0bd87082d..4df17364a 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs @@ -356,10 +356,15 @@ namespace FlaxEditor.GUI.ContextMenu private void OnWindowGotFocus() { - if (_childCM != null && _window && _window.IsForegroundWindow) + var child = _childCM; + if (child != null && _window && _window.IsForegroundWindow) { // Hide child if user clicked over parent (do it next frame to process other events before - eg. child windows focus loss) - FlaxEngine.Scripting.InvokeOnUpdate(HideChild); + FlaxEngine.Scripting.InvokeOnUpdate(() => + { + if (child == _childCM) + HideChild(); + }); } } From 59568fb5c865b35bae640b6f85169f561bef9583 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 1 Jan 2023 23:17:23 +0100 Subject: [PATCH 14/14] Minor editor tweaks --- Source/Editor/Content/Items/JsonAssetItem.cs | 5 ++++- Source/Editor/Content/Proxy/JsonAssetProxy.cs | 4 ++-- Source/Editor/Windows/ContentWindow.ContextMenu.cs | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Content/Items/JsonAssetItem.cs b/Source/Editor/Content/Items/JsonAssetItem.cs index 09deb4ace..2cd22e799 100644 --- a/Source/Editor/Content/Items/JsonAssetItem.cs +++ b/Source/Editor/Content/Items/JsonAssetItem.cs @@ -11,7 +11,10 @@ namespace FlaxEditor.Content /// public class JsonAssetItem : AssetItem { - private readonly SpriteHandle _thumbnail; + /// + /// Asset icon. + /// + protected SpriteHandle _thumbnail; /// /// Initializes a new instance of the class. diff --git a/Source/Editor/Content/Proxy/JsonAssetProxy.cs b/Source/Editor/Content/Proxy/JsonAssetProxy.cs index 8bfaae384..f526d12bc 100644 --- a/Source/Editor/Content/Proxy/JsonAssetProxy.cs +++ b/Source/Editor/Content/Proxy/JsonAssetProxy.cs @@ -127,7 +127,7 @@ namespace FlaxEditor.Content /// Generic Json assets proxy (supports all json assets that don't have dedicated proxy). /// /// - public sealed class GenericJsonAssetProxy : JsonAssetProxy + public class GenericJsonAssetProxy : JsonAssetProxy { /// public override string TypeName => typeof(JsonAsset).FullName; @@ -161,7 +161,7 @@ namespace FlaxEditor.Content /// Content proxy for a json assets of the given type that can be spawned in the editor. /// /// - public sealed class SpawnableJsonAssetProxy : JsonAssetProxy where T : new() + public class SpawnableJsonAssetProxy : JsonAssetProxy where T : new() { /// public override string Name { get; } = Utilities.Utils.GetPropertyNameUI(typeof(T).Name); diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index 89f13d6a3..06ad40e5c 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -155,7 +155,7 @@ namespace FlaxEditor.Windows var scriptType = new ScriptType(typeof(Script)); foreach (var type in Editor.CodeEditing.All.Get()) { - if (type.IsAbstract) + if (type.IsAbstract || type.Type == null) continue; if (actorType.IsAssignableFrom(type) || scriptType.IsAssignableFrom(type)) continue;