1 /** Structure of arrays similar to builtin feature in the Jai programming language. 2 * 3 * See_Also: https://maikklein.github.io/post/soa-d/ 4 * See_Also: http://forum.dlang.org/post/wvulryummkqtskiwrusb@forum.dlang.org 5 * See_Also: https://forum.dlang.org/post/purhollnapramxczmcka@forum.dlang.org 6 */ 7 module nxt.soa; 8 9 /** Structure of arrays similar to members of `S`. 10 */ 11 struct SOA(S) 12 if (is(S == struct)) // TODO extend to `isAggregate!S`? 13 { 14 import core.lifetime : move, moveEmplace; 15 import nxt.pure_mallocator : PureMallocator; 16 17 private alias toType(string s) = typeof(__traits(getMember, S, s)); 18 private alias Types = typeof(S.tupleof); 19 20 this(size_t initialCapacity) 21 { 22 _capacity = initialCapacity; 23 allocate(initialCapacity); 24 } 25 26 auto opDispatch(string name)() 27 { 28 static foreach (index, memberSymbol; S.tupleof) 29 { 30 static if (name == memberSymbol.stringof) 31 { 32 return getArray!index; 33 } 34 } 35 // TODO static assert(0, S.stringof ~ " has no field named " ~ name); 36 } 37 38 /// Push element (struct) `value` to back of array. 39 void insertBack()(S value) @trusted // template-lazy 40 { 41 reserveOneExtra(); 42 static foreach (const index, memberSymbol; S.tupleof) 43 { 44 moveEmplace(__traits(getMember, value, memberSymbol.stringof), 45 getArray!index[_length]); // TODO assert that 46 } 47 ++_length; 48 } 49 50 /// Push element (struct) `value` to back of array using its data members `members`. 51 void insertBackMembers()(Types members) @trusted // template-lazy 52 { 53 reserveOneExtra(); 54 // move each member to its position respective array 55 static foreach (const index, _; members) 56 { 57 moveEmplace(members[index], getArray!index[_length]); // same as `getArray!index[_length] = members[index];` 58 } 59 ++_length; 60 } 61 62 void opOpAssign(string op, S)(S value) 63 if (op == "~") 64 { 65 pragma(inline, true); 66 insertBack(move(value)); // TODO remove when compiler does this for us 67 } 68 69 /// Length of this array. 70 @property size_t length() const @safe pure nothrow @nogc 71 { 72 return _length; 73 } 74 75 /// Capacity of this array. 76 @property size_t capacity() const @safe pure nothrow @nogc 77 { 78 return _capacity; 79 } 80 81 ~this() @trusted @nogc 82 { 83 import std.experimental.allocator : dispose; 84 static foreach (const index, _; S.tupleof) 85 { 86 PureMallocator.instance.dispose(getArray!index); 87 } 88 } 89 90 /** Index operator. */ 91 inout(SOAElementRef!S) opIndex()(size_t elementIndex) inout return // template-lazy 92 { 93 assert(elementIndex < _length); 94 return typeof(return)(&this, elementIndex); 95 } 96 97 /** Slice operator. */ 98 inout(SOASlice!S) opSlice()() inout return // template-lazy 99 { 100 return typeof(return)(&this); 101 } 102 103 private: 104 105 // generate array definitions 106 static foreach (index, Type; Types) 107 { 108 mixin(Type.stringof ~ `[] _container` ~ index.stringof ~ ";"); 109 } 110 111 /// Get array of all fields at aggregate field index `index`. 112 ref inout(Types[index][]) getArray(size_t index)() inout return 113 { 114 mixin(`return _container` ~ index.stringof ~ ";"); 115 } 116 117 size_t _length = 0; 118 size_t _capacity = 0; 119 short _growthFactor = 2; 120 121 void allocate(size_t newCapacity) @trusted 122 { 123 // if (_alloc is null) 124 // { 125 // _alloc = allocatorObject(Mallocator.instance); 126 // } 127 import std.experimental.allocator : makeArray; 128 static foreach (const index, _; S.tupleof) 129 { 130 getArray!index = PureMallocator.instance.makeArray!(Types[index])(newCapacity); 131 } 132 } 133 134 void grow() @trusted 135 { 136 import std.algorithm.comparison : max; 137 const newCapacity = max(1, _capacity * _growthFactor); 138 const expandSize = newCapacity - _capacity; 139 140 if (_capacity is 0) 141 { 142 allocate(newCapacity); 143 } 144 else 145 { 146 import std.experimental.allocator : expandArray; 147 static foreach (const index, _; S.tupleof) 148 { 149 PureMallocator.instance.expandArray(getArray!index, expandSize); 150 } 151 } 152 _capacity = newCapacity; 153 } 154 155 void reserveOneExtra() 156 { 157 if (_length == _capacity) { grow(); } 158 } 159 } 160 alias StructArrays = SOA; 161 162 /// Reference to element in `soaPtr` at index `elementIndex`. 163 private struct SOAElementRef(S) 164 if (is(S == struct)) // TODO extend to `isAggregate!S`? 165 { 166 SOA!S* soaPtr; 167 size_t elementIndex; 168 169 @disable this(this); 170 171 /// Access member name `memberName`. 172 auto ref opDispatch(string memberName)() 173 @trusted return scope 174 { 175 mixin(`return ` ~ `(*soaPtr).` ~ memberName ~ `[elementIndex];`); 176 } 177 } 178 179 /// Reference to slice in `soaPtr`. 180 private struct SOASlice(S) 181 if (is(S == struct)) // TODO extend to `isAggregate!S`? 182 { 183 SOA!S* soaPtr; 184 185 @disable this(this); 186 187 /// Access aggregate at `index`. 188 inout(S) opIndex(size_t index) inout @trusted return scope 189 { 190 S s = void; 191 static foreach (memberIndex, memberSymbol; S.tupleof) 192 { 193 mixin(`s.` ~ memberSymbol.stringof ~ `= (*soaPtr).getArray!` ~ memberIndex.stringof ~ `[index];`); 194 } 195 return s; 196 } 197 } 198 199 @safe: 200 201 @safe pure nothrow @nogc unittest 202 { 203 import nxt.dip_traits : isDIP1000; 204 205 struct S { int i; float f; } 206 207 auto x = SOA!S(); 208 209 static assert(is(typeof(x.getArray!0()) == int[])); 210 static assert(is(typeof(x.getArray!1()) == float[])); 211 212 assert(x.length == 0); 213 214 x.insertBack(S.init); 215 assert(x.length == 1); 216 217 x ~= S.init; 218 assert(x.length == 2); 219 220 x.insertBackMembers(42, 43f); 221 assert(x.length == 3); 222 assert(x.i[2] == 42); 223 assert(x.f[2] == 43f); 224 225 // uses opDispatch 226 assert(x[2].i == 42); 227 assert(x[2].f == 43f); 228 229 const x3 = SOA!S(3); 230 assert(x3.length == 0); 231 assert(x3.capacity == 3); 232 233 // TODO make foreach work 234 // foreach (_; x[]) 235 // { 236 // } 237 238 static if (isDIP1000) 239 { 240 static assert(!__traits(compiles, 241 { 242 ref int testScope() @safe 243 { 244 auto y = SOA!S(1); 245 y ~= S(42, 43f); 246 return y[0].i; 247 } 248 })); 249 } 250 }