Add API_INTERFACE to scripting API bindings for implementing interfaces

This commit is contained in:
Wojtek Figat
2021-01-04 14:18:59 +01:00
parent e242dbf89f
commit 8dc5b11f51
14 changed files with 421 additions and 151 deletions

View File

@@ -22,6 +22,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;

View File

@@ -13,6 +13,7 @@ namespace Flax.Build.Bindings
{
public static readonly string Enum = "API_ENUM";
public static readonly string Class = "API_CLASS";
public static readonly string Interface = "API_INTERFACE";
public static readonly string Struct = "API_STRUCT";
public static readonly string Function = "API_FUNCTION";
public static readonly string Property = "API_PROPERTY";
@@ -27,6 +28,7 @@ namespace Flax.Build.Bindings
Enum,
Class,
Struct,
Interface,
};
}

View File

@@ -947,9 +947,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")
baseType = null;
else if (classInfo != null && classInfo.IsBaseTypeHidden)
if (classInfo != null && classInfo.IsBaseTypeHidden)
baseType = null;
CppAutoSerializeFields.Clear();
CppAutoSerializeProperties.Clear();
@@ -1038,6 +1036,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;
@@ -1311,6 +1328,9 @@ namespace Flax.Build.Bindings
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}), ");
@@ -1330,8 +1350,9 @@ namespace Flax.Build.Bindings
}
else
{
contents.Append($"&{classTypeNameInternal}Internal::Ctor, &{classTypeNameInternal}Internal::Dtor");
contents.Append($"&{classTypeNameInternal}Internal::Ctor, &{classTypeNameInternal}Internal::Dtor, nullptr");
}
contents.Append(", ").Append(interfacesTable);
contents.Append(");");
contents.AppendLine();
@@ -1526,6 +1547,42 @@ namespace Flax.Build.Bindings
}
}
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 bool GenerateCppType(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, object type)
{
if (type is ApiTypeInfo apiTypeInfo && apiTypeInfo.IsInBuild)
@@ -1537,6 +1594,8 @@ 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

View File

@@ -383,6 +383,95 @@ 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
@@ -410,64 +499,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,6 +556,68 @@ namespace Flax.Build.Bindings
return desc;
}
private static InterfaceInfo ParseInterface(ref ParsingContext context)
{
var desc = new InterfaceInfo
{
Children = new List<ApiTypeInfo>(),
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
@@ -875,55 +970,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)

View File

@@ -254,6 +254,16 @@ namespace Flax.Build.Bindings
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)

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.Collections.Generic;
using System.Linq;
@@ -8,7 +8,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,9 +22,6 @@ namespace Flax.Build.Bindings
"Actor",
};
public AccessLevel Access;
public TypeInfo BaseType;
public AccessLevel BaseTypeInheritance;
public bool IsBaseTypeHidden;
public bool IsStatic;
public bool IsSealed;
@@ -52,7 +49,7 @@ 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.Type == "ISerializable";
IsBaseTypeHidden = BaseTypeInheritance == AccessLevel.Private || BaseType == null;
// Cache if it it Scripting Object type
if (InBuildScriptingObjectTypes.Contains(Name))

View File

@@ -0,0 +1,40 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
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;
}
}
}
}

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

@@ -8,10 +8,8 @@ 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 bool IsAutoSerialization;
@@ -27,7 +25,7 @@ namespace Flax.Build.Bindings
{
base.Init(buildData);
if (ForceNoPod)
if (ForceNoPod || (InterfaceNames != null && InterfaceNames.Count != 0))
{
_isPod = false;
return;

View File

@@ -69,12 +69,14 @@
<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" />