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 }