Merge remote-tracking branch 'origin/1.1' into linux-editor

# Conflicts:
#	Source/FlaxEngine.Gen.cs
#	Source/Tools/Flax.Build/Utilities/Utilities.cs
This commit is contained in:
Wojtek Figat
2021-02-16 18:54:25 +01:00
654 changed files with 19651 additions and 10556 deletions

View File

@@ -1,16 +1,17 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.IO;
namespace Flax.Build.Bindings
{
/// <summary>
/// The native type information for bindings generator.
/// </summary>
public class ApiTypeInfo
public class ApiTypeInfo : IBindingsCache
{
public ApiTypeInfo Parent;
public List<ApiTypeInfo> Children;
public List<ApiTypeInfo> Children = new List<ApiTypeInfo>();
public string NativeName;
public string Name;
public string Namespace;
@@ -22,6 +23,7 @@ namespace Flax.Build.Bindings
public virtual bool IsClass => false;
public virtual bool IsStruct => false;
public virtual bool IsEnum => false;
public virtual bool IsInterface => false;
public virtual bool IsValueType => false;
public virtual bool IsScriptingObject => false;
public virtual bool IsPod => false;
@@ -90,6 +92,35 @@ namespace Flax.Build.Bindings
Children.Add(apiTypeInfo);
}
public virtual void Write(BinaryWriter writer)
{
BindingsGenerator.Write(writer, NativeName);
BindingsGenerator.Write(writer, Name);
BindingsGenerator.Write(writer, Namespace);
BindingsGenerator.Write(writer, Attributes);
BindingsGenerator.Write(writer, Comment);
writer.Write(IsInBuild);
BindingsGenerator.Write(writer, Children);
}
public virtual void Read(BinaryReader reader)
{
NativeName = BindingsGenerator.Read(reader, NativeName);
Name = BindingsGenerator.Read(reader, Name);
Namespace = BindingsGenerator.Read(reader, Namespace);
Attributes = BindingsGenerator.Read(reader, Attributes);
Comment = BindingsGenerator.Read(reader, Comment);
IsInBuild = reader.ReadBoolean();
Children = BindingsGenerator.Read(reader, Children);
// Fix hierarchy
for (int i = 0; i < Children.Count; i++)
{
var child = Children[i];
child.Parent = this;
}
}
public override string ToString()
{
return Name;

View File

@@ -14,6 +14,7 @@ namespace Flax.Build.Bindings
public const string Enum = "API_ENUM";
public const string Class = "API_CLASS";
public const string Struct = "API_STRUCT";
public const string Interface = "API_INTERFACE";
public const string Function = "API_FUNCTION";
public const string Property = "API_PROPERTY";
public const string Field = "API_FIELD";
@@ -27,6 +28,7 @@ namespace Flax.Build.Bindings
Enum,
Class,
Struct,
Interface,
};
}

View File

@@ -66,6 +66,14 @@ namespace Flax.Build.Bindings
return null;
}
// Special case for Engine TEXT macro
if (value.Contains("TEXT(\""))
return value.Replace("TEXT(\"", "(\"");
// Special case for value constructors
if (value.Contains('(') && value.Contains(')'))
return "new " + value;
// Convert from C++ to C#
switch (value)
{
@@ -124,8 +132,8 @@ namespace Flax.Build.Bindings
return result;
}
// ScriptingObjectReference or AssetReference or WeakAssetReference
if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null)
// ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference
if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null)
return typeInfo.GenericArgs[0].Type.Replace("::", ".");
// Array or Span
@@ -180,8 +188,8 @@ namespace Flax.Build.Bindings
return "IntPtr";
}
// ScriptingObjectReference or AssetReference or WeakAssetReference
if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null)
// ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference
if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null)
return "IntPtr";
return GenerateCSharpNativeToManaged(buildData, typeInfo, caller);
@@ -223,8 +231,8 @@ namespace Flax.Build.Bindings
return "FlaxEngine.Object.GetUnmanagedPtr";
}
// ScriptingObjectReference or AssetReference or WeakAssetReference
if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null)
// ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference
if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null)
return "FlaxEngine.Object.GetUnmanagedPtr";
// Default
@@ -386,14 +394,36 @@ namespace Flax.Build.Bindings
}
}
private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, string attributes, string[] comment, bool canUseTooltip, bool useUnmanaged)
private static bool IsDefaultValueSupported(string value)
{
// TEXT macro (eg. TEXT("text"))
// TODO: support string for default value attribute
if (value.Contains("TEXT(\""))
return false;
// Value constructors (eg. Vector2(1, 2))
// TODO: support value constructors for default value attribute
if (value.Contains('(') && value.Contains(')'))
return false;
// Constants (eg. Vector2::Zero)
// TODO: support constants for default value attribute
if (value.Contains("::"))
return false;
return true;
}
private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, string attributes, string[] comment, bool canUseTooltip, bool useUnmanaged, string defaultValue = null)
{
var writeTooltip = true;
var writeDefaultValue = true;
if (!string.IsNullOrEmpty(attributes))
{
// Write attributes
contents.Append(indent).Append('[').Append(attributes).Append(']').AppendLine();
writeTooltip = !attributes.Contains("Tooltip(");
writeTooltip = !attributes.Contains("Tooltip(") && !attributes.Contains("HideInEditor");
writeDefaultValue = !attributes.Contains("DefaultValue(");
}
if (useUnmanaged)
@@ -417,20 +447,27 @@ namespace Flax.Build.Bindings
contents.Append(indent).Append("[Tooltip(\"").Append(tooltip).Append("\")]").AppendLine();
}
}
if (!string.IsNullOrEmpty(defaultValue) && writeDefaultValue && IsDefaultValueSupported(defaultValue))
{
// Write default value attribute
defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, defaultValue, apiTypeInfo);
contents.Append(indent).Append("[DefaultValue(").Append(defaultValue).Append(")]").AppendLine();
}
}
private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, bool useUnmanaged)
private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, bool useUnmanaged, string defaultValue = null)
{
GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo.Attributes, apiTypeInfo.Comment, true, useUnmanaged);
GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo, apiTypeInfo.Attributes, apiTypeInfo.Comment, true, useUnmanaged, defaultValue);
}
private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, MemberInfo memberInfo, bool useUnmanaged)
private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, MemberInfo memberInfo, bool useUnmanaged, string defaultValue = null)
{
GenerateCSharpAttributes(buildData, contents, indent, memberInfo.Attributes, memberInfo.Comment, true, useUnmanaged);
GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo, memberInfo.Attributes, memberInfo.Comment, true, useUnmanaged, defaultValue);
}
private static void GenerateCSharpClass(BuildData buildData, StringBuilder contents, string indent, ClassInfo classInfo)
{
var useUnmanaged = classInfo.IsStatic || classInfo.IsScriptingObject;
contents.AppendLine();
// Namespace begin
@@ -446,7 +483,7 @@ namespace Flax.Build.Bindings
GenerateCSharpComment(contents, indent, classInfo.Comment);
// Class begin
GenerateCSharpAttributes(buildData, contents, indent, classInfo, true);
GenerateCSharpAttributes(buildData, contents, indent, classInfo, useUnmanaged);
contents.Append(indent);
if (classInfo.Access == AccessLevel.Public)
contents.Append("public ");
@@ -461,7 +498,7 @@ namespace Flax.Build.Bindings
else if (classInfo.IsAbstract)
contents.Append("abstract ");
contents.Append("unsafe partial class ").Append(classInfo.Name);
if (classInfo.BaseType != null && classInfo.BaseTypeInheritance != AccessLevel.Private)
if (classInfo.BaseType != null && !classInfo.IsBaseTypeHidden)
contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, classInfo.BaseType, classInfo));
contents.AppendLine();
contents.Append(indent + "{");
@@ -482,19 +519,20 @@ namespace Flax.Build.Bindings
// Events
foreach (var eventInfo in classInfo.Events)
{
if (!useUnmanaged)
throw new NotImplementedException("TODO: support events inside non-static and non-scripting API class types.");
var paramsCount = eventInfo.Type.GenericArgs?.Count ?? 0;
contents.AppendLine();
foreach (var comment in eventInfo.Comment)
{
if (comment.Contains("/// <returns>"))
continue;
var c = comment.Replace("::", ".");
contents.Append(indent);
contents.Append(c);
contents.AppendLine();
contents.Append(indent).Append(comment.Replace("::", ".")).AppendLine();
}
GenerateCSharpAttributes(buildData, contents, indent, eventInfo, true);
GenerateCSharpAttributes(buildData, contents, indent, classInfo, eventInfo, useUnmanaged);
contents.Append(indent);
if (eventInfo.Access == AccessLevel.Public)
contents.Append("public ");
@@ -505,10 +543,10 @@ namespace Flax.Build.Bindings
if (eventInfo.IsStatic)
contents.Append("static ");
string eventSignature = "event Action";
if (eventInfo.Type.GenericArgs.Count != 0)
if (paramsCount != 0)
{
eventSignature += '<';
for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++)
for (var i = 0; i < paramsCount; i++)
{
if (i != 0)
eventSignature += ", ";
@@ -546,7 +584,7 @@ namespace Flax.Build.Bindings
if (eventInfo.IsStatic)
contents.Append("static ");
contents.Append($"void Internal_{eventInfo.Name}_Invoke(");
for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++)
for (var i = 0; i < paramsCount; i++)
{
if (i != 0)
contents.Append(", ");
@@ -557,7 +595,7 @@ namespace Flax.Build.Bindings
contents.Append(indent).Append('{').AppendLine();
contents.Append(indent).Append(" Internal_").Append(eventInfo.Name);
contents.Append('(');
for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++)
for (var i = 0; i < paramsCount; i++)
{
if (i != 0)
contents.Append(", ");
@@ -586,14 +624,10 @@ namespace Flax.Build.Bindings
{
if (comment.Contains("/// <returns>"))
continue;
var c = comment.Replace("::", ".");
contents.Append(indent);
contents.Append(c);
contents.AppendLine();
contents.Append(indent).Append(comment.Replace("::", ".")).AppendLine();
}
GenerateCSharpAttributes(buildData, contents, indent, fieldInfo, true);
GenerateCSharpAttributes(buildData, contents, indent, classInfo, fieldInfo, useUnmanaged, fieldInfo.DefaultValue);
contents.Append(indent);
if (fieldInfo.Access == AccessLevel.Public)
contents.Append("public ");
@@ -604,8 +638,19 @@ namespace Flax.Build.Bindings
if (fieldInfo.IsStatic)
contents.Append("static ");
var returnValueType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, classInfo);
contents.Append(returnValueType).Append(' ').AppendLine(fieldInfo.Name);
contents.AppendLine(indent + "{");
contents.Append(returnValueType).Append(' ').Append(fieldInfo.Name);
if (!useUnmanaged)
{
if (fieldInfo.DefaultValue != null)
{
var defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, fieldInfo.DefaultValue, classInfo);
if (!string.IsNullOrEmpty(defaultValue))
contents.Append(" = ").Append(defaultValue);
}
contents.AppendLine(";");
continue;
}
contents.AppendLine().AppendLine(indent + "{");
indent += " ";
contents.Append(indent).Append("get { ");
@@ -615,7 +660,7 @@ namespace Flax.Build.Bindings
if (!fieldInfo.IsReadOnly)
{
contents.Append(indent).Append("set { ");
GenerateCSharpWrapperFunctionCall(buildData, contents, classInfo, fieldInfo.Setter, true);
GenerateCSharpWrapperFunctionCall(buildData, contents, classInfo, fieldInfo.Setter, useUnmanaged);
contents.Append(" }").AppendLine();
}
@@ -630,23 +675,19 @@ namespace Flax.Build.Bindings
// Properties
foreach (var propertyInfo in classInfo.Properties)
{
if (!useUnmanaged)
throw new NotImplementedException("TODO: support properties inside non-static and non-scripting API class types.");
contents.AppendLine();
foreach (var comment in propertyInfo.Comment)
{
if (comment.Contains("/// <returns>") || comment.Contains("<param name="))
continue;
var c = comment.Replace("::", ".");
contents.Append(indent);
if (propertyInfo.Getter != null && propertyInfo.Setter != null)
contents.Append(c.Replace("/// Gets ", "/// Gets or sets "));
else
contents.Append(c);
contents.AppendLine();
contents.Append(indent).Append(comment.Replace("::", ".")).AppendLine();
}
GenerateCSharpAttributes(buildData, contents, indent, propertyInfo, true);
GenerateCSharpAttributes(buildData, contents, indent, classInfo, propertyInfo, useUnmanaged);
contents.Append(indent);
if (propertyInfo.Access == AccessLevel.Public)
contents.Append("public ");
@@ -692,11 +733,14 @@ namespace Flax.Build.Bindings
// Functions
foreach (var functionInfo in classInfo.Functions)
{
if (!useUnmanaged)
throw new Exception($"Not supported function {functionInfo.Name} inside non-static and non-scripting class type {classInfo.Name}.");
if (!functionInfo.NoProxy)
{
contents.AppendLine();
GenerateCSharpComment(contents, indent, functionInfo.Comment);
GenerateCSharpAttributes(buildData, contents, indent, functionInfo, true);
GenerateCSharpAttributes(buildData, contents, indent, classInfo, functionInfo, true);
contents.Append(indent);
if (functionInfo.Access == AccessLevel.Public)
contents.Append("public ");
@@ -808,7 +852,7 @@ namespace Flax.Build.Bindings
foreach (var comment in fieldInfo.Comment)
contents.Append(indent).Append(comment).AppendLine();
GenerateCSharpAttributes(buildData, contents, indent, fieldInfo, fieldInfo.IsStatic);
GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic, fieldInfo.DefaultValue);
contents.Append(indent);
if (fieldInfo.Access == AccessLevel.Public)
contents.Append("public ");
@@ -832,7 +876,7 @@ namespace Flax.Build.Bindings
contents.AppendLine();
foreach (var comment in fieldInfo.Comment)
contents.Append(indent).Append(comment).AppendLine();
GenerateCSharpAttributes(buildData, contents, indent, fieldInfo, fieldInfo.IsStatic);
GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic);
contents.Append(indent);
if (fieldInfo.Access == AccessLevel.Public)
contents.Append("public ");
@@ -938,7 +982,7 @@ namespace Flax.Build.Bindings
foreach (var comment in entryInfo.Comment)
contents.Append(indent).Append(comment).AppendLine();
GenerateCSharpAttributes(buildData, contents, indent, entryInfo.Attributes, entryInfo.Comment, true, false);
GenerateCSharpAttributes(buildData, contents, indent, enumInfo, entryInfo.Attributes, entryInfo.Comment, true, false);
contents.Append(indent).Append(entryInfo.Name);
if (!string.IsNullOrEmpty(entryInfo.Value))
contents.Append(" = ").Append(entryInfo.Value);
@@ -984,9 +1028,6 @@ namespace Flax.Build.Bindings
private static void GenerateCSharp(BuildData buildData, ModuleInfo moduleInfo, ref BindingsResult bindings)
{
if (!bindings.UseBindings)
return;
var contents = new StringBuilder();
buildData.Modules.TryGetValue(moduleInfo.Module, out var moduleBuildInfo);
@@ -1037,12 +1078,6 @@ namespace Flax.Build.Bindings
// Save generated file
contents.AppendLine("#endif");
Utilities.WriteFileIfChanged(bindings.GeneratedCSharpFilePath, contents.ToString());
// Ensure that generated file is included into build
if (useBindings && moduleBuildInfo != null && !moduleBuildInfo.SourceFiles.Contains(bindings.GeneratedCSharpFilePath))
{
moduleBuildInfo.SourceFiles.Add(bindings.GeneratedCSharpFilePath);
}
}
internal struct GuidInterop

View File

@@ -0,0 +1,266 @@
// (c) 2012-2020 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Flax.Build.NativeCpp;
namespace Flax.Build.Bindings
{
internal interface IBindingsCache
{
void Write(BinaryWriter writer);
void Read(BinaryReader reader);
}
partial class BindingsGenerator
{
private static readonly Dictionary<string, Type> _typeCache = new Dictionary<string, Type>();
internal static void Write(BinaryWriter writer, string e)
{
var valid = e != null;
writer.Write(valid);
if (valid)
writer.Write(e);
}
internal static void Write(BinaryWriter writer, string[] list)
{
if (list != null)
{
writer.Write(list.Length);
for (int i = 0; i < list.Length; i++)
writer.Write(list[i]);
}
else
{
writer.Write(0);
}
}
internal static void Write(BinaryWriter writer, IEnumerable<string> list)
{
if (list != null)
{
writer.Write(list.Count());
foreach (var e in list)
writer.Write(e);
}
else
{
writer.Write(0);
}
}
internal static void Write<T>(BinaryWriter writer, T e) where T : IBindingsCache
{
if (e != null)
{
writer.Write(e.GetType().FullName);
e.Write(writer);
}
else
{
writer.Write(string.Empty);
}
}
internal static void Write<T>(BinaryWriter writer, IList<T> list) where T : IBindingsCache
{
if (list != null)
{
var count = list.Count;
writer.Write(count);
for (int i = 0; i < count; i++)
{
var e = list[i];
writer.Write(e.GetType().FullName);
e.Write(writer);
}
}
else
{
writer.Write(0);
}
}
internal static string Read(BinaryReader reader, string e)
{
var valid = reader.ReadBoolean();
if (valid)
e = reader.ReadString();
return e;
}
internal static string[] Read(BinaryReader reader, string[] list)
{
var count = reader.ReadInt32();
if (count != 0)
{
list = new string[count];
for (int i = 0; i < count; i++)
list[i] = reader.ReadString();
}
return list;
}
internal static T Read<T>(BinaryReader reader, T e) where T : IBindingsCache
{
var typename = reader.ReadString();
if (string.IsNullOrEmpty(typename))
return e;
if (!_typeCache.TryGetValue(typename, out var type))
{
type = Builder.BuildTypes.FirstOrDefault(x => x.FullName == typename);
if (type == null)
{
var msg = $"Missing type {typename}.";
Log.Error(msg);
throw new Exception(msg);
}
_typeCache.Add(typename, type);
}
e = (T)Activator.CreateInstance(type);
e.Read(reader);
return e;
}
internal static List<T> Read<T>(BinaryReader reader, List<T> list) where T : IBindingsCache
{
var count = reader.ReadInt32();
if (count != 0)
{
list = new List<T>(count);
for (int i = 0; i < count; i++)
{
var typename = reader.ReadString();
if (!_typeCache.TryGetValue(typename, out var type))
{
type = Builder.BuildTypes.FirstOrDefault(x => x.FullName == typename);
if (type == null)
{
var msg = $"Missing type {typename}.";
Log.Error(msg);
throw new Exception(msg);
}
_typeCache.Add(typename, type);
}
var e = (T)Activator.CreateInstance(type);
e.Read(reader);
list.Add(e);
}
}
return list;
}
private static string GetCachePath(Module module, BuildOptions moduleOptions)
{
return Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.cache");
}
private static void SaveCache(ModuleInfo moduleInfo, BuildOptions moduleOptions, List<string> headerFiles)
{
var path = GetCachePath(moduleInfo.Module, moduleOptions);
using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
using (var writer = new BinaryWriter(stream, Encoding.UTF8))
{
// Version
writer.Write(4);
writer.Write(File.GetLastWriteTime(Assembly.GetExecutingAssembly().Location).Ticks);
// Build options
writer.Write(moduleOptions.IntermediateFolder);
writer.Write((int)moduleOptions.Platform.Target);
writer.Write((int)moduleOptions.Architecture);
writer.Write((int)moduleOptions.Configuration);
Write(writer, moduleOptions.PublicDefinitions);
Write(writer, moduleOptions.PrivateDefinitions);
Write(writer, moduleOptions.CompileEnv.PreprocessorDefinitions);
// Header files
writer.Write(headerFiles.Count);
for (int i = 0; i < headerFiles.Count; i++)
{
var headerFile = headerFiles[i];
writer.Write(headerFile);
writer.Write(File.GetLastWriteTime(headerFile).Ticks);
}
// Info
moduleInfo.Write(writer);
}
}
private static bool LoadCache(ref ModuleInfo moduleInfo, BuildOptions moduleOptions, List<string> headerFiles)
{
var path = GetCachePath(moduleInfo.Module, moduleOptions);
if (!File.Exists(path))
return false;
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var reader = new BinaryReader(stream, Encoding.UTF8))
{
// Version
var version = reader.ReadInt32();
if (version != 4)
return false;
if (File.GetLastWriteTime(Assembly.GetExecutingAssembly().Location).Ticks != reader.ReadInt64())
return false;
// Build options
if (reader.ReadString() != moduleOptions.IntermediateFolder ||
reader.ReadInt32() != (int)moduleOptions.Platform.Target ||
reader.ReadInt32() != (int)moduleOptions.Architecture ||
reader.ReadInt32() != (int)moduleOptions.Configuration)
return false;
var publicDefinitions = Read(reader, Utilities.GetEmptyArray<string>());
if (publicDefinitions.Length != moduleOptions.PublicDefinitions.Count || publicDefinitions.Any(x => !moduleOptions.PublicDefinitions.Contains(x)))
return false;
var privateDefinitions = Read(reader, Utilities.GetEmptyArray<string>());
if (privateDefinitions.Length != moduleOptions.PrivateDefinitions.Count || privateDefinitions.Any(x => !moduleOptions.PrivateDefinitions.Contains(x)))
return false;
var preprocessorDefinitions = Read(reader, Utilities.GetEmptyArray<string>());
if (preprocessorDefinitions.Length != moduleOptions.CompileEnv.PreprocessorDefinitions.Count || preprocessorDefinitions.Any(x => !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(x)))
return false;
// Header files
var headerFilesCount = reader.ReadInt32();
if (headerFilesCount != headerFiles.Count)
return false;
for (int i = 0; i < headerFilesCount; i++)
{
var headerFile = headerFiles[i];
if (headerFile != reader.ReadString())
return false;
if (File.GetLastWriteTime(headerFile).Ticks > reader.ReadInt64())
return false;
}
// Info
var newModuleInfo = new ModuleInfo
{
Module = moduleInfo.Module,
Name = moduleInfo.Name,
Namespace = moduleInfo.Namespace,
IsFromCache = true,
};
try
{
newModuleInfo.Read(reader);
// Skip parsing and use data loaded from cache
moduleInfo = newModuleInfo;
return true;
}
catch
{
// Skip loading cache
return false;
}
}
}
}
}

