Optimize C# bindings in Engine code to static functions that native ABI matches managed signature

This commit is contained in:
Wojtek Figat
2022-02-19 00:29:09 +01:00
parent 71b9324bcb
commit 56491569df
11 changed files with 81 additions and 95 deletions

View File

@@ -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);

View File

@@ -193,12 +193,6 @@ public:
/// <param name="path">The path.</param>
API_FUNCTION() static void OnFileAdded(CodeEditorTypes editorType, const String& path);
/// <summary>
/// Opens the solution project. Handles async opening.
/// </summary>
/// <param name="editor">The code editor.</param>
static void OpenSolution(CodeEditor* editor);
/// <summary>
/// The asynchronous open begins.
/// </summary>

View File

@@ -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())

View File

@@ -63,7 +63,7 @@ public:
/// </summary>
/// <param name="timeout">Time to use for checking.</param>
/// <returns>True if source code is dirty, otherwise false.</returns>
static bool IsSourceDirty(const TimeSpan& timeout);
static bool IsSourceDirtyFor(const TimeSpan& timeout);
/// <summary>
/// Returns true if scripts are being now compiled/reloaded.

View File

@@ -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

View File

@@ -19,6 +19,7 @@ namespace Flax.Build.Bindings
internal static readonly Dictionary<string, string> CSharpNativeToManagedBasicTypes = new Dictionary<string, string>()
{
// Language types
{ "bool", "bool" },
{ "int8", "sbyte" },
{ "int16", "short" },
{ "int32", "int" },

View File

@@ -19,7 +19,7 @@ namespace Flax.Build.Bindings
partial class BindingsGenerator
{
private static readonly Dictionary<string, Type> TypeCache = new Dictionary<string, Type>();
private const int CacheVersion = 11;
private const int CacheVersion = 12;
internal static void Write(BinaryWriter writer, string e)
{

View File

@@ -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<KeyValuePair<string, string>> CppInternalCalls = new List<KeyValuePair<string, string>>();
public static readonly List<ApiTypeInfo> CppUsedNonPodTypes = new List<ApiTypeInfo>();
private static readonly List<ApiTypeInfo> CppUsedNonPodTypesList = new List<ApiTypeInfo>();
public static readonly HashSet<FileInfo> CppReferencesFiles = new HashSet<FileInfo>();
@@ -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<string, string>(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<string, string>(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<string, string>(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);

View File

@@ -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)
{

View File

@@ -16,6 +16,7 @@ namespace Flax.Build.Bindings
public ClassStructInfo BaseType;
public List<InterfaceInfo> Interfaces;
public List<TypeInfo> Inheritance; // Data from parsing, used to interfaces and base type construct in Init
public List<FunctionInfo> Functions = new List<FunctionInfo>();
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
/// </summary>
public abstract class VirtualClassInfo : ClassStructInfo
{
public List<FunctionInfo> Functions = new List<FunctionInfo>();
internal HashSet<string> 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;

View File

@@ -12,7 +12,6 @@ namespace Flax.Build.Bindings
public class StructureInfo : ClassStructInfo
{
public List<FieldInfo> Fields = new List<FieldInfo>();
public List<FunctionInfo> Functions = new List<FunctionInfo>();
public bool IsAutoSerialization;
public bool ForceNoPod;