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