1 /++
2 Initialization, termination, and registration of D libraries in Godot
3 +/
4 module godot.d.register;
5 
6 import std.format;
7 import std.meta, std.traits;
8 import std.experimental.allocator, std.experimental.allocator.mallocator;
9 import core.stdc.stdlib : malloc, free;
10 
11 import godot.d.meta;
12 import godot.d.script;
13 import godot.d.wrap;
14 import godot.d.udas;
15 import godot.d.reference;
16 
17 import godot.core, godot.c;
18 
19 import godot.gdnativelibrary;
20 
21 alias GodotInitOptions = const(godot_gdnative_init_options*);
22 alias GodotTerminateOptions = const(godot_gdnative_terminate_options*);
23 
24 /++
25 Pass this enum to GodotNativeInit and GodotNativeTerminate to skip D runtime
26 initialization/termination.
27 +/
28 enum NoDRuntime;
29 
30 /++
31 This mixin will generate the GDNative C interface functions for this D library.
32 Pass to it a name string for the library, followed by the GodotScript types to
33 register, functions to call, and other options to configure Godot-D.
34 
35 The symbolPrefix must match the GDNativeLibrary's symbolPrefix in Godot.
36 
37 D runtime will be initialized and terminated, unless you pass $(D NoDRuntime).
38 
39 Functions taking GodotInitOptions or no arguments will be called at init.
40 Functions taking GodotTerminateOptions will be called at termination.
41 
42 Example:
43 ---
44 import godot, godot.node;
45 class TestClass : GodotScript!Node
46 { }
47 mixin GodotNativeLibrary!(
48 	"testlib",
49 	TestClass,
50 	(GodotInitOptions o){ print("Initialized"); },
51 	(GodotTerminateOptions o){ print("Terminated"); }
52 );
53 ---
54 +/
55 mixin template GodotNativeLibrary(string symbolPrefix, Args...)
56 {
57 	private static import godot.c;
58 	private static import godot.gdnativelibrary;
59 	private import godot.d.reference;
60 	
61 	private __gshared Ref!(godot.gdnativelibrary.GDNativeLibrary) _GODOT_library;
62 	private __gshared void* _GODOT_library_handle;
63 	
64 	/// HACK: empty main to force the compiler to add emulated TLS.
65 	version(Android) void main() { }
66 	
67 	pragma(mangle, symbolPrefix~"gdnative_init")
68 	export extern(C) static void godot_gdnative_init(godot.c.godot_gdnative_init_options* options)
69 	{
70 		import godot.c.api;
71 		import godot.d.reference;
72 		import std.meta, std.traits;
73 		import core.runtime : Runtime;
74 		static if(staticIndexOf!(NoDRuntime, Args) == -1) Runtime.initialize();
75 		
76 		godot_gdnative_api_struct_init(options.api_struct);
77 		
78 		import core.exception : assertHandler;
79 		assertHandler = (options.in_editor) ? (&godotAssertHandlerEditorDebug)
80 			: (&godotAssertHandlerCrash);
81 		
82 		*cast(typeof(options.gd_native_library)*)&_GODOT_library = options.gd_native_library;
83 		_GODOT_library.reference();
84 		
85 		foreach(Arg; Args)
86 		{
87 			static if(is(Arg)) { } // is type
88 			else static if( isCallable!Arg )
89 			{
90 				static if( is(typeof(Arg())) ) Arg();
91 				else static if( is(typeof(Arg(options))) ) Arg(options);
92 			}
93 			else static if(Arg == NoDRuntime) { }
94 			else
95 			{
96 				static assert(0, "Unrecognized argument <"~Arg.stringof~"> passed to GodotNativeLibrary");
97 			}
98 		}
99 	}
100 	pragma(mangle, symbolPrefix~"nativescript_init")
101 	export extern(C) static void godot_nativescript_init(void* handle)
102 	{
103 		import std.meta, std.traits;
104 		import godot.d.register : register;
105 		
106 		_GODOT_library_handle = handle;
107 		
108 		foreach(Arg; Args)
109 		{
110 			static if(is(Arg)) // is type
111 			{
112 				static assert(is(Arg == class) && extendsGodotBaseClass!Arg,
113 					fullyQualifiedName!Arg ~ " is not a D class that extends a Godot class!");
114 				register!Arg(_GODOT_library_handle, _GODOT_library);
115 			}
116 			else static if( isCallable!Arg )
117 			{
118 				
119 			}
120 			else static if(Arg == NoDRuntime) { }
121 			else
122 			{
123 				static assert(0, "Unrecognized argument <"~Arg.stringof~"> passed to GodotNativeLibrary");
124 			}
125 		}
126 	}
127 	pragma(mangle, symbolPrefix~"gdnative_terminate")
128 	export extern(C) static void godot_gdnative_terminate(godot.c.godot_gdnative_terminate_options* options)
129 	{
130 		import std.meta, std.traits;
131 		import godot.d.script : NativeScriptTemplate;
132 		foreach(Arg; Args)
133 		{
134 			static if(is(Arg)) // is type
135 			{
136 				NativeScriptTemplate!Arg.unref();
137 			}
138 			else static if(isCallable!Arg)
139 			{
140 				static if(is(typeof(Arg(options)))) Arg(options);
141 			}
142 			else static if(Arg == NoDRuntime) { }
143 			else
144 			{
145 				static assert(0, "Unrecognized argument <"~Arg.stringof~"> passed to GodotNativeLibrary");
146 			}
147 		}
148 		
149 		_GODOT_library.unref();
150 		
151 		import core.runtime : Runtime;
152 		static if(staticIndexOf!(NoDRuntime, Args) == -1) Runtime.terminate();
153 	}
154 }
155 
156 private extern(C)
157 godot_variant _GODOT_nop(godot_object o, void* methodData,
158 	void* userData, int numArgs, godot_variant** args)
159 {
160 	godot_variant n;
161 	_godot_api.godot_variant_new_nil(&n);
162 	return n;
163 }
164 
165 /++
166 Register a class and all its $(D @GodotMethod) member functions into Godot.
167 +/
168 void register(T)(void* handle, GDNativeLibrary lib) if(is(T == class))
169 {
170 	import godot.c;
171 	import godot.object, godot.resource;
172 	import godot.d;
173 	static import godot.nativescript;
174 	import std.string : toStringz, fromStringz; /// TODO: remove GCed functions
175 
176 	static if(BaseClassesTuple!T.length == 2) // base class is GodotScript; use owner
177 	{
178 		alias Base = typeof(T.owner);
179 		alias baseName = Base._GODOT_internal_name;
180 	}
181 	else // base class is another D script
182 	{
183 		alias Base = BaseClassesTuple!T[0];
184 		static if(hasUDA!(Base, Rename)) enum immutable(char*) baseName = TemplateArgsOf!(
185 			getUDAs!(Base, Rename)[0])[0];
186 		else enum immutable(char*) baseName = fullyQualifiedName!Base;
187 	}
188 	
189 	static if(hasUDA!(T, Rename)) enum immutable(char*) name = TemplateArgsOf!(
190 		getUDAs!(T, Rename)[0])[0];
191 	else enum immutable(char*) name = fullyQualifiedName!T;
192 	
193 	auto icf = godot_instance_create_func(&createFunc!T, null, null);
194 	auto idf = godot_instance_destroy_func(&destroyFunc!T, null, null);
195 	
196 	static if(hasUDA!(T, Tool)) _godot_nativescript_api.godot_nativescript_register_tool_class(handle, name, baseName, icf, idf);
197 	else _godot_nativescript_api.godot_nativescript_register_class(handle, name, baseName, icf, idf);
198 	
199 	if(GDNativeVersion.hasNativescript!(1, 1))
200 	{
201 		_godot_nativescript_api.godot_nativescript_set_type_tag(handle, name, NativeScriptTag!T.tag);
202 	}
203 	else // register a no-op function that indicates this is a D class
204 	{
205 		godot_instance_method md;
206 		md.method = &_GODOT_nop;
207 		md.free_func = null;
208 		_godot_nativescript_api.godot_nativescript_register_method(handle, name, "_GDNATIVE_D_typeid", godot_method_attributes.init, md);
209 	}
210 	
211 	foreach(mf; godotMethods!T)
212 	{
213 		godot_method_attributes ma;
214 		static if(is( getUDAs!(mf, Method)[0] )) ma.rpc_type = godot_method_rpc_mode
215 			.GODOT_METHOD_RPC_MODE_DISABLED;
216 		else
217 		{
218 			ma.rpc_type = cast(godot_method_rpc_mode)(getUDAs!(mf, Method)[0].rpcMode);
219 		}
220 		
221 		godot_instance_method md;
222 		static if(godotName!mf == "_ready" && onReadyFieldNames!T.length)
223 		{
224 			md.method = &OnReadyWrapper!T.callOnReady;
225 		}
226 		else md.method = &MethodWrapper!(T, mf).callMethod;
227 		md.free_func = null;
228 		
229 		_godot_nativescript_api.godot_nativescript_register_method(handle, name, godotName!mf.toStringz, ma, md); /// TODO: remove GCed functions
230 	}
231 	
232 	// OnReady when there is no _ready method
233 	static if(staticIndexOf!("_ready", staticMap!(godotName, godotMethods!T)) == -1
234 		&& onReadyFieldNames!T.length)
235 	{
236 		enum ma = godot_method_attributes.init;
237 		godot_instance_method md;
238 		md.method = &OnReadyWrapper!T.callOnReady;
239 		_godot_nativescript_api.godot_nativescript_register_method(handle, name, "_ready", ma, md);
240 	}
241 	
242 	foreach(sName; godotSignals!T)
243 	{
244 		alias s = Alias!(mixin("T."~sName));
245 		static assert(hasStaticMember!(T, sName), "Signal declaration "~fullyQualifiedName!s
246 			~" must be static. Otherwise it would take up memory in every instance of "~T.stringof);
247 		
248 		godot_signal gs;
249 		(*cast(String*)&gs.name) = String(godotName!s);
250 		gs.num_args = Parameters!s.length;
251 		
252 		static if(Parameters!s.length)
253 		{
254 			godot_signal_argument[Parameters!s.length] args;
255 			gs.args = args.ptr;
256 		}
257 		
258 		foreach(pi, P; Parameters!s)
259 		{
260 			static assert(Variant.compatible!P, fullyQualifiedName!s~" parameter "~pi.text~" \""
261 				~ParameterIdentifierTuple!s[pi]~"\": type "~P.stringof~" is incompatible with Godot");
262 			(*cast(String*)&args[pi].name) = (ParameterIdentifierTuple!s[pi].length)
263 				? String(ParameterIdentifierTuple!s[pi])
264 				: (String(P.stringof) ~ String("Arg") ~ Variant(pi).as!String);
265 			args[pi].type = Variant.variantTypeOf!P;
266 			args[pi].usage = cast(godot_property_usage_flags)Property.Usage.defaultUsage;
267 		}
268 		
269 		_godot_nativescript_api.godot_nativescript_register_signal(handle, name, &gs);
270 	}
271 	
272 	enum bool matchName(string p, alias a) = (godotName!a == p);
273 	foreach(pName; godotPropertyNames!T)
274 	{
275 		alias getterMatches = Filter!(ApplyLeft!(matchName, pName), godotPropertyGetters!T);
276 		static assert(getterMatches.length <= 1); /// TODO: error message
277 		alias setterMatches = Filter!(ApplyLeft!(matchName, pName), godotPropertySetters!T);
278 		static assert(setterMatches.length <= 1);
279 		
280 		godot_property_set_func sf;
281 		godot_property_get_func gf;
282 		godot_property_attributes attr;
283 		
284 		static if(getterMatches.length) alias P = NonRef!(ReturnType!(getterMatches[0]));
285 		else alias P = Parameters!(setterMatches[0])[0];
286 		static assert(!is(P : Ref!U, U)); /// TODO: proper Ref handling
287 		enum Variant.Type vt = extractPropertyVariantType!(getterMatches, setterMatches);
288 		attr.type = cast(godot_int)vt;
289 		
290 		enum Property uda = extractPropertyUDA!(getterMatches, setterMatches);
291 		attr.rset_type = cast(godot_method_rpc_mode)uda.rpcMode;
292 		attr.hint = cast(godot_property_hint)uda.hint;
293 
294 		static if(vt == Variant.Type.object && extends!(P, Resource))
295 		{
296 			attr.hint |= godot_property_hint.GODOT_PROPERTY_HINT_RESOURCE_TYPE;
297 		}
298 
299 		static if(uda.hintString.length) _godot_api.godot_string_parse_utf8(
300 			&attr.hint_string, uda.hintString.ptr);
301 		else
302 		{
303 			static if(vt == Variant.Type.object)
304 			{
305 				_godot_api.godot_string_parse_utf8(&attr.hint_string,
306 					GodotClass!P._GODOT_internal_name);
307 			}
308 			else _godot_api.godot_string_new(&attr.hint_string);
309 		}
310 		attr.usage = cast(godot_property_usage_flags)(uda.usage |
311 			Property.Usage.scriptVariable);
312 		
313 		Variant defval;
314 		enum gDef = getterMatches.length && hasUDA!(getterMatches[0], DefaultValue);
315 		enum sDef = getterMatches.length && hasUDA!(setterMatches[0], DefaultValue);
316 		static if(gDef || sDef)
317 		{
318 			static if(gDef) alias defExprSeq = TemplateArgsOf!(getUDAs!(getterMatches[0], DefaultValue)[0]);
319 			else alias defExprSeq = TemplateArgsOf!(getUDAs!(setterMatches[0], DefaultValue)[0]);
320 			defval = defExprSeq[0];
321 		}
322 		else static if( is(typeof( { P p; } )) ) // use type's default value
323 		{
324 			static if(isFloatingPoint!P)
325 			{
326 				// Godot doesn't support NaNs. Initialize properties to 0.0 instead.
327 				defval = 0.0;
328 			}
329 			else defval = P.init;
330 		}
331 		else
332 		{
333 			/// FIXME: call default constructor function
334 			defval = null;
335 		}
336 		attr.default_value = defval._godot_variant;
337 		
338 		static if(getterMatches.length)
339 		{
340 			alias GetWrapper = MethodWrapper!(T, getterMatches[0]);
341 			gf.get_func = &GetWrapper.callPropertyGet;
342 			gf.free_func = null;
343 		}
344 		else
345 		{
346 			gf.get_func = &emptyGetter;
347 		}
348 		
349 		static if(setterMatches.length)
350 		{
351 			alias SetWrapper = MethodWrapper!(T, setterMatches[0]);
352 			sf.set_func = &SetWrapper.callPropertySet;
353 			sf.free_func = null;
354 		}
355 		else
356 		{
357 			sf.set_func = &emptySetter;
358 		}
359 		
360 		_godot_nativescript_api.godot_nativescript_register_property(handle, name, pName.toStringz, &attr, sf, gf); /// TODO: remove GCed functions
361 	}
362 	foreach(pName; godotPropertyVariableNames!T)
363 	{
364 		immutable(char*) propName = godotName!(mixin("T."~pName)).toStringz; /// TODO: remove GCed functions
365 		
366 		import std.string;
367 		
368 		godot_property_set_func sf;
369 		godot_property_get_func gf;
370 		godot_property_attributes attr;
371 		
372 		alias P = typeof(mixin("T."~pName));
373 		enum Variant.Type vt = Variant.variantTypeOf!P;
374 		attr.type = cast(godot_int)vt;
375 		
376 		alias udas = getUDAs!(mixin("T."~pName), Property);
377 		enum Property uda = is(udas[0]) ? Property.init : udas[0];
378 		attr.rset_type = cast(godot_method_rpc_mode)uda.rpcMode;
379 		attr.hint = cast(godot_property_hint)uda.hint;
380 
381 		static if(vt == Variant.Type.object && is(GodotClass!P : Resource))
382 		{
383 			attr.hint |= godot_property_hint.GODOT_PROPERTY_HINT_RESOURCE_TYPE;
384 		}
385 
386 		static if(uda.hintString.length) _godot_api.godot_string_parse_utf8(
387 			&attr.hint_string, uda.hintString.ptr);
388 		else
389 		{
390 			static if(vt == Variant.Type.object)
391 			{
392 				_godot_api.godot_string_parse_utf8(&attr.hint_string,
393 					GodotClass!P._GODOT_internal_name);
394 			}
395 			else _godot_api.godot_string_new(&attr.hint_string);
396 		}
397 		attr.usage = cast(godot_property_usage_flags)uda.usage |
398 			cast(godot_property_usage_flags)Property.Usage.scriptVariable;
399 		
400 		static if(hasUDA!(mixin("T."~pName), DefaultValue))
401 		{
402 			alias defExprSeq = TemplateArgsOf!(getUDAs!(mixin("T."~pName), DefaultValue)[0]);
403 			Variant defval = defExprSeq[0];
404 		}
405 		else static if( is(typeof( { P p; } )) )
406 		{
407 			import std.math : isNaN;
408 			static if(isFloatingPoint!P && (mixin("T."~pName).init).isNaN)
409 			{
410 				// Godot doesn't support NaNs. Initialize properties to 0.0 instead.
411 				Variant defval = P(0.0);
412 			}
413 			else Variant defval = (mixin("T."~pName)).init;
414 		}
415 		else
416 		{
417 			/// FIXME: call default constructor function
418 			Variant defval = null;
419 		}
420 		attr.default_value = defval._godot_variant;
421 		
422 		alias Wrapper = VariableWrapper!(T, pName);
423 		
424 		{
425 			gf.method_data = null;
426 			gf.get_func = &Wrapper.callPropertyGet;
427 			gf.free_func = null;
428 		}
429 		
430 		{
431 			sf.method_data = null;
432 			sf.set_func = &Wrapper.callPropertySet;
433 			sf.free_func = null;
434 		}
435 		
436 		_godot_nativescript_api.godot_nativescript_register_property(handle, name, propName, &attr, sf, gf);
437 	}
438 	// TODO: signals
439 	
440 	
441 	
442 	godot.d.script.NativeScriptTemplate!T = memnew!(godot.nativescript.NativeScript);
443 	godot.d.script.NativeScriptTemplate!T.setLibrary(lib);
444 	godot.d.script.NativeScriptTemplate!T.setClassName(String(name));
445 }
446