1 /++
2 Implementation templates for new Godot-D native scripts
3 +/
4 module godot.d.script;
5 
6 import std.meta, std.traits;
7 import std.experimental.allocator, std.experimental.allocator.mallocator;
8 import core.stdc.stdlib : malloc, free;
9 
10 import godot.c, godot.core;
11 import godot.d.udas;
12 import godot.d.meta, godot.d.wrap;
13 import godot.d.reference;
14 
15 /++
16 Base class for D native scripts. Native script instances will be attached to a
17 Godot (C++) object of Base class.
18 
19 To simulate OOP inheritance, also include `alias owner this;` in your class.
20 +/
21 class GodotScript(Base) if(isGodotBaseClass!Base)
22 {
23 	Base owner;
24 	
25 	pragma(inline, true)
26 	inout(To) as(To)() inout if(isGodotBaseClass!To)
27 	{
28 		static assert(extends!(Base, To), typeof(this).stringof~" does not extend "~To.stringof);
29 		return inout(To)(owner.getGDNativeObject);
30 	}
31 	pragma(inline, true)
32 	inout(To) as(To, this From)() inout if(extendsGodotBaseClass!To)
33 	{
34 		static assert(extends!(From, To) || extends!(To, From), From.stringof~
35 			" is not polymorphic to " ~ To.stringof);
36 		return opCast!To(); // use D dynamic cast
37 	}
38 
39 	@disable new(size_t s);
40 	
41 	/// HACK to work around evil bug in which cast(void*) invokes `alias this`
42 	/// https://issues.dlang.org/show_bug.cgi?id=6777
43 	void* opCast(T : void*)()
44 	{
45 		import std.traits;
46 		alias This = typeof(this);
47 		static assert(!is(Unqual!This == Unqual!Base));
48 		union U{ void* ptr; This c; }
49 		U u;
50 		u.c = this;
51 		return u.ptr;
52 	}
53 	const(void*) opCast(T : const(void*))() const
54 	{
55 		import std.traits;
56 		alias This = typeof(this);
57 		static assert(!is(Unqual!This == Unqual!Base));
58 		union U{ const(void*) ptr; const(This) c; }
59 		U u;
60 		u.c = this;
61 		return u.ptr;
62 	}
63 }
64 
65 /++
66 Storage for the NativeScript associated with each D class. Workflow using the
67 editor is to create a .gdns NativeScript for each class, but this serves the
68 opposite purpose: assigning a Script to D classes created from D with `memnew`.
69 
70 Assigned by the `register` function.
71 +/
72 public template NativeScriptTemplate(T) if(extendsGodotBaseClass!T)
73 {
74 	private static import godot.nativescript;
75 	private static import godot.d.reference;
76 	__gshared godot.d.reference.Ref!(godot.nativescript.NativeScript) NativeScriptTemplate;
77 }
78 
79 /++
80 Static storage for a D script's typetag.
81 
82 The $(D tag) for T is the *address* of $(D base), while $(D base) itself points
83 to the base D script's tag.
84 +/
85 package(godot) struct NativeScriptTag(T) if(extendsGodotBaseClass!T)
86 {
87 	private import std.traits : BaseClassesTuple;
88 	
89 	static if(BaseClassesTuple!T.length == 2) __gshared static immutable void* base = null;
90 	else __gshared static immutable void* base = NativeScriptTag!(BaseClassesTuple!T[0]).tag;
91 	
92 	static immutable(void*) tag() { return cast(immutable(void*))(&base); }
93 	static bool matches(in void* tag)
94 	{
95 		const(void)* ptr = tag;
96 		do
97 		{
98 			if(tag is ptr) return true;
99 			ptr = *cast(const(void)**)ptr;
100 		}
101 		while(ptr);
102 		return false;
103 	}
104 }
105 
106 package(godot) void initialize(T)(T t) if(extendsGodotBaseClass!T)
107 {
108 	import godot.node;
109 	
110 	template isRAII(string memberName)
111 	{
112 		static if(__traits(getProtection, __traits(getMember, T, memberName)) == "public")
113 			enum bool isRAII = hasUDA!( __traits(getMember, T, memberName), RAII);
114 		else enum bool isRAII = false;
115 	}
116 	foreach(n; Filter!(isRAII, FieldNameTuple!T ))
117 	{
118 		alias M = typeof(mixin("t."~n));
119 		static assert(getUDAs!(mixin("t."~n), RAII).length == 1, "Multiple RAIIs on "
120 			~T.stringof~"."~n);
121 		
122 		enum RAII raii = is(getUDAs!(mixin("t."~n), RAII)[0]) ?
123 			RAII.makeDefault!(M, T)() : getUDAs!(mixin("t."~n), RAII)[0];
124 		
125 		static if(raii.autoCreate)
126 		{
127 			mixin("t."~n) = memnew!M();
128 			static if( raii.autoAddChild && RAII.canAddChild!(M, T) )
129 			{
130 				t.owner.addChild( mixin("t."~n).getGodotObject );
131 			}
132 		}
133 	}
134 	
135 	// call _init
136 	foreach(mf; godotMethods!T)
137 	{
138 		enum string funcName = godotName!mf;
139 		alias Args = Parameters!mf;
140 		static if(funcName == "_init" && Args.length == 0) t._init();
141 	}
142 }
143 
144 package(godot) void finalize(T)(T t) if(extendsGodotBaseClass!T)
145 {
146 	import godot.node;
147 	
148 	template isRAII(string memberName)
149 	{
150 		static if(__traits(getProtection, __traits(getMember, T, memberName)) == "public")
151 			enum bool isRAII = hasUDA!( __traits(getMember, T, memberName), RAII);
152 		else enum bool isRAII = false;
153 	}
154 	foreach(n; Filter!(isRAII, FieldNameTuple!T ))
155 	{
156 		alias M = typeof(mixin("t."~n));
157 		enum RAII raii(string rn : n) = is(getUDAs!(mixin("t."~n), RAII)[0]) ?
158 			RAII.makeDefault!(M, T)() : getUDAs!(mixin("t."~n), RAII)[0];
159 		
160 		static if(raii!n.autoDelete)
161 		{
162 			memdelete(mixin("t."~n));
163 		}
164 	}
165 }
166 
167 /++
168 Generic null check for all Godot classes. Limitations in D prevent using `is null`
169 on Godot base classes because they're really struct wrappers.
170 +/
171 @nogc nothrow pragma(inline, true)
172 bool isNull(T)(in T t) if(isGodotClass!T)
173 {
174 	static if(extendsGodotBaseClass!T) return t is null;
175 	else return t._godot_object.ptr is null;
176 }
177 
178 /++
179 Allocate a new T and attach it to a new Godot object.
180 +/
181 RefOrT!T memnew(T)() if(extendsGodotBaseClass!T)
182 {
183 	import godot.reference;
184 	GodotClass!T o = GodotClass!T._new();
185 	static if(extends!(T, Reference))
186 	{
187 		bool success = o.initRef();
188 		assert(success, "Failed to init refcount");
189 	}
190 	// Set script and let Object create the script instance
191 	o.setScript(NativeScriptTemplate!T);
192 	// Skip typecheck in release; should always be T
193 	assert(o.as!T);
194 	T t = cast(T)_godot_nativescript_api.godot_nativescript_get_userdata(o._godot_object);
195 	return refOrT(t);
196 }
197 
198 RefOrT!T memnew(T)() if(isGodotBaseClass!T)
199 {
200 	import godot.reference;
201 	/// FIXME: block those that aren't marked instanciable in API JSON (actually a generator bug)
202 	T t = T._new();
203 	static if(extends!(T, Reference))
204 	{
205 		bool success = t.initRef();
206 		assert(success, "Failed to init refcount");
207 	}
208 	return refOrT(t); /// TODO: remove _new and use only this function?
209 }
210 
211 void memdelete(T)(T t) if(isGodotClass!T)
212 {
213 	_godot_api.godot_object_destroy(t.getGDNativeObject);
214 }
215 
216 extern(C) package(godot) void* createFunc(T)(godot_object self, void* methodData)
217 {
218 	static import godot;
219 	
220 	T t = Mallocator.instance.make!T();
221 	static if(extendsGodotBaseClass!T)
222 	{
223 		t.owner._godot_object = self;
224 	}
225 	
226 	godot.initialize(t);
227 	
228 	return cast(void*)t;
229 }
230 
231 extern(C) package(godot) void destroyFunc(T)(godot_object self, void* methodData, void* userData)
232 {
233 	static import godot;
234 	
235 	T t = cast(T)userData;
236 	godot.finalize(t);
237 	Mallocator.instance.dispose(t);
238 }
239