1 /// Advanced stores and loads, endianness, cloning, and more. 2 module tern.object; 3 4 import tern.memory; 5 import tern.traits; 6 import std.meta; 7 8 public enum Endianness 9 { 10 Native, 11 LittleEndian, 12 BigEndian 13 } 14 15 public: 16 pure: 17 /** 18 * Swaps the endianness of the provided value, if applicable. 19 * 20 * Params: 21 * val = The value to swap endianness. 22 * endianness = The desired endianness. 23 * 24 * Returns: 25 * The value with swapped endianness. 26 */ 27 @trusted T makeEndian(T)(T val, Endianness endianness) 28 { 29 version (LittleEndian) 30 { 31 if (endianness == Endianness.BigEndian) 32 byteswap(reference!val, sizeof!T); 33 } 34 else version (BigEndian) 35 { 36 if (endianness == Endianness.LittleEndian) 37 byteswap(reference!val, sizeof!T); 38 } 39 return val; 40 } 41 42 /** 43 * Dynamically tries to load the length of `val`, this is useful for arbitrary range types. 44 * 45 * Params: 46 * val = The value to load the length of. 47 * 48 * Remarks: 49 * Returns 1 if `T` has no length apparent. 50 * 51 * Returns: 52 * The loaded length. 53 */ 54 pragma(inline) 55 size_t loadLength(size_t DIM : 0, T)(T val) 56 { 57 static if (__traits(compiles, { auto _ = val.opDollar!DIM; })) 58 return val.opDollar!DIM; 59 else static if (DIM == 0) 60 return opDollar(); 61 else static if (isForward!T) 62 { 63 size_t length; 64 foreach (u; val[DIM]) 65 length++; 66 return length; 67 } 68 else 69 return 1; 70 } 71 72 /// ditto 73 pragma(inline) 74 size_t loadLength(T)(T val) 75 { 76 static if (__traits(compiles, { auto _ = val.opDollar(); })) 77 return val.opDollar(); 78 else static if (__traits(compiles, { auto _ = val.length; })) 79 return val.length; 80 else static if (isForward!T) 81 { 82 size_t length; 83 foreach (u; val) 84 length++; 85 return length; 86 } 87 else 88 return 1; 89 } 90 91 /** 92 * Shallow clones a value. 93 * 94 * Params: 95 * val = The value to be shallow cloned. 96 * 97 * Returns: 98 * A shallow clone of the provided value. 99 * 100 * Example: 101 * ```d 102 * A a; 103 * A b = a.dup(); 104 * ``` 105 */ 106 pragma(inline) 107 T dup(T)(T val) 108 if (!isArray!T && !isAssignable!(T, Object)) 109 { 110 return val; 111 } 112 113 /** 114 * Deep clones a value. 115 * 116 * Params: 117 * val = The value to be deep cloned. 118 * 119 * Returns: 120 * A deep clone of the provided value. 121 * 122 * Example: 123 * ```d 124 * B a; // where B is a class containing indirection 125 * B b = a.ddup(); 126 * ``` 127 */ 128 pragma(inline) 129 T ddup(T)(T val) 130 if (!isArray!T && !isAssociativeArray!T) 131 { 132 static if (!hasIndirections!T || isPointer!T) 133 return val; 134 else 135 { 136 T ret = factory!T; 137 static foreach (field; Fields!T) 138 { 139 static if (isMutable!(TypeOf!(T, field))) 140 { 141 static if (!hasIndirections!(TypeOf!(T, field))) 142 __traits(getMember, ret, field) = __traits(getMember, val, field); 143 else 144 __traits(getMember, ret, field) = __traits(getMember, val, field).ddup(); 145 } 146 } 147 return ret; 148 } 149 } 150 151 /// ditto 152 pragma(inline) 153 T ddup(T)(T arr) 154 if (isArray!T && !isAssociativeArray!T) 155 { 156 T ret; 157 foreach (u; arr) 158 ret ~= u.ddup(); 159 return ret; 160 } 161 162 /// ditto 163 pragma(inline) 164 T ddup(T)(T arr) 165 if (isAssociativeArray!T) 166 { 167 T ret; 168 foreach (key, value; arr) 169 ret[key.ddup()] = value.ddup(); 170 return ret; 171 } 172 173 /** 174 * Duplicates `val` using soft[de]serialization, avoiding deep cloning. 175 * 176 * This is safer than a normal shallow copy, as it ensures that the new value has a totally new instance. 177 * 178 * Params: 179 * val = The value to be duplicated. 180 * 181 * Returns: 182 * Clone of `val`. 183 */ 184 pragma(inline) 185 @trusted T qdup(T)(T val) 186 { 187 static if (isArray!T) 188 { 189 size_t size = ElementType!T.sizeof * val.length; 190 T ret = factory!T(size / ElementType!T.sizeof); 191 memcpy(cast(void*)val.ptr, cast(void*)ret.ptr, size); 192 return ret; 193 } 194 else 195 { 196 T ret = factory!T; 197 memcpy(reference!val, reference!ret, sizeof!T); 198 return ret; 199 } 200 } 201 202 /// Creates a new instance of `T` dynamically based on its traits, with optional construction args. 203 pragma(inline) 204 T factory(T, ARGS...)(ARGS args) 205 { 206 static if (isDynamicArray!T) 207 { 208 static if (ARGS.length == 0) 209 return new T(0); 210 else 211 return new T(args); 212 } 213 else static if (isReferenceType!T) 214 return new T(args); 215 else 216 { 217 static if (ARGS.length != 0) 218 return T(args); 219 else 220 return T.init; 221 } 222 } 223 224 @nogc: 225 /** 226 * Dynamically stores all data from `src` to `dst`. 227 * 228 * Params: 229 * src = Value to copy data from. 230 * dst = Value to copy data to. 231 */ 232 pragma(inline) 233 @trusted void store(A, B)(const scope A src, ref B dst) 234 { 235 static if (isReinterpretable!(A, B)) 236 memcpy(reference!src, reference!dst, sizeof!B); 237 else 238 { 239 static foreach (field; Fields!B) 240 { 241 static if (hasChild!(A, field) && isMutable!(getChild!(A, field)) && isMutable!(getChild!(B, field))) 242 __traits(getMember, dst, field).store(__traits(getMember, src, field)); 243 } 244 } 245 } 246 247 /// Defines `L` as a store field, for use in store merging. 248 public enum mergeAs(alias F) = "mergeAs"~identifier!F; 249 250 /** 251 * Performs a merged store on `dst` from a sequence of fields. 252 * 253 * This is done by portioning all fields to as little writes as necessary, and then doing hardware-accelerated 254 * writes to set all field data. This can be substantially faster than normal set operations. 255 * 256 * Params: 257 * VARS = The variables that will be written to `dst` as field data. 258 * dst = The destination data to write fields to. 259 * 260 * Remarks: 261 * - All variables in `VARS` must be sequentially ordered in stack memory. 262 * - All variables in `VARS` must be named as the fields in `dst` they correspond to, or have a `mergeAs!(T.x)` attribute set. 263 * - This may be unusable in situations where the compiler does memory operations due to the aforementioned. 264 */ 265 public template storeMerged(VARS...) 266 if ((allSatisfy!(isLocal, VARS) || allSatisfy!(isField, VARS)) && VARS.length >= 1) 267 { 268 private: 269 pure size_t alignTo(size_t size, size_t alignment)() 270 { 271 return size + (alignment - ((size % alignment) | alignment)); 272 } 273 274 template names() 275 { 276 enum udaName(alias VAR) = 277 { 278 static foreach (ATTR; __traits(getAttributes, VAR)) 279 { 280 static if (__traits(compiles, { auto _ = ATTR; }) && isString!(typeof(ATTR)) && ATTR.length > 6) 281 return ATTR[6..$]; 282 } 283 return null; 284 }(); 285 286 alias names = AliasSeq!(); 287 288 static foreach (VAR; VARS) 289 { 290 static if (udaName!VAR != null) 291 names = AliasSeq!(names, udaName!VAR); 292 else 293 names = AliasSeq!(names, __traits(identifier, VAR)); 294 } 295 } 296 297 template prepare(A) 298 { 299 alias fields = AliasSeq!(); 300 alias sizes = AliasSeq!(); 301 302 static foreach (i, name; s!()) 303 { 304 static if (i == 0) 305 { 306 fields = AliasSeq!(fields, name); 307 sizes = AliasSeq!(sizes, alignTo!(typeof(getChild!(A, name)).sizeof, typeof(getChild!(A, name)).alignof)()); 308 } 309 else static if ((getChild!(A, name).offsetof - getChild!(A, s!()[i - 1]).offsetof) != alignTo!(getChild!(A, s!()[i - 1]).sizeof, getChild!(A, name).alignof)) 310 { 311 fields = AliasSeq!(fields, name); 312 sizes = AliasSeq!(sizes, alignTo!(typeof(getChild!(A, name)).sizeof, typeof(getChild!(A, name)).alignof)()); 313 } 314 else 315 { 316 sizes = AliasSeq!(sizes[0..$-1], alignTo!(sizes[$-1], getChild!(A, name).alignof)() + getChild!(A, name).sizeof); 317 } 318 } 319 } 320 321 public: 322 /** 323 * Performs a merged store on `dst` from a sequence of fields. 324 * 325 * This is done by portioning all fields to as little writes as necessary, and then doing hardware-accelerated 326 * writes to set all field data. This can be substantially faster than normal set operations. 327 * 328 * Params: 329 * VARS = The variables that will be written to `dst` as field data. 330 * dst = The destination data to write fields to. 331 * 332 * Remarks: 333 * - All variables in `VARS` must be sequentially ordered in stack memory. 334 * - All variables in `VARS` must be named as the fields in `dst` they correspond to, or have a `mergeAs!(T.x)` attribute set. 335 * - This may be unusable in situations where the compiler does memory operations due to the aforementioned. 336 */ 337 @trusted void storeMerged(A)(ref A dst) 338 { 339 static assert(Fields!A.length >= VARS.length, "'"~A.stringof~"' must have the same number or greater fields as locals being storeted!"); 340 341 debug 342 { 343 size_t offset; 344 foreach (i, VAR; VARS) 345 { 346 offset += VAR.alignof - ((offset % VAR.alignof) | VAR.alignof); 347 assert(cast(void*)&(VARS[0]) + offset == cast(void*)&(VARS[i]), 348 "Variables must be sequentially declared to use specialized field storeting, if they are, the compiler is optimizing this out!"); 349 offset += VAR.sizeof; 350 } 351 } 352 353 alias fields = prepare!A.fields; 354 alias sizes = prepare!A.sizes; 355 356 static foreach (i, VAR; VARS) 357 {{ 358 static assert(hasChild!(A, s!()[i]) && isField!(A, s!()[i]) && is(Unqual!(typeof(getChild!(A, s!()[i]))) == Unqual!(typeof(VAR))), 359 "Variable '"~identifier!VAR~"' does not represent a valid field in '"~A.stringof~"'!"); 360 }} 361 362 static foreach (i, field; fields) 363 memcpy!(sizes[i])(cast(void*)&VARS[staticIndexOf!(field, s!())], cast(void*)&__traits(getMember, dst, field)); 364 } 365 } 366 367 /** 368 * Performs a merged load from `dst` to a sequence of variables. 369 * 370 * This is done by portioning all fields to as little reads as necessary, and then doing hardware-accelerated 371 * writes from all of the field data. This can be substantially faster than normal read operations. 372 * 373 * Params: 374 * VARS = The variables that will be written to `dst` as field data. 375 * dst = The destination data to read fields from. 376 * 377 * Remarks: 378 * - All variables in `VARS` must be sequentially ordered in stack memory. 379 * - All variables in `VARS` must be named as the fields in `dst` they correspond to, or have a `mergeAs!(T.x)` attribute set. 380 * - This may be unusable in situations where the compiler does memory operations due to the aforementioned. 381 */ 382 public template loadMerged(VARS...) 383 if ((allSatisfy!(isLocal, VARS) || allSatisfy!(isField, VARS)) && VARS.length >= 1) 384 { 385 private: 386 pure size_t alignTo(size_t size, size_t alignment)() 387 { 388 return size + (alignment - ((size % alignment) | alignment)); 389 } 390 391 template names() 392 { 393 enum udaName(alias VAR) = 394 { 395 static foreach (ATTR; __traits(getAttributes, VAR)) 396 { 397 static if (__traits(compiles, { auto _ = ATTR; }) && isString!(typeof(ATTR)) && ATTR.length > 6) 398 return ATTR[6..$]; 399 } 400 return null; 401 }(); 402 403 alias names = AliasSeq!(); 404 405 static foreach (VAR; VARS) 406 { 407 static if (udaName!VAR != null) 408 names = AliasSeq!(names, udaName!VAR); 409 else 410 names = AliasSeq!(names, __traits(identifier, VAR)); 411 } 412 } 413 414 template prepare(A) 415 { 416 alias fields = AliasSeq!(); 417 alias sizes = AliasSeq!(); 418 419 static foreach (i, name; s!()) 420 { 421 static if (i == 0) 422 { 423 fields = AliasSeq!(fields, name); 424 sizes = AliasSeq!(sizes, alignTo!(typeof(getChild!(A, name)).sizeof, typeof(getChild!(A, name)).alignof)()); 425 } 426 else static if ((getChild!(A, name).offsetof - getChild!(A, s!()[i - 1]).offsetof) != alignTo!(getChild!(A, s!()[i - 1]).sizeof, getChild!(A, name).alignof)) 427 { 428 fields = AliasSeq!(fields, name); 429 sizes = AliasSeq!(sizes, alignTo!(typeof(getChild!(A, name)).sizeof, typeof(getChild!(A, name)).alignof)()); 430 } 431 else 432 { 433 sizes = AliasSeq!(sizes[0..$-1], alignTo!(sizes[$-1], getChild!(A, name).alignof)() + getChild!(A, name).sizeof); 434 } 435 } 436 } 437 438 public: 439 /** 440 * Performs a merged load from `dst` to a sequence of variables. 441 * 442 * This is done by portioning all fields to as little reads as necessary, and then doing hardware-accelerated 443 * writes from all of the field data. This can be substantially faster than normal read operations. 444 * 445 * Params: 446 * VARS = The variables that will be written to `dst` as field data. 447 * dst = The destination data to read fields from. 448 * 449 * Remarks: 450 * - All variables in `VARS` must be sequentially ordered in stack memory. 451 * - All variables in `VARS` must be named as the fields in `dst` they correspond to, or have a `mergeAs!(T.x)` attribute set. 452 * - This may be unusable in situations where the compiler does memory operations due to the aforementioned. 453 */ 454 @trusted void loadMerged(A)(ref A dst) 455 { 456 static assert(Fields!A.length >= VARS.length, "'"~A.stringof~"' must have the same number or greater fields as locals being storeted!"); 457 458 debug 459 { 460 size_t offset; 461 foreach (i, VAR; VARS) 462 { 463 offset += VAR.alignof - ((offset % VAR.alignof) | VAR.alignof); 464 assert(cast(void*)&(VARS[0]) + offset == cast(void*)&(VARS[i]), 465 "Variables must be sequentially declared to use specialized field storeting, if they are, the compiler is optimizing this out!"); 466 offset += VAR.sizeof; 467 } 468 } 469 470 alias fields = prepare!A.fields; 471 alias sizes = prepare!A.sizes; 472 473 static foreach (i, VAR; VARS) 474 {{ 475 static assert(hasChild!(A, s!()[i]) && isField!(A, s!()[i]) && is(Unqual!(typeof(getChild!(A, s!()[i]))) == Unqual!(typeof(VAR))), 476 "Variable '"~identifier!VAR~"' does not represent a valid field in '"~A.stringof~"'!"); 477 }} 478 479 static foreach (i, field; fields) 480 memcpy!(sizes[i])(cast(void*)&__traits(getMember, dst, field), cast(void*)&VARS[staticIndexOf!(field, s!())]); 481 } 482 }