1 /++ 2 Compile-time introspection of Godot types 3 +/ 4 module godot.api.traits; 5 6 import godot.util.string; 7 import godot.api.udas; 8 import godot.api.reference; 9 10 import std.meta, std.traits; 11 12 import godot, godot.abi; 13 import godot.object; 14 15 /// https://p0nce.github.io/d-idioms/#Bypassing-@nogc 16 /// Casts @nogc out of a function or delegate type. 17 auto assumeNoGC(T)(T t) if (isFunctionPointer!T || isDelegate!T) { 18 enum attrs = functionAttributes!T | FunctionAttribute.nogc; 19 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 20 } 21 22 @nogc nothrow: 23 24 template from(string moduleName) { 25 mixin("import from = " ~ moduleName ~ ";"); 26 } 27 28 /++ 29 Adds the Ref wrapper to T, if T is a Reference type 30 +/ 31 template RefOrT(T) { 32 import godot.refcounted; 33 34 static if (isGodotClass!T && extends!(T, RefCounted)) 35 alias RefOrT = Ref!T; 36 else 37 alias RefOrT = T; 38 } 39 40 /++ 41 Removes the Ref wrapper from R, if present 42 +/ 43 template NonRef(R) { 44 static if (is(R : Ref!T, T)) 45 alias NonRef = T; 46 else 47 alias NonRef = R; 48 } 49 50 /++ 51 A UDA with which base Godot classes are marked. NOT used by new D classes. 52 +/ 53 package(godot) enum GodotBaseClass; 54 55 /++ 56 Determine if T is a class originally from the Godot Engine (but *not* a new D 57 class registered to Godot). 58 +/ 59 template isGodotBaseClass(T) { 60 static if (is(T == struct)) 61 enum bool isGodotBaseClass = 62 hasUDA!(T, GodotBaseClass); 63 else 64 enum bool isGodotBaseClass = false; 65 } 66 67 /++ 68 Determine if T is a D native script (extends a Godot base class). 69 +/ 70 template extendsGodotBaseClass(T) { 71 static if (is(T == class) && hasMember!(T, "owner")) { 72 enum bool extendsGodotBaseClass = isGodotBaseClass!(typeof(T.owner)); 73 } else 74 enum bool extendsGodotBaseClass = false; 75 } 76 77 /++ 78 A list of all of T's base classes, both script and C++, ending with GodotObject. 79 80 Has the same purpose as std.traits.BaseClassesTuple, but accounts for Godot's 81 script inheritance system. 82 +/ 83 template GodotBaseClasses(T) { 84 static if (isGodotBaseClass!T) 85 alias GodotBaseClasses = T.BaseClasses; 86 else static if (extendsGodotBaseClass!T) { 87 import std.traits : BaseClassesTuple; 88 89 // the last two D base classes are GodotScript!<Base> and Object. 90 alias GodotBaseClasses = AliasSeq!(BaseClassesTuple!(Unqual!T)[0 .. $ - 2], 91 GodotClass!T, GodotClass!T.BaseClasses); 92 } 93 } 94 95 /++ 96 Checks whether R is a subtype of ParentR by Godot's script inheritance system. 97 Both D script and C++ classes are accounted for. 98 If R and ParentR are the same, `extends` is true as well. 99 +/ 100 template extends(R, ParentR) { 101 alias T = NonRef!R; 102 alias Parent = NonRef!ParentR; 103 static if (is(Unqual!T : Unqual!Parent)) 104 enum bool extends = true; 105 else 106 enum bool extends = staticIndexOf!(Unqual!Parent, GodotBaseClasses!T) != -1; 107 } 108 109 /++ 110 Get the Godot class of R (the class of the `owner` for D native scripts) 111 +/ 112 template GodotClass(R) { 113 alias T = NonRef!R; 114 static if (isGodotBaseClass!T) 115 alias GodotClass = T; 116 else static if (extendsGodotBaseClass!T) 117 alias GodotClass = typeof(T.owner); 118 } 119 120 /++ 121 Determine if T is any Godot class (base C++ class or D native script, but NOT 122 a godot struct) 123 +/ 124 enum bool isGodotClass(T) = extendsGodotBaseClass!T || isGodotBaseClass!T; 125 126 /++ 127 Get the C++ Godot Object pointer of either a Godot Object OR a D native script. 128 129 Useful for generic code. 130 +/ 131 GodotClass!T getGodotObject(T)(in T t) if (isGodotClass!T) { 132 GodotClass!T ret; 133 ret._godot_object = t.getGDExtensionObject; 134 return ret; 135 } 136 137 GodotClass!(NonRef!R) getGodotObject(R)(auto ref R r) if (is(R : Ref!U, U)) { 138 return r._reference; 139 } 140 141 package(godot) godot_object getGDExtensionObject(T)(in T t) if (isGodotClass!T) { 142 static if (isGodotBaseClass!T) 143 return cast(godot_object) t._godot_object; 144 static if (extendsGodotBaseClass!T) { 145 return (t) ? cast(godot_object) t.owner._godot_object : godot_object.init; 146 } 147 } 148 149 package(godot) godot_object getGDExtensionObject(R)(auto ref R r) if (is(R : Ref!U, U)) { 150 return r._reference._godot_object; 151 } 152 153 /++ 154 Alias to default-constructed T, as an expression. 155 156 A few Godot core types can't use D's `init` because they need to call a C++ 157 constructor through GDExtension. 158 +/ 159 template godotDefaultInit(T) { 160 static if (is(T : Array)) 161 alias godotDefaultInit = Alias!(Array.make); 162 else static if (is(T : Dictionary)) 163 alias godotDefaultInit = Alias!( 164 Dictionary.make); 165 else 166 alias godotDefaultInit = Alias!(T.init); 167 } 168 169 /++ 170 Get the Godot-compatible default value of a field in T. 171 +/ 172 auto getDefaultValueFromAlias(T, string fieldName)() { 173 alias a = Alias!(mixin("T." ~ fieldName)); 174 alias P = typeof(a); 175 176 static if (hasUDA!(a, DefaultValue)) { 177 alias defExprSeq = TemplateArgsOf!(getUDAs!(a, DefaultValue)[0]); 178 static if (isCallable!(defExprSeq[0])) 179 return defExprSeq[0](); 180 else 181 return defExprSeq[0]; 182 } else static if (is(typeof({ P p; }))) { 183 import std.math : isNaN; 184 185 static if (isFloatingPoint!P && a.init.isNaN) { 186 // Godot doesn't support NaNs. Initialize properties to 0.0 instead. 187 return P(0.0); 188 } else 189 return a.init; 190 } else { 191 return Variant.init; 192 } 193 } 194 195 package(godot) enum string dName(alias a) = __traits(identifier, a); 196 package(godot) template godotName(alias a) { 197 alias udas = getUDAs!(a, Rename); 198 static if (udas.length == 0) { 199 static if (is(a == class) || is(a == struct)) { 200 import std.string; 201 202 // for classes keep using upper-case type name to match godot style 203 enum string godotName = __traits(identifier, a).capitalize; 204 } else { 205 version (GodotNoAutomaticNamingConvention) 206 enum string godotName = __traits(identifier, a); 207 else 208 enum string godotName = __traits(identifier, a).camelToSnake; 209 } 210 } else { 211 static assert(udas.length == 1, "Multiple Rename UDAs on " ~ 212 fullyQualifiedName!a ~ "? Why?"); 213 214 static if (is(udas[0])) 215 static assert(0, "Construct the UDA with a string: @Rename(\"name\")"); 216 else { 217 enum Rename uda = udas[0]; 218 enum string godotName = uda.name; 219 } 220 } 221 }