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