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 }