1 /++
2 Implementation templates for new Godot-D native scripts
3 +/
4 module godot.api.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.abi, godot;
11 import godot.api.udas;
12 import godot.api.traits, godot.api.wrap;
13 import godot.api.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     Base owner;
21     alias owner this;
22 
23     pragma(inline, true)
24     inout(To) as(To)() inout if (isGodotBaseClass!To) {
25         static assert(extends!(Base, To), typeof(this).stringof ~ " does not extend " ~ To.stringof);
26         return cast(inout(To))(owner.getGDExtensionObject);
27     }
28 
29     pragma(inline, true)
30     inout(To) as(To, this From)() inout if (extendsGodotBaseClass!To) {
31         static assert(extends!(From, To) || extends!(To, From), From.stringof ~
32                 " is not polymorphic to " ~ To.stringof);
33         return opCast!To(); // use D dynamic cast
34     }
35 
36     ///
37     pragma(inline, true)
38     bool opEquals(T, this This)(in T other) const 
39             if (extends!(T, This) || extends!(This, T)) {
40         static if (extendsGodotBaseClass!T)
41             return this is other;
42         else {
43             const void* a = owner._godot_object.ptr, b = other._godot_object.ptr;
44             return a is b;
45         }
46     }
47     ///
48     pragma(inline, true)
49     int opCmp(T)(in T other) const if (isGodotClass!T) {
50         const void* a = owner._godot_object.ptr, b = other.getGodotObject._godot_object.ptr;
51         return a is b ? 0 : a < b ? -1 : 1;
52     }
53 
54     //@disable new(size_t s);
55 
56     /// HACK to work around evil bug in which cast(void*) invokes `alias this`
57     /// https://issues.dlang.org/show_bug.cgi?id=6777
58     void* opCast(T : void*)() {
59         import std.traits;
60 
61         alias This = typeof(this);
62         static assert(!is(Unqual!This == Unqual!Base));
63         union U {
64             void* ptr;
65             This c;
66         }
67 
68         U u;
69         u.c = this;
70         return u.ptr;
71     }
72 
73     const(void*) opCast(T : const(void*))() const {
74         import std.traits;
75 
76         alias This = typeof(this);
77         static assert(!is(Unqual!This == Unqual!Base));
78         union U {
79             const(void*) ptr;
80             const(This) c;
81         }
82 
83         U u;
84         u.c = this;
85         return u.ptr;
86     }
87 }
88 
89 package(godot) void initialize(T)(T t) if (extendsGodotBaseClass!T) {
90     import godot.node;
91 
92     template isOnInit(string memberName) {
93         static if (__traits(getProtection, __traits(getMember, T, memberName)) == "public")
94             enum bool isOnInit = hasUDA!(__traits(getMember, T, memberName), OnInit);
95         else
96             enum bool isOnInit = false;
97     }
98 
99     foreach (n; Filter!(isOnInit, FieldNameTuple!T)) {
100         alias M = typeof(mixin("t." ~ n));
101         static assert(getUDAs!(mixin("t." ~ n), OnInit).length == 1, "Multiple OnInits on "
102                 ~ T.stringof ~ "." ~ n);
103 
104         enum OnInit raii = is(getUDAs!(mixin("t." ~ n), OnInit)[0]) ?
105             OnInit.makeDefault!(M, T)() : getUDAs!(mixin("t." ~ n), OnInit)[0];
106 
107         static if (raii.autoCreate) {
108             mixin("t." ~ n) = memnew!M();
109             static if (raii.autoAddChild && OnInit.canAddChild!(M, T)) {
110                 t.owner.addChild(mixin("t." ~ n).getGodotObject);
111             }
112         }
113     }
114 
115     // call _init
116     foreach (mf; godotMethods!T) {
117         enum string funcName = godotName!mf;
118         alias Args = Parameters!mf;
119         static if (funcName == "_init" && Args.length == 0)
120             t._init();
121     }
122 }
123 
124 package(godot) void finalize(T)(T t) if (extendsGodotBaseClass!T) {
125 }
126 
127 /++
128 Generic null check for all Godot classes. Limitations in D prevent using `is null`
129 on Godot base classes because they're really struct wrappers.
130 +/
131 @nogc nothrow pragma(inline, true)
132 bool isNull(T)(in T t) if (isGodotClass!T) {
133     static if (extendsGodotBaseClass!T)
134         return t is null;
135     else
136         return t._godot_object.ptr is null;
137 }
138 
139 /++
140 Allocate a new T and attach it to a new Godot object.
141 +/
142 RefOrT!T memnew(T)() if (extendsGodotBaseClass!T) {
143     import godot.refcounted;
144 
145     // NOTE: Keep in sync with register.d register(T) template
146     static if (hasUDA!(T, Rename))
147         enum string name = godotName!T;
148     else 
149         enum string name = __traits(identifier, T);
150 
151     //GodotClass!T o = GodotClass!T._new();
152     auto snName = StringName(name);
153     auto obj = _godot_api.classdb_construct_object(cast(GDExtensionStringNamePtr) snName);
154     assert(obj !is null);
155 
156     T o = cast(T) _godot_api.object_get_instance_binding(obj, _GODOT_library, &_instanceCallbacks);
157     //static if(extends!(T, RefCounted))
158     //{
159     //	bool success = o.initRef();
160     //	assert(success, "Failed to init refcount");
161     //}
162     // Set script and let Object create the script instance
163     //o.setScript(NativeScriptTemplate!T);
164     // Skip typecheck in release; should always be T
165     //assert(o.as!T);
166     //T t = cast(T)_godot_nativescript_api.godot_nativescript_get_userdata(o._godot_object);
167     //T t = cast(T) &o._godot_object;
168     return refOrT(o);
169 }
170 
171 RefOrT!T memnew(T)() if (isGodotBaseClass!T) {
172     import godot.refcounted;
173 
174     /// FIXME: block those that aren't marked instanciable in API JSON (actually a generator bug)
175     T t = T._new();
176     static if (extends!(T, RefCounted)) {
177         bool success = t.initRef();
178         assert(success, "Failed to init refcount");
179     }
180     return refOrT(t); /// TODO: remove _new and use only this function?
181 }
182 
183 void memdelete(T)(T t) if (isGodotClass!T) {
184     _godot_api.object_destroy(t.getGDExtensionObject.ptr);
185 }
186 
187 package(godot) extern (C) __gshared GDExtensionInstanceBindingCallbacks _instanceCallbacks = {
188     &___binding_create_callback,
189     &___binding_free_callback,
190     &___binding_reference_callback
191 };
192 
193 extern (C) static void* ___binding_create_callback(void* p_token, void* p_instance) {
194     return null;
195 }
196 
197 extern (C) static void ___binding_free_callback(void* p_token, void* p_instance, void* p_binding) {
198 }
199 
200 extern (C) static GDExtensionBool ___binding_reference_callback(void* p_token, void* p_instance, GDExtensionBool p_reference) {
201     return cast(GDExtensionBool) true;
202 }
203 
204 extern (C) package(godot) void* createFunc(T)(void* data) //nothrow @nogc
205 {
206     import std.conv;
207 
208     static assert(is(T == class));
209     static assert(__traits(compiles, new T()), "script class " ~ T.stringof ~ " must have default constructor");
210     static import godot;
211 
212     import std.exception;
213     import godot.api.register : _GODOT_library;
214 
215     // NOTE: Keep in sync with register.d register(T) template
216     static if (hasUDA!(T, Rename))
217         enum string name = godotName!T;
218     else 
219         enum string name = __traits(identifier, T);
220 
221     T t = cast(T) _godot_api.mem_alloc(__traits(classInstanceSize, T));
222 
223     emplace(t);
224     // class must have default ctor to be properly initialized
225     // t.__ctor();
226 
227     //static if(extendsGodotBaseClass!T)
228     {
229         StringName classname = name;
230         StringName snInternalName = (GodotClass!T)._GODOT_internal_name;
231         if (!t.owner._godot_object.ptr)
232             t.owner._godot_object.ptr = _godot_api.classdb_construct_object(cast(GDExtensionStringNamePtr) snInternalName);
233         _godot_api.object_set_instance(cast(void*) t.owner._godot_object.ptr, cast(GDExtensionStringNamePtr) classname, cast(
234                 void*) t);
235     }
236     //else
237     //	t.owner._godot_object.ptr = cast(void*) t;
238     godot.initialize(t);
239 
240     _godot_api.object_set_instance_binding(cast(void*) t.owner._godot_object.ptr, _GODOT_library, cast(
241             void*) t, &_instanceCallbacks);
242 
243     return cast(void*) t.owner._godot_object.ptr;
244 }
245 
246 extern (C) package(godot) void destroyFunc(T)(void* userData, void* instance) //nothrow @nogc
247 {
248     static import godot;
249 
250     T t = cast(T) instance;
251     godot.finalize(t);
252     _godot_api.mem_free(cast(void*) t);
253     //Mallocator.instance.dispose(t);
254 }