From 56491569df56d24442a413889f4f13651c4f0bad Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 19 Feb 2022 00:29:09 +0100 Subject: [PATCH] Optimize C# bindings in Engine code to static functions that native ABI matches managed signature --- Source/Editor/Scripting/CodeEditor.cpp | 37 +++---- Source/Editor/Scripting/CodeEditor.h | 6 -- Source/Editor/Scripting/ScriptsBuilder.cpp | 4 +- Source/Editor/Scripting/ScriptsBuilder.h | 2 +- .../Engine/Graphics/PixelFormatExtensions.cpp | 1 - .../Bindings/BindingsGenerator.CSharp.cs | 1 + .../Bindings/BindingsGenerator.Cache.cs | 2 +- .../Bindings/BindingsGenerator.Cpp.cs | 102 ++++++++++-------- .../Flax.Build/Bindings/BindingsGenerator.cs | 1 + .../Flax.Build/Bindings/ClassStructInfo.cs | 19 +--- .../Flax.Build/Bindings/StructureInfo.cs | 1 - 11 files changed, 81 insertions(+), 95 deletions(-) diff --git a/Source/Editor/Scripting/CodeEditor.cpp b/Source/Editor/Scripting/CodeEditor.cpp index 198d28396..1cfc4e737 100644 --- a/Source/Editor/Scripting/CodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditor.cpp @@ -180,7 +180,22 @@ void CodeEditingManager::OpenSolution(CodeEditorTypes editorType) const auto editor = GetCodeEditor(editorType); if (editor) { - OpenSolution(editor); + // Ensure that no async task is running + if (IsAsyncOpenRunning()) + { + // TODO: enqueue action and handle many actions in the queue + LOG(Warning, "Cannot use code editor during async open action."); + return; + } + + if (editor->UseAsyncForOpen()) + { + AsyncOpenTask::OpenSolution(editor); + } + else + { + editor->OpenSolution(); + } } else { @@ -201,26 +216,6 @@ void CodeEditingManager::OnFileAdded(CodeEditorTypes editorType, const String& p } } -void CodeEditingManager::OpenSolution(CodeEditor* editor) -{ - // Ensure that no async task is running - if (IsAsyncOpenRunning()) - { - // TODO: enqueue action and handle many actions in the queue - LOG(Warning, "Cannot use code editor during async open action."); - return; - } - - if (editor->UseAsyncForOpen()) - { - AsyncOpenTask::OpenSolution(editor); - } - else - { - editor->OpenSolution(); - } -} - void OnAsyncBegin(Thread* thread) { ASSERT(AsyncOpenThread == nullptr); diff --git a/Source/Editor/Scripting/CodeEditor.h b/Source/Editor/Scripting/CodeEditor.h index 638f1d997..a837f0d6e 100644 --- a/Source/Editor/Scripting/CodeEditor.h +++ b/Source/Editor/Scripting/CodeEditor.h @@ -193,12 +193,6 @@ public: /// The path. API_FUNCTION() static void OnFileAdded(CodeEditorTypes editorType, const String& path); - /// - /// Opens the solution project. Handles async opening. - /// - /// The code editor. - static void OpenSolution(CodeEditor* editor); - /// /// The asynchronous open begins. /// diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp index 8005524e7..25e22eaca 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.cpp +++ b/Source/Editor/Scripting/ScriptsBuilder.cpp @@ -156,7 +156,7 @@ bool ScriptsBuilder::IsSourceWorkspaceDirty() return _wasProjectStructureChanged; } -bool ScriptsBuilder::IsSourceDirty(const TimeSpan& timeout) +bool ScriptsBuilder::IsSourceDirtyFor(const TimeSpan& timeout) { ScopeLock scopeLock(_locker); return _lastSourceCodeEdited > (_lastCompileAction + timeout); @@ -626,7 +626,7 @@ void ScriptsBuilderService::Update() // Check if compile code (if has been edited) const TimeSpan timeToCallCompileIfDirty = TimeSpan::FromMilliseconds(50); auto mainWindow = Engine::MainWindow; - if (ScriptsBuilder::IsSourceDirty(timeToCallCompileIfDirty) && mainWindow && mainWindow->IsFocused()) + if (ScriptsBuilder::IsSourceDirtyFor(timeToCallCompileIfDirty) && mainWindow && mainWindow->IsFocused()) { // Check if auto reload is enabled if (Editor::Managed->CanAutoReloadScripts()) diff --git a/Source/Editor/Scripting/ScriptsBuilder.h b/Source/Editor/Scripting/ScriptsBuilder.h index df6ff604a..52fdd8457 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.h +++ b/Source/Editor/Scripting/ScriptsBuilder.h @@ -63,7 +63,7 @@ public: /// /// Time to use for checking. /// True if source code is dirty, otherwise false. - static bool IsSourceDirty(const TimeSpan& timeout); + static bool IsSourceDirtyFor(const TimeSpan& timeout); /// /// Returns true if scripts are being now compiled/reloaded. diff --git a/Source/Engine/Graphics/PixelFormatExtensions.cpp b/Source/Engine/Graphics/PixelFormatExtensions.cpp index e79699830..c22c9aba6 100644 --- a/Source/Engine/Graphics/PixelFormatExtensions.cpp +++ b/Source/Engine/Graphics/PixelFormatExtensions.cpp @@ -1,7 +1,6 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "PixelFormatExtensions.h" -#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Math/Math.h" // ReSharper disable CppClangTidyClangDiagnosticSwitchEnum diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 5c419ccb5..e7e4b5133 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -19,6 +19,7 @@ namespace Flax.Build.Bindings internal static readonly Dictionary CSharpNativeToManagedBasicTypes = new Dictionary() { // Language types + { "bool", "bool" }, { "int8", "sbyte" }, { "int16", "short" }, { "int32", "int" }, diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs index 0072c1240..e9d2076ed 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs @@ -19,7 +19,7 @@ namespace Flax.Build.Bindings partial class BindingsGenerator { private static readonly Dictionary TypeCache = new Dictionary(); - private const int CacheVersion = 11; + private const int CacheVersion = 12; internal static void Write(BinaryWriter writer, string e) { diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 77821e29f..25269ca6a 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -16,6 +16,7 @@ namespace Flax.Build.Bindings private static readonly string[] CppParamsThatNeedConversionWrappers = new string[64]; private static readonly string[] CppParamsThatNeedConversionTypes = new string[64]; private static readonly string[] CppParamsWrappersCache = new string[64]; + public static readonly List> CppInternalCalls = new List>(); public static readonly List CppUsedNonPodTypes = new List(); private static readonly List CppUsedNonPodTypesList = new List(); public static readonly HashSet CppReferencesFiles = new HashSet(); @@ -771,8 +772,53 @@ namespace Flax.Build.Bindings return $"MUtils::Box<{nativeType}>({value}, {GenerateCppGetNativeClass(buildData, typeInfo, caller, null)})"; } + private static bool GenerateCppWrapperFunctionImplicitBinding(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) + { + if (typeInfo.IsVoid) + return true; + if (typeInfo.IsPtr || typeInfo.IsRef || typeInfo.IsArray || typeInfo.IsBitField || (typeInfo.GenericArgs != null && typeInfo.GenericArgs.Count != 0)) + return false; + if (CSharpNativeToManagedBasicTypes.ContainsKey(typeInfo.Type) || CSharpNativeToManagedBasicTypes.ContainsValue(typeInfo.Type)) + return true; + var apiType = FindApiTypeInfo(buildData, typeInfo, caller); + if (apiType != null) + { + if (apiType.IsEnum) + return true; + } + return false; + } + + private static bool GenerateCppWrapperFunctionImplicitBinding(BuildData buildData, FunctionInfo functionInfo, ApiTypeInfo caller) + { + if (!functionInfo.IsStatic || functionInfo.Access != AccessLevel.Public || (functionInfo.Glue.CustomParameters != null && functionInfo.Glue.CustomParameters.Count != 0)) + return false; + if (!GenerateCppWrapperFunctionImplicitBinding(buildData, functionInfo.ReturnType, caller)) + return false; + for (int i = 0; i < functionInfo.Parameters.Count; i++) + { + var parameterInfo = functionInfo.Parameters[i]; + if (parameterInfo.IsOut || parameterInfo.IsRef || !GenerateCppWrapperFunctionImplicitBinding(buildData, parameterInfo.Type, caller)) + return false; + } + return true; + } + private static void GenerateCppWrapperFunction(BuildData buildData, StringBuilder contents, ApiTypeInfo caller, FunctionInfo functionInfo, string callFormat = "{0}({1})") { + // Optimize static function wrappers that match C# internal call ABI exactly + // Use it for Engine-internally only because in games this makes it problematic to use the same function name but with different signature that is not visible to scripting + if (CurrentModule.Module is EngineModule && callFormat == "{0}({1})" && GenerateCppWrapperFunctionImplicitBinding(buildData, functionInfo, caller)) + { + // Ensure the function name is unique within a class/structure + if (caller is ClassStructInfo classStructInfo && classStructInfo.Functions.All(f => f.Name != functionInfo.Name || f == functionInfo)) + { + // Use native method binding directly (no generated wrapper) + CppInternalCalls.Add(new KeyValuePair(functionInfo.UniqueName, classStructInfo.Name + "::" + functionInfo.Name)); + return; + } + } + // Setup function binding glue to ensure that wrapper method signature matches for C++ and C# functionInfo.Glue = new FunctionInfo.GlueInfo { @@ -797,6 +843,7 @@ namespace Flax.Build.Bindings }); } + CppInternalCalls.Add(new KeyValuePair(functionInfo.UniqueName, functionInfo.UniqueName)); contents.AppendFormat(" static {0} {1}(", returnValueType, functionInfo.UniqueName); var separator = false; @@ -1453,6 +1500,7 @@ namespace Flax.Build.Bindings var useScripting = classInfo.IsStatic || classInfo.IsScriptingObject; var useCSharp = EngineConfiguration.WithCSharp(buildData.TargetOptions); var hasInterface = classInfo.Interfaces != null && classInfo.Interfaces.Any(x => x.Access == AccessLevel.Public); + CppInternalCalls.Clear(); if (classInfo.IsAutoSerialization) GenerateCppAutoSerialization(buildData, contents, moduleInfo, classInfo, classTypeNameNative); @@ -1520,7 +1568,7 @@ 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 _); + var managedToNative = GenerateCppWrapperManagedToNative(buildData, paramType, classInfo, out var managedType, null, out _); var passAsParamPtr = managedType.EndsWith("*"); var paramValue = $"({managedType}{(passAsParamPtr ? "" : "*")})params[{i}]"; if (!string.IsNullOrEmpty(managedToNative)) @@ -1538,6 +1586,7 @@ namespace Flax.Build.Bindings contents.Append(" }").AppendLine().AppendLine(); // C# event wrapper binding method (binds/unbinds C# wrapper to C++ delegate) + CppInternalCalls.Add(new KeyValuePair(eventInfo.Name + "_Bind", eventInfo.Name + "_ManagedBind")); contents.AppendFormat(" static void {0}_ManagedBind(", eventInfo.Name); if (!eventInfo.IsStatic) contents.AppendFormat("{0}* obj, ", classTypeNameNative); @@ -1685,47 +1734,9 @@ namespace Flax.Build.Bindings } if (useScripting && useCSharp) { - foreach (var eventInfo in classInfo.Events) + foreach (var e in CppInternalCalls) { - if (eventInfo.IsHidden) - continue; - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{eventInfo.Name}_Bind\", &{eventInfo.Name}_ManagedBind);"); - } - foreach (var fieldInfo in classInfo.Fields) - { - if (fieldInfo.IsHidden) - continue; - if (fieldInfo.Getter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{fieldInfo.Getter.UniqueName}\", &{fieldInfo.Getter.UniqueName});"); - if (fieldInfo.Setter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{fieldInfo.Setter.UniqueName}\", &{fieldInfo.Setter.UniqueName});"); - } - foreach (var propertyInfo in classInfo.Properties) - { - if (propertyInfo.IsHidden) - continue; - if (propertyInfo.Getter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{propertyInfo.Getter.UniqueName}\", &{propertyInfo.Getter.UniqueName});"); - if (propertyInfo.Setter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{propertyInfo.Setter.UniqueName}\", &{propertyInfo.Setter.UniqueName});"); - } - foreach (var functionInfo in classInfo.Functions) - { - if (functionInfo.IsHidden) - continue; - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{functionInfo.UniqueName}\", &{functionInfo.UniqueName});"); - } - if (hasInterface) - { - foreach (var interfaceInfo in classInfo.Interfaces) - { - if (interfaceInfo.Access != AccessLevel.Public) - continue; - foreach (var functionInfo in interfaceInfo.Functions) - { - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{functionInfo.UniqueName}\", &{functionInfo.UniqueName});"); - } - } + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{e.Key}\", &{e.Value});"); } } GenerateCppClassInitRuntime?.Invoke(buildData, classInfo, contents); @@ -1801,6 +1812,7 @@ namespace Flax.Build.Bindings if (structureInfo.Parent != null && !(structureInfo.Parent is FileInfo)) structureTypeNameInternal = structureInfo.Parent.FullNameNative + '_' + structureTypeNameInternal; var useCSharp = EngineConfiguration.WithCSharp(buildData.TargetOptions); + CppInternalCalls.Clear(); if (structureInfo.IsAutoSerialization) GenerateCppAutoSerialization(buildData, contents, moduleInfo, structureInfo, structureTypeNameNative); @@ -1869,12 +1881,9 @@ namespace Flax.Build.Bindings if (useCSharp) { - foreach (var fieldInfo in structureInfo.Fields) + foreach (var e in CppInternalCalls) { - if (fieldInfo.Getter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{structureTypeNameManagedInternalCall}::Internal_{fieldInfo.Getter.UniqueName}\", &{fieldInfo.Getter.UniqueName});"); - if (fieldInfo.Setter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{structureTypeNameManagedInternalCall}::Internal_{fieldInfo.Setter.UniqueName}\", &{fieldInfo.Setter.UniqueName});"); + contents.AppendLine($" ADD_INTERNAL_CALL(\"{structureTypeNameManagedInternalCall}::Internal_{e.Key}\", &{e.Value});"); } } @@ -2189,6 +2198,7 @@ namespace Flax.Build.Bindings CppIncludeFilesList.Clear(); CppVariantToTypes.Clear(); CppVariantFromTypes.Clear(); + CurrentModule = moduleInfo; // Disable C# scripting based on configuration ScriptingLangInfos[0].Enabled = EngineConfiguration.WithCSharp(buildData.TargetOptions); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs index 7a84947b0..ca683066a 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs @@ -48,6 +48,7 @@ namespace Flax.Build.Bindings public static event GenerateModuleBindingsDelegate GenerateModuleBindings; public static event GenerateBinaryModuleBindingsDelegate GenerateBinaryModuleBindings; + public static ModuleInfo CurrentModule; public static ModuleInfo ParseModule(BuildData buildData, Module module, BuildOptions moduleOptions = null) { diff --git a/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs index 36d97cfde..3f212a3a5 100644 --- a/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs @@ -16,6 +16,7 @@ namespace Flax.Build.Bindings public ClassStructInfo BaseType; public List Interfaces; public List Inheritance; // Data from parsing, used to interfaces and base type construct in Init + public List Functions = new List(); public override void Init(Builder.BuildData buildData) { @@ -52,6 +53,7 @@ namespace Flax.Build.Bindings writer.Write((byte)BaseTypeInheritance); BindingsGenerator.Write(writer, BaseType); BindingsGenerator.Write(writer, Inheritance); + BindingsGenerator.Write(writer, Functions); base.Write(writer); } @@ -62,6 +64,7 @@ namespace Flax.Build.Bindings BaseTypeInheritance = (AccessLevel)reader.ReadByte(); BaseType = BindingsGenerator.Read(reader, BaseType); Inheritance = BindingsGenerator.Read(reader, Inheritance); + Functions = BindingsGenerator.Read(reader, Functions); base.Read(reader); } @@ -72,8 +75,6 @@ namespace Flax.Build.Bindings /// public abstract class VirtualClassInfo : ClassStructInfo { - public List Functions = new List(); - internal HashSet UniqueFunctionNames; public override void Init(Builder.BuildData buildData) @@ -100,20 +101,6 @@ namespace Flax.Build.Bindings public abstract int GetScriptVTableOffset(VirtualClassInfo classInfo); - public override void Write(BinaryWriter writer) - { - BindingsGenerator.Write(writer, Functions); - - base.Write(writer); - } - - public override void Read(BinaryReader reader) - { - Functions = BindingsGenerator.Read(reader, Functions); - - base.Read(reader); - } - public override void AddChild(ApiTypeInfo apiTypeInfo) { apiTypeInfo.Namespace = null; diff --git a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs index adc527c18..24c700c22 100644 --- a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs @@ -12,7 +12,6 @@ namespace Flax.Build.Bindings public class StructureInfo : ClassStructInfo { public List Fields = new List(); - public List Functions = new List(); public bool IsAutoSerialization; public bool ForceNoPod;