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 }