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.meta, 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 To simulate OOP inheritance, also include `alias owner this;` in your class. 20 +/ 21 class GodotScript(Base) if(isGodotBaseClass!Base) 22 { 23 Base owner; 24 25 pragma(inline, true) 26 inout(To) as(To)() inout if(isGodotBaseClass!To) 27 { 28 static assert(extends!(Base, To), typeof(this).stringof~" does not extend "~To.stringof); 29 return inout(To)(owner.getGDNativeObject); 30 } 31 pragma(inline, true) 32 inout(To) as(To, this From)() inout if(extendsGodotBaseClass!To) 33 { 34 static assert(extends!(From, To) || extends!(To, From), From.stringof~ 35 " is not polymorphic to " ~ To.stringof); 36 return opCast!To(); // use D dynamic cast 37 } 38 39 @disable new(size_t s); 40 41 /// HACK to work around evil bug in which cast(void*) invokes `alias this` 42 /// https://issues.dlang.org/show_bug.cgi?id=6777 43 void* opCast(T : void*)() 44 { 45 import std.traits; 46 alias This = typeof(this); 47 static assert(!is(Unqual!This == Unqual!Base)); 48 union U{ void* ptr; This c; } 49 U u; 50 u.c = this; 51 return u.ptr; 52 } 53 const(void*) opCast(T : const(void*))() const 54 { 55 import std.traits; 56 alias This = typeof(this); 57 static assert(!is(Unqual!This == Unqual!Base)); 58 union U{ const(void*) ptr; const(This) c; } 59 U u; 60 u.c = this; 61 return u.ptr; 62 } 63 } 64 65 /++ 66 Storage for the NativeScript associated with each D class. Workflow using the 67 editor is to create a .gdns NativeScript for each class, but this serves the 68 opposite purpose: assigning a Script to D classes created from D with `memnew`. 69 70 Assigned by the `register` function. 71 +/ 72 public template NativeScriptTemplate(T) if(extendsGodotBaseClass!T) 73 { 74 private static import godot.nativescript; 75 private static import godot.d.reference; 76 __gshared godot.d.reference.Ref!(godot.nativescript.NativeScript) NativeScriptTemplate; 77 } 78 79 /++ 80 Static storage for a D script's typetag. 81 82 The $(D tag) for T is the *address* of $(D base), while $(D base) itself points 83 to the base D script's tag. 84 +/ 85 package(godot) struct NativeScriptTag(T) if(extendsGodotBaseClass!T) 86 { 87 private import std.traits : BaseClassesTuple; 88 89 static if(BaseClassesTuple!T.length == 2) __gshared static immutable void* base = null; 90 else __gshared static immutable void* base = NativeScriptTag!(BaseClassesTuple!T[0]).tag; 91 92 static immutable(void*) tag() { return cast(immutable(void*))(&base); } 93 static bool matches(in void* tag) 94 { 95 const(void)* ptr = tag; 96 do 97 { 98 if(tag is ptr) return true; 99 ptr = *cast(const(void)**)ptr; 100 } 101 while(ptr); 102 return false; 103 } 104 } 105 106 package(godot) void initialize(T)(T t) if(extendsGodotBaseClass!T) 107 { 108 import godot.node; 109 110 template isRAII(string memberName) 111 { 112 static if(__traits(getProtection, __traits(getMember, T, memberName)) == "public") 113 enum bool isRAII = hasUDA!( __traits(getMember, T, memberName), RAII); 114 else enum bool isRAII = false; 115 } 116 foreach(n; Filter!(isRAII, FieldNameTuple!T )) 117 { 118 alias M = typeof(mixin("t."~n)); 119 static assert(getUDAs!(mixin("t."~n), RAII).length == 1, "Multiple RAIIs on " 120 ~T.stringof~"."~n); 121 122 enum RAII raii = is(getUDAs!(mixin("t."~n), RAII)[0]) ? 123 RAII.makeDefault!(M, T)() : getUDAs!(mixin("t."~n), RAII)[0]; 124 125 static if(raii.autoCreate) 126 { 127 mixin("t."~n) = memnew!M(); 128 static if( raii.autoAddChild && RAII.canAddChild!(M, T) ) 129 { 130 t.owner.addChild( mixin("t."~n).getGodotObject ); 131 } 132 } 133 } 134 135 // call _init 136 foreach(mf; godotMethods!T) 137 { 138 enum string funcName = godotName!mf; 139 alias Args = Parameters!mf; 140 static if(funcName == "_init" && Args.length == 0) t._init(); 141 } 142 } 143 144 package(godot) void finalize(T)(T t) if(extendsGodotBaseClass!T) 145 { 146 import godot.node; 147 148 template isRAII(string memberName) 149 { 150 static if(__traits(getProtection, __traits(getMember, T, memberName)) == "public") 151 enum bool isRAII = hasUDA!( __traits(getMember, T, memberName), RAII); 152 else enum bool isRAII = false; 153 } 154 foreach(n; Filter!(isRAII, FieldNameTuple!T )) 155 { 156 alias M = typeof(mixin("t."~n)); 157 enum RAII raii(string rn : n) = is(getUDAs!(mixin("t."~n), RAII)[0]) ? 158 RAII.makeDefault!(M, T)() : getUDAs!(mixin("t."~n), RAII)[0]; 159 160 static if(raii!n.autoDelete) 161 { 162 memdelete(mixin("t."~n)); 163 } 164 } 165 } 166 167 /++ 168 Generic null check for all Godot classes. Limitations in D prevent using `is null` 169 on Godot base classes because they're really struct wrappers. 170 +/ 171 @nogc nothrow pragma(inline, true) 172 bool isNull(T)(in T t) if(isGodotClass!T) 173 { 174 static if(extendsGodotBaseClass!T) return t is null; 175 else return t._godot_object.ptr is null; 176 } 177 178 /++ 179 Allocate a new T and attach it to a new Godot object. 180 +/ 181 RefOrT!T memnew(T)() if(extendsGodotBaseClass!T) 182 { 183 import godot.reference; 184 GodotClass!T o = GodotClass!T._new(); 185 static if(extends!(T, Reference)) 186 { 187 bool success = o.initRef(); 188 assert(success, "Failed to init refcount"); 189 } 190 // Set script and let Object create the script instance 191 o.setScript(NativeScriptTemplate!T); 192 // Skip typecheck in release; should always be T 193 assert(o.as!T); 194 T t = cast(T)_godot_nativescript_api.godot_nativescript_get_userdata(o._godot_object); 195 return refOrT(t); 196 } 197 198 RefOrT!T memnew(T)() if(isGodotBaseClass!T) 199 { 200 import godot.reference; 201 /// FIXME: block those that aren't marked instanciable in API JSON (actually a generator bug) 202 T t = T._new(); 203 static if(extends!(T, Reference)) 204 { 205 bool success = t.initRef(); 206 assert(success, "Failed to init refcount"); 207 } 208 return refOrT(t); /// TODO: remove _new and use only this function? 209 } 210 211 void memdelete(T)(T t) if(isGodotClass!T) 212 { 213 _godot_api.godot_object_destroy(t.getGDNativeObject); 214 } 215 216 extern(C) package(godot) void* createFunc(T)(godot_object self, void* methodData) 217 { 218 static import godot; 219 220 T t = Mallocator.instance.make!T(); 221 static if(extendsGodotBaseClass!T) 222 { 223 t.owner._godot_object = self; 224 } 225 226 godot.initialize(t); 227 228 return cast(void*)t; 229 } 230 231 extern(C) package(godot) void destroyFunc(T)(godot_object self, void* methodData, void* userData) 232 { 233 static import godot; 234 235 T t = cast(T)userData; 236 godot.finalize(t); 237 Mallocator.instance.dispose(t); 238 } 239