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.getGDNativeObject);
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     //GodotClass!T o = GodotClass!T._new();
146     auto obj = _godot_api.classdb_construct_object(godotName!T);
147     assert(obj !is null);
148 
149     auto id = _godot_api.object_get_instance_id(obj);
150     T o = cast(T) _godot_api.object_get_instance_from_id(id);
151     //static if(extends!(T, RefCounted))
152     //{
153     //	bool success = o.initRef();
154     //	assert(success, "Failed to init refcount");
155     //}
156     // Set script and let Object create the script instance
157     //o.setScript(NativeScriptTemplate!T);
158     // Skip typecheck in release; should always be T
159     //assert(o.as!T);
160     //T t = cast(T)_godot_nativescript_api.godot_nativescript_get_userdata(o._godot_object);
161     //T t = cast(T) &o._godot_object;
162     return refOrT(o);
163 }
164 
165 RefOrT!T memnew(T)() if (isGodotBaseClass!T) {
166     import godot.refcounted;
167 
168     /// FIXME: block those that aren't marked instanciable in API JSON (actually a generator bug)
169     T t = T._new();
170     static if (extends!(T, RefCounted)) {
171         bool success = t.initRef();
172         assert(success, "Failed to init refcount");
173     }
174     return refOrT(t); /// TODO: remove _new and use only this function?
175 }
176 
177 void memdelete(T)(T t) if (isGodotClass!T) {
178     _godot_api.object_destroy(t.getGDNativeObject.ptr);
179 }
180 
181 package(godot) extern (C) __gshared GDNativeInstanceBindingCallbacks _instanceCallbacks = {
182     &___binding_create_callback,
183     &___binding_free_callback,
184     &___binding_reference_callback
185 };
186 
187 extern (C) static void* ___binding_create_callback(void* p_token, void* p_instance) {
188     return null;
189 }
190 
191 extern (C) static void ___binding_free_callback(void* p_token, void* p_instance, void* p_binding) {
192 }
193 
194 extern (C) static GDNativeBool ___binding_reference_callback(void* p_token, void* p_instance, GDNativeBool p_reference) {
195     return cast(GDNativeBool) true;
196 }
197 
198 extern (C) package(godot) void* createFunc(T)(void* data) //nothrow @nogc
199 {
200     import std.conv;
201 
202     static assert(is(T == class));
203     static assert(__traits(compiles, new T()), "script class " ~ T.stringof ~ " must have default constructor");
204     static import godot;
205 
206     import std.exception;
207     import godot.api.register : _GODOT_library;
208 
209     enum classname = cast(const char*)(godotName!T ~ '\0');
210     T t = cast(T) _godot_api.mem_alloc(__traits(classInstanceSize, T));
211 
212     emplace(t);
213     // class must have default ctor to be properly initialized
214     // t.__ctor();
215 
216     //static if(extendsGodotBaseClass!T)
217     {
218         if (!t.owner._godot_object.ptr)
219             t.owner._godot_object.ptr = _godot_api.classdb_construct_object((GodotClass!T)
220                     ._GODOT_internal_name);
221         _godot_api.object_set_instance(cast(void*) t.owner._godot_object.ptr, classname, cast(
222                 void*) t);
223     }
224     //else
225     //	t.owner._godot_object.ptr = cast(void*) t;
226     godot.initialize(t);
227 
228     _godot_api.object_set_instance_binding(cast(void*) t.owner._godot_object.ptr, _GODOT_library, cast(
229             void*) t, &_instanceCallbacks);
230 
231     return cast(void*) t.owner._godot_object.ptr;
232 }
233 
234 extern (C) package(godot) void destroyFunc(T)(void* userData, void* instance) //nothrow @nogc
235 {
236     static import godot;
237 
238     T t = cast(T) instance;
239     godot.finalize(t);
240     _godot_api.mem_free(cast(void*) t);
241     //Mallocator.instance.dispose(t);
242 }