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