public void HandleEnumType(TypeDefinition type) { if (type.HasAttribute("CppCustomImpl")) { return; } BindGem.WarningIf(type.HasAttribute("CppName"), $"Type {type} has CppName but is not CppCustomImpl -- CppName will be ignored for fully generated types"); var enumSize = type.GetEnumSize(); string baseTypeStr = null; if (enumSize == 8) { baseTypeStr = "int8_t"; } else if (enumSize == 16) { baseTypeStr = "int16_t"; } else if (enumSize == 32) { baseTypeStr = "int32_t"; } else if (enumSize == 64) { baseTypeStr = "int64_t"; } string enumTemplate = @"enum class {{{typeName}}} : {{{baseEnumType}}} { {{{name=value}}} };"; var enumList = type.GetEnumKeyValues(); var enumValues = enumList.Select(e => $" {e.key} = {e.value}"); var code = ExpandStringTemplate(enumTemplate, "typeName", type.Name, "baseEnumType", baseTypeStr, "name=value", string.Join(",\n", enumValues)); if (!BindGem.DOTS) { // Register Enum var name = type.Name; var namespacedName = type.JSName(); var identifier = type.FullyQualifiedCppName("_").ToLower(); var members = enumList.Select(e => $" ut::meta::enumv(\"{e.key}\", {name}::{e.key})"); var body = $"{(members.Count() > 0 ? ",\n" : "")}{string.Join(",\n", members)}"; code += ExpandStringTemplate( "\nREFLECT_ENUM({{{name}}}, \"{{{namespacedName}}}\", {{{identifier}}}{{{body}}})", "name", name, "namespacedName", namespacedName, "identifier", identifier, "body", body); } hpp(WrapInNamespace(code, type.CppNamespace())); }
internal static JSSpecialType JavaScriptSpecialType(this TypeReference typeRef) { TypeDefinition type = typeRef.Resolve(); if (!type.HasAttribute("JSCustomImpl")) { return(JSSpecialType.None); } switch (type.Name) { case "Vector2": return(JSSpecialType.Vector2); case "Vector3": return(JSSpecialType.Vector3); case "Vector4": return(JSSpecialType.Vector4); case "Quaternion": return(JSSpecialType.Quaternion); case "Matrix3x3": return(JSSpecialType.Matrix3); case "Matrix4x4": return(JSSpecialType.Matrix4); case null: return(JSSpecialType.None); } BindGem.FatalError($"Unknown JSCustomImpl '{type.Name}'");//, type.Locations[0]); return(JSSpecialType.None); }
public void HandleStructType(TypeDefinition type) { string cppCode = "{{{sizeCheckNoteForInternalStructTypes}}}" + InsertEmscriptenStaticAssertsForStruct(type, !type.HasAttribute("CppCustomImpl"), 32) + InsertEmscriptenStaticAssertsForStruct(type, !type.HasAttribute("CppCustomImpl"), 64); if (type.HasAttribute("CppCustomImpl")) { // insert static asserts for offsets cppCode = ExpandStringTemplate(cppCode, "sizeCheckNoteForInternalStructTypes", $"/* Generating only size check for internal struct {type.FullName} */\n"); } else { // Anything that's internal has the C++ side handled by hand cppCode = ExpandStringTemplate(cppCode, "sizeCheckNoteForInternalStructTypes", ""); BindGem.WarningIf(type.HasAttribute("CppName"), $"Type {type} has CppName but is not CppCustomImpl -- CppName will be ignored for fully generated types"); } cpp(cppCode); if (!type.HasAttribute("CppCustomImpl")) { BindGem.FatalErrorIf(type.HasAttribute("SharedPtrAttribute") /*, type*/, $"Can't have a SharedPtr type {type} that's not also [Internal]"); var allFields = type.Fields.Where(f => !f.IsStatic); var fields = allFields.Select(field => (field.FieldType.IsDynamicArray() ? $"/* DYNAMIC ARRAY -- {field.Name}; */" : $"{field.FieldType.CppFieldType()} {field.Name};")); string hppTemplate = @"struct {{{structName}}} { {{{type field;}}} };"; var hppCode = ExpandStringTemplate(hppTemplate, "structName", type.Name, "type field;", fields); if (!BindGem.DOTS) { // Register Struct var name = type.Name; var namespacedName = type.JSName(); var identifier = type.FullyQualifiedCppName("_").ToLower(); var members = type.Fields.Select(f => $" ut::meta::member(\"{f.Name}\", &{name}::{f.Name})"); var body = $"{(members.Count() > 0 ? ",\n" : "")}{string.Join(",\n", members)}"; hppCode += ExpandStringTemplate( "\nREFLECT_STRUCT({{{name}}}, \"{{{namespacedName}}}\", {{{identifier}}}{{{body}}})", "name", name, "namespacedName", namespacedName, "identifier", identifier, "body", body); } hpp(WrapInNamespace(hppCode, type.Namespace)); } }
public void HandleCallbackType(TypeDefinition type) { var cppFnTypeName = type.FullyQualifiedCppName("_"); if (BindGem.PureJS) { BindGem.FatalError( "Callback (delegates) cannot be used with pure JS generation (what are you trying to bind anyway?)"); } var fn = type.DelegateInvokeMethod(); var fnParams = fn.Parameters.Select(p => p.ParameterType.Resolve()).ToList(); var numParams = fn.Parameters.Count; BindGem.FatalErrorIf(fn.ReturnType.Resolve().MetadataType != MetadataType.Void, /*type,*/ $"Delegate (callback) {type.Name} returning non-void not supported (yet, fixable!)"); // for delegates, we have to convert each parameter to its JS representation, similar to what we would do for return types // We only handle very simple types here exportSymbol($"{type.CppFnPrefix()}_call"); cpp($"using {type.CppFnPrefix()}_callType = std::function<void({string.Join(",", fnParams.Select(p=>p.EmCppArgType()).ToArray())})>;"); cpp($"UT_JSFN void {type.CppFnPrefix()}_call("); cpp($" uint32_t self"); for (int p = 0; p < numParams; ++p) { cpp($" ,{fnParams[p].EmCppArgType()} arg{p + 1}"); } cpp($") {{"); cpp($"#ifdef __EMSCRIPTEN__"); cpp($" EM_ASM({{"); var passParams = new StringBuilder(); var argParams = new StringBuilder(); for (int p = 0; p < numParams; ++p) { string passParam; cpp($" {fnParams[p].JSCallbackArgToEmscripten(p + 1, out passParam)}"); passParams.Append($", {passParam}"); argParams.Append($"${p + 1}"); if (p != numParams - 1) { argParams.Append(", "); } } cpp($" {type.JSName()}._cb.map[$0]({argParams});"); cpp($" }}, self{passParams});"); cpp($"#else"); cpp($" // TODO: Invoke callback for C#"); cpp($"#endif"); cpp($"}}"); }
// Outputs the given block of code wrapped inside nested // "namespace A { namespace B { /* code */ } }" format. // Use C# period "." to address nested namespaces, instead of C++ "::", // i.e. "A.B" instead of "A::B". // If the code contains template blocks {{{beginNamespace}}} or {{{endNamespace}}}, // the namespace is expanded in their place. Otherwise the whole code block // is wrapped inside the namespace. internal static string WrapInNamespace(string code, string nameSpace) { if (nameSpace == null || nameSpace.Length == 0) { code = code.Replace("{{{beginNamespace}}}", ""); code = code.Replace("{{{endNamespace}}}", ""); return(code); } var namespaces = nameSpace.Split('.'); namespaces[0] = BindGem.TranslateCppTopLevelNamespace(namespaces[0]); string pre = "", post = ""; foreach (var name in namespaces) { pre += $"namespace {name} {{ "; post += "}"; } if (code.Contains("{{{beginNamespace}}}")) { code = code.Replace("{{{beginNamespace}}}", pre); } else { code = pre + '\n' + code; } if (code.Contains("{{{endNamespace}}}")) { code = code.Replace("{{{endNamespace}}}", post); } else { code = code + '\n' + post; } return(code); }
public void HandleComponentType(TypeDefinition type) { if (type.HasAttribute("CppCustomImpl")) { cpp($"/* Skipped generating code for internal component {type} */"); return; } BindGem.WarningIf(type.HasAttribute("CppName"), $"Type {type} has CppName but is not CppCustomImpl -- CppName will be ignored for fully generated types"); // Validate the field names if (!BindGem.DOTS) { foreach (var f in type.Fields) { BindGem.FatalErrorIf(!Char.IsLower(f.Name[0]), /*f, TODO*/ $"Field {f.Name} of {type.Name} must start with a lowercase letter!"); } } // g++ has a bug where it doesn't actually emit this function unless it's explicitly marked as used. // But we must not use this for emscripten, because (used) means export it to JS as well. string entityFixupTemplate = @" struct ComponentReflectionData { using ReflectionDataType_ = {{{name}}}; static const std::initializer_list<int>& #if defined(__GNUC__) && !defined(__EMSCRIPTEN__) && !defined(__clang__) __attribute__((noinline, used)) #endif EntityOffsets() { static const std::initializer_list<int> l = { {{{entityfixups}}} }; return l; } };"; string hppTemplate = @"struct {{{name}}} {{{maybeExtends}}}{ {{{fields}}} {{{constructors}}} {{{memberFunctions}}} {{{entityOffsetDecl}}} };"; // Data fields var fields = type.Fields.Where(f => !f.IsStatic).Select(field => $"{field.FieldType.CppFieldType()} {field.Name};").ToList(); string entityOffsetDecl = null; List <string> ctors = new List <string>(); List <string> memberFunctions = new List <string>(); if (!BindGem.DOTS) { // Internal entity field offsets, if any // Note we are assuming 32-bit platform here for offset calculations var entityOffsets = TypeUtils.GetEntityFieldOffsets(type, 32); if (entityOffsets.Count != 0) { entityOffsetDecl = ExpandStringTemplate(entityFixupTemplate, "name", type.Name, "entityfixups", string.Join(",", entityOffsets)); } // Constructors bool hasZeroArgConstructor = false; foreach (var ctor in type.Constructors()) { //var args = ctor.Parameters.Select(arg => $"{arg.ParameterType.Resolve().EmCppArgType()} {arg.Name}"); var args = UnnamedParameterList(ctor.Parameters); if (args.Count() == 0) { hasZeroArgConstructor = true; } ctors.Add($"{type.Name}({string.Join(",", args)});"); } if (ctors.Count > 0 && !hasZeroArgConstructor) { ctors.Insert(0, $"{type.Name}() = default;"); } // Member functions foreach (var method in type.MemberFunctions()) { // TODO: I think this is better to keep the argument names when generating, rather than an unnamed list "arg0,arg1", but keep the output // identical for easy verification for now. // var args = method.Parameters.Select(arg => $"{arg.ParameterType.Resolve().EmCppArgType()} {arg.Name}"); var args = UnnamedParameterList(method.Parameters); memberFunctions.Add( $"{type.EmCppReturnType(method)} {method.CppName()}({string.Join(",", args)});"); } } var extends = ""; var ns = "ut"; if (BindGem.DOTS) { ns = "Unity::Entities"; } if (type.IsComplex()) { extends = $": {ns}::IComplexComponentData "; } else if (type.IsSharedComponentType()) { extends = $": {ns}::ISharedComponentData "; } else if (type.IsSystemStateComponentType()) { extends = $": {ns}::ISystemStateComponentData "; } else if (type.IsBufferElementComponentType()) { extends = $": {ns}::IBufferElementData "; } string hppCode = ExpandStringTemplate(hppTemplate, "name", type.Name, "maybeExtends", extends, "fields", fields, "constructors", ctors, "memberFunctions", memberFunctions, "entityOffsetDecl", entityOffsetDecl); if (!BindGem.DOTS) { // Register Component var name = type.Name; var namespacedName = type.JSName(); var identifier = type.FullyQualifiedCppName("_").ToLower(); var members = type.Fields.Select(f => $" ut::meta::member(\"{f.Name}\", &{name}::{f.Name})"); var body = $"{(members.Count() > 0 ? ",\n" : "")}{string.Join(",\n", members)}"; hppCode += ExpandStringTemplate( "\nREFLECT_COMPONENT({{{name}}}, \"{{{namespacedName}}}\", {{{identifier}}}{{{body}}})", "name", name, "namespacedName", namespacedName, "identifier", identifier, "body", body); } hpp(WrapInNamespace(hppCode, type.Namespace)); // insert static asserts for field offsets in the C++ cpp(InsertEmscriptenStaticAssertsForStruct(type, true, 32)); cpp(InsertEmscriptenStaticAssertsForStruct(type, true, 64)); // Make the component info templated functions extern for compile time speedups // Explicitly don't do this for ComponentId<T>, since we want that to get inlined // as much as possible // extern template ComponentTypeId ComponentId<::{nsn}>(); // template ComponentTypeId ComponentId<::{nsn}>(); var nsn = type.FullyQualifiedCppName(); var thisprivate = $"priv_{BindGem.BaseOutputName}_{componentIndex}"; componentIndex++; if (!BindGem.DOTS) { hpp($@" namespace ut {{ #if !defined({BindGem.DefineGuard}) extern template ComponentInfo& InitComponentInfoFor<::{nsn}>(); #endif }} "); } else { hpp($@" #if !defined({BindGem.DefineGuard}) extern DLLIMPORT ComponentTypeId {thisprivate}_cid; #else extern DLLEXPORT ComponentTypeId {thisprivate}_cid; #endif template<> inline ComponentTypeId ComponentId<::{nsn}>() {{ return {thisprivate}_cid; }} template<> inline ComponentTypeId InitComponentId<::{nsn}>() {{ if ({thisprivate}_cid == -1) {{ {thisprivate}_cid = Unity::Entities::TypeManager::TypeIndexForStableTypeHash({type.CalculateStableTypeHash()}ull); }} return {thisprivate}_cid; }} "); } if (!BindGem.DOTS) { cpp($@" namespace ut {{ template ComponentInfo& InitComponentInfoFor<::{nsn}>(); ComponentTypeId {thisprivate}_cid = -1; template<> ComponentTypeId InitComponentId<::{nsn}>() {{ if ({thisprivate}_cid == -1) {{ {thisprivate}_cid = InitComponentInfoFor<::{nsn}>().cid; }} return {thisprivate}_cid; }} }} "); } else { cpp($@" DLLEXPORT ComponentTypeId {thisprivate}_cid = -1; "); } }
public void HandleInterfaceType(TypeDefinition type) { if (type.HasAttribute("PureJSService")) { return; } BindGem.FatalErrorIf(BindGem.PureJS, "Interfaces cannot be used with pure JS generation (what are you trying to bind anyway?)"); var jsTypeName = type.JSName(); var cppTypeName = type.FullyQualifiedCppName(); var cppFnTypeName = type.CppFnPrefix(); // C# interface with [SharedPtr] bool isSharedPtr = type.IsSharedPtrType(); bool isNonSharedPtr = type.IsNonSharedPtrType(); // C# interface with [Service] bool isService = type.IsServiceRefType(); // C# struct bool isStruct = type.IsStructValueType(); // // Constructors and preamble // // At some point in the future we may support multiple constructors and constructors with arguments, // and some way to specify them. // For now, we have a Constructable attribute that enables a single, no-argument constructor. bool isConstructable = type.HasAttribute("Constructable"); if (isConstructable) { dts($"function _{type.CppFnPrefix()}_{type.Name}(): number;"); exportSymbol($"{type.CppFnPrefix()}_{type.Name}"); cpp($"UT_JSFN {cppTypeName}* {type.CppFnPrefix()}_{type.Name}() {{"); if (isSharedPtr) { cpp($" return ut::PtrTable::persist<{cppTypeName}>(std::make_shared<{cppTypeName}>());"); } else { cpp($" return new {cppTypeName}();"); } cpp($"}}"); } if (isSharedPtr || isNonSharedPtr) { string releaseTemplate; exportSymbol($"{cppFnTypeName}_shRelease"); if (isSharedPtr) { releaseTemplate = @" UT_JSFN void {{{cppFnTypeName}}}_shRelease({{{cppTypeName}}}* ptr) { ut::PtrTable::release<{{{cppTypeName}}}>(ptr); } "; } else { releaseTemplate = @"UT_JSFN void {{{cppFnTypeName}}}_shRelease({{{cppTypeName}}}* ptr) { delete ptr; }"; } dts($"function _{type.CppFnPrefix()}_shRelease(self: number): void;"); var releaseFnString = CppBindingsGenerator.ExpandStringTemplate(releaseTemplate, "cppTypeName", cppTypeName, "cppFnTypeName", cppFnTypeName); cpp(releaseFnString); } HandleTypeMethods(type); }