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