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 }