1 /**
2     Inochi2D Properties
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.property;
14 import numem.core.traits;
15 import numem.core.meta;
16 import nulib.quark;
17 import nulib;
18 import numem;
19 
20 /**
21     A type which contains properties.
22 
23     The public property interface interprets everything as floating point values.
24 */
25 interface IPropertyOwner {
26 @nogc nothrow:
27 
28     /**
29         Gets whether a property with the given name exists
30         in the object.
31 
32         Params:
33             key = The name of the property.
34         
35         Returns:
36             $(D true) if the property exists,
37             $(D false) otherwise.
38     */
39     bool hasProperty(quark key) const;
40 
41     /**
42         Gets the value of a given property.
43 
44         Params:
45             key = The name of the property.
46         
47         Returns:
48             The floating point value of the property.
49     */
50     float getProperty(quark key) const;
51 
52     /**
53         Gets the default value of a given property.
54 
55         Params:
56             key = The name of the property.
57         
58         Returns:
59             The default value of the property.
60     */
61     float getPropertyDefault(quark key) const;
62 
63     /**
64         Sets the value of the property.
65 
66         Params:
67             key =   The name of the property.
68             value = The value to set the property to.
69     */
70     void setProperty(quark key, float value);
71 
72     /**
73         Resets the given property.
74 
75         Params:
76             key = The name of the property.
77     */
78     void resetProperty(quark key);
79 
80     /**
81         Resets all properties.
82     */
83     void resetProperties();
84 }
85 
86 /**
87     The name of a property quark to initialize it to.
88 */
89 struct propkey {
90     string key;
91 }
92 
93 /**
94     Registers all quarks within the current module on library
95     initialization.
96 */
97 mixin template RegisterQuarks() {
98     alias module_ = mixin(__MODULE__);
99     enum modname = __traits(identifier, module_);
100 
101     pragma(crt_constructor)
102     pragma(mangle, "__in_" ~ modname ~ "_quark_init")
103     export extern (C) void __register_quarks() {
104         import numem.core.traits : getUDAs, hasUDA;
105 
106         static foreach (member; __traits(allMembers, module_)) {
107             static if (is(typeof(__traits(getMember, module_, member)) == immutable(quark))) {
108                 {
109                     alias __member = __traits(getMember, module_, member);
110                     static if (hasUDA!(__member, propkey)) {
111                         enum KEY = getUDAs!(__member, propkey)[0].key;
112 
113                         pragma(msg, "Registering property ", KEY, " for ", member, "...");
114                         __member = nu_quarkof(KEY);
115                     } else {
116                         pragma(msg, "Warning: ", member, " does not have a property name set, ignoring...");
117                     }
118                 }
119             }
120         }
121     }
122 }
123 
124 /**
125     A memory manager for properties.
126 
127     Properties can be of any type.
128 */
129 struct PropertyStore {
130 private:
131 @nogc nothrow:
132     HashTable!(quark, PropInfo) lut_;
133     vector!quark keys_;
134     size_t length_;
135     void* vbuffer_;
136     void* dbuffer_;
137 
138     static struct PropInfo {
139         size_t offset;
140         size_t size;
141     }
142 
143     // Grows the property store's buffers by a given amount
144     // of bytes, rounded up to 32-bits.
145     size_t grow(size_t by) {
146         size_t start = length_;
147         this.length_ += nu_alignup(by - 1, 4);
148         this.vbuffer_ = nu_realloc(vbuffer_, length_);
149         this.dbuffer_ = nu_realloc(dbuffer_, length_);
150         return start;
151     }
152 
153 public:
154 
155     /**
156         All of the stored properties, untyped.
157     */
158     @property void[] properties() => vbuffer_[0 .. length_];
159 
160     /**
161         The keys the store knows about.
162     */
163     @property quark[] keys() => keys_[];
164 
165     /**
166         Size of the store, in bytes.
167     */
168     @property size_t size() nothrow pure => length_;
169 
170     /// Destructor
171     ~this() {
172         lut_.clear();
173         keys_.clear();
174         nu_free(vbuffer_);
175         nu_free(dbuffer_);
176     }
177 
178     /**
179         Defines a property in the store.
180     
181         Params:
182             key =       The key of the property.
183             default_ =  The default value for the property.
184 
185         Returns:
186             The index assigned to the definition.
187     */
188     size_t define(T)(quark key, T default_) {
189 
190         // If we already have a defintion, use that.
191         if (auto i = key in lut_) {
192             return i.offset;
193         }
194 
195         // Otherwise make a new one.
196         size_t offset = this.grow(T.sizeof);
197         lut_[key] = PropInfo(offset, T.sizeof);
198         keys_ ~= key;
199 
200         // Set default value and copy it to the set value.
201         nu_memcpy(&dbuffer_[offset], cast(void*)&default_, T.sizeof);
202         return offset;
203     }
204 
205     /**
206         Defines an overlay for a quark at the given memory location.
207 
208         Params:
209             key =       The key to assign
210             offset =    The offset to assign it at.
211 
212         Returns:
213             The offset that the overlay was created at,
214             $(D -1) if the overlay could not be created.
215     */
216     ptrdiff_t defineOverlay(T)(quark key, size_t offset) {
217 
218         // If we already have a defintion, use that.
219         if (auto i = key in lut_) {
220             return i.offset;
221         }
222 
223         // Make sure we're in range.
224         if (offset + T.sizeof <= this.length_) {
225             lut_[key] = PropInfo(offset, T.sizeof);
226             keys_ ~= key;
227             return offset;
228         }
229         return -1;
230     }
231 
232     /**
233         Gets the offset of the given quark in the
234         property store.
235 
236         Params:
237             q = The quark to look up.
238     
239         Returns:
240             The index of $(D q) in $(D properties) if found,
241             $(D -1) otherwise.
242     */
243     ptrdiff_t offsetOf(quark q) inout {
244         if (auto i = q in lut_)
245             return i.offset;
246         return -1;
247     }
248 
249     /**
250         Gets the given value from the given offset.
251 
252         Params:
253             offset = The offset of the value to get.
254 
255         Returns:
256             The value at that given offset if found,
257             $(D T.init) otherwise.
258     */
259     T getFromIndex(T)(size_t offset) inout {
260         if (offset + T.sizeof > this.length_)
261             return T.init;
262 
263         return *(cast(T*)(vbuffer_ + offset));
264     }
265 
266     /**
267         Gets a property from its quark.
268 
269         Params:
270             q = The property quark.
271 
272         Returns:
273             The value for the given quark if found,
274             $(D initial) otherwise.
275     */
276     T get(T)(quark q) inout {
277         if (auto i = q in lut_) {
278             assert(i.size == T.sizeof, "Tried to get property type of mismatching size.");
279             if (i.size != T.sizeof)
280                 return T.init;
281 
282             return (*cast(T*)(&vbuffer_[i.offset]));
283         }
284         return T.init;
285     }
286 
287     /**
288         Gets the default value of a property from its quark.
289 
290         Params:
291             q = The property quark.
292 
293         Returns:
294             The default value of the property if found,
295             $(D initial) otherwise.
296     */
297     T getDefault(T)(quark q) inout {
298         if (auto i = q in lut_) {
299             assert(i.size == T.sizeof, "Tried to get property type of mismatching size.");
300             if (i.size != T.sizeof)
301                 return T.init;
302 
303             return (*cast(T*)(&dbuffer_[i.offset]));
304         }
305         return T.init;
306     }
307 
308     /**
309         Sets a property from its quark.
310 
311         Params:
312             q =     The property quark.
313             value = The value to set.
314     */
315     void set(T)(quark q, T value) {
316         if (auto i = q in lut_) {
317             assert(i.size == T.sizeof, "Tried to set mismatched size data for property.");
318             if (i.size != T.sizeof)
319                 return;
320 
321             (*cast(T*)(&vbuffer_[i.offset])) = value;
322         }
323     }
324 
325     /**
326         Sets a property to its default value.
327 
328         Params:
329             q =     The property quark.
330     */
331     void reset(quark q) {
332         if (auto i = q in lut_)
333             nu_memcpy(&vbuffer_[i.offset], &dbuffer_[i.offset], i.size);
334     }
335 
336     /**
337         Resets all of the properties of the store.
338     */
339     void resetAll() {
340         nu_memcpy(vbuffer_, dbuffer_, length_);
341     }
342 }
343 
344 /**
345     A type which contains a value and an offset for said value.
346 */
347 struct offset_value(T, string offsetOp = "+") { // @suppress(dscanner.style.phobos_naming_convention)
348 public:
349 @nogc:
350     alias value this;
351     T base;
352     T offset;
353 
354     /**
355         The combined value.
356     */
357     @property T value() => mixin("base ", offsetOp, " offset");
358 }