View File

@@ -100,7 +100,7 @@ namespace Flax.Build.Bindings
return value;
if (typeInfo.Type == "String")
return $"Variant(StringView({value}))";
if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "ScriptingObjectReference")
if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference")
return $"Variant({value}.Get())";
if (typeInfo.IsArray)
{
@@ -149,7 +149,7 @@ namespace Flax.Build.Bindings
return $"((StringView){value}).GetText()";
if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference")
return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})";
if (typeInfo.Type == "ScriptingObjectReference")
if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference")
return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})";
if (typeInfo.IsArray)
throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'.");
@@ -340,8 +340,8 @@ namespace Flax.Build.Bindings
}
}
// ScriptingObjectReference or AssetReference or WeakAssetReference
if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null)
// ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference
if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null)
{
type = "MonoObject*";
return "{0}.GetManagedInstance()";
@@ -448,8 +448,8 @@ namespace Flax.Build.Bindings
type = "MonoReflectionType*";
return "MUtils::UnboxVariantType({0})";
default:
// ScriptingObjectReference or AssetReference or WeakAssetReference
if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null)
// ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference
if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null)
{
// For non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code)
if (CppNonPodTypesConvertingGeneration)
@@ -832,7 +832,7 @@ namespace Flax.Build.Bindings
contents.AppendLine(" ScriptingTypeHandle managedTypeHandle = object->GetTypeHandle();");
contents.AppendLine(" const ScriptingType* managedTypePtr = &managedTypeHandle.GetType();");
contents.AppendLine(" while (managedTypePtr->Class.Spawn != &ManagedBinaryModule::ManagedObjectSpawn)");
contents.AppendLine(" while (managedTypePtr->Script.Spawn != &ManagedBinaryModule::ManagedObjectSpawn)");
contents.AppendLine(" {");
contents.AppendLine(" managedTypeHandle = managedTypePtr->GetBaseType();");
contents.AppendLine(" managedTypePtr = &managedTypeHandle.GetType();");
@@ -841,7 +841,7 @@ namespace Flax.Build.Bindings
contents.AppendLine(" if (IsDuringWrapperCall)");
contents.AppendLine(" {");
contents.AppendLine(" // Prevent stack overflow by calling native base method");
contents.AppendLine(" const auto scriptVTableBase = managedTypePtr->Class.ScriptVTableBase;");
contents.AppendLine(" const auto scriptVTableBase = managedTypePtr->Script.ScriptVTableBase;");
contents.Append($" return (object->**({functionInfo.UniqueName}_Signature*)&scriptVTableBase[{scriptVTableIndex} + 2])(");
separator = false;
for (var i = 0; i < functionInfo.Parameters.Count; i++)
@@ -854,7 +854,7 @@ namespace Flax.Build.Bindings
}
contents.AppendLine(");");
contents.AppendLine(" }");
contents.AppendLine(" auto scriptVTable = (MMethod**)managedTypePtr->Class.ScriptVTable;");
contents.AppendLine(" auto scriptVTable = (MMethod**)managedTypePtr->Script.ScriptVTable;");
contents.AppendLine($" ASSERT(scriptVTable && scriptVTable[{scriptVTableIndex}]);");
contents.AppendLine($" auto method = scriptVTable[{scriptVTableIndex}];");
contents.AppendLine(" MonoObject* exception = nullptr;");
@@ -961,7 +961,7 @@ namespace Flax.Build.Bindings
var classInfo = typeInfo as ClassInfo;
var structureInfo = typeInfo as StructureInfo;
var baseType = classInfo?.BaseType ?? structureInfo?.BaseType;
if (baseType != null && baseType.Type == "ISerializable")
if (classInfo != null && classInfo.IsBaseTypeHidden)
baseType = null;
CppAutoSerializeFields.Clear();
CppAutoSerializeProperties.Clear();
@@ -1050,6 +1050,25 @@ namespace Flax.Build.Bindings
contents.Append('}').AppendLine();
}
private static string GenerateCppInterfaceInheritanceTable(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, ClassStructInfo typeInfo, string typeNameNative)
{
var interfacesPtr = "nullptr";
var interfaces = typeInfo.Interfaces;
if (interfaces != null)
{
interfacesPtr = typeNameNative + "_Interfaces";
contents.Append("static const ScriptingType::InterfaceImplementation ").Append(interfacesPtr).AppendLine("[] = {");
for (int i = 0; i < interfaces.Count; i++)
{
var interfaceInfo = interfaces[i];
contents.Append(" { &").Append(interfaceInfo.NativeName).Append("::TypeInitializer, (int16)VTABLE_OFFSET(").Append(typeInfo.NativeName).Append(", ").Append(interfaceInfo.NativeName).AppendLine(") },");
}
contents.AppendLine(" { nullptr, 0 },");
contents.AppendLine("};");
}
return interfacesPtr;
}
private static void GenerateCppClass(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, ClassInfo classInfo)
{
var classTypeNameNative = classInfo.FullNameNative;
@@ -1058,6 +1077,7 @@ namespace Flax.Build.Bindings
var classTypeNameInternal = classInfo.NativeName;
if (classInfo.Parent != null && !(classInfo.Parent is FileInfo))
classTypeNameInternal = classInfo.Parent.FullNameNative + '_' + classTypeNameInternal;
var useScripting = classInfo.IsStatic || classInfo.IsScriptingObject;
if (classInfo.IsAutoSerialization)
GenerateCppAutoSerialization(buildData, contents, moduleInfo, classInfo, classTypeNameNative);
@@ -1070,32 +1090,34 @@ namespace Flax.Build.Bindings
// Events
foreach (var eventInfo in classInfo.Events)
{
CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MEvent.h");
if (!useScripting)
continue;
var paramsCount = eventInfo.Type.GenericArgs?.Count ?? 0;
// C# event invoking wrapper (calls C# event from C++ delegate)
CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MEvent.h");
contents.Append(" ");
if (eventInfo.IsStatic)
contents.Append("static ");
contents.AppendFormat("void {0}_ManagedWrapper(", eventInfo.Name);
for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++)
for (var i = 0; i < paramsCount; i++)
{
if (i != 0)
contents.Append(", ");
contents.Append(eventInfo.Type.GenericArgs[i]);
contents.Append(" arg" + i);
contents.Append(eventInfo.Type.GenericArgs[i]).Append(" arg" + i);
}
contents.Append(')').AppendLine();
contents.Append(" {").AppendLine();
contents.Append(" static MMethod* mmethod = nullptr;").AppendLine();
contents.Append(" if (!mmethod)").AppendLine();
contents.AppendFormat(" mmethod = {1}::GetStaticClass()->GetMethod(\"Internal_{0}_Invoke\", {2});", eventInfo.Name, classTypeNameNative, eventInfo.Type.GenericArgs.Count).AppendLine();
contents.AppendFormat(" mmethod = {1}::GetStaticClass()->GetMethod(\"Internal_{0}_Invoke\", {2});", eventInfo.Name, classTypeNameNative, paramsCount).AppendLine();
contents.Append(" CHECK(mmethod);").AppendLine();
contents.Append(" MonoObject* exception = nullptr;").AppendLine();
if (eventInfo.Type.GenericArgs.Count == 0)
if (paramsCount == 0)
contents.AppendLine(" void** params = nullptr;");
else
contents.AppendLine($" void* params[{eventInfo.Type.GenericArgs.Count}];");
for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++)
contents.AppendLine($" void* params[{paramsCount}];");
for (var i = 0; i < paramsCount; i++)
{
var paramType = eventInfo.Type.GenericArgs[i];
var paramName = "arg" + i;
@@ -1118,7 +1140,7 @@ namespace Flax.Build.Bindings
contents.Append("bool bind)").AppendLine();
contents.Append(" {").AppendLine();
contents.Append(" Function<void(");
for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++)
for (var i = 0; i < paramsCount; i++)
{
if (i != 0)
contents.Append(", ");
@@ -1135,11 +1157,63 @@ namespace Flax.Build.Bindings
contents.Append(" else").AppendLine();
contents.AppendFormat(" {0}{1}.Unbind(f);", bindPrefix, eventInfo.Name).AppendLine();
contents.Append(" }").AppendLine().AppendLine();
// Generic scripting event invoking wrapper (calls scripting code from C++ delegate)
CppIncludeFiles.Add("Engine/Scripting/Events.h");
contents.Append(" ");
if (eventInfo.IsStatic)
contents.Append("static ");
contents.AppendFormat("void {0}_Wrapper(", eventInfo.Name);
for (var i = 0; i < paramsCount; i++)
{
if (i != 0)
contents.Append(", ");
contents.Append(eventInfo.Type.GenericArgs[i]).Append(" arg" + i);
}
contents.Append(')').AppendLine();
contents.Append(" {").AppendLine();
if (paramsCount == 0)
contents.AppendLine(" Variant* params = nullptr;");
else
contents.AppendLine($" Variant params[{paramsCount}];");
for (var i = 0; i < paramsCount; i++)
{
var paramType = eventInfo.Type.GenericArgs[i];
var paramName = "arg" + i;
var paramValue = GenerateCppWrapperNativeToVariant(buildData, paramType, classInfo, paramName);
contents.Append($" params[{i}] = {paramValue};").AppendLine();
}
contents.AppendLine($" ScriptingEvents::Event({(eventInfo.IsStatic ? "nullptr" : "(ScriptingObject*)this")}, Span<Variant>(params, {paramsCount}), {classTypeNameNative}::TypeInitializer, StringView(TEXT(\"{eventInfo.Name}\"), {eventInfo.Name.Length}));");
contents.Append(" }").AppendLine().AppendLine();
// Scripting event wrapper binding method (binds/unbinds generic wrapper to C++ delegate)
contents.AppendFormat(" static void {0}_Bind(", eventInfo.Name);
contents.AppendFormat("{0}* obj, void* instance, bool bind)", classTypeNameNative).AppendLine();
contents.Append(" {").AppendLine();
contents.Append(" Function<void(");
for (var i = 0; i < paramsCount; i++)
{
if (i != 0)
contents.Append(", ");
contents.Append(eventInfo.Type.GenericArgs[i]);
}
contents.Append(")> f;").AppendLine();
if (eventInfo.IsStatic)
contents.AppendFormat(" f.Bind<{0}_Wrapper>();", eventInfo.Name).AppendLine();
else
contents.AppendFormat(" f.Bind<{1}Internal, &{1}Internal::{0}_Wrapper>(({1}Internal*)instance);", eventInfo.Name, classTypeNameInternal).AppendLine();
contents.Append(" if (bind)").AppendLine();
contents.AppendFormat(" {0}{1}.Bind(f);", bindPrefix, eventInfo.Name).AppendLine();
contents.Append(" else").AppendLine();
contents.AppendFormat(" {0}{1}.Unbind(f);", bindPrefix, eventInfo.Name).AppendLine();
contents.Append(" }").AppendLine().AppendLine();
}
// Fields
foreach (var fieldInfo in classInfo.Fields)
{
if (!useScripting)
continue;
if (fieldInfo.Getter != null)
GenerateCppWrapperFunction(buildData, contents, classInfo, fieldInfo.Getter, "{0}");
if (fieldInfo.Setter != null)
@@ -1149,6 +1223,8 @@ namespace Flax.Build.Bindings
// Properties
foreach (var propertyInfo in classInfo.Properties)
{
if (!useScripting)
continue;
if (propertyInfo.Getter != null)
GenerateCppWrapperFunction(buildData, contents, classInfo, propertyInfo.Getter);
if (propertyInfo.Setter != null)
@@ -1158,6 +1234,8 @@ namespace Flax.Build.Bindings
// Functions
foreach (var functionInfo in classInfo.Functions)
{
if (!useScripting)
throw new Exception($"Not supported function {functionInfo.Name} inside non-static and non-scripting class type {classInfo.Name}.");
GenerateCppWrapperFunction(buildData, contents, classInfo, functionInfo);
}
@@ -1267,50 +1345,88 @@ namespace Flax.Build.Bindings
// Runtime initialization (internal methods binding)
contents.AppendLine(" static void InitRuntime()");
contents.AppendLine(" {");
foreach (var eventInfo in classInfo.Events)
if (useScripting)
{
contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{eventInfo.Name}_Bind\", &{eventInfo.Name}_ManagedBind);");
}
foreach (var fieldInfo in classInfo.Fields)
{
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.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)
{
contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{functionInfo.UniqueName}\", &{functionInfo.UniqueName});");
foreach (var eventInfo in classInfo.Events)
{
contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{eventInfo.Name}_Bind\", &{eventInfo.Name}_ManagedBind);");
// Register scripting event binder
contents.AppendLine($" ScriptingEvents::EventsTable[Pair<ScriptingTypeHandle, StringView>({classTypeNameNative}::TypeInitializer, StringView(TEXT(\"{eventInfo.Name}\"), {eventInfo.Name.Length}))] = (void(*)(ScriptingObject*, void*, bool)){classTypeNameInternal}Internal::{eventInfo.Name}_Bind;");
}
foreach (var fieldInfo in classInfo.Fields)
{
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.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)
{
contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{functionInfo.UniqueName}\", &{functionInfo.UniqueName});");
}
}
GenerateCppClassInitRuntime?.Invoke(buildData, classInfo, contents);
contents.AppendLine(" }");
contents.Append('}');
contents.Append(';');
contents.AppendLine();
contents.AppendLine(" }").AppendLine();
if (!useScripting && !classInfo.IsAbstract)
{
// Constructor
contents.AppendLine(" static void Ctor(void* ptr)");
contents.AppendLine(" {");
contents.AppendLine($" new(ptr){classTypeNameNative}();");
contents.AppendLine(" }").AppendLine();
// Destructor
contents.AppendLine(" static void Dtor(void* ptr)");
contents.AppendLine(" {");
contents.AppendLine($" (({classTypeNameNative}*)ptr)->~{classInfo.NativeName}();");
contents.AppendLine(" }").AppendLine();
}
contents.Append('}').Append(';').AppendLine();
contents.AppendLine();
// Interfaces
var interfacesTable = GenerateCppInterfaceInheritanceTable(buildData, contents, moduleInfo, classInfo, classTypeNameNative);
// Type initializer
contents.Append($"ScriptingTypeInitializer {classTypeNameNative}::TypeInitializer((BinaryModule*)GetBinaryModule{moduleInfo.Name}(), ");
contents.Append($"StringAnsiView(\"{classTypeNameManaged}\", {classTypeNameManaged.Length}), ");
contents.Append($"sizeof({classTypeNameNative}), ");
contents.Append($"&{classTypeNameInternal}Internal::InitRuntime, ");
if (!classInfo.IsStatic && !classInfo.NoSpawn)
contents.Append($"(ScriptingType::SpawnHandler)&{classTypeNameNative}::Spawn, ");
if (useScripting)
{
if (classInfo.IsStatic || classInfo.NoSpawn)
contents.Append("&ScriptingType::DefaultSpawn, ");
else
contents.Append($"(ScriptingType::SpawnHandler)&{classTypeNameNative}::Spawn, ");
if (classInfo.BaseType != null && useScripting)
contents.Append($"&{classInfo.BaseType}::TypeInitializer, ");
else
contents.Append("nullptr, ");
contents.Append(setupScriptVTable);
}
else
contents.Append("&ScriptingType::DefaultSpawn, ");
if (classInfo.BaseType != null)
contents.Append($"&{classInfo.BaseType}::TypeInitializer, ");
else
contents.Append("nullptr, ");
contents.Append(setupScriptVTable);
{
if (classInfo.IsAbstract)
contents.Append("nullptr, nullptr, ");
else
contents.Append($"&{classTypeNameInternal}Internal::Ctor, &{classTypeNameInternal}Internal::Dtor, ");
if (classInfo.BaseType != null)
contents.Append($"&{classInfo.BaseType}::TypeInitializer");
else
contents.Append("nullptr");
}
contents.Append(", ").Append(interfacesTable);
contents.Append(");");
contents.AppendLine();
@@ -1505,10 +1621,46 @@ namespace Flax.Build.Bindings
}
}
private static bool GenerateCppType(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, object type)
private static void GenerateCppInterface(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, InterfaceInfo interfaceInfo)
{
var interfaceTypeNameNative = interfaceInfo.FullNameNative;
var interfaceTypeNameManaged = interfaceInfo.FullNameManaged;
var interfaceTypeNameManagedInternalCall = interfaceTypeNameManaged.Replace('+', '/');
var interfaceTypeNameInternal = interfaceInfo.NativeName;
if (interfaceInfo.Parent != null && !(interfaceInfo.Parent is FileInfo))
interfaceTypeNameInternal = interfaceInfo.Parent.FullNameNative + '_' + interfaceTypeNameInternal;
contents.AppendLine();
contents.AppendFormat("class {0}Internal", interfaceTypeNameInternal).AppendLine();
contents.Append('{').AppendLine();
contents.AppendLine("public:");
// Runtime initialization (internal methods binding)
contents.AppendLine(" static void InitRuntime()");
contents.AppendLine(" {");
contents.AppendLine(" }").AppendLine();
contents.Append('}').Append(';').AppendLine();
contents.AppendLine();
// Type initializer
contents.Append($"ScriptingTypeInitializer {interfaceTypeNameNative}::TypeInitializer((BinaryModule*)GetBinaryModule{moduleInfo.Name}(), ");
contents.Append($"StringAnsiView(\"{interfaceTypeNameManaged}\", {interfaceTypeNameManaged.Length}), ");
contents.Append($"&{interfaceTypeNameInternal}Internal::InitRuntime");
contents.Append(");");
contents.AppendLine();
// Nested types
foreach (var apiTypeInfo in interfaceInfo.Children)
{
GenerateCppType(buildData, contents, moduleInfo, apiTypeInfo);
}
}
private static void GenerateCppType(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, object type)
{
if (type is ApiTypeInfo apiTypeInfo && apiTypeInfo.IsInBuild)
return false;
return;
try
{
@@ -1516,18 +1668,16 @@ namespace Flax.Build.Bindings
GenerateCppClass(buildData, contents, moduleInfo, classInfo);
else if (type is StructureInfo structureInfo)
GenerateCppStruct(buildData, contents, moduleInfo, structureInfo);
else if (type is InterfaceInfo interfaceInfo)
GenerateCppInterface(buildData, contents, moduleInfo, interfaceInfo);
else if (type is InjectCppCodeInfo injectCppCodeInfo)
contents.AppendLine(injectCppCodeInfo.Code);
else
return false;
}
catch
{
Log.Error($"Failed to generate C++ bindings for {type}.");
throw;
}
return true;
}
private static void GenerateCppCppUsedNonPodTypes(BuildData buildData, ApiTypeInfo apiType)
@@ -1575,21 +1725,16 @@ namespace Flax.Build.Bindings
CppReferencesFiles.Add(fileInfo);
}
}
var headerPos = contents.Length;
foreach (var child in moduleInfo.Children)
{
foreach (var apiTypeInfo in child.Children)
{
if (GenerateCppType(buildData, contents, moduleInfo, apiTypeInfo))
bindings.UseBindings = true;
GenerateCppType(buildData, contents, moduleInfo, apiTypeInfo);
}
}
if (!bindings.UseBindings)
return;
GenerateCppModuleSource?.Invoke(buildData, moduleInfo, contents);
{
@@ -1883,7 +2028,7 @@ namespace Flax.Build.Bindings
var binaryModuleSourcePath = Path.Combine(project.ProjectFolderPath, "Source", binaryModuleName + ".Gen.cpp");
contents.AppendLine("// This code was auto-generated. Do not modify it.");
contents.AppendLine();
contents.AppendLine($"#include \"Engine/Scripting/BinaryModule.h\"");
contents.AppendLine("#include \"Engine/Scripting/BinaryModule.h\"");
contents.AppendLine($"#include \"{binaryModuleName}.Gen.h\"");
contents.AppendLine();
contents.AppendLine($"StaticallyLinkedBinaryModuleInitializer StaticallyLinkedBinaryModule{binaryModuleName}(GetBinaryModule{binaryModuleName});");

View File

@@ -17,6 +17,7 @@ namespace Flax.Build.Bindings
public Stack<ApiTypeInfo> ScopeTypeStack;
public Stack<AccessLevel> ScopeAccessStack;
public Dictionary<string, string> PreprocessorDefines;
public List<string> StringCache;
public ApiTypeInfo ValidScopeInfoFromStack
{
@@ -46,14 +47,12 @@ namespace Flax.Build.Bindings
}
}
private static List<string> _commentCache;
private static string[] ParseComment(ref ParsingContext context)
{
if (_commentCache == null)
_commentCache = new List<string>();
if (context.StringCache == null)
context.StringCache = new List<string>();
else
_commentCache.Clear();
context.StringCache.Clear();
int tokensCount = 0;
bool isValid = true;
@@ -77,7 +76,7 @@ namespace Flax.Build.Bindings
if (commentLine.StartsWith("// "))
commentLine = "/// " + commentLine.Substring(3);
_commentCache.Insert(0, commentLine);
context.StringCache.Insert(0, commentLine);
break;
}
default:
@@ -90,14 +89,14 @@ namespace Flax.Build.Bindings
for (var i = 0; i < tokensCount; i++)
context.Tokenizer.NextToken(true, true);
if (_commentCache.Count == 1)
if (context.StringCache.Count == 1)
{
// Ensure to have summary begin/end pair
_commentCache.Insert(0, "/// <summary>");
_commentCache.Add("/// </summary>");
context.StringCache.Insert(0, "/// <summary>");
context.StringCache.Add("/// </summary>");
}
return _commentCache.ToArray();
return context.StringCache.ToArray();
}
private struct TagParameter
@@ -383,17 +382,101 @@ namespace Flax.Build.Bindings
return name;
}
private static void ParseInheritance(ref ParsingContext context, ClassStructInfo desc, out bool isFinal)
{
desc.BaseType = null;
desc.BaseTypeInheritance = AccessLevel.Private;
var token = context.Tokenizer.NextToken();
isFinal = token.Value == "final";
if (isFinal)
token = context.Tokenizer.NextToken();
if (token.Type == TokenType.Colon)
{
while (token.Type != TokenType.LeftCurlyBrace)
{
var accessToken = context.Tokenizer.ExpectToken(TokenType.Identifier);
switch (accessToken.Value)
{
case "public":
desc.BaseTypeInheritance = AccessLevel.Public;
token = context.Tokenizer.ExpectToken(TokenType.Identifier);
break;
case "protected":
desc.BaseTypeInheritance = AccessLevel.Protected;
token = context.Tokenizer.ExpectToken(TokenType.Identifier);
break;
case "private":
token = context.Tokenizer.ExpectToken(TokenType.Identifier);
break;
default:
token = accessToken;
break;
}
var baseTypeInfo = new TypeInfo
{
Type = token.Value,
};
if (token.Value.Length > 2 && token.Value[0] == 'I' && char.IsUpper(token.Value[1]))
{
// Interface
if (desc.InterfaceNames == null)
desc.InterfaceNames = new List<TypeInfo>();
desc.InterfaceNames.Add(baseTypeInfo);
token = context.Tokenizer.NextToken();
continue;
}
if (desc.BaseType != null)
{
// Allow for multiple base classes, just the first one needs to be a valid base type
break;
throw new Exception($"Invalid '{desc.Name}' inheritance (only single base class is allowed for scripting types, excluding interfaces).");
}
desc.BaseType = baseTypeInfo;
token = context.Tokenizer.NextToken();
if (token.Type == TokenType.LeftCurlyBrace)
{
break;
}
if (token.Type == TokenType.LeftAngleBracket)
{
var genericType = context.Tokenizer.ExpectToken(TokenType.Identifier);
token = context.Tokenizer.ExpectToken(TokenType.RightAngleBracket);
desc.BaseType.GenericArgs = new List<TypeInfo>
{
new TypeInfo
{
Type = genericType.Value,
}
};
// TODO: find better way to resolve this (custom base type attribute?)
if (desc.BaseType.Type == "ShaderAssetTypeBase")
{
desc.BaseType = desc.BaseType.GenericArgs[0];
}
token = context.Tokenizer.NextToken();
}
}
token = context.Tokenizer.PreviousToken();
}
else
{
// No base type
token = context.Tokenizer.PreviousToken();
}
}
private static ClassInfo ParseClass(ref ParsingContext context)
{
var desc = new ClassInfo
{
Children = new List<ApiTypeInfo>(),
Access = context.CurrentAccessLevel,
BaseTypeInheritance = AccessLevel.Private,
Functions = new List<FunctionInfo>(),
Properties = new List<PropertyInfo>(),
Fields = new List<FieldInfo>(),
Events = new List<EventInfo>(),
};
// Read the documentation comment
@@ -410,64 +493,8 @@ namespace Flax.Build.Bindings
// Read name
desc.Name = desc.NativeName = ParseName(ref context);
// Read class inheritance
token = context.Tokenizer.NextToken();
var isFinal = token.Value == "final";
if (isFinal)
token = context.Tokenizer.NextToken();
if (token.Type == TokenType.Colon)
{
// Current class does have inheritance defined
var accessToken = context.Tokenizer.ExpectToken(TokenType.Identifier);
switch (accessToken.Value)
{
case "public":
desc.BaseTypeInheritance = AccessLevel.Public;
token = context.Tokenizer.ExpectToken(TokenType.Identifier);
break;
case "protected":
desc.BaseTypeInheritance = AccessLevel.Protected;
token = context.Tokenizer.ExpectToken(TokenType.Identifier);
break;
case "private":
token = context.Tokenizer.ExpectToken(TokenType.Identifier);
break;
}
desc.BaseType = new TypeInfo
{
Type = token.Value,
};
token = context.Tokenizer.NextToken();
if (token.Type == TokenType.LeftAngleBracket)
{
var genericType = context.Tokenizer.ExpectToken(TokenType.Identifier);
context.Tokenizer.ExpectToken(TokenType.RightAngleBracket);
desc.BaseType.GenericArgs = new List<TypeInfo>
{
new TypeInfo
{
Type = genericType.Value,
}
};
// TODO: find better way to resolve this (custom base type attribute?)
if (desc.BaseType.Type == "ShaderAssetTypeBase")
{
desc.BaseType = desc.BaseType.GenericArgs[0];
}
}
else
{
token = context.Tokenizer.PreviousToken();
}
}
else
{
// No base type
token = context.Tokenizer.PreviousToken();
desc.BaseType = null;
}
// Read inheritance
ParseInheritance(ref context, desc, out var isFinal);
// Process tag parameters
foreach (var tag in tagParams)
@@ -523,12 +550,72 @@ namespace Flax.Build.Bindings
return desc;
}
private static InterfaceInfo ParseInterface(ref ParsingContext context)
{
var desc = new InterfaceInfo
{
Access = context.CurrentAccessLevel,
};
// Read the documentation comment
desc.Comment = ParseComment(ref context);
// Read parameters from the tag
var tagParams = ParseTagParameters(ref context);
// Read 'class' keyword
var token = context.Tokenizer.NextToken();
if (token.Value != "class")
throw new Exception($"Invalid API_INTERFACE usage (expected 'class' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}').");
// Read name
desc.Name = desc.NativeName = ParseName(ref context);
if (desc.Name.Length < 2 || desc.Name[0] != 'I' || !char.IsUpper(desc.Name[1]))
throw new Exception($"Invalid API_INTERFACE name '{desc.Name}' (it must start with 'I' character followed by the uppercase character).");
// Read inheritance
ParseInheritance(ref context, desc, out _);
// Process tag parameters
foreach (var tag in tagParams)
{
switch (tag.Tag.ToLower())
{
case "public":
desc.Access = AccessLevel.Public;
break;
case "protected":
desc.Access = AccessLevel.Protected;
break;
case "private":
desc.Access = AccessLevel.Private;
break;
case "inbuild":
desc.IsInBuild = true;
break;
case "attributes":
desc.Attributes = tag.Value;
break;
case "name":
desc.Name = tag.Value;
break;
case "namespace":
desc.Namespace = tag.Value;
break;
default:
Log.Warning($"Unknown or not supported tag parameter {tag} used on interface {desc.Name} at line {context.Tokenizer.CurrentLine}");
break;
}
}
return desc;
}
private static FunctionInfo ParseFunction(ref ParsingContext context)
{
var desc = new FunctionInfo
{
Access = context.CurrentAccessLevel,
Parameters = new List<FunctionInfo.ParameterInfo>(),
};
// Read the documentation comment
@@ -691,6 +778,35 @@ namespace Flax.Build.Bindings
else
propertyInfo.Setter = functionInfo;
if (propertyInfo.Getter != null && propertyInfo.Setter != null)
{
// Check if getter and setter types are matching (const and ref specifiers are skipped)
var getterType = propertyInfo.Getter.ReturnType;
var setterType = propertyInfo.Setter.Parameters[0].Type;
if (!string.Equals(getterType.Type, setterType.Type) ||
getterType.IsPtr != setterType.IsPtr ||
getterType.IsArray != setterType.IsArray ||
getterType.IsBitField != setterType.IsBitField ||
getterType.ArraySize != setterType.ArraySize ||
getterType.BitSize != setterType.BitSize ||
!TypeInfo.Equals(getterType.GenericArgs, setterType.GenericArgs))
{
// Skip compatible types
if (getterType.Type == "String" && setterType.Type == "StringView")
return propertyInfo;
if (getterType.Type == "Array" && setterType.Type == "Span" && getterType.GenericArgs?.Count == 1 && setterType.GenericArgs?.Count == 1 && getterType.GenericArgs[0].Equals(setterType.GenericArgs[0]))
return propertyInfo;
throw new Exception($"Property {propertyName} in class {classInfo.Name} (line {context.Tokenizer.CurrentLine}) has mismatching getter return type ({getterType}) and setter parameter type ({setterType}). Both getter and setter methods must use the same value type used for property.");
}
// Fix documentation comment to reflect both getter and setters available
for (var i = 0; i < propertyInfo.Comment.Length; i++)
{
ref var comment = ref propertyInfo.Comment[i];
comment = comment.Replace("/// Gets ", "/// Gets or sets ");
}
}
return propertyInfo;
}
@@ -698,9 +814,7 @@ namespace Flax.Build.Bindings
{
var desc = new EnumInfo
{
Children = new List<ApiTypeInfo>(),
Access = context.CurrentAccessLevel,
Entries = new List<EnumInfo.EntryInfo>(),
};
// Read the documentation comment
@@ -855,10 +969,7 @@ namespace Flax.Build.Bindings
{
var desc = new StructureInfo
{
Children = new List<ApiTypeInfo>(),
Access = context.CurrentAccessLevel,
Fields = new List<FieldInfo>(),
Functions = new List<FunctionInfo>(),
};
// Read the documentation comment
@@ -875,55 +986,8 @@ namespace Flax.Build.Bindings
// Read name
desc.Name = desc.NativeName = ParseName(ref context);
// Read structure inheritance
token = context.Tokenizer.NextToken();
if (token.Type == TokenType.Colon)
{
// Current class does have inheritance defined
var accessToken = context.Tokenizer.ExpectToken(TokenType.Identifier);
switch (accessToken.Value)
{
case "public":
token = context.Tokenizer.ExpectToken(TokenType.Identifier);
break;
case "protected":
token = context.Tokenizer.ExpectToken(TokenType.Identifier);
break;
case "private":
token = context.Tokenizer.ExpectToken(TokenType.Identifier);
break;
default:
token = accessToken;
break;
}
desc.BaseType = new TypeInfo
{
Type = token.Value,
};
token = context.Tokenizer.NextToken();
if (token.Type == TokenType.LeftAngleBracket)
{
var genericType = context.Tokenizer.ExpectToken(TokenType.Identifier);
context.Tokenizer.ExpectToken(TokenType.RightAngleBracket);
desc.BaseType.GenericArgs = new List<TypeInfo>
{
new TypeInfo
{
Type = genericType.Value,
}
};
}
else
{
token = context.Tokenizer.PreviousToken();
}
}
else
{
// No base type
token = context.Tokenizer.PreviousToken();
}
// Read inheritance
ParseInheritance(ref context, desc, out _);
// Process tag parameters
foreach (var tag in tagParams)
@@ -974,11 +1038,30 @@ namespace Flax.Build.Bindings
// Read parameters from the tag
var tagParams = ParseTagParameters(ref context);
context.Tokenizer.SkipUntil(TokenType.Identifier);
// Read 'static' keyword
desc.IsStatic = context.Tokenizer.NextToken().Value == "static";
if (!desc.IsStatic)
context.Tokenizer.PreviousToken();
// Read 'static' or 'mutable'
Token token;
var isMutable = false;
while (true)
{
token = context.Tokenizer.CurrentToken;
if (!desc.IsStatic && token.Value == "static")
{
desc.IsStatic = true;
context.Tokenizer.NextToken();
}
else if (!isMutable && token.Value == "mutable")
{
isMutable = true;
context.Tokenizer.NextToken();
}
else
{
context.Tokenizer.PreviousToken();
break;
}
}
// Read type
desc.Type = ParseType(ref context);
@@ -987,11 +1070,10 @@ namespace Flax.Build.Bindings
desc.Name = ParseName(ref context);
// Read ';' or default value or array size or bit-field size
var token = context.Tokenizer.ExpectAnyTokens(new[] { TokenType.SemiColon, TokenType.Equal, TokenType.LeftBracket, TokenType.Colon });
token = context.Tokenizer.ExpectAnyTokens(new[] { TokenType.SemiColon, TokenType.Equal, TokenType.LeftBracket, TokenType.Colon });
if (token.Type == TokenType.Equal)
{
//context.Tokenizer.SkipUntil(TokenType.SemiColon, out var defaultValue);
context.Tokenizer.SkipUntil(TokenType.SemiColon);
context.Tokenizer.SkipUntil(TokenType.SemiColon, out desc.DefaultValue, false);
}
else if (token.Type == TokenType.LeftBracket)
{
@@ -1106,7 +1188,6 @@ namespace Flax.Build.Bindings
context.Tokenizer.ExpectToken(TokenType.LeftParent);
var desc = new InjectCppCodeInfo
{
Children = new List<ApiTypeInfo>(),
Code = context.Tokenizer.ExpectToken(TokenType.String).Value.Replace("\\\"", "\""),
};
desc.Code = desc.Code.Substring(1, desc.Code.Length - 2);

View File

@@ -4,6 +4,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Flax.Build.NativeCpp;
using BuildData = Flax.Build.Builder.BuildData;
@@ -58,16 +60,12 @@ namespace Flax.Build.Bindings
private static ModuleInfo ParseModuleInner(BuildData buildData, Module module, BuildOptions moduleOptions = null)
{
if (buildData.ModulesInfo.TryGetValue(module, out var moduleInfo))
return moduleInfo;
// Setup bindings module info descriptor
moduleInfo = new ModuleInfo
var moduleInfo = new ModuleInfo
{
Module = module,
Name = module.BinaryModuleName,
Namespace = string.Empty,
Children = new List<ApiTypeInfo>(),
};
if (string.IsNullOrEmpty(moduleInfo.Name))
throw new Exception("Module name cannot be empty.");
@@ -81,320 +79,375 @@ namespace Flax.Build.Bindings
throw new Exception($"Cannot parse module {module.Name} without options.");
// Collect all header files that can have public symbols for API
var headerFiles = moduleOptions.SourceFiles.Where(x => x.EndsWith(".h")).ToList();
// Skip if no header files to process
var headerFiles = new List<string>(moduleOptions.SourceFiles.Count / 2);
for (int i = 0; i < moduleOptions.SourceFiles.Count; i++)
{
if (moduleOptions.SourceFiles[i].EndsWith(".h", StringComparison.OrdinalIgnoreCase))
headerFiles.Add(moduleOptions.SourceFiles[i]);
}
if (headerFiles.Count == 0)
return moduleInfo;
// Find and load files with API tags
string[] headerFilesContents = null;
//using (new ProfileEventScope("LoadHeaderFiles"))
if (module.Name == "Core")
{
var anyApi = false;
// Special case for Core module to ignore API tags defines
for (int i = 0; i < headerFiles.Count; i++)
{
// Skip scripting types definitions file
if (headerFiles[i].Replace('\\', '/').EndsWith("Engine/Core/Config.h", StringComparison.Ordinal) ||
headerFiles[i].EndsWith("EditorContextAPI.h", StringComparison.Ordinal))
continue;
// Check if file contains any valid API tag
var contents = File.ReadAllText(headerFiles[i]);
for (int j = 0; j < ApiTokens.SearchTags.Length; j++)
if (headerFiles[i].EndsWith("Config.h", StringComparison.Ordinal))
{
if (contents.Contains(ApiTokens.SearchTags[j]))
{
if (headerFilesContents == null)
headerFilesContents = new string[headerFiles.Count];
headerFilesContents[i] = contents;
anyApi = true;
break;
}
}
}
if (!anyApi)
return moduleInfo;
}
// Skip if none of the headers was modified after last time generated C++ file was edited
// TODO: skip parsing if module has API cache file -> then load it from disk
/*if (!forceGenerate)
{
var lastGenerateTime = File.GetLastWriteTime(bindings.GeneratedCppFilePath);
var anyModified = false;
for (int i = 0; i < headerFiles.Count; i++)
{
if (File.GetLastWriteTime(headerFiles[i]) > lastGenerateTime)
{
anyModified = true;
headerFiles.RemoveAt(i);
break;
}
}
}
if (!anyModified)
return;
}*/
// Sort file paths to have stable results
headerFiles.Sort();
Log.Verbose($"Parsing API bindings for {module.Name} ({moduleInfo.Name})");
// Process all header files to generate the module API code reflection
var context = new ParsingContext
// Load cache
using (new ProfileEventScope("LoadCache"))
{
CurrentAccessLevel = AccessLevel.Public,
ScopeTypeStack = new Stack<ApiTypeInfo>(),
ScopeAccessStack = new Stack<AccessLevel>(),
PreprocessorDefines = new Dictionary<string, string>(),
};
for (int i = 0; i < headerFiles.Count; i++)
{
if (headerFilesContents[i] == null)
continue;
var fileInfo = new FileInfo
if (LoadCache(ref moduleInfo, moduleOptions, headerFiles))
{
Parent = null,
Children = new List<ApiTypeInfo>(),
Name = headerFiles[i],
Namespace = moduleInfo.Name,
};
moduleInfo.AddChild(fileInfo);
buildData.ModulesInfo[module] = moduleInfo;
try
{
// Tokenize the source
var tokenizer = new Tokenizer();
tokenizer.Tokenize(headerFilesContents[i]);
// Init the context
context.Tokenizer = tokenizer;
context.File = fileInfo;
context.ScopeInfo = null;
context.ScopeTypeStack.Clear();
context.ScopeAccessStack.Clear();
context.PreprocessorDefines.Clear();
context.EnterScope(fileInfo);
// Process the source code
ApiTypeInfo scopeType = null;
Token prevToken = null;
while (true)
// Initialize API
using (new ProfileEventScope("Init"))
{
// Move to the next token
var token = tokenizer.NextToken();
if (token == null)
continue;
if (token.Type == TokenType.EndOfFile)
break;
// Parse API_.. tags in source code
if (token.Type == TokenType.Identifier && token.Value.StartsWith("API_", StringComparison.Ordinal))
{
if (string.Equals(token.Value, ApiTokens.Class, StringComparison.Ordinal))
{
if (!(context.ScopeInfo is FileInfo))
throw new NotImplementedException("TODO: add support for nested classes in scripting API");
var classInfo = ParseClass(ref context);
scopeType = classInfo;
context.ScopeInfo.AddChild(scopeType);
context.CurrentAccessLevel = AccessLevel.Public;
}
else if (string.Equals(token.Value, ApiTokens.Property, StringComparison.Ordinal))
{
var propertyInfo = ParseProperty(ref context);
}
else if (string.Equals(token.Value, ApiTokens.Function, StringComparison.Ordinal))
{
var functionInfo = ParseFunction(ref context);
if (context.ScopeInfo is ClassInfo classInfo)
classInfo.Functions.Add(functionInfo);
else if (context.ScopeInfo is StructureInfo structureInfo)
structureInfo.Functions.Add(functionInfo);
else
throw new Exception($"Not supported free-function {functionInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it.");
}
else if (string.Equals(token.Value, ApiTokens.Enum, StringComparison.Ordinal))
{
var enumInfo = ParseEnum(ref context);
context.ScopeInfo.AddChild(enumInfo);
}
else if (string.Equals(token.Value, ApiTokens.Struct, StringComparison.Ordinal))
{
var structureInfo = ParseStructure(ref context);
scopeType = structureInfo;
context.ScopeInfo.AddChild(scopeType);
context.CurrentAccessLevel = AccessLevel.Public;
}
else if (string.Equals(token.Value, ApiTokens.Field, StringComparison.Ordinal))
{
var fieldInfo = ParseField(ref context);
var scopeInfo = context.ValidScopeInfoFromStack;
if (scopeInfo is ClassInfo classInfo)
classInfo.Fields.Add(fieldInfo);
else if (scopeInfo is StructureInfo structureInfo)
structureInfo.Fields.Add(fieldInfo);
else
throw new Exception($"Not supported location for field {fieldInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class or structure to use API bindings for it.");
}
else if (string.Equals(token.Value, ApiTokens.Event, StringComparison.Ordinal))
{
var eventInfo = ParseEvent(ref context);
var scopeInfo = context.ValidScopeInfoFromStack;
if (scopeInfo is ClassInfo classInfo)
classInfo.Events.Add(eventInfo);
else
throw new Exception($"Not supported location for event {eventInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it.");
}
else if (string.Equals(token.Value, ApiTokens.InjectCppCode, StringComparison.Ordinal))
{
var injectCppCodeInfo = ParseInjectCppCode(ref context);
fileInfo.AddChild(injectCppCodeInfo);
}
else if (string.Equals(token.Value, ApiTokens.AutoSerialization, StringComparison.Ordinal))
{
if (context.ScopeInfo is ClassInfo classInfo)
classInfo.IsAutoSerialization = true;
else if (context.ScopeInfo is StructureInfo structureInfo)
structureInfo.IsAutoSerialization = true;
else
throw new Exception($"Not supported location for {ApiTokens.AutoSerialization} at line {tokenizer.CurrentLine}. Place it in the class or structure that uses API bindings.");
}
}
// Track access level inside class
if (context.ScopeInfo != null && token.Type == TokenType.Colon && prevToken != null && prevToken.Type == TokenType.Identifier)
{
if (string.Equals(prevToken.Value, "public", StringComparison.Ordinal))
{
context.CurrentAccessLevel = AccessLevel.Public;
}
else if (string.Equals(prevToken.Value, "protected", StringComparison.Ordinal))
{
context.CurrentAccessLevel = AccessLevel.Protected;
}
else if (string.Equals(prevToken.Value, "private", StringComparison.Ordinal))
{
context.CurrentAccessLevel = AccessLevel.Private;
}
}
// Handle preprocessor blocks
if (token.Type == TokenType.Preprocessor)
{
token = tokenizer.NextToken();
switch (token.Value)
{
case "define":
{
token = tokenizer.NextToken();
var name = token.Value;
var value = string.Empty;
token = tokenizer.NextToken(true);
while (token.Type != TokenType.Newline)
{
value += token.Value;
token = tokenizer.NextToken(true);
}
value = value.Trim();
context.PreprocessorDefines[name] = value;
break;
}
case "if":
{
// Parse condition
var condition = string.Empty;
token = tokenizer.NextToken(true);
while (token.Type != TokenType.Newline)
{
condition += token.Value;
token = tokenizer.NextToken(true);
}
// Replace contents with defines
condition = condition.Trim();
condition = ReplacePreProcessorDefines(condition, context.PreprocessorDefines.Keys);
condition = ReplacePreProcessorDefines(condition, moduleOptions.PublicDefinitions);
condition = ReplacePreProcessorDefines(condition, moduleOptions.PrivateDefinitions);
condition = ReplacePreProcessorDefines(condition, moduleOptions.CompileEnv.PreprocessorDefinitions);
condition = condition.Replace("false", "0");
condition = condition.Replace("true", "1");
// Check condition
// TODO: support expressions in preprocessor defines in API headers?
if (condition != "1")
{
// Skip chunk of code
ParsePreprocessorIf(fileInfo, tokenizer, ref token);
}
break;
}
case "ifdef":
{
// Parse condition
var define = string.Empty;
token = tokenizer.NextToken(true);
while (token.Type != TokenType.Newline)
{
define += token.Value;
token = tokenizer.NextToken(true);
}
// Check condition
define = define.Trim();
if (!context.PreprocessorDefines.ContainsKey(define) && !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(define))
{
ParsePreprocessorIf(fileInfo, tokenizer, ref token);
}
break;
}
}
}
// Scope tracking
if (token.Type == TokenType.LeftCurlyBrace)
{
context.ScopeTypeStack.Push(scopeType);
context.ScopeInfo = context.ScopeTypeStack.Peek();
scopeType = null;
}
else if (token.Type == TokenType.RightCurlyBrace)
{
context.ScopeTypeStack.Pop();
if (context.ScopeTypeStack.Count == 0)
throw new Exception($"Mismatch of the {{}} braces pair in file '{fileInfo.Name}' at line {tokenizer.CurrentLine}.");
context.ScopeInfo = context.ScopeTypeStack.Peek();
if (context.ScopeInfo is FileInfo)
context.CurrentAccessLevel = AccessLevel.Public;
}
prevToken = token;
moduleInfo.Init(buildData);
}
}
catch (Exception ex)
{
Log.Error($"Failed to parse '{fileInfo.Name}' file to generate bindings.");
Log.Exception(ex);
throw;
return moduleInfo;
}
}
// Parse bindings
Log.Verbose($"Parsing API bindings for {module.Name} ({moduleInfo.Name})");
int concurrency = Math.Min(Math.Max(1, (int)(Environment.ProcessorCount * Configuration.ConcurrencyProcessorScale)), Configuration.MaxConcurrency);
concurrency = 1; // Disable concurrency for parsing (the gain is unnoticeable or even worse in some cases)
if (concurrency == 1 || headerFiles.Count < 2 * concurrency)
{
// Single-threaded
for (int i = 0; i < headerFiles.Count; i++)
{
using (new ProfileEventScope(Path.GetFileName(headerFiles[i])))
{
ParseModuleInnerAsync(moduleInfo, moduleOptions, headerFiles, i);
}
}
}
else
{
// Multi-threaded
ThreadPool.GetMinThreads(out var workerThreads, out var completionPortThreads);
if (workerThreads != concurrency)
ThreadPool.SetMaxThreads(concurrency, completionPortThreads);
Parallel.For(0, headerFiles.Count, (i, state) =>
{
using (new ProfileEventScope(Path.GetFileName(headerFiles[i])))
{
ParseModuleInnerAsync(moduleInfo, moduleOptions, headerFiles, i);
}
});
}
// Save cache
using (new ProfileEventScope("SaveCache"))
{
SaveCache(moduleInfo, moduleOptions, headerFiles);
}
// Initialize API
moduleInfo.Init(buildData);
using (new ProfileEventScope("Init"))
{
moduleInfo.Init(buildData);
}
return moduleInfo;
}
private static void ParseModuleInnerAsync(ModuleInfo moduleInfo, BuildOptions moduleOptions, List<string> headerFiles, int workIndex)
{
// Find and load files with API tags
bool hasApi = false;
string headerFileContents = File.ReadAllText(headerFiles[workIndex]);
for (int j = 0; j < ApiTokens.SearchTags.Length; j++)
{
if (headerFileContents.Contains(ApiTokens.SearchTags[j]))
{
hasApi = true;
break;
}
}
if (!hasApi)
return;
// Process header file to generate the module API code reflection
var fileInfo = new FileInfo
{
Parent = null,
Name = headerFiles[workIndex],
Namespace = moduleInfo.Name,
};
lock (moduleInfo)
{
moduleInfo.AddChild(fileInfo);
}
try
{
// Tokenize the source
var tokenizer = new Tokenizer();
tokenizer.Tokenize(headerFileContents);
// Init the context
var context = new ParsingContext
{
File = fileInfo,
Tokenizer = tokenizer,
ScopeInfo = null,
CurrentAccessLevel = AccessLevel.Public,
ScopeTypeStack = new Stack<ApiTypeInfo>(),
ScopeAccessStack = new Stack<AccessLevel>(),
PreprocessorDefines = new Dictionary<string, string>(),
};
context.EnterScope(fileInfo);
// Process the source code
ApiTypeInfo scopeType = null;
Token prevToken = null;
while (true)
{
// Move to the next token
var token = tokenizer.NextToken();
if (token == null)
continue;
if (token.Type == TokenType.EndOfFile)
break;
// Parse API_.. tags in source code
if (token.Type == TokenType.Identifier && token.Value.StartsWith("API_", StringComparison.Ordinal))
{
if (string.Equals(token.Value, ApiTokens.Class, StringComparison.Ordinal))
{
if (!(context.ScopeInfo is FileInfo))
throw new NotImplementedException("TODO: add support for nested classes in scripting API");
var classInfo = ParseClass(ref context);
scopeType = classInfo;
context.ScopeInfo.AddChild(scopeType);
context.CurrentAccessLevel = AccessLevel.Public;
}
else if (string.Equals(token.Value, ApiTokens.Property, StringComparison.Ordinal))
{
var propertyInfo = ParseProperty(ref context);
}
else if (string.Equals(token.Value, ApiTokens.Function, StringComparison.Ordinal))
{
var functionInfo = ParseFunction(ref context);
if (context.ScopeInfo is ClassInfo classInfo)
classInfo.Functions.Add(functionInfo);
else if (context.ScopeInfo is StructureInfo structureInfo)
structureInfo.Functions.Add(functionInfo);
else
throw new Exception($"Not supported free-function {functionInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it.");
}
else if (string.Equals(token.Value, ApiTokens.Enum, StringComparison.Ordinal))
{
var enumInfo = ParseEnum(ref context);
context.ScopeInfo.AddChild(enumInfo);
}
else if (string.Equals(token.Value, ApiTokens.Struct, StringComparison.Ordinal))
{
var structureInfo = ParseStructure(ref context);
scopeType = structureInfo;
context.ScopeInfo.AddChild(scopeType);
context.CurrentAccessLevel = AccessLevel.Public;
}
else if (string.Equals(token.Value, ApiTokens.Field, StringComparison.Ordinal))
{
var fieldInfo = ParseField(ref context);
var scopeInfo = context.ValidScopeInfoFromStack;
if (scopeInfo is ClassInfo classInfo)
classInfo.Fields.Add(fieldInfo);
else if (scopeInfo is StructureInfo structureInfo)
structureInfo.Fields.Add(fieldInfo);
else
throw new Exception($"Not supported location for field {fieldInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class or structure to use API bindings for it.");
}
else if (string.Equals(token.Value, ApiTokens.Event, StringComparison.Ordinal))
{
var eventInfo = ParseEvent(ref context);
var scopeInfo = context.ValidScopeInfoFromStack;
if (scopeInfo is ClassInfo classInfo)
classInfo.Events.Add(eventInfo);
else
throw new Exception($"Not supported location for event {eventInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it.");
}
else if (string.Equals(token.Value, ApiTokens.InjectCppCode, StringComparison.Ordinal))
{
var injectCppCodeInfo = ParseInjectCppCode(ref context);
fileInfo.AddChild(injectCppCodeInfo);
}
else if (string.Equals(token.Value, ApiTokens.Interface, StringComparison.Ordinal))
{
if (!(context.ScopeInfo is FileInfo))
throw new NotImplementedException("TODO: add support for nested interfaces in scripting API");
var interfaceInfo = ParseInterface(ref context);
scopeType = interfaceInfo;
context.ScopeInfo.AddChild(scopeType);
context.CurrentAccessLevel = AccessLevel.Public;
}
else if (string.Equals(token.Value, ApiTokens.AutoSerialization, StringComparison.Ordinal))
{
if (context.ScopeInfo is ClassInfo classInfo)
classInfo.IsAutoSerialization = true;
else if (context.ScopeInfo is StructureInfo structureInfo)
structureInfo.IsAutoSerialization = true;
else
throw new Exception($"Not supported location for {ApiTokens.AutoSerialization} at line {tokenizer.CurrentLine}. Place it in the class or structure that uses API bindings.");
}
}
// Track access level inside class
if (context.ScopeInfo != null && token.Type == TokenType.Colon && prevToken != null && prevToken.Type == TokenType.Identifier)
{
if (string.Equals(prevToken.Value, "public", StringComparison.Ordinal))
{
context.CurrentAccessLevel = AccessLevel.Public;
}
else if (string.Equals(prevToken.Value, "protected", StringComparison.Ordinal))
{
context.CurrentAccessLevel = AccessLevel.Protected;
}
else if (string.Equals(prevToken.Value, "private", StringComparison.Ordinal))
{
context.CurrentAccessLevel = AccessLevel.Private;
}
}
// Handle preprocessor blocks
if (token.Type == TokenType.Preprocessor)
{
token = tokenizer.NextToken();
switch (token.Value)
{
case "define":
{
token = tokenizer.NextToken();
var name = token.Value;
var value = string.Empty;
token = tokenizer.NextToken(true);
while (token.Type != TokenType.Newline)
{
value += token.Value;
token = tokenizer.NextToken(true);
}
value = value.Trim();
context.PreprocessorDefines[name] = value;
break;
}
case "if":
{
// Parse condition
var condition = string.Empty;
token = tokenizer.NextToken(true);
while (token.Type != TokenType.Newline)
{
var tokenValue = token.Value.Trim();
if (tokenValue.Length == 0)
{
token = tokenizer.NextToken(true);
continue;
}
// Very simple defines processing
tokenValue = ReplacePreProcessorDefines(tokenValue, context.PreprocessorDefines.Keys);
tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PublicDefinitions);
tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PrivateDefinitions);
tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.CompileEnv.PreprocessorDefinitions);
tokenValue = tokenValue.Replace("false", "0");
tokenValue = tokenValue.Replace("true", "1");
tokenValue = tokenValue.Replace("||", "|");
if (tokenValue.Length != 0 && tokenValue != "1" && tokenValue != "0" && tokenValue != "|")
tokenValue = "0";
condition += tokenValue;
token = tokenizer.NextToken(true);
}
// Filter condition
condition = condition.Replace("1|1", "1");
condition = condition.Replace("1|0", "1");
condition = condition.Replace("0|1", "1");
// Skip chunk of code of condition fails
if (condition != "1")
{
ParsePreprocessorIf(fileInfo, tokenizer, ref token);
}
break;
}
case "ifdef":
{
// Parse condition
var define = string.Empty;
token = tokenizer.NextToken(true);
while (token.Type != TokenType.Newline)
{
define += token.Value;
token = tokenizer.NextToken(true);
}
// Check condition
define = define.Trim();
if (!context.PreprocessorDefines.ContainsKey(define) && !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(define))
{
ParsePreprocessorIf(fileInfo, tokenizer, ref token);
}
break;
}
}
}
// Scope tracking
if (token.Type == TokenType.LeftCurlyBrace)
{
context.ScopeTypeStack.Push(scopeType);
context.ScopeInfo = context.ScopeTypeStack.Peek();
scopeType = null;
}
else if (token.Type == TokenType.RightCurlyBrace)
{
context.ScopeTypeStack.Pop();
if (context.ScopeTypeStack.Count == 0)
throw new Exception($"Mismatch of the {{}} braces pair in file '{fileInfo.Name}' at line {tokenizer.CurrentLine}.");
context.ScopeInfo = context.ScopeTypeStack.Peek();
if (context.ScopeInfo is FileInfo)
context.CurrentAccessLevel = AccessLevel.Public;
}
prevToken = token;
}
}
catch (Exception ex)
{
Log.Error($"Failed to parse '{fileInfo.Name}' file to generate bindings.");
Log.Exception(ex);
throw;
}
}
private static string ReplacePreProcessorDefines(string text, IEnumerable<string> defines)
{
foreach (var define in defines)
{
if (text.Contains(define))
if (string.Equals(text, define, StringComparison.Ordinal))
text = text.Replace(define, "1");
}
return text;
@@ -426,41 +479,82 @@ namespace Flax.Build.Bindings
throw new Exception($"Invalid #if/endif pairing in file '{fileInfo.Name}'. Failed to generate API bindings for it.");
}
private static bool UseBindings(object type)
{
var apiTypeInfo = type as ApiTypeInfo;
if (apiTypeInfo != null && apiTypeInfo.IsInBuild)
return false;
if ((type is ModuleInfo || type is FileInfo) && apiTypeInfo != null)
{
foreach (var child in apiTypeInfo.Children)
{
if (UseBindings(child))
return true;
}
}
return type is ClassInfo ||
type is StructureInfo ||
type is InterfaceInfo ||
type is InjectCppCodeInfo;
}
/// <summary>
/// The API bindings generation utility that can produce scripting bindings for another languages to the native code.
/// </summary>
public static void GenerateBindings(BuildData buildData, Module module, ref BuildOptions moduleOptions, out BindingsResult bindings)
{
// Parse module (or load from cache)
var moduleInfo = ParseModule(buildData, module, moduleOptions);
bindings = new BindingsResult
{
UseBindings = UseBindings(moduleInfo),
GeneratedCppFilePath = Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.Gen.cpp"),
GeneratedCSharpFilePath = Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.Gen.cs"),
};
var moduleInfo = ParseModule(buildData, module, moduleOptions);
if (bindings.UseBindings)
{
buildData.Modules.TryGetValue(moduleInfo.Module, out var moduleBuildInfo);
// Ensure that generated files are included into build
if (!moduleBuildInfo.SourceFiles.Contains(bindings.GeneratedCSharpFilePath))
moduleBuildInfo.SourceFiles.Add(bindings.GeneratedCSharpFilePath);
}
// Skip if module is cached (no scripting API changed)
if (moduleInfo.IsFromCache)
return;
// Process parsed API
foreach (var child in moduleInfo.Children)
using (new ProfileEventScope("Process"))
{
try
foreach (var child in moduleInfo.Children)
{
foreach (var apiTypeInfo in child.Children)
ProcessAndValidate(buildData, apiTypeInfo);
}
catch (Exception)
{
if (child is FileInfo fileInfo)
Log.Error($"Failed to validate '{fileInfo.Name}' file to generate bindings.");
throw;
try
{
foreach (var apiTypeInfo in child.Children)
ProcessAndValidate(buildData, apiTypeInfo);
}
catch (Exception)
{
if (child is FileInfo fileInfo)
Log.Error($"Failed to validate '{fileInfo.Name}' file to generate bindings.");
throw;
}
}
}
// Generate bindings for scripting
Log.Verbose($"Generating API bindings for {module.Name} ({moduleInfo.Name})");
GenerateCpp(buildData, moduleInfo, ref bindings);
GenerateCSharp(buildData, moduleInfo, ref bindings);
if (bindings.UseBindings)
{
Log.Verbose($"Generating API bindings for {module.Name} ({moduleInfo.Name})");
using (new ProfileEventScope("Cpp"))
GenerateCpp(buildData, moduleInfo, ref bindings);
using (new ProfileEventScope("CSharp"))
GenerateCSharp(buildData, moduleInfo, ref bindings);
// TODO: add support for extending this code and support generating bindings for other scripting languages
// TODO: add support for extending this code and support generating bindings for other scripting languages
}
}
/// <summary>
@@ -568,7 +662,6 @@ namespace Flax.Build.Bindings
{
foreach (var fieldInfo in structureInfo.Fields)
{
// TODO: support bit-fields in structure fields
if (fieldInfo.Type.IsBitField)
throw new NotImplementedException($"TODO: support bit-fields in structure fields (found field {fieldInfo} in structure {structureInfo.Name})");

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Flax.Build.Bindings
@@ -8,7 +9,7 @@ namespace Flax.Build.Bindings
/// <summary>
/// The native class information for bindings generator.
/// </summary>
public class ClassInfo : ApiTypeInfo
public class ClassInfo : ClassStructInfo
{
private static readonly HashSet<string> InBuildScriptingObjectTypes = new HashSet<string>
{
@@ -22,19 +23,17 @@ namespace Flax.Build.Bindings
"Actor",
};
public AccessLevel Access;
public TypeInfo BaseType;
public AccessLevel BaseTypeInheritance;
public bool IsBaseTypeHidden;
public bool IsStatic;
public bool IsSealed;
public bool IsAbstract;
public bool IsAutoSerialization;
public bool NoSpawn;
public bool NoConstructor;
public List<FunctionInfo> Functions;
public List<PropertyInfo> Properties;
public List<FieldInfo> Fields;
public List<EventInfo> Events;
public List<FunctionInfo> Functions = new List<FunctionInfo>();
public List<PropertyInfo> Properties = new List<PropertyInfo>();
public List<FieldInfo> Fields = new List<FieldInfo>();
public List<EventInfo> Events = new List<EventInfo>();
internal HashSet<string> UniqueFunctionNames;
@@ -50,6 +49,9 @@ namespace Flax.Build.Bindings
{
base.Init(buildData);
// Internal base types are usually hidden from bindings (used in core-only internally)
IsBaseTypeHidden = BaseTypeInheritance == AccessLevel.Private || BaseType == null;
// Cache if it it Scripting Object type
if (InBuildScriptingObjectTypes.Contains(Name))
_isScriptingObject = true;
@@ -73,6 +75,40 @@ namespace Flax.Build.Bindings
}
}
public override void Write(BinaryWriter writer)
{
// TODO: convert into flags
writer.Write(IsStatic);
writer.Write(IsSealed);
writer.Write(IsAbstract);
writer.Write(IsAutoSerialization);
writer.Write(NoSpawn);
writer.Write(NoConstructor);
BindingsGenerator.Write(writer, Functions);
BindingsGenerator.Write(writer, Properties);
BindingsGenerator.Write(writer, Fields);
BindingsGenerator.Write(writer, Events);
base.Write(writer);
}
public override void Read(BinaryReader reader)
{
// TODO: convert into flags
IsStatic = reader.ReadBoolean();
IsSealed = reader.ReadBoolean();
IsAbstract = reader.ReadBoolean();
IsAutoSerialization = reader.ReadBoolean();
NoSpawn = reader.ReadBoolean();
NoConstructor = reader.ReadBoolean();
Functions = BindingsGenerator.Read(reader, Functions);
Properties = BindingsGenerator.Read(reader, Properties);
Fields = BindingsGenerator.Read(reader, Fields);
Events = BindingsGenerator.Read(reader, Events);
base.Read(reader);
}
public int GetScriptVTableSize(Builder.BuildData buildData, out int offset)
{
if (_scriptVTableSize == -1)

View File

@@ -0,0 +1,60 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.IO;
namespace Flax.Build.Bindings
{
/// <summary>
/// The native class/structure information for bindings generator.
/// </summary>
public abstract class ClassStructInfo : ApiTypeInfo
{
public AccessLevel Access;
public AccessLevel BaseTypeInheritance;
public TypeInfo BaseType;
public List<InterfaceInfo> Interfaces; // Optional
public List<TypeInfo> InterfaceNames; // Optional
public override void Init(Builder.BuildData buildData)
{
base.Init(buildData);
if (Interfaces == null && InterfaceNames != null && InterfaceNames.Count != 0)
{
Interfaces = new List<InterfaceInfo>();
for (var i = 0; i < InterfaceNames.Count; i++)
{
var interfaceName = InterfaceNames[i];
var apiTypeInfo = BindingsGenerator.FindApiTypeInfo(buildData, interfaceName, this);
if (apiTypeInfo is InterfaceInfo interfaceInfo)
{
Interfaces.Add(interfaceInfo);
}
}
if (Interfaces.Count == 0)
Interfaces = null;
}
}
public override void Write(BinaryWriter writer)
{
writer.Write((byte)Access);
writer.Write((byte)BaseTypeInheritance);
BindingsGenerator.Write(writer, BaseType);
BindingsGenerator.Write(writer, InterfaceNames);
base.Write(writer);
}
public override void Read(BinaryReader reader)
{
Access = (AccessLevel)reader.ReadByte();
BaseTypeInheritance = (AccessLevel)reader.ReadByte();
BaseType = BindingsGenerator.Read(reader, BaseType);
InterfaceNames = BindingsGenerator.Read(reader, InterfaceNames);
base.Read(reader);
}
}
}

View File

@@ -1,7 +1,8 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
namespace Flax.Build.Bindings
{
@@ -10,7 +11,7 @@ namespace Flax.Build.Bindings
/// </summary>
public class EnumInfo : ApiTypeInfo
{
public struct EntryInfo
public struct EntryInfo : IBindingsCache
{
public string Name;
public string[] Comment;
@@ -21,11 +22,27 @@ namespace Flax.Build.Bindings
{
return Name + (string.IsNullOrEmpty(Value) ? string.Empty : " = " + Value);
}
public void Write(BinaryWriter writer)
{
writer.Write(Name);
BindingsGenerator.Write(writer, Comment);
BindingsGenerator.Write(writer, Value);
BindingsGenerator.Write(writer, Attributes);
}
public void Read(BinaryReader reader)
{
Name = reader.ReadString();
Comment = BindingsGenerator.Read(reader, Comment);
Value = BindingsGenerator.Read(reader, Value);
Attributes = BindingsGenerator.Read(reader, Attributes);
}
}
public AccessLevel Access;
public TypeInfo UnderlyingType;
public List<EntryInfo> Entries;
public List<EntryInfo> Entries = new List<EntryInfo>();
public override bool IsValueType => true;
public override bool IsEnum => true;
@@ -36,6 +53,24 @@ namespace Flax.Build.Bindings
throw new NotSupportedException("API enums cannot have sub-types.");
}
public override void Write(BinaryWriter writer)
{
writer.Write((byte)Access);
BindingsGenerator.Write(writer, UnderlyingType);
BindingsGenerator.Write(writer, Entries);
base.Write(writer);
}
public override void Read(BinaryReader reader)
{
Access = (AccessLevel)reader.ReadByte();
UnderlyingType = BindingsGenerator.Read(reader, UnderlyingType);
Entries = BindingsGenerator.Read(reader, Entries);
base.Read(reader);
}
public override string ToString()
{
return "enum " + Name;

View File

@@ -1,4 +1,6 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System.IO;
namespace Flax.Build.Bindings
{
@@ -9,6 +11,20 @@ namespace Flax.Build.Bindings
{
public TypeInfo Type;
public override void Write(BinaryWriter writer)
{
BindingsGenerator.Write(writer, Type);
base.Write(writer);
}
public override void Read(BinaryReader reader)
{
Type = BindingsGenerator.Read(reader, Type);
base.Read(reader);
}
public override string ToString()
{
var result = string.Empty;

View File

@@ -1,4 +1,6 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System.IO;
namespace Flax.Build.Bindings
{
@@ -12,6 +14,31 @@ namespace Flax.Build.Bindings
public bool NoArray;
public FunctionInfo Getter;
public FunctionInfo Setter;
public string DefaultValue;
public bool HasDefaultValue => !string.IsNullOrEmpty(DefaultValue);
public override void Write(BinaryWriter writer)
{
BindingsGenerator.Write(writer, Type);
// TODO: convert into flags
writer.Write(IsReadOnly);
writer.Write(NoArray);
BindingsGenerator.Write(writer, DefaultValue);
base.Write(writer);
}
public override void Read(BinaryReader reader)
{
Type = BindingsGenerator.Read(reader, Type);
// TODO: convert into flags
IsReadOnly = reader.ReadBoolean();
NoArray = reader.ReadBoolean();
DefaultValue = BindingsGenerator.Read(reader, DefaultValue);
base.Read(reader);
}
public override string ToString()
{
@@ -19,6 +46,8 @@ namespace Flax.Build.Bindings
if (IsStatic)
result += "static ";
result += Type + " " + Name;
if (HasDefaultValue)
result += " = " + DefaultValue;
return result;
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
@@ -7,7 +7,7 @@ namespace Flax.Build.Bindings
/// <summary>
/// The native file information for bindings generator.
/// </summary>
public class FileInfo : ApiTypeInfo, IComparable<FileInfo>
public class FileInfo : ApiTypeInfo, IComparable, IComparable<FileInfo>
{
public override void AddChild(ApiTypeInfo apiTypeInfo)
{
@@ -26,5 +26,12 @@ namespace Flax.Build.Bindings
{
return System.IO.Path.GetFileName(Name);
}
public int CompareTo(object obj)
{
if (obj is ApiTypeInfo apiTypeInfo)
return Name.CompareTo(apiTypeInfo.Name);
return 0;
}
}
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.IO;
namespace Flax.Build.Bindings
{
@@ -9,7 +10,7 @@ namespace Flax.Build.Bindings
/// </summary>
public class FunctionInfo : MemberInfo
{
public struct ParameterInfo
public struct ParameterInfo : IBindingsCache
{
public string Name;
public TypeInfo Type;
@@ -25,6 +26,28 @@ namespace Flax.Build.Bindings
return Attributes != null && Attributes.Contains(name);
}
public void Write(BinaryWriter writer)
{
writer.Write(Name);
BindingsGenerator.Write(writer, Type);
BindingsGenerator.Write(writer, DefaultValue);
BindingsGenerator.Write(writer, Attributes);
// TODO: convert into flags
writer.Write(IsRef);
writer.Write(IsOut);
}
public void Read(BinaryReader reader)
{
Name = reader.ReadString();
Type = BindingsGenerator.Read(reader, Type);
DefaultValue = BindingsGenerator.Read(reader, DefaultValue);
Attributes = BindingsGenerator.Read(reader, Attributes);
// TODO: convert into flags
IsRef = reader.ReadBoolean();
IsOut = reader.ReadBoolean();
}
public override string ToString()
{
var result = Type + " " + Name;
@@ -42,12 +65,36 @@ namespace Flax.Build.Bindings
public string UniqueName;
public TypeInfo ReturnType;
public List<ParameterInfo> Parameters;
public List<ParameterInfo> Parameters = new List<ParameterInfo>();
public bool IsVirtual;
public bool IsConst;
public bool NoProxy;
public GlueInfo Glue;
public override void Write(BinaryWriter writer)
{
BindingsGenerator.Write(writer, ReturnType);
BindingsGenerator.Write(writer, Parameters);
// TODO: convert into flags
writer.Write(IsVirtual);
writer.Write(IsConst);
writer.Write(NoProxy);
base.Write(writer);
}
public override void Read(BinaryReader reader)
{
ReturnType = BindingsGenerator.Read(reader, ReturnType);
Parameters = BindingsGenerator.Read(reader, Parameters);
// TODO: convert into flags
IsVirtual = reader.ReadBoolean();
IsConst = reader.ReadBoolean();
NoProxy = reader.ReadBoolean();
base.Read(reader);
}
public override string ToString()
{
var result = string.Empty;

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
namespace Flax.Build.Bindings
{

View File

@@ -1,4 +1,6 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System.IO;
namespace Flax.Build.Bindings
{
@@ -9,6 +11,20 @@ namespace Flax.Build.Bindings
{
public string Code;
public override void Write(BinaryWriter writer)
{
writer.Write(Code);
base.Write(writer);
}
public override void Read(BinaryReader reader)
{
Code = reader.ReadString();
base.Read(reader);
}
/// <inheritdoc />
public override string ToString()
{

View File

@@ -0,0 +1,24 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
namespace Flax.Build.Bindings
{
/// <summary>
/// The native class/structure interface information for bindings generator.
/// </summary>
public class InterfaceInfo : ClassStructInfo
{
public override bool IsInterface => true;
public override void AddChild(ApiTypeInfo apiTypeInfo)
{
apiTypeInfo.Namespace = null;
base.AddChild(apiTypeInfo);
}
public override string ToString()
{
return "interface " + Name;
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;

View File

@@ -1,11 +1,13 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System.IO;
namespace Flax.Build.Bindings
{
/// <summary>
/// The native member information for bindings generator.
/// </summary>
public class MemberInfo
public class MemberInfo : IBindingsCache
{
public string Name;
public string[] Comment;
@@ -17,5 +19,23 @@ namespace Flax.Build.Bindings
{
return Attributes != null && Attributes.Contains(name);
}
public virtual void Write(BinaryWriter writer)
{
writer.Write(Name);
BindingsGenerator.Write(writer, Comment);
writer.Write(IsStatic);
writer.Write((byte)Access);
BindingsGenerator.Write(writer, Attributes);
}
public virtual void Read(BinaryReader reader)
{
Name = reader.ReadString();
Comment = BindingsGenerator.Read(reader, Comment);
IsStatic = reader.ReadBoolean();
Access = (AccessLevel)reader.ReadByte();
Attributes = BindingsGenerator.Read(reader, Attributes);
}
}
}

View File

@@ -1,4 +1,7 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.IO;
namespace Flax.Build.Bindings
{
@@ -8,10 +11,41 @@ namespace Flax.Build.Bindings
public class ModuleInfo : ApiTypeInfo
{
public Module Module;
public bool IsFromCache;
public override string ToString()
{
return "module " + Name;
}
/// <inheritdoc />
public override void Init(Builder.BuildData buildData)
{
base.Init(buildData);
// Sort module files to prevent bindings rebuild due to order changes (list might be created in async)
Children.Sort();
}
public override void Write(BinaryWriter writer)
{
writer.Write(Module.Name);
writer.Write(Module.FilePath);
BindingsGenerator.Write(writer, Module.BinaryModuleName);
writer.Write(Module.BuildNativeCode);
base.Write(writer);
}
public override void Read(BinaryReader reader)
{
if (reader.ReadString() != Module.Name ||
reader.ReadString() != Module.FilePath ||
BindingsGenerator.Read(reader, Module.BinaryModuleName) != Module.BinaryModuleName ||
reader.ReadBoolean() != Module.BuildNativeCode)
throw new Exception();
base.Read(reader);
}
}
}

View File

@@ -1,4 +1,6 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System.IO;
namespace Flax.Build.Bindings
{
@@ -11,6 +13,24 @@ namespace Flax.Build.Bindings
public FunctionInfo Getter;
public FunctionInfo Setter;
public override void Write(BinaryWriter writer)
{
BindingsGenerator.Write(writer, Type);
BindingsGenerator.Write(writer, Getter);
BindingsGenerator.Write(writer, Setter);
base.Write(writer);
}
public override void Read(BinaryReader reader)
{
Type = BindingsGenerator.Read(reader, Type);
Getter = BindingsGenerator.Read(reader, Getter);
Setter = BindingsGenerator.Read(reader, Setter);
base.Read(reader);
}
public override string ToString()
{
return Type + " " + Name;

View File

@@ -1,19 +1,18 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
namespace Flax.Build.Bindings
{
/// <summary>
/// The native structure information for bindings generator.
/// </summary>
public class StructureInfo : ApiTypeInfo
public class StructureInfo : ClassStructInfo
{
public AccessLevel Access;
public TypeInfo BaseType;
public List<FieldInfo> Fields;
public List<FunctionInfo> Functions;
public List<FieldInfo> Fields = new List<FieldInfo>();
public List<FunctionInfo> Functions = new List<FunctionInfo>();
public bool IsAutoSerialization;
public bool ForceNoPod;
@@ -27,7 +26,7 @@ namespace Flax.Build.Bindings
{
base.Init(buildData);
if (ForceNoPod)
if (ForceNoPod || (InterfaceNames != null && InterfaceNames.Count != 0))
{
_isPod = false;
return;
@@ -45,6 +44,26 @@ namespace Flax.Build.Bindings
}
}
public override void Write(BinaryWriter writer)
{
BindingsGenerator.Write(writer, Fields);
BindingsGenerator.Write(writer, Functions);
writer.Write(IsAutoSerialization);
writer.Write(ForceNoPod);
base.Write(writer);
}
public override void Read(BinaryReader reader)
{
Fields = BindingsGenerator.Read(reader, Fields);
Functions = BindingsGenerator.Read(reader, Functions);
IsAutoSerialization = reader.ReadBoolean();
ForceNoPod = reader.ReadBoolean();
base.Read(reader);
}
public override void AddChild(ApiTypeInfo apiTypeInfo)
{
if (apiTypeInfo is EnumInfo)

View File

@@ -1,7 +1,8 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
@@ -10,7 +11,7 @@ namespace Flax.Build.Bindings
/// <summary>
/// The native type information for bindings generator.
/// </summary>
public class TypeInfo : IEquatable<TypeInfo>
public class TypeInfo : IEquatable<TypeInfo>, IBindingsCache
{
public string Type;
public bool IsConst;
@@ -51,6 +52,34 @@ namespace Flax.Build.Bindings
return true;
}
public void Write(BinaryWriter writer)
{
BindingsGenerator.Write(writer, Type);
// TODO: pack as flags
writer.Write(IsConst);
writer.Write(IsRef);
writer.Write(IsPtr);
writer.Write(IsArray);
writer.Write(IsBitField);
writer.Write(ArraySize);
writer.Write(BitSize);
BindingsGenerator.Write(writer, GenericArgs);
}
public void Read(BinaryReader reader)
{
Type = BindingsGenerator.Read(reader, Type);
// TODO: convert into flags
IsConst = reader.ReadBoolean();
IsRef = reader.ReadBoolean();
IsPtr = reader.ReadBoolean();
IsArray = reader.ReadBoolean();
IsBitField = reader.ReadBoolean();
ArraySize = reader.ReadInt32();
BitSize = reader.ReadInt32();
GenericArgs = BindingsGenerator.Read(reader, GenericArgs);
}
public override string ToString()
{
var sb = new StringBuilder(64);
@@ -83,7 +112,7 @@ namespace Flax.Build.Bindings
return sb.ToString();
}
private static bool Equals(List<TypeInfo> a, List<TypeInfo> b)
public static bool Equals(List<TypeInfo> a, List<TypeInfo> b)
{
if (a == null && b == null)
return true;

View File

@@ -12,12 +12,31 @@ namespace Flax.Build
partial class Builder
{
private static RulesAssembly _rules;
private static Type[] _buildTypes;
/// <summary>
/// The build configuration files postfix.
/// </summary>
public static string BuildFilesPostfix = ".Build.cs";
/// <summary>
/// The cached list of types from Flax.Build assembly. Reused by other build tool utilities to improve performance.
/// </summary>
internal static Type[] BuildTypes
{
get
{
if (_buildTypes == null)
{
using (new ProfileEventScope("CacheBuildTypes"))
{
_buildTypes = typeof(Program).Assembly.GetTypes();
}
}
return _buildTypes;
}
}
/// <summary>
/// The rules assembly data.
/// </summary>
@@ -128,7 +147,7 @@ namespace Flax.Build
using (new ProfileEventScope("InitInBuildPlugins"))
{
foreach (var type in typeof(Program).Assembly.GetTypes().Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Plugin))))
foreach (var type in BuildTypes.Where(x => x.IsClass && !x.IsAbstract && x.IsSubclassOf(typeof(Plugin))))
{
var plugin = (Plugin)Activator.CreateInstance(type);
plugin.Init();
@@ -176,67 +195,74 @@ namespace Flax.Build
// Prepare targets and modules objects
Type[] types;
Target[] targetObjects;
Module[] moduleObjects;
Plugin[] pluginObjects;
var targetObjects = new List<Target>(16);
var moduleObjects = new List<Module>(256);
var pluginObjects = new List<Plugin>();
using (new ProfileEventScope("GetTypes"))
{
types = assembly.GetTypes();
targetObjects = types.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Target))).Select(type =>
for (var i = 0; i < types.Length; i++)
{
var target = (Target)Activator.CreateInstance(type);
var targetFilename = target.Name + BuildFilesPostfix;
target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase));
if (target.FilePath == null)
var type = types[i];
if (!type.IsClass || type.IsAbstract)
continue;
if (type.IsSubclassOf(typeof(Target)))
{
targetFilename = target.Name + "Target" + BuildFilesPostfix;
var target = (Target)Activator.CreateInstance(type);
var targetFilename = target.Name + BuildFilesPostfix;
target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase));
if (target.FilePath == null)
{
if (target.Name.EndsWith("Target"))
{
targetFilename = target.Name.Substring(0, target.Name.Length - "Target".Length) + BuildFilesPostfix;
target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase));
}
targetFilename = target.Name + "Target" + BuildFilesPostfix;
target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase));
if (target.FilePath == null)
{
throw new Exception(string.Format("Failed to find source file path for {0}", target));
if (target.Name.EndsWith("Target"))
{
targetFilename = target.Name.Substring(0, target.Name.Length - "Target".Length) + BuildFilesPostfix;
target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase));
}
if (target.FilePath == null)
{
throw new Exception(string.Format("Failed to find source file path for {0}", target));
}
}
}
target.FolderPath = Path.GetDirectoryName(target.FilePath);
target.Init();
targetObjects.Add(target);
}
target.FolderPath = Path.GetDirectoryName(target.FilePath);
target.Init();
return target;
}).ToArray();
moduleObjects = types.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Module))).Select(type =>
{
var module = (Module)Activator.CreateInstance(type);
var moduleFilename = module.Name + BuildFilesPostfix;
module.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), moduleFilename, StringComparison.OrdinalIgnoreCase));
if (module.FilePath == null)
else if (type.IsSubclassOf(typeof(Module)))
{
moduleFilename = module.Name + "Module" + BuildFilesPostfix;
var module = (Module)Activator.CreateInstance(type);
var moduleFilename = module.Name + BuildFilesPostfix;
module.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), moduleFilename, StringComparison.OrdinalIgnoreCase));
if (module.FilePath == null)
{
throw new Exception(string.Format("Failed to find source file path for {0}", module));
moduleFilename = module.Name + "Module" + BuildFilesPostfix;
module.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), moduleFilename, StringComparison.OrdinalIgnoreCase));
if (module.FilePath == null)
{
throw new Exception(string.Format("Failed to find source file path for {0}", module));
}
}
module.FolderPath = Path.GetDirectoryName(module.FilePath);
module.Init();
moduleObjects.Add(module);
}
module.FolderPath = Path.GetDirectoryName(module.FilePath);
module.Init();
return module;
}).ToArray();
pluginObjects = types.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Plugin))).Select(type =>
{
var plugin = (Plugin)Activator.CreateInstance(type);
plugin.Init();
return plugin;
}).ToArray();
else if (type.IsSubclassOf(typeof(Plugin)))
{
var plugin = (Plugin)Activator.CreateInstance(type);
plugin.Init();
pluginObjects.Add(plugin);
}
}
}
_rules = new RulesAssembly(assembly, targetObjects, moduleObjects, pluginObjects);
_rules = new RulesAssembly(assembly, targetObjects.ToArray(), moduleObjects.ToArray(), pluginObjects.ToArray());
}
return _rules;

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using Flax.Build.Graph;
@@ -27,12 +28,12 @@ namespace Flax.Build.BuildSystem.Graph
/// <summary>
/// The maximum amount of threads to be used for the parallel execution.
/// </summary>
public int ThreadCountMax = 1410;
public int ThreadCountMax = Configuration.MaxConcurrency;
/// <summary>
/// The amount of threads to allocate per processor. Use it to allocate more threads for faster execution or use less to keep reduce CPU usage during build.
/// </summary>
public float ProcessorCountScale = 1.0f;
public float ProcessorCountScale = Configuration.ConcurrencyProcessorScale;
/// <inheritdoc />
public override int Execute(List<Task> tasks)
@@ -178,7 +179,12 @@ namespace Flax.Build.BuildSystem.Graph
private int ExecuteTask(Task task)
{
ProcessStartInfo startInfo = new ProcessStartInfo
string name = "Task";
if (task.ProducedFiles != null && task.ProducedFiles.Count != 0)
name = Path.GetFileName(task.ProducedFiles[0]);
var profilerEvent = Profiling.Begin(name);
var startInfo = new ProcessStartInfo
{
WorkingDirectory = task.WorkingDirectory,
FileName = task.CommandPath,
@@ -230,10 +236,13 @@ namespace Flax.Build.BuildSystem.Graph
// Hang until process end
process.WaitForExit();
Profiling.End(profilerEvent);
return process.ExitCode;
}
finally
{
Profiling.End(profilerEvent);
// Ensure to cleanup data
process?.Close();
}

View File

@@ -111,5 +111,16 @@ namespace Flax.Build
// By default deploy all C++ header files
files.AddRange(Directory.GetFiles(FolderPath, "*.h", SearchOption.AllDirectories));
}
/// <summary>
/// Adds the file to the build sources if exists.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="path">The source file path.</param>
protected void AddSourceFileIfExists(BuildOptions options, string path)
{
if (File.Exists(path))
options.SourceFiles.Add(path);
}
}
}

View File

@@ -330,7 +330,12 @@ namespace Flax.Build
}
// Collect all files to compile
var cppFiles = moduleOptions.SourceFiles.Where(x => x.EndsWith(".cpp")).ToList();
var cppFiles = new List<string>(moduleOptions.SourceFiles.Count / 2);
for (int i = 0; i < moduleOptions.SourceFiles.Count; i++)
{
if (moduleOptions.SourceFiles[i].EndsWith(".cpp", StringComparison.OrdinalIgnoreCase))
cppFiles.Add(moduleOptions.SourceFiles[i]);
}
if (!string.IsNullOrEmpty(module.BinaryModuleName))
{

View File

@@ -56,6 +56,11 @@ namespace Flax.Build.NativeCpp
/// </summary>
public bool Optimization = false;
/// <summary>
/// Enables the whole program optimization.
/// </summary>
public bool WholeProgramOptimization = false;
/// <summary>
/// Enables functions level linking support.
/// </summary>
@@ -131,6 +136,7 @@ namespace Flax.Build.NativeCpp
RuntimeTypeInfo = RuntimeTypeInfo,
Inlining = Inlining,
Optimization = Optimization,
WholeProgramOptimization = WholeProgramOptimization,
FunctionLevelLinking = FunctionLevelLinking,
DebugInformation = DebugInformation,
UseDebugCRT = UseDebugCRT,

View File

@@ -157,7 +157,7 @@ namespace Flax.Build
if (_platforms == null)
{
using (new ProfileEventScope("GetPlatforms"))
_platforms = typeof(Platform).Assembly.GetTypes().Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Platform))).Select(Activator.CreateInstance).Cast<Platform>().ToArray();
_platforms = Builder.BuildTypes.Where(x => x.IsClass && !x.IsAbstract && x.IsSubclassOf(typeof(Platform))).Select(Activator.CreateInstance).Cast<Platform>().ToArray();
}
foreach (var platform in _platforms)

View File

@@ -22,7 +22,7 @@ namespace Flax.Build.Plugins
private void OnGenerateCppScriptWrapperFunction(Builder.BuildData buildData, ClassInfo classInfo, FunctionInfo functionInfo, int scriptVTableSize, int scriptVTableIndex, StringBuilder contents)
{
// Generate C++ wrapper function to invoke Visual Script instead of overriden native function (with support for base method callback)
// Generate C++ wrapper function to invoke Visual Script instead of overridden native function (with support for base method callback)
BindingsGenerator.CppIncludeFiles.Add("Engine/Content/Assets/VisualScript.h");
@@ -48,7 +48,7 @@ namespace Flax.Build.Plugins
contents.AppendLine(" if (IsDuringWrapperCall)");
contents.AppendLine(" {");
contents.AppendLine(" // Prevent stack overflow by calling base method");
contents.AppendLine(" const auto scriptVTableBase = object->GetType().Class.ScriptVTableBase;");
contents.AppendLine(" const auto scriptVTableBase = object->GetType().Script.ScriptVTableBase;");
contents.Append($" return (object->**({functionInfo.UniqueName}_Signature*)&scriptVTableBase[{scriptVTableIndex} + 2])(");
separator = false;
for (var i = 0; i < functionInfo.Parameters.Count; i++)
@@ -61,7 +61,7 @@ namespace Flax.Build.Plugins
}
contents.AppendLine(");");
contents.AppendLine(" }");
contents.AppendLine(" auto scriptVTable = (VisualScript::Method**)object->GetType().Class.ScriptVTable;");
contents.AppendLine(" auto scriptVTable = (VisualScript::Method**)object->GetType().Script.ScriptVTable;");
contents.AppendLine($" ASSERT(scriptVTable && scriptVTable[{scriptVTableIndex}]);");
if (functionInfo.Parameters.Count != 0)

View File

@@ -4,6 +4,7 @@ using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace Flax.Build
{
@@ -12,7 +13,7 @@ namespace Flax.Build
/// </summary>
public class ProfileEventScope : IDisposable
{
private int _id;
private readonly int _id;
/// <summary>
/// Initializes a new instance of the <see cref="ProfileEventScope"/> class.
@@ -61,6 +62,11 @@ namespace Flax.Build
/// The event call depth (for parent-children events evaluation).
/// </summary>
public int Depth;
/// <summary>
/// The calling thread id.
/// </summary>
public int ThreadId;
}
private static int _depth;
@@ -78,6 +84,7 @@ namespace Flax.Build
e.StartTime = DateTime.Now;
e.Duration = TimeSpan.Zero;
e.Depth = _depth++;
e.ThreadId = Thread.CurrentThread.ManagedThreadId;
_events.Add(e);
return _events.Count - 1;
}
@@ -147,8 +154,8 @@ namespace Flax.Build
for (int i = 0; i < _events.Count; i++)
{
var e = _events[i];
contents.Append($"{{ \"pid\":1, \"tid\":1, \"ts\":{(int)((e.StartTime - startTime).TotalMilliseconds * 1000.0)}, \"dur\":{(int)(e.Duration.TotalMilliseconds * 1000.0)}, \"ph\":\"X\", \"name\":\"{e.Name}\", \"args\":{{ \"startTime\":\"{e.StartTime.ToShortTimeString()}\" }} }}\n");
contents.Append($"{{ \"pid\":{e.ThreadId}, \"tid\":1, \"ts\":{(int)((e.StartTime - startTime).TotalMilliseconds * 1000.0)}, \"dur\":{(int)(e.Duration.TotalMilliseconds * 1000.0)}, \"ph\":\"X\", \"name\":\"{e.Name}\", \"args\":{{ \"startTime\":\"{e.StartTime.ToShortTimeString()}\" }} }}\n");
if (i + 1 != _events.Count)
contents.Append(",");

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
@@ -68,7 +68,7 @@ namespace Flax.Build
using (new ProfileEventScope("GetSdks"))
{
_sdks = new Dictionary<string, Sdk>();
var types = typeof(Sdk).Assembly.GetTypes().Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Sdk)));
var types = Builder.BuildTypes.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Sdk)));
foreach (var type in types)
{
object instance = null;

View File

@@ -225,7 +225,7 @@ namespace Flax.Build
}
/// <summary>
/// Setups the target building environment (native C++). Allows to modify compiler and linker options. Options applied here are used by all modules included into this target (can be overriden per module).
/// Setups the target building environment (native C++). Allows to modify compiler and linker options. Options applied here are used by all modules included into this target (can be overridden per module).
/// </summary>
/// <param name="options">The build options.</param>
public virtual void SetupTargetEnvironment(BuildOptions options)
@@ -256,6 +256,7 @@ namespace Flax.Build
options.CompileEnv.IntrinsicFunctions = false;
options.CompileEnv.BufferSecurityCheck = true;
options.CompileEnv.Inlining = false;
options.CompileEnv.WholeProgramOptimization = false;
options.LinkEnv.DebugInformation = true;
options.LinkEnv.LinkTimeCodeGeneration = false;
@@ -273,11 +274,11 @@ namespace Flax.Build
options.CompileEnv.IntrinsicFunctions = true;
options.CompileEnv.BufferSecurityCheck = true;
options.CompileEnv.Inlining = true;
//options.CompileEnv.WholeProgramOptimization = true;
options.CompileEnv.WholeProgramOptimization = false;
options.LinkEnv.DebugInformation = true;
options.LinkEnv.LinkTimeCodeGeneration = true;
options.LinkEnv.UseIncrementalLinking = false;
options.LinkEnv.LinkTimeCodeGeneration = false;
options.LinkEnv.UseIncrementalLinking = true;
options.LinkEnv.Optimization = true;
break;
case TargetConfiguration.Release:
@@ -291,7 +292,7 @@ namespace Flax.Build
options.CompileEnv.IntrinsicFunctions = true;
options.CompileEnv.BufferSecurityCheck = false;
options.CompileEnv.Inlining = true;
//options.CompileEnv.WholeProgramOptimization = true;
options.CompileEnv.WholeProgramOptimization = true;
options.LinkEnv.DebugInformation = false;
options.LinkEnv.LinkTimeCodeGeneration = true;

View File

@@ -1,5 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
namespace Flax.Build
{
/// <summary>
@@ -133,6 +135,18 @@ namespace Flax.Build
[CommandLine("logfile", "<path>", "The log file path relative to the working directory. Set to empty to disable it/")]
public static string LogFile = "Cache/Intermediate/Log.txt";
/// <summary>
/// The maximum allowed concurrency for a build system (maximum active worker threads count).
/// </summary>
[CommandLine("maxConcurrency", "<threads>", "The maximum allowed concurrency for a build system (maximum active worker threads count).")]
public static int MaxConcurrency = 1410;
/// <summary>
/// The concurrency scale for a build system that specifies how many worker threads allocate per-logical processor.
/// </summary>
[CommandLine("concurrencyProcessorScale", "<scale>", "The concurrency scale for a build system that specifies how many worker threads allocate per-logical processor.")]
public static float ConcurrencyProcessorScale = 1.0f;
/// <summary>
/// The output binaries folder path relative to the working directory.
/// </summary>
@@ -186,5 +200,10 @@ namespace Flax.Build
/// </summary>
[CommandLine("customProjectFormat", "<type>", "Generates code project files for a custom project format type. Valid only with -genproject option.")]
public static string ProjectFormatCustom = null;
/// <summary>
/// Custom configuration defines provided via command line for the build tool.
/// </summary>
public static List<string> CustomDefines = new List<string>();
}
}

View File

@@ -312,7 +312,7 @@ namespace Flax.Deps.Dependencies
}
// Get the source
//CloneGitRepoSingleBranch(root, "https://github.com/NVIDIAGameWorks/PhysX.git", "4.1");
CloneGitRepoSingleBranch(root, "https://github.com/NVIDIAGameWorks/PhysX.git", "4.1");
foreach (var platform in options.Platforms)
{

View File

@@ -258,9 +258,8 @@ namespace Flax.Deps.Dependencies
"mono_type_normalize",
};
private void BuildMsvc(BuildOptions options, TargetPlatform platform, TargetArchitecture architecture)
private void BuildMsvc(BuildOptions options, TargetPlatform platform, TargetArchitecture architecture, string configuration = "Release")
{
var configuration = "Release";
string buildPlatform;
switch (architecture)
{
@@ -491,12 +490,13 @@ namespace Flax.Deps.Dependencies
{
case TargetPlatform.Windows:
{
BuildMsvc(options, platform, TargetArchitecture.x64);
var configuration = "Release";
BuildMsvc(options, platform, TargetArchitecture.x64, configuration);
//BuildBcl(options, platform);
// Export header files
Deploy.VCEnvironment.BuildSolution(Path.Combine(root, "msvc", "libmono-dynamic.vcxproj"), "Release", "x64");
Deploy.VCEnvironment.BuildSolution(Path.Combine(root, "msvc", "build-install.vcxproj"), "Release", "x64");
Deploy.VCEnvironment.BuildSolution(Path.Combine(root, "msvc", "libmono-dynamic.vcxproj"), configuration, "x64");
Deploy.VCEnvironment.BuildSolution(Path.Combine(root, "msvc", "build-install.vcxproj"), configuration, "x64");
// Get exported mono methods to forward them in engine module (on Win32 platforms)
GetMonoExports(options);

View File

@@ -51,7 +51,7 @@ namespace Flax.Deps
}
// Get all deps
var dependencies = typeof(DepsBuilder).Assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(Dependency))).Select(Activator.CreateInstance).Cast<Dependency>().ToArray();
var dependencies = Builder.BuildTypes.Where(x => x.IsSubclassOf(typeof(Dependency))).Select(Activator.CreateInstance).Cast<Dependency>().ToArray();
if (dependencies.Length == 0)
Log.Warning("No dependencies found!");
for (var i = 0; i < dependencies.Length; i++)

View File

@@ -64,17 +64,20 @@
<ItemGroup>
<Compile Include="Bindings\ApiTypeInfo.cs" />
<Compile Include="Bindings\BindingsGenerator.Api.cs" />
<Compile Include="Bindings\BindingsGenerator.Cache.cs" />
<Compile Include="Bindings\BindingsGenerator.Cpp.cs" />
<Compile Include="Bindings\BindingsGenerator.cs" />
<Compile Include="Bindings\BindingsGenerator.CSharp.cs" />
<Compile Include="Bindings\BindingsGenerator.Parsing.cs" />
<Compile Include="Bindings\ClassInfo.cs" />
<Compile Include="Bindings\ClassStructInfo.cs" />
<Compile Include="Bindings\EnumInfo.cs" />
<Compile Include="Bindings\EventInfo.cs" />
<Compile Include="Bindings\FieldInfo.cs" />
<Compile Include="Bindings\FileInfo.cs" />
<Compile Include="Bindings\InheritanceInfo.cs" />
<Compile Include="Bindings\InjectCppCodeInfo.cs" />
<Compile Include="Bindings\InterfaceInfo.cs" />
<Compile Include="Bindings\LangType.cs" />
<Compile Include="Bindings\MemberInfo.cs" />
<Compile Include="Bindings\FunctionInfo.cs" />

View File

@@ -36,7 +36,7 @@ namespace Flax.Build
var path = Path.GetDirectoryName(Configuration.LogFile);
if (!string.IsNullOrEmpty(path) && !Directory.Exists(path))
Directory.CreateDirectory(path);
_logFile = new FileStream(Configuration.LogFile, FileMode.Create);
_logFile = new FileStream(Configuration.LogFile, FileMode.Create, FileAccess.Write, FileShare.Read);
_logFileWriter = new StreamWriter(_logFile);
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.IO;

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.IO;

View File

@@ -467,8 +467,11 @@ namespace Flax.Build.Platforms
// Frame-Pointer Omission
commonArgs.Add("/Oy");
// Whole Program Optimization
commonArgs.Add("/GL");
if (compileEnvironment.WholeProgramOptimization)
{
// Whole Program Optimization
commonArgs.Add("/GL");
}
}
else
{
@@ -721,7 +724,7 @@ namespace Flax.Build.Platforms
args.Add("/PDBALTPATH:%_PDB%");
// Optimize
if (linkEnvironment.Optimization)
if (linkEnvironment.Optimization && !linkEnvironment.UseIncrementalLinking)
{
// Generate an EXE checksum
args.Add("/RELEASE");

View File

@@ -32,6 +32,16 @@ namespace Flax.Build
{
// Setup
CommandLine.Configure(typeof(Configuration));
foreach (var option in CommandLine.GetOptions())
{
if (option.Name.Length > 1 && option.Name[0] == 'D')
{
var define = option.Name.Substring(1);
if (!string.IsNullOrEmpty(option.Value))
define += "=" + option.Value;
Configuration.CustomDefines.Add(define);
}
}
if (Configuration.CurrentDirectory != null)
Environment.CurrentDirectory = Configuration.CurrentDirectory;
Globals.Root = Directory.GetCurrentDirectory();

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
@@ -460,6 +460,24 @@ namespace Flax.Build
}
}
/// <summary>
/// Skips all tokens until the tokenizer steps into token of given type (and it is also skipped, so, NextToken will give the next token).
/// </summary>
/// <param name="tokenType">The expected token type.</param>
/// <param name="context">The output contents of the skipped tokens.</param>
/// <param name="includeWhitespaces">When false, all white-space tokens will be ignored.</param>
public void SkipUntil(TokenType tokenType, out string context, bool includeWhitespaces)
{
context = string.Empty;
while (NextToken(true).Type != tokenType)
{
var token = CurrentToken;
if (!includeWhitespaces && (token.Type == TokenType.Newline || token.Type == TokenType.Whitespace))
continue;
context += token.Value;
}
}
/// <summary>
/// Disposes the <see cref="Tokenizer"/>.
/// </summary>

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;

View File

@@ -19,7 +19,7 @@ namespace Flax.Build
/// </summary>
/// <param name="str">The input string.</param>
/// <returns>The file size text.</returns>
internal static uint GetHashCode(string str)
public static uint GetHashCode(string str)
{
uint hash = 5381;
if (str != null)
@@ -33,6 +33,16 @@ namespace Flax.Build
return hash;
}
/// <summary>
/// Gets the empty array of the given type (shared one).
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <returns>The empty array object.</returns>
public static T[] GetEmptyArray<T>()
{
return Enumerable.Empty<T>() as T[];
}
/// <summary>
/// Gets the size of the file as a readable string.
/// </summary>

View File

@@ -75,14 +75,14 @@ namespace Flax.Stats
/// <summary>
/// Gets total amount of memory used by that node and all child nodes
/// </summary>
public long TotaSizeOnDisk
public long TotalSizeOnDisk
{
get
{
long result = SizeOnDisk;
for (int i = 0; i < Children.Length; i++)
{
result += Children[i].TotaSizeOnDisk;
result += Children[i].TotalSizeOnDisk;
}
return result;
}
@@ -153,15 +153,15 @@ namespace Flax.Stats
/// <summary>
/// Gets total amount of lines of code per language
/// </summary>
/// <param name="languge">Language</param>
/// <param name="language">Language</param>
/// <returns>Result amount of lines</returns>
public long GetTotalLinesOfCode(Languages languge)
public long GetTotalLinesOfCode(Languages language)
{
long result = 0;
result += LinesOfCode[(int)languge];
result += LinesOfCode[(int)language];
for (int i = 0; i < Children.Length; i++)
{
result += Children[i].GetTotalLinesOfCode(languge);
result += Children[i].GetTotalLinesOfCode(language);
}
return result;
}
@@ -270,9 +270,9 @@ namespace Flax.Stats
public void CleanupDirectories()
{
var chld = Children.ToList();
chld.RemoveAll(e => ignoredFolders.Contains(e.ShortName.ToLower()));
Children = chld.ToArray();
var child = Children.ToList();
child.RemoveAll(e => ignoredFolders.Contains(e.ShortName.ToLower()));
Children = child.ToArray();
foreach (var a in Children)
{

View File

@@ -103,7 +103,7 @@ namespace Flax.Stats
}
/// <summary>
/// Write string in UTF-8 encoding to the stream and ofset data
/// Write string in UTF-8 encoding to the stream and offset data
/// </summary>
/// <param name="fs">File stream</param>
/// <param name="data">Data to write</param>
@@ -292,7 +292,7 @@ namespace Flax.Stats
}
/// <summary>
/// Write arry of Guids to the stream
/// Write array of Guids to the stream
/// </summary>
/// <param name="fs">File stream</param>
/// <param name="val">Value to write</param>

View File

@@ -143,7 +143,29 @@ namespace FlaxEngine.Tests
Transform ab = a.LocalToWorld(b);
Transform ba = a.WorldToLocal(ab);
Assert.IsTrue(Transform.NearEqual(ref b, ref ba, 0.00001f));
Assert.IsTrue(Transform.NearEqual(ref b, ref ba, 0.00001f), $"Got: {b} but expected {ba}");
}
}
/// <summary>
/// Test conversions between transform local/world space
/// </summary>
[Test]
public void TestAddSubtract()
{
var rand = new Random(10);
for (int i = 0; i < 10; i++)
{
Transform a = new Transform(rand.NextVector3(), Quaternion.Euler(i * 10, 0, i), rand.NextVector3() * 10.0f);
Transform b = new Transform(rand.NextVector3(), Quaternion.Euler(i, 1, 22), rand.NextVector3() * 0.3f);
Transform ab = a + b;
Transform newA = ab - b;
Assert.IsTrue(Transform.NearEqual(ref a, ref newA, 0.00001f), $"Got: {newA} but expected {a}");
Transform ba = b + a;
Transform newB = ba - a;
Assert.IsTrue(Transform.NearEqual(ref b, ref newB, 0.00001f), $"Got: {newB} but expected {b}");
}
}
}