1 /** 2 Shared core subsystem for type registration and instantiation. 3 4 Copyright: 5 Copyright © 2020-2026, Inochi2D Project 6 7 License: 8 $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License) 9 10 Authors: 11 Luna Nielsen 12 */ 13 module inochi2d.core.registry; 14 import numem.core.lifetime; 15 import numem.core.memory; 16 import numem.core.traits; 17 import inp.format; 18 import numem; 19 import nulib; 20 21 /** 22 A UDA applied to types in Inochi2D to allow them to be instantiated 23 using an object model. Types both may have a string and numeric ID. 24 */ 25 struct TypeId { 26 string sid; 27 uint nid; 28 29 enum nil = TypeId(null, uint.max); 30 } 31 32 /** 33 Creates a reserved Inochi2D ID tag. 34 */ 35 enum IN_MAKE_TAG(ubyte subclass, ubyte superclass) = 36 (cast(uint)subclass << 8) | 37 (cast(uint)superclass); 38 39 /** 40 Tells the registry only to register the TypeId, 41 but not the factories for a type. 42 */ 43 struct TypeIdAbstract; 44 45 /** 46 Tells the registry that the type should be used as a fallback 47 if the type can't be resolved. 48 */ 49 struct RegisterFallback; 50 51 /** 52 Template which registers a type into a given type registry. 53 */ 54 mixin template Register(T, alias registry) { 55 import numem.core.traits : hasUDA; 56 57 static if (hasUDA!(T, TypeId)) { 58 pragma(msg, "Registering ", T.stringof, " in ", registry.stringof, "..."); 59 60 pragma(crt_constructor) 61 pragma(mangle, "__in_register_" ~ T.stringof) 62 export extern (C) void __register_type() { 63 registry.register!T(); 64 } 65 } 66 67 static if (hasUDA!(T, RegisterFallback)) { 68 pragma(msg, "Registering ", T.stringof, " as fallback type in ", registry.stringof, "..."); 69 } 70 } 71 72 /** 73 A type registry that stores mappings between name and numeric IDs 74 and their classes. 75 */ 76 struct TypeRegistry(T, Args...) { 77 private: 78 // dfmt off 79 alias __TypeMap(Key, Value) = MapImpl!(Key, Value, (a, b) => a < b, false, false); 80 static X __construct(X)(Args args) @nogc { 81 return assumeNoGC((Args args) { 82 return new(nu_mallocT!X()) X(args); 83 }, 84 args 85 ); 86 } 87 // dfmt on 88 89 @nogc: 90 alias factory_t = T function(Args); 91 __TypeMap!(void*, TypeId) typeIdStore; 92 __TypeMap!(string, factory_t) factoryStoreS; 93 __TypeMap!(uint, factory_t) factoryStoreN; 94 factory_t fallbackFactory; 95 vector!size_t sizeStore; 96 97 public: 98 99 /** 100 Arguments 101 */ 102 alias ArgsT = Args; 103 104 /** 105 The alignment needed to store registered objects in sequential memory. 106 */ 107 @property size_t alignment() { 108 size_t result = 0; 109 foreach (sz; sizeStore) 110 if (sz > result) 111 result = sz; 112 113 return result; 114 } 115 116 /** 117 Registers the given type in the type registry. 118 119 Params: 120 X = The object to register. 121 */ 122 void register(X)() { 123 import numem.core.traits : getUDAs, hasUDA; 124 125 static assert(hasUDA!(X, TypeId), X.stringof ~ " does not have a TypeId UDA!"); 126 127 alias _tids = getUDAs!(X, TypeId); 128 typeIdStore[cast(void*)typeid(X)] = _tids[0]; 129 sizeStore ~= AllocSize!X; 130 131 // Register type factory. 132 static if (!hasUDA!(X, TypeIdAbstract)) { 133 static foreach (tid; _tids) { 134 factoryStoreS[tid.sid] = &__construct!X; 135 factoryStoreN[tid.nid] = &__construct!X; 136 } 137 } 138 139 // Register fallback factory. 140 static if (hasUDA!(X, RegisterFallback)) { 141 fallbackFactory = &__construct!X; 142 } 143 } 144 145 /** 146 Looks up a type within the type registry. 147 148 Params: 149 object = The object to look up 150 151 Returns: 152 The TypeId registered for the type, 153 or $(D TypeId.nil) if it wasn't found. 154 */ 155 TypeId lookup(T object) { 156 if (object) 157 return this.lookup(cast(TypeInfo)typeid(object)); 158 return TypeId.nil; 159 } 160 161 /** 162 Looks up a type within the type registry. 163 164 Params: 165 object = The D typeid to look up. 166 167 Returns: 168 The TypeId registered for the type, 169 or $(D TypeId.nil) if it wasn't found. 170 */ 171 TypeId lookup(TypeInfo object) { 172 if (cast(void*)object in typeIdStore) 173 return typeIdStore[cast(void*)object]; 174 return TypeId.nil; 175 } 176 177 /** 178 Gets whether the type registry has a given 179 string ID registered within it. 180 181 Params: 182 sid = The string id to look up. 183 184 Returns: 185 $(D true) if the ID was found, 186 $(D false) otherwise. 187 */ 188 bool has(string sid) { 189 return (sid in factoryStoreS) !is null; 190 } 191 192 /** 193 Gets whether the type registry has a given 194 numeric ID registered within it. 195 196 Params: 197 nid = The numeric id to look up. 198 199 Returns: 200 $(D true) if the ID was found, 201 $(D false) otherwise. 202 */ 203 bool has(uint nid) { 204 return (nid in factoryStoreN) !is null; 205 } 206 207 /** 208 Creates an instance of a type registered within 209 the registry. 210 211 Params: 212 sid = The string id to look up. 213 args = Arguments to pass to the constructor 214 215 Returns: 216 A new instance of the given type, 217 $(D null) if not found. 218 */ 219 T create(string sid, Args args) { 220 if (sid in factoryStoreS) 221 return factoryStoreS[sid](args); 222 223 return fallbackFactory ? 224 fallbackFactory(args) : T.init; 225 } 226 227 /** 228 Creates an instance of a type registered within 229 the registry. 230 231 Params: 232 nid = The numeric id to look up. 233 args = Arguments to pass to the constructor 234 235 Returns: 236 A new instance of the given type, 237 $(D null) if not found. 238 */ 239 T create(uint nid, Args args) { 240 if (nid in factoryStoreN) 241 return factoryStoreN[nid](args); 242 243 return fallbackFactory ? 244 fallbackFactory(args) : T.init; 245 } 246 247 /** 248 Allows iterating over the registry. 249 250 Params: 251 dg = The function to call on each iteration. 252 */ 253 int opApply(scope int delegate(TypeId) dg) @nogc @trusted { 254 return typeIdStore.opApply(dg); 255 } 256 257 /** 258 Tries to create a type from the registry based on a DataNode. 259 The DataNode must have a field called $(D type). 260 261 Params: 262 object = The Object DataNode to deserialize from. 263 args = Arguments to pass to the type's constructor 264 265 Returns: 266 A newly allocated type based on the $(D type) tag, 267 or $(D null) if this failed. 268 */ 269 T tryCreateFrom(ref DataNode object, Args args) { 270 if (object.isObject && "type" in object) { 271 if (string type = object["type"].tryCoerce!string(null)) { 272 if (!this.has(type)) 273 return fallbackFactory ? 274 fallbackFactory(args) : T.init; 275 276 return this.create(type, args); 277 } 278 } 279 return T.init; 280 } 281 }