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