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 }