diff --git a/Source/Engine/Core/Config.h b/Source/Engine/Core/Config.h index 152a170b2..55fae1cd4 100644 --- a/Source/Engine/Core/Config.h +++ b/Source/Engine/Core/Config.h @@ -44,6 +44,7 @@ // Scripting API defines (see C++ scripting documentation for more info) #define API_ENUM(...) #define API_CLASS(...) +#define API_INTERFACE(...) #define API_STRUCT(...) #define API_FUNCTION(...) #define API_PROPERTY(...) @@ -52,3 +53,4 @@ #define API_PARAM(...) #define API_INJECT_CPP_CODE(...) #define API_AUTO_SERIALIZATION(...) public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; +#define DECLARE_SCRIPTING_TYPE_MINIMAL(type) public: friend class type##Internal; static struct ScriptingTypeInitializer TypeInitializer; diff --git a/Source/Engine/Core/Types/BaseTypes.h b/Source/Engine/Core/Types/BaseTypes.h index 63638e304..580728b2c 100644 --- a/Source/Engine/Core/Types/BaseTypes.h +++ b/Source/Engine/Core/Types/BaseTypes.h @@ -108,7 +108,5 @@ class Dictionary; inline T& operator&= (T& a, T b) { return (T&)((int&)a &= (int)b); } \ inline T& operator^= (T& a, T b) { return (T&)((int&)a ^= (int)b); } -#define DECLARE_SCRIPTING_TYPE_MINIMAL(type) \ - public: \ - friend class type##Internal; \ - static struct ScriptingTypeInitializer TypeInitializer; +// Returns byte offset from the object pointer in vtable to the begin of the given inherited type implementation +#define VTABLE_OFFSET(type, baseType) (((intptr)static_cast((type*)1))-1) diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 862b95347..e3688949b 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -62,6 +62,7 @@ ScriptingType::ScriptingType() , Fullname(nullptr, 0) , Type(ScriptingTypes::Script) , BaseTypePtr(nullptr) + , Interfaces(nullptr) { Script.Spawn = nullptr; Script.VTable = nullptr; @@ -72,7 +73,7 @@ ScriptingType::ScriptingType() Script.DefaultInstance = nullptr; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, const InterfaceImplementation* interfaces) : ManagedClass(nullptr) , Module(module) , InitRuntime(initRuntime) @@ -80,6 +81,7 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul , Type(ScriptingTypes::Script) , BaseTypeHandle(baseType) , BaseTypePtr(nullptr) + , Interfaces(interfaces) , Size(size) { Script.Spawn = spawn; @@ -91,13 +93,14 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul Script.DefaultInstance = nullptr; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, ScriptingTypeInitializer* baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, ScriptingTypeInitializer* baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, const InterfaceImplementation* interfaces) : ManagedClass(nullptr) , Module(module) , InitRuntime(initRuntime) , Fullname(fullname) , Type(ScriptingTypes::Script) , BaseTypePtr(baseType) + , Interfaces(interfaces) , Size(size) { Script.Spawn = spawn; @@ -109,26 +112,28 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul Script.DefaultInstance = nullptr; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces) : ManagedClass(nullptr) , Module(module) , InitRuntime(initRuntime) , Fullname(fullname) , Type(ScriptingTypes::Class) , BaseTypePtr(baseType) + , Interfaces(interfaces) , Size(size) { Class.Ctor = ctor; Class.Dtor = dtor; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces) : ManagedClass(nullptr) , Module(module) , InitRuntime(initRuntime) , Fullname(fullname) , Type(ScriptingTypes::Structure) , BaseTypePtr(baseType) + , Interfaces(interfaces) , Size(size) { Struct.Ctor = ctor; @@ -140,6 +145,18 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul Struct.SetField = setField; } +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces) + : ManagedClass(nullptr) + , Module(module) + , InitRuntime(initRuntime) + , Fullname(fullname) + , Type(ScriptingTypes::Interface) + , BaseTypePtr(baseType) + , Interfaces(interfaces) + , Size(0) +{ +} + ScriptingType::ScriptingType(const ScriptingType& other) : ManagedClass(other.ManagedClass) , Module(other.Module) @@ -148,6 +165,7 @@ ScriptingType::ScriptingType(const ScriptingType& other) , Type(other.Type) , BaseTypeHandle(other.BaseTypeHandle) , BaseTypePtr(other.BaseTypePtr) + , Interfaces(other.Interfaces) , Size(other.Size) { switch (other.Type) @@ -176,6 +194,8 @@ ScriptingType::ScriptingType(const ScriptingType& other) break; case ScriptingTypes::Enum: break; + case ScriptingTypes::Interface: + break; default: ; } } @@ -188,6 +208,7 @@ ScriptingType::ScriptingType(ScriptingType&& other) , Type(other.Type) , BaseTypeHandle(other.BaseTypeHandle) , BaseTypePtr(other.BaseTypePtr) + , Interfaces(other.Interfaces) , Size(other.Size) { switch (other.Type) @@ -220,6 +241,8 @@ ScriptingType::ScriptingType(ScriptingType&& other) break; case ScriptingTypes::Enum: break; + case ScriptingTypes::Interface: + break; default: ; } } @@ -240,6 +263,8 @@ ScriptingType::~ScriptingType() break; case ScriptingTypes::Enum: break; + case ScriptingTypes::Interface: + break; default: ; } } @@ -269,16 +294,40 @@ ScriptingObject* ScriptingType::GetDefaultInstance() const return Script.DefaultInstance; } +const ScriptingType::InterfaceImplementation* ScriptingType::GetInterface(const ScriptingTypeInitializer* interfaceType) const +{ + const InterfaceImplementation* interfaces = Interfaces; + if (interfaces) + { + while (interfaces->InterfaceType) + { + if (interfaces->InterfaceType == interfaceType) + return interfaces; + interfaces++; + } + } + if (BaseTypeHandle) + { + return BaseTypeHandle.GetType().GetInterface(interfaceType); + } + if (BaseTypePtr) + { + return BaseTypePtr->GetType().GetInterface(interfaceType); + } + return nullptr; +} + String ScriptingType::ToString() const { return String(Fullname.Get(), Fullname.Length()); } -ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SpawnHandler spawn, ScriptingTypeInitializer* baseType, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable) +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SpawnHandler spawn, ScriptingTypeInitializer* baseType, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { + // Script module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable, interfaces); const MString typeName(fullname.Get(), fullname.Length()); #if BUILD_DEBUG if (module->TypeNameToTypeIndex.ContainsKey(typeName)) @@ -289,11 +338,12 @@ ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const S module->TypeNameToTypeIndex[typeName] = TypeIndex; } -ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType) +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { + // Class module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, baseType); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, baseType, interfaces); const MString typeName(fullname.Get(), fullname.Length()); #if BUILD_DEBUG if (module->TypeNameToTypeIndex.ContainsKey(typeName)) @@ -304,11 +354,28 @@ ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const S module->TypeNameToTypeIndex[typeName] = TypeIndex; } -ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType) +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { + // Structure module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType, interfaces); + const MString typeName(fullname.Get(), fullname.Length()); +#if BUILD_DEBUG + if (module->TypeNameToTypeIndex.ContainsKey(typeName)) + { + LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); + } +#endif + module->TypeNameToTypeIndex[typeName] = TypeIndex; +} + +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) + : ScriptingTypeHandle(module, module->Types.Count()) +{ + // Interface + module->Types.AddUninitialized(); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, initRuntime, baseType, interfaces); const MString typeName(fullname.Get(), fullname.Length()); #if BUILD_DEBUG if (module->TypeNameToTypeIndex.ContainsKey(typeName)) diff --git a/Source/Engine/Scripting/ScriptingType.h b/Source/Engine/Scripting/ScriptingType.h index acde5612f..4c41a68a8 100644 --- a/Source/Engine/Scripting/ScriptingType.h +++ b/Source/Engine/Scripting/ScriptingType.h @@ -97,6 +97,7 @@ enum class ScriptingTypes Structure = 1, Enum = 2, Class = 3, + Interface = 4, }; /// @@ -116,6 +117,15 @@ struct FLAXENGINE_API ScriptingType typedef void (*GetField)(void* ptr, const String& name, Variant& value); typedef void (*SetField)(void* ptr, const String& name, const Variant& value); + struct InterfaceImplementation + { + // Pointer to the type of the implemented interface. + const ScriptingTypeInitializer* InterfaceType; + + // The offset (in bytes) from the object pointer to the interface implementation. Used for casting object to the interface. + int16 VTableOffset; + }; + /// /// The managed class (cached, can be null if missing). /// @@ -151,6 +161,11 @@ struct FLAXENGINE_API ScriptingType /// const ScriptingTypeInitializer* BaseTypePtr; + /// + /// The list of interfaces implemented by this type (null if unused, list ends with null entry). + /// + const InterfaceImplementation* Interfaces; + /// /// The native size of the type value (in bytes). /// @@ -196,15 +211,6 @@ struct FLAXENGINE_API ScriptingType mutable ScriptingObject* DefaultInstance; } Script; - struct - { - // Class constructor method pointer - Ctor Ctor; - - // Class destructor method pointer - Dtor Dtor; - } Class; - struct { // Structure constructor method pointer @@ -228,13 +234,23 @@ struct FLAXENGINE_API ScriptingType // Structure field value setter SetField SetField; } Struct; + + struct + { + // Class constructor method pointer + Ctor Ctor; + + // Class destructor method pointer + Dtor Dtor; + } Class; }; ScriptingType(); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime = DefaultInitRuntime, SpawnHandler spawn = DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const InterfaceImplementation* interfaces = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime = DefaultInitRuntime, SpawnHandler spawn = DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const InterfaceImplementation* interfaces = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); ScriptingType(const ScriptingType& other); ScriptingType(ScriptingType&& other); ScriptingType& operator=(ScriptingType&& other) = delete; @@ -270,6 +286,11 @@ struct FLAXENGINE_API ScriptingType /// ScriptingObject* GetDefaultInstance() const; + /// + /// Gets the pointer to the implementation of the given interface type for this scripting type (including base types). Returns null if given interface is not implemented. + /// + const InterfaceImplementation* GetInterface(const ScriptingTypeInitializer* interfaceType) const; + String ToString() const; }; @@ -278,9 +299,10 @@ struct FLAXENGINE_API ScriptingType /// struct FLAXENGINE_API ScriptingTypeInitializer : ScriptingTypeHandle { - ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime = ScriptingType::DefaultInitRuntime, ScriptingType::SpawnHandler spawn = ScriptingType::DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, ScriptingType::SetupScriptVTableHandler setupScriptVTable = nullptr, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr); - ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType = nullptr); - ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime = ScriptingType::DefaultInitRuntime, ScriptingType::SpawnHandler spawn = ScriptingType::DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, ScriptingType::SetupScriptVTableHandler setupScriptVTable = nullptr, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); }; /// diff --git a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs index 60074883c..06cb55257 100644 --- a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs @@ -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; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs index b70cc8e63..7de3f96fe 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs @@ -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, }; } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index d7711a13a..b9096b3bb 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -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 diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index f164d2744..b21a4a135 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -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(); + 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 + { + 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 - { - 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(), + 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 - { - 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) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs index 5b58a8d8f..00cf0c7c3 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs @@ -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) diff --git a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs index 3ac5b37fc..a83ac2b55 100644 --- a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs @@ -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 /// /// The native class information for bindings generator. /// - public class ClassInfo : ApiTypeInfo + public class ClassInfo : ClassStructInfo { private static readonly HashSet InBuildScriptingObjectTypes = new HashSet { @@ -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)) diff --git a/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs new file mode 100644 index 000000000..dcb2d2e9e --- /dev/null +++ b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs @@ -0,0 +1,40 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; + +namespace Flax.Build.Bindings +{ + /// + /// The native class/structure information for bindings generator. + /// + public abstract class ClassStructInfo : ApiTypeInfo + { + public AccessLevel Access; + public AccessLevel BaseTypeInheritance; + public TypeInfo BaseType; + public List Interfaces; // Optional + public List InterfaceNames; // Optional + + public override void Init(Builder.BuildData buildData) + { + base.Init(buildData); + + if (Interfaces == null && InterfaceNames != null && InterfaceNames.Count != 0) + { + Interfaces = new List(); + 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; + } + } + } +} diff --git a/Source/Tools/Flax.Build/Bindings/InterfaceInfo.cs b/Source/Tools/Flax.Build/Bindings/InterfaceInfo.cs new file mode 100644 index 000000000..e0225f288 --- /dev/null +++ b/Source/Tools/Flax.Build/Bindings/InterfaceInfo.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +namespace Flax.Build.Bindings +{ + /// + /// The native class/structure interface information for bindings generator. + /// + 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; + } + } +} diff --git a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs index ca0a02247..baf80a6fe 100644 --- a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs @@ -8,10 +8,8 @@ namespace Flax.Build.Bindings /// /// The native structure information for bindings generator. /// - public class StructureInfo : ApiTypeInfo + public class StructureInfo : ClassStructInfo { - public AccessLevel Access; - public TypeInfo BaseType; public List Fields; public List 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; diff --git a/Source/Tools/Flax.Build/Flax.Build.csproj b/Source/Tools/Flax.Build/Flax.Build.csproj index a8221455f..9d415beed 100644 --- a/Source/Tools/Flax.Build/Flax.Build.csproj +++ b/Source/Tools/Flax.Build/Flax.Build.csproj @@ -69,12 +69,14 @@ + +