From 40124341023d54c81ab0b93126e3995849f5194c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 3 Jan 2023 23:04:09 +0100 Subject: [PATCH 1/8] Fix test --- Source/Engine/Scripting/Scripting.cpp | 22 +++++++++++++++---- Source/Engine/Scripting/Scripting.h | 7 ++++++ Source/Engine/Tests/TestScripting.cpp | 13 ++++++----- .../Bindings/BindingsGenerator.Cpp.cs | 4 ++-- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index ac7a6f8e8..bb603f4df 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -732,6 +732,23 @@ ScriptingTypeHandle Scripting::FindScriptingType(const StringAnsiView& fullname) return ScriptingTypeHandle(); } +ScriptingObject* Scripting::NewObject(const ScriptingTypeHandle& type) +{ + if (!type) + { + LOG(Error, "Invalid type."); + return nullptr; + } + const ScriptingType& scriptingType = type.GetType(); + + // Create unmanaged object + const ScriptingObjectSpawnParams params(Guid::New(), type); + ScriptingObject* obj = scriptingType.Script.Spawn(params); + if (obj == nullptr) + LOG(Error, "Failed to spawn object of type \'{0}\'.", scriptingType.ToString()); + return obj; +} + ScriptingObject* Scripting::NewObject(const MClass* type) { if (type == nullptr) @@ -762,10 +779,7 @@ ScriptingObject* Scripting::NewObject(const MClass* type) const ScriptingObjectSpawnParams params(Guid::New(), ScriptingTypeHandle(module, typeIndex)); ScriptingObject* obj = scriptingType.Script.Spawn(params); if (obj == nullptr) - { - LOG(Error, "Failed to spawn object of type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass))); - return nullptr; - } + LOG(Error, "Failed to spawn object of type \'{0}\'.", scriptingType.ToString()); return obj; #else LOG(Error, "Not supported object creation from Managed class."); diff --git a/Source/Engine/Scripting/Scripting.h b/Source/Engine/Scripting/Scripting.h index dc13b696c..0a94eb3e0 100644 --- a/Source/Engine/Scripting/Scripting.h +++ b/Source/Engine/Scripting/Scripting.h @@ -109,6 +109,13 @@ public: /// The scripting type or invalid type if missing. static ScriptingTypeHandle FindScriptingType(const StringAnsiView& fullname); + /// + /// Creates a new instance of the given type object (native construction). + /// + /// The scripting object type class. + /// The created object or null if failed. + static ScriptingObject* NewObject(const ScriptingTypeHandle& type); + /// /// Creates a new instance of the given class object (native construction). /// diff --git a/Source/Engine/Tests/TestScripting.cpp b/Source/Engine/Tests/TestScripting.cpp index 29c7d5eb5..0523b28e8 100644 --- a/Source/Engine/Tests/TestScripting.cpp +++ b/Source/Engine/Tests/TestScripting.cpp @@ -16,7 +16,7 @@ TEST_CASE("Scripting") // Test native class ScriptingTypeHandle type = Scripting::FindScriptingType("FlaxEngine.TestClassNative"); CHECK(type == TestClassNative::TypeInitializer); - ScriptingObject* object = Scripting::NewObject(type.GetType().ManagedClass); + ScriptingObject* object = Scripting::NewObject(type); CHECK(object); CHECK(object->Is()); TestClassNative* testClass = (TestClassNative*)object; @@ -29,7 +29,7 @@ TEST_CASE("Scripting") // Test managed class type = Scripting::FindScriptingType("FlaxEngine.TestClassManaged"); CHECK(type); - object = Scripting::NewObject(type.GetType().ManagedClass); + object = Scripting::NewObject(type); CHECK(object); CHECK(object->Is()); testClass = (TestClassNative*)object; @@ -46,7 +46,7 @@ TEST_CASE("Scripting") { ScriptingTypeHandle type = Scripting::FindScriptingType("FlaxEngine.TestClassManaged"); CHECK(type); - ScriptingObject* object = Scripting::NewObject(type.GetType().ManagedClass); + ScriptingObject* object = Scripting::NewObject(type); CHECK(object); MObject* managed = object->GetOrCreateManagedInstance(); // Ensure to create C# object and run it's ctor CHECK(managed); @@ -65,12 +65,13 @@ 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); + ScriptingObject* object = Scripting::NewObject(type); CHECK(object); TestClassNative* testClass = (TestClassNative*)object; int32 methodResult = testClass->TestInterfaceMethod(TEXT("123")); @@ -86,7 +87,7 @@ TEST_CASE("Scripting") // Test managed interface override type = Scripting::FindScriptingType("FlaxEngine.TestClassManaged"); CHECK(type); - object = Scripting::NewObject(type.GetType().ManagedClass); + object = Scripting::NewObject(type); CHECK(object); testClass = (TestClassNative*)object; methodResult = testClass->TestInterfaceMethod(TEXT("123")); @@ -102,7 +103,7 @@ TEST_CASE("Scripting") // Test managed interface implementation type = Scripting::FindScriptingType("FlaxEngine.TestInterfaceManaged"); CHECK(type); - object = Scripting::NewObject(type.GetType().ManagedClass); + object = Scripting::NewObject(type); CHECK(object); interface = ScriptingObject::ToInterface(object); CHECK(interface); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 083c892e6..2599aae28 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -158,13 +158,13 @@ namespace Flax.Build.Bindings { var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo); CppVariantFromTypes[wrapperName] = typeInfo; - return $"VariantFrom{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}Array({value}, {typeInfo.ArraySize})"; + return $"VariantFrom{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}Array((const {typeInfo}*){value}, {typeInfo.ArraySize})"; } if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) { var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo.GenericArgs[0]); CppVariantFromTypes[wrapperName] = typeInfo; - return $"VariantFrom{wrapperName}Array({value}.Get(), {value}.Count())"; + return $"VariantFrom{wrapperName}Array((const {typeInfo.GenericArgs[0]}*){value}.Get(), {value}.Count())"; } if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) { From b753b186829fecbf4e189e330147fa2e12d3b851 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 6 Jan 2023 13:36:12 +0100 Subject: [PATCH 2/8] Add profiler events for plugins init/deinit --- Source/Engine/Scripting/Plugins/PluginManager.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Engine/Scripting/Plugins/PluginManager.cpp b/Source/Engine/Scripting/Plugins/PluginManager.cpp index 458755703..74df1d4ae 100644 --- a/Source/Engine/Scripting/Plugins/PluginManager.cpp +++ b/Source/Engine/Scripting/Plugins/PluginManager.cpp @@ -107,6 +107,9 @@ void PluginManagerService::InvokeInitialize(Plugin* plugin) { if (plugin->_initialized) return; + StringAnsiView typeName = plugin->GetType().GetName(); + PROFILE_CPU(); + ZoneName(typeName.Get(), typeName.Length()); LOG(Info, "Loading plugin {}", plugin->ToString()); @@ -122,6 +125,9 @@ void PluginManagerService::InvokeDeinitialize(Plugin* plugin) { if (!plugin->_initialized) return; + StringAnsiView typeName = plugin->GetType().GetName(); + PROFILE_CPU(); + ZoneName(typeName.Get(), typeName.Length()) LOG(Info, "Unloading plugin {}", plugin->ToString()); From c011e8af62adb0d44e7318b022e0712bc703014a Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 6 Jan 2023 13:45:36 +0100 Subject: [PATCH 3/8] Add support for `CanRender` in postFx to depend on specific render setup --- Source/Engine/Graphics/PostProcessEffect.h | 9 +++++++++ Source/Engine/Graphics/RenderTask.cpp | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Graphics/PostProcessEffect.h b/Source/Engine/Graphics/PostProcessEffect.h index 7e5ce96d2..fd1c38fb7 100644 --- a/Source/Engine/Graphics/PostProcessEffect.h +++ b/Source/Engine/Graphics/PostProcessEffect.h @@ -43,6 +43,15 @@ public: { return GetEnabled(); } + + /// + /// Gets a value indicating whether this effect can be rendered. + /// + /// The target render context. + API_FUNCTION() virtual bool CanRender(const RenderContext& renderContext) const + { + return CanRender(); + } /// /// Pre-rendering event called before scene rendering begin. Can be used to perform custom rendering or customize render view/setup. diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index c88837823..61ea2a397 100644 --- a/Source/Engine/Graphics/RenderTask.cpp +++ b/Source/Engine/Graphics/RenderTask.cpp @@ -238,13 +238,13 @@ void SceneRenderTask::OnCollectDrawCalls(RenderContextBatch& renderContextBatch, { for (PostProcessEffect* fx : GlobalCustomPostFx) { - if (fx && fx->CanRender()) + if (fx && fx->CanRender(renderContext)) postFx.Add(fx); } } for (PostProcessEffect* fx : CustomPostFx) { - if (fx && fx->CanRender()) + if (fx && fx->CanRender(renderContext)) postFx.Add(fx); } if (const auto* camera = Camera.Get()) @@ -252,7 +252,7 @@ void SceneRenderTask::OnCollectDrawCalls(RenderContextBatch& renderContextBatch, for (Script* script : camera->Scripts) { auto* fx = Cast(script); - if (fx && fx->CanRender()) + if (fx && fx->CanRender(renderContext)) postFx.Add(fx); } } From 064994eb1a143fc9559329c4a3e9dd921e9a052c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 8 Jan 2023 00:33:37 +0100 Subject: [PATCH 4/8] Add more test cases for various scripting bindings features --- Source/Engine/Tests/TestScripting.cpp | 18 ++++++++++++++++-- Source/Engine/Tests/TestScripting.cs | 18 ++++++++++++++++-- Source/Engine/Tests/TestScripting.h | 22 +++++++++++++++++++++- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Tests/TestScripting.cpp b/Source/Engine/Tests/TestScripting.cpp index 0523b28e8..b244b04cb 100644 --- a/Source/Engine/Tests/TestScripting.cpp +++ b/Source/Engine/Tests/TestScripting.cpp @@ -23,8 +23,13 @@ TEST_CASE("Scripting") CHECK(testClass->SimpleField == 1); CHECK(testClass->SimpleStruct.Object == nullptr); CHECK(testClass->SimpleStruct.Vector == Float3::One); - int32 methodResult = testClass->TestMethod(TEXT("123")); + Array struct1 = { testClass->SimpleStruct }; + Array struct2 = { testClass->SimpleStruct }; + Array objects; + TestStructPOD pod; + int32 methodResult = testClass->TestMethod(TEXT("123"), pod, struct1, struct2, objects); CHECK(methodResult == 3); + CHECK(objects.Count() == 0); // Test managed class type = Scripting::FindScriptingType("FlaxEngine.TestClassManaged"); @@ -38,8 +43,17 @@ TEST_CASE("Scripting") CHECK(testClass->SimpleField == 2); CHECK(testClass->SimpleStruct.Object == testClass); CHECK(testClass->SimpleStruct.Vector == Float3::UnitX); - methodResult = testClass->TestMethod(TEXT("123")); + struct1 = { testClass->SimpleStruct }; + struct2 = { testClass->SimpleStruct }; + objects.Clear(); + pod.Vector = Float3::One; + methodResult = testClass->TestMethod(TEXT("123"), pod, struct1, struct2, objects); CHECK(methodResult == 6); + CHECK(pod.Vector == Float3::Half); + CHECK(struct2.Count() == 2); + CHECK(struct2[0] == testClass->SimpleStruct); + CHECK(struct2[1] == testClass->SimpleStruct); + CHECK(objects.Count() == 3); } SECTION("Test Event") diff --git a/Source/Engine/Tests/TestScripting.cs b/Source/Engine/Tests/TestScripting.cs index 6203de314..f75aa9fd1 100644 --- a/Source/Engine/Tests/TestScripting.cs +++ b/Source/Engine/Tests/TestScripting.cs @@ -63,10 +63,24 @@ namespace FlaxEngine } /// - public override int TestMethod(string str) + public override int TestMethod(string str, ref TestStructPOD pod, TestStruct[] struct1, ref TestStruct[] struct2, out Object[] objects) { + objects = new Object[3]; + if (struct1 == null || struct1.Length != 1) + return -1; + if (struct2 == null || struct2.Length != 1) + return -2; + if (pod.Vector != Float3.One) + return -3; + struct2 = new TestStruct[2] + { + struct1[0], + SimpleStruct, + }; + pod.Vector = Float3.Half; + // Test C++ base method invocation - return str.Length + base.TestMethod(str); + return str.Length + base.TestMethod(str, ref pod, struct1, ref struct2, out _); } /// diff --git a/Source/Engine/Tests/TestScripting.h b/Source/Engine/Tests/TestScripting.h index c5ba39b10..1bbc7ff28 100644 --- a/Source/Engine/Tests/TestScripting.h +++ b/Source/Engine/Tests/TestScripting.h @@ -17,6 +17,26 @@ API_STRUCT(NoDefault) struct TestStruct : public ISerializable API_FIELD() Float3 Vector = Float3::One; // Ref API_FIELD() ScriptingObject* Object = nullptr; + + friend bool operator==(const TestStruct& lhs, const TestStruct& rhs) + { + return lhs.Vector == rhs.Vector && lhs.Object == rhs.Object; + } +}; + +// Test structure. +API_STRUCT(NoDefault) struct TestStructPOD +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(TestStructPOD); + + // Var + API_FIELD() Float3 Vector = Float3::One; +}; + +template<> +struct TIsPODType +{ + enum { Value = true }; }; // Test interface. @@ -46,7 +66,7 @@ public: API_EVENT() Delegate&, Array&> SimpleEvent; // Test virtual method - API_FUNCTION() virtual int32 TestMethod(const String& str) + API_FUNCTION() virtual int32 TestMethod(const String& str, API_PARAM(Ref) TestStructPOD& pod, const Array& struct1, API_PARAM(Ref) Array& struct2, API_PARAM(Out) Array& objects) { return str.Length(); } From cfcf29a62e634a4e3317c8c129b5fd48aaf36d12 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 8 Jan 2023 00:34:07 +0100 Subject: [PATCH 5/8] Fixes for passing new unit tests with edge cases of scripting bindigns usage --- Source/Engine/Core/Collections/Array.h | 8 +- .../Bindings/BindingsGenerator.Api.cs | 10 ++ .../Bindings/BindingsGenerator.Cpp.cs | 142 ++++++++++++++---- 3 files changed, 128 insertions(+), 32 deletions(-) diff --git a/Source/Engine/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h index d2cd50afd..3790db4eb 100644 --- a/Source/Engine/Core/Collections/Array.h +++ b/Source/Engine/Core/Collections/Array.h @@ -144,11 +144,11 @@ public: /// The reference to this. Array& operator=(std::initializer_list initList) noexcept { - Memory::DestructItems(_allocation.Get(), _count); - _count = _capacity = (int32)initList.size(); - if (_capacity > 0) + Clear(); + if (initList.size() > 0) { - _allocation.Allocate(_capacity); + EnsureCapacity((int32)initList.size()); + _count = (int32)initList.size(); Memory::ConstructItems(_allocation.Get(), initList.begin(), _count); } return *this; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs index e4f8eb2aa..a05043485 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs @@ -239,6 +239,16 @@ namespace Flax.Build.Bindings // Find across in-build types if (InBuildTypes.TryGetValue(typeInfo, out result)) return result; + if (typeInfo.IsRef) + { + typeInfo.IsRef = false; + if (InBuildTypes.TryGetValue(typeInfo, out result)) + { + typeInfo.IsRef = true; + return result; + } + typeInfo.IsRef = true; + } // Find across all loaded modules for this build foreach (var e in buildData.ModulesInfo) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 2599aae28..ddf3a0f18 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -99,7 +99,7 @@ namespace Flax.Build.Bindings return sb.ToString(); } - private static string GenerateCppWrapperNativeToManagedParam(BuildData buildData, StringBuilder contents, TypeInfo paramType, string paramName, ApiTypeInfo caller, bool isOut, out bool useLocalVar) + private static string GenerateCppWrapperNativeToManagedParam(BuildData buildData, StringBuilder contents, TypeInfo paramType, string paramName, ApiTypeInfo caller, bool isRef, out bool useLocalVar) { useLocalVar = false; var nativeToManaged = GenerateCppWrapperNativeToManaged(buildData, paramType, caller, out var managedTypeAsNative, null); @@ -107,7 +107,7 @@ namespace Flax.Build.Bindings if (!string.IsNullOrEmpty(nativeToManaged)) { result = string.Format(nativeToManaged, paramName); - if (managedTypeAsNative[managedTypeAsNative.Length - 1] == '*' && !isOut) + if (managedTypeAsNative[managedTypeAsNative.Length - 1] == '*' && !isRef) { // Pass pointer value } @@ -124,7 +124,7 @@ namespace Flax.Build.Bindings else { result = paramName; - if (paramType.IsRef && !paramType.IsConst && !isOut) + if (paramType.IsRef && !paramType.IsConst && !isRef) { // Pass reference as a pointer result = '&' + result; @@ -814,10 +814,10 @@ namespace Flax.Build.Bindings } } - private static string GenerateCppWrapperNativeToBox(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, string value) + private static string GenerateCppWrapperNativeToBox(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, out ApiTypeInfo apiType, string value) { // Optimize passing scripting objects - var apiType = FindApiTypeInfo(buildData, typeInfo, caller); + apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null && apiType.IsScriptingObject) return $"ScriptingObject::ToManaged((ScriptingObject*){value})"; @@ -1208,6 +1208,8 @@ namespace Flax.Build.Bindings contents.Append(')'); contents.AppendLine(); contents.AppendLine(" {"); + + // Get object string scriptVTableOffset; if (classInfo.IsInterface) { @@ -1227,6 +1229,7 @@ namespace Flax.Build.Bindings } contents.AppendLine(" static THREADLOCAL void* WrapperCallInstance = nullptr;"); + // Base method call contents.AppendLine(" ScriptingTypeHandle managedTypeHandle = object->GetTypeHandle();"); contents.AppendLine(" const ScriptingType* managedTypePtr = &managedTypeHandle.GetType();"); contents.AppendLine(" while (managedTypePtr->Script.Spawn != &ManagedBinaryModule::ManagedObjectSpawn)"); @@ -1234,53 +1237,80 @@ namespace Flax.Build.Bindings contents.AppendLine(" managedTypeHandle = managedTypePtr->GetBaseType();"); contents.AppendLine(" managedTypePtr = &managedTypeHandle.GetType();"); contents.AppendLine(" }"); - contents.AppendLine(" if (WrapperCallInstance == object)"); contents.AppendLine(" {"); GenerateCppVirtualWrapperCallBaseMethod(buildData, contents, classInfo, functionInfo, "managedTypePtr->Script.ScriptVTableBase", scriptVTableOffset); contents.AppendLine(" }"); + contents.AppendLine(" auto scriptVTable = (MMethod**)managedTypePtr->Script.ScriptVTable;"); contents.AppendLine($" ASSERT(scriptVTable && scriptVTable[{scriptVTableOffset}]);"); contents.AppendLine($" auto method = scriptVTable[{scriptVTableOffset}];"); - contents.AppendLine($" PROFILE_CPU_NAMED(\"{classInfo.FullNameManaged}::{functionInfo.Name}\");"); - contents.AppendLine(" MonoObject* exception = nullptr;"); + contents.AppendLine($" PROFILE_CPU_NAMED(\"{classInfo.FullNameManaged}::{functionInfo.Name}\");"); + + contents.AppendLine(" MonoObject* exception = nullptr;"); contents.AppendLine(" auto prevWrapperCallInstance = WrapperCallInstance;"); contents.AppendLine(" WrapperCallInstance = object;"); - contents.AppendLine("#if USE_MONO_AOT"); if (functionInfo.Parameters.Count == 0) contents.AppendLine(" void** params = nullptr;"); else contents.AppendLine($" void* params[{functionInfo.Parameters.Count}];"); - for (var i = 0; i < functionInfo.Parameters.Count; i++) + contents.AppendLine("#if USE_MONO_AOT"); + + // Convert parameters into managed format as pointers to value + if (functionInfo.Parameters.Count != 0) { - var parameterInfo = functionInfo.Parameters[i]; - var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, parameterInfo.Type, parameterInfo.Name, classInfo, parameterInfo.IsOut, out _); - contents.Append($" params[{i}] = {paramValue};").AppendLine(); + for (var i = 0; i < functionInfo.Parameters.Count; i++) + { + var parameterInfo = functionInfo.Parameters[i]; + var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut; + var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, parameterInfo.Type, parameterInfo.Name, classInfo, paramIsRef, out CppParamsThatNeedLocalVariable[i]); + contents.Append($" params[{i}] = {paramValue};").AppendLine(); + } } + // Invoke method contents.AppendLine(" auto __result = mono_runtime_invoke(method->GetNative(), object->GetOrCreateManagedInstance(), params, &exception);"); + contents.AppendLine("#else"); + // Convert parameters into managed format as boxed values var thunkParams = string.Empty; var thunkCall = string.Empty; - separator = functionInfo.Parameters.Count != 0; - for (var i = 0; i < functionInfo.Parameters.Count; i++) + if (functionInfo.Parameters.Count != 0) { - var parameterInfo = functionInfo.Parameters[i]; - if (separator) - thunkParams += ", "; - if (separator) - thunkCall += ", "; - separator = true; - thunkParams += "void*"; + separator = functionInfo.Parameters.Count != 0; + for (var i = 0; i < functionInfo.Parameters.Count; i++) + { + var parameterInfo = functionInfo.Parameters[i]; + var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut; + var paramValue = GenerateCppWrapperNativeToBox(buildData, parameterInfo.Type, classInfo, out var apiType, parameterInfo.Name); + var useLocalVar = false; + if (paramIsRef) + { + // Pass as pointer to value when using ref/out parameter + contents.Append($" auto __param_{parameterInfo.Name} = {paramValue};").AppendLine(); + var useLocalVarPointer = !apiType.IsValueType; + paramValue = $"{(useLocalVarPointer ? "&" : "")}__param_{parameterInfo.Name}"; + CppParamsThatNeedConversion[i] = useLocalVarPointer; + useLocalVar = true; + } + CppParamsThatNeedLocalVariable[i] = useLocalVar; - // Mono thunk call uses boxed values as objects - thunkCall += GenerateCppWrapperNativeToBox(buildData, parameterInfo.Type, classInfo, parameterInfo.Name); + if (separator) + thunkParams += ", "; + if (separator) + thunkCall += ", "; + separator = true; + thunkParams += "void*"; + contents.Append($" params[{i}] = {paramValue};").AppendLine(); + thunkCall += $"params[{i}]"; + } } + // Invoke method thunk var returnType = functionInfo.ReturnType; if (returnType.IsVoid) { @@ -1300,11 +1330,66 @@ namespace Flax.Build.Bindings contents.AppendLine(" if (exception)"); contents.AppendLine(" DebugLog::LogException(exception);"); + // Convert parameter values back from managed to native (could be modified there) + bool anyRefOut = false; + for (var i = 0; i < functionInfo.Parameters.Count; i++) + { + var parameterInfo = functionInfo.Parameters[i]; + var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut; + if (paramIsRef && !parameterInfo.Type.IsConst) + { + if (!anyRefOut) + { + anyRefOut = true; + contents.AppendLine("#if USE_MONO_AOT"); + } + + // Direct value convert + var managedToNative = GenerateCppWrapperManagedToNative(buildData, parameterInfo.Type, classInfo, out var managedType, out var apiType, null, out _); + var passAsParamPtr = managedType.EndsWith("*"); + var useLocalVarPointer = CppParamsThatNeedConversion[i] && !apiType.IsValueType; + var paramValue = useLocalVarPointer ? $"*({managedType}{(passAsParamPtr ? "" : "*")}*)params[{i}]" : $"({managedType}{(passAsParamPtr ? "" : "*")})params[{i}]"; + if (!string.IsNullOrEmpty(managedToNative)) + { + if (!passAsParamPtr) + paramValue = '*' + paramValue; + paramValue = string.Format(managedToNative, paramValue); + } + else if (!passAsParamPtr) + paramValue = '*' + paramValue; + contents.Append($" {parameterInfo.Name} = {paramValue};").AppendLine(); + } + } + if (anyRefOut) + { + contents.AppendLine("#else"); + for (var i = 0; i < functionInfo.Parameters.Count; i++) + { + var parameterInfo = functionInfo.Parameters[i]; + var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut; + if (paramIsRef && !parameterInfo.Type.IsConst) + { + // Unbox from MonoObject* + parameterInfo.Type.IsRef = false; + var useLocalVarPointer = CppParamsThatNeedConversion[i]; + var boxedValueCast = useLocalVarPointer ? "*(MonoObject**)" : "(MonoObject*)"; + contents.Append($" {parameterInfo.Name} = MUtils::Unbox<{parameterInfo.Type}>({boxedValueCast}params[{i}]);").AppendLine(); + parameterInfo.Type.IsRef = true; + } + } + contents.AppendLine("#endif"); + } + + // Unbox returned value if (!returnType.IsVoid) { if (returnType.IsRef) throw new NotSupportedException($"Passing return value by reference is not supported for virtual API methods. Used on method '{functionInfo}'."); + // mono_runtime_invoke always returns boxed value as MonoObject*, but thunk might return value within pointer (eg. as int or boolean) + contents.AppendLine("#if USE_MONO_AOT"); + contents.AppendLine($" return MUtils::Unbox<{returnType}>(__result);"); + contents.AppendLine("#else"); switch (returnType.Type) { case "bool": @@ -1326,6 +1411,7 @@ namespace Flax.Build.Bindings contents.AppendLine($" return MUtils::Unbox<{returnType}>(__result);"); break; } + contents.AppendLine("#endif"); } contents.AppendLine(" }"); @@ -1649,8 +1735,8 @@ namespace Flax.Build.Bindings { var paramType = eventInfo.Type.GenericArgs[i]; var paramName = "arg" + i; - var paramIsOut = paramType.IsRef && !paramType.IsConst; - var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, paramType, paramName, classInfo, paramIsOut, out CppParamsThatNeedConversion[i]); + var paramIsRef = paramType.IsRef && !paramType.IsConst; + var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, paramType, paramName, classInfo, paramIsRef, out CppParamsThatNeedConversion[i]); contents.Append($" params[{i}] = {paramValue};").AppendLine(); } if (eventInfo.IsStatic) @@ -1663,8 +1749,8 @@ namespace Flax.Build.Bindings for (var i = 0; i < paramsCount; i++) { var paramType = eventInfo.Type.GenericArgs[i]; - var paramIsOut = paramType.IsRef && !paramType.IsConst; - if (paramIsOut) + var paramIsRef = paramType.IsRef && !paramType.IsConst; + if (paramIsRef) { // Convert value back from managed to native (could be modified there) paramType.IsRef = false; From 58844622a084c72d1d72ded9f78e5e7b9c395e52 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 8 Jan 2023 13:50:45 +0100 Subject: [PATCH 6/8] Simplify managed method invoke generation for AOT vs JIT platforms --- .../Bindings/BindingsGenerator.Cpp.cs | 247 +++++++++--------- Source/Tools/Flax.Build/Build/Platform.cs | 5 + .../Flax.Build/Platforms/UWP/UWPPlatform.cs | 3 + 3 files changed, 129 insertions(+), 126 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index ddf3a0f18..4cf911131 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1257,112 +1257,60 @@ namespace Flax.Build.Bindings else contents.AppendLine($" void* params[{functionInfo.Parameters.Count}];"); - contents.AppendLine("#if USE_MONO_AOT"); - - // Convert parameters into managed format as pointers to value - if (functionInfo.Parameters.Count != 0) - { - for (var i = 0; i < functionInfo.Parameters.Count; i++) - { - var parameterInfo = functionInfo.Parameters[i]; - var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut; - var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, parameterInfo.Type, parameterInfo.Name, classInfo, paramIsRef, out CppParamsThatNeedLocalVariable[i]); - contents.Append($" params[{i}] = {paramValue};").AppendLine(); - } - } - - // Invoke method - contents.AppendLine(" auto __result = mono_runtime_invoke(method->GetNative(), object->GetOrCreateManagedInstance(), params, &exception);"); - - contents.AppendLine("#else"); - - // Convert parameters into managed format as boxed values - var thunkParams = string.Empty; - var thunkCall = string.Empty; - if (functionInfo.Parameters.Count != 0) - { - separator = functionInfo.Parameters.Count != 0; - for (var i = 0; i < functionInfo.Parameters.Count; i++) - { - var parameterInfo = functionInfo.Parameters[i]; - var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut; - var paramValue = GenerateCppWrapperNativeToBox(buildData, parameterInfo.Type, classInfo, out var apiType, parameterInfo.Name); - var useLocalVar = false; - if (paramIsRef) - { - // Pass as pointer to value when using ref/out parameter - contents.Append($" auto __param_{parameterInfo.Name} = {paramValue};").AppendLine(); - var useLocalVarPointer = !apiType.IsValueType; - paramValue = $"{(useLocalVarPointer ? "&" : "")}__param_{parameterInfo.Name}"; - CppParamsThatNeedConversion[i] = useLocalVarPointer; - useLocalVar = true; - } - CppParamsThatNeedLocalVariable[i] = useLocalVar; - - if (separator) - thunkParams += ", "; - if (separator) - thunkCall += ", "; - separator = true; - thunkParams += "void*"; - contents.Append($" params[{i}] = {paramValue};").AppendLine(); - thunkCall += $"params[{i}]"; - } - } - - // Invoke method thunk + // If platform supports JITed code execution then use method thunk, otherwise fallback to generic mono_runtime_invoke var returnType = functionInfo.ReturnType; - if (returnType.IsVoid) + var useThunk = buildData.Platform.HasDynamicCodeExecutionSupport; + if (useThunk) { - contents.AppendLine($" typedef void (*Thunk)(void* instance{thunkParams}, MonoObject** exception);"); - contents.AppendLine(" const auto thunk = (Thunk)method->GetThunk();"); - contents.AppendLine($" thunk(object->GetOrCreateManagedInstance(){thunkCall}, &exception);"); - } - else - { - contents.AppendLine($" typedef MonoObject* (*Thunk)(void* instance{thunkParams}, MonoObject** exception);"); - contents.AppendLine(" const auto thunk = (Thunk)method->GetThunk();"); - contents.AppendLine($" auto __result = thunk(object->GetOrCreateManagedInstance(){thunkCall}, &exception);"); - } - - contents.AppendLine("#endif"); - contents.AppendLine(" WrapperCallInstance = prevWrapperCallInstance;"); - contents.AppendLine(" if (exception)"); - contents.AppendLine(" DebugLog::LogException(exception);"); - - // Convert parameter values back from managed to native (could be modified there) - bool anyRefOut = false; - for (var i = 0; i < functionInfo.Parameters.Count; i++) - { - var parameterInfo = functionInfo.Parameters[i]; - var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut; - if (paramIsRef && !parameterInfo.Type.IsConst) + // Convert parameters into managed format as boxed values + var thunkParams = string.Empty; + var thunkCall = string.Empty; + if (functionInfo.Parameters.Count != 0) { - if (!anyRefOut) + separator = functionInfo.Parameters.Count != 0; + for (var i = 0; i < functionInfo.Parameters.Count; i++) { - anyRefOut = true; - contents.AppendLine("#if USE_MONO_AOT"); - } + var parameterInfo = functionInfo.Parameters[i]; + var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut; + var paramValue = GenerateCppWrapperNativeToBox(buildData, parameterInfo.Type, classInfo, out var apiType, parameterInfo.Name); + var useLocalVar = false; + if (paramIsRef) + { + // Pass as pointer to value when using ref/out parameter + contents.Append($" auto __param_{parameterInfo.Name} = {paramValue};").AppendLine(); + var useLocalVarPointer = !apiType.IsValueType; + paramValue = $"{(useLocalVarPointer ? "&" : "")}__param_{parameterInfo.Name}"; + CppParamsThatNeedConversion[i] = useLocalVarPointer; + useLocalVar = true; + } + CppParamsThatNeedLocalVariable[i] = useLocalVar; - // Direct value convert - var managedToNative = GenerateCppWrapperManagedToNative(buildData, parameterInfo.Type, classInfo, out var managedType, out var apiType, null, out _); - var passAsParamPtr = managedType.EndsWith("*"); - var useLocalVarPointer = CppParamsThatNeedConversion[i] && !apiType.IsValueType; - var paramValue = useLocalVarPointer ? $"*({managedType}{(passAsParamPtr ? "" : "*")}*)params[{i}]" : $"({managedType}{(passAsParamPtr ? "" : "*")})params[{i}]"; - if (!string.IsNullOrEmpty(managedToNative)) - { - if (!passAsParamPtr) - paramValue = '*' + paramValue; - paramValue = string.Format(managedToNative, paramValue); + if (separator) + thunkParams += ", "; + if (separator) + thunkCall += ", "; + separator = true; + thunkParams += "void*"; + contents.Append($" params[{i}] = {paramValue};").AppendLine(); + thunkCall += $"params[{i}]"; } - else if (!passAsParamPtr) - paramValue = '*' + paramValue; - contents.Append($" {parameterInfo.Name} = {paramValue};").AppendLine(); } - } - if (anyRefOut) - { - contents.AppendLine("#else"); + + // Invoke method thunk + if (returnType.IsVoid) + { + contents.AppendLine($" typedef void (*Thunk)(void* instance{thunkParams}, MonoObject** exception);"); + contents.AppendLine(" const auto thunk = (Thunk)method->GetThunk();"); + contents.AppendLine($" thunk(object->GetOrCreateManagedInstance(){thunkCall}, &exception);"); + } + else + { + contents.AppendLine($" typedef MonoObject* (*Thunk)(void* instance{thunkParams}, MonoObject** exception);"); + contents.AppendLine(" const auto thunk = (Thunk)method->GetThunk();"); + contents.AppendLine($" auto __result = thunk(object->GetOrCreateManagedInstance(){thunkCall}, &exception);"); + } + + // Convert parameter values back from managed to native (could be modified there) for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; @@ -1377,41 +1325,88 @@ namespace Flax.Build.Bindings parameterInfo.Type.IsRef = true; } } - contents.AppendLine("#endif"); + } + else + { + // Convert parameters into managed format as pointers to value + if (functionInfo.Parameters.Count != 0) + { + for (var i = 0; i < functionInfo.Parameters.Count; i++) + { + var parameterInfo = functionInfo.Parameters[i]; + var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut; + var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, parameterInfo.Type, parameterInfo.Name, classInfo, paramIsRef, out CppParamsThatNeedLocalVariable[i]); + contents.Append($" params[{i}] = {paramValue};").AppendLine(); + } + } + + // Invoke method + contents.AppendLine(" auto __result = mono_runtime_invoke(method->GetNative(), object->GetOrCreateManagedInstance(), params, &exception);"); + + // Convert parameter values back from managed to native (could be modified there) + for (var i = 0; i < functionInfo.Parameters.Count; i++) + { + var parameterInfo = functionInfo.Parameters[i]; + var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut; + if (paramIsRef && !parameterInfo.Type.IsConst) + { + // Direct value convert + var managedToNative = GenerateCppWrapperManagedToNative(buildData, parameterInfo.Type, classInfo, out var managedType, out var apiType, null, out _); + var passAsParamPtr = managedType.EndsWith("*"); + var useLocalVarPointer = CppParamsThatNeedConversion[i] && !apiType.IsValueType; + var paramValue = useLocalVarPointer ? $"*({managedType}{(passAsParamPtr ? "" : "*")}*)params[{i}]" : $"({managedType}{(passAsParamPtr ? "" : "*")})params[{i}]"; + if (!string.IsNullOrEmpty(managedToNative)) + { + if (!passAsParamPtr) + paramValue = '*' + paramValue; + paramValue = string.Format(managedToNative, paramValue); + } + else if (!passAsParamPtr) + paramValue = '*' + paramValue; + contents.Append($" {parameterInfo.Name} = {paramValue};").AppendLine(); + } + } } - // Unbox returned value + contents.AppendLine(" WrapperCallInstance = prevWrapperCallInstance;"); + contents.AppendLine(" if (exception)"); + contents.AppendLine(" DebugLog::LogException(exception);"); + + // Unpack returned value if (!returnType.IsVoid) { if (returnType.IsRef) throw new NotSupportedException($"Passing return value by reference is not supported for virtual API methods. Used on method '{functionInfo}'."); - - // mono_runtime_invoke always returns boxed value as MonoObject*, but thunk might return value within pointer (eg. as int or boolean) - contents.AppendLine("#if USE_MONO_AOT"); - contents.AppendLine($" return MUtils::Unbox<{returnType}>(__result);"); - contents.AppendLine("#else"); - switch (returnType.Type) + if (useThunk) { - case "bool": - contents.AppendLine(" return __result != 0;"); - break; - case "int8": - case "int16": - case "int32": - case "int64": - contents.AppendLine($" return ({returnType.Type})(intptr)__result;"); - break; - case "uint8": - case "uint16": - case "uint32": - case "uint64": - contents.AppendLine($" return ({returnType.Type})(uintptr)__result;"); - break; - default: - contents.AppendLine($" return MUtils::Unbox<{returnType}>(__result);"); - break; + // Thunk might return value within pointer (eg. as int or boolean) + switch (returnType.Type) + { + case "bool": + contents.AppendLine(" return __result != 0;"); + break; + case "int8": + case "int16": + case "int32": + case "int64": + contents.AppendLine($" return ({returnType.Type})(intptr)__result;"); + break; + case "uint8": + case "uint16": + case "uint32": + case "uint64": + contents.AppendLine($" return ({returnType.Type})(uintptr)__result;"); + break; + default: + contents.AppendLine($" return MUtils::Unbox<{returnType}>(__result);"); + break; + } + } + else + { + // mono_runtime_invoke always returns boxed value as MonoObject* + contents.AppendLine($" return MUtils::Unbox<{returnType}>(__result);"); } - contents.AppendLine("#endif"); } contents.AppendLine(" }"); diff --git a/Source/Tools/Flax.Build/Build/Platform.cs b/Source/Tools/Flax.Build/Build/Platform.cs index 99edc7889..02033283e 100644 --- a/Source/Tools/Flax.Build/Build/Platform.cs +++ b/Source/Tools/Flax.Build/Build/Platform.cs @@ -106,6 +106,11 @@ namespace Flax.Build /// public virtual bool HasExecutableFileReferenceSupport => false; + /// + /// Gets a value indicating whether that platform supports executing native code generated dynamically (JIT), otherwise requires ahead-of-time compilation (AOT). + /// + public virtual bool HasDynamicCodeExecutionSupport => true; + /// /// Gets the executable file extension (including leading dot). /// diff --git a/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs b/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs index 165a2f6fe..b2e92cce2 100644 --- a/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs @@ -15,6 +15,9 @@ namespace Flax.Build.Platforms /// public override TargetPlatform Target => TargetPlatform.UWP; + /// + public override bool HasDynamicCodeExecutionSupport => false; + /// /// Initializes a new instance of the class. /// From 8db75ffa367c4e69dbdd5031c87496eafe9ede76 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 8 Jan 2023 13:52:33 +0100 Subject: [PATCH 7/8] Fix name upper-case --- Content/Editor/Icons/{Skylight.flax => SkyLight1.flax} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Content/Editor/Icons/{Skylight.flax => SkyLight1.flax} (100%) diff --git a/Content/Editor/Icons/Skylight.flax b/Content/Editor/Icons/SkyLight1.flax similarity index 100% rename from Content/Editor/Icons/Skylight.flax rename to Content/Editor/Icons/SkyLight1.flax From 4c671012bf22100f25f4ced0c0b0b7eae33cd64b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 8 Jan 2023 13:52:45 +0100 Subject: [PATCH 8/8] Fix name upper-case --- Content/Editor/Icons/{SkyLight1.flax => SkyLight.flax} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Content/Editor/Icons/{SkyLight1.flax => SkyLight.flax} (100%) diff --git a/Content/Editor/Icons/SkyLight1.flax b/Content/Editor/Icons/SkyLight.flax similarity index 100% rename from Content/Editor/Icons/SkyLight1.flax rename to Content/Editor/Icons/SkyLight.flax