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 }