1 module nxt.container.variant_arrays; 2 3 // version = nxt_benchmark; 4 5 // private mixin template VariantArrayOf(Type) 6 // { 7 // import nxt.container.dynamic_array : DynamicArray; 8 // DynamicArray!Type store; 9 // } 10 11 /** Array storage of variants. 12 13 Enables lightweight storage of polymorphic objects. 14 15 Each element is indexed by a corresponding `VariantRef`. 16 */ 17 struct VariantArrays(Types...) { /+ TODO: Change to AliasSeq TypesTuple, Allocatar = GCAllocator +/ 18 alias Size = size_t; 19 alias Ref = VariantRef!(Size, Types); 20 21 import nxt.container.dynamic_array : DynamicArray; 22 import std.experimental.allocator.mallocator : Mallocator; 23 24 /// Returns: array type (as a string) of `Type`. 25 private static immutable(string) arrayTypeStringOfIndex(uint typeIndex)() { 26 pragma(inline, true); 27 return `DynamicArray!(Types[` ~ typeIndex.stringof ~ `], Mallocator)`; /+ TODO: Make Mallocator a parameter +/ 28 } 29 30 /** Returns: array instance (as a strinng) storing `SomeKind`. 31 * TODO: make this a template mixin 32 */ 33 private static immutable(string) arrayInstanceString(SomeKind)() 34 if (Ref.canReferenceType!SomeKind) { 35 pragma(inline, true); 36 return `_store` ~ Ref.nrOfKind!(SomeKind).stringof; // previously `SomeKind.mangleof` 37 } 38 39 /// Make reference to type `SomeKind` at offset `index`. 40 static Ref makeRef(SomeKind)(Ref.Size index) 41 if (Ref.canReferenceType!SomeKind) { 42 pragma(inline, true); 43 return Ref(Ref.nrOfKind!SomeKind, index); 44 } 45 46 /** Insert `value` at back. 47 */ 48 Ref insertBack(SomeKind)(SomeKind value) /+ TODO: add array type overload +/ 49 if (Ref.canReferenceType!SomeKind) { 50 mixin(`alias arrayInstance = ` ~ arrayInstanceString!SomeKind ~ `;`); 51 const currentIndex = arrayInstance.length; 52 arrayInstance.insertBackMove(value); 53 return Ref(Ref.nrOfKind!SomeKind, currentIndex); 54 } 55 alias put = insertBack; // polymorphic `OutputRange` support 56 57 /** Move (emplace) `value` into back. 58 */ 59 Ref insertBackMove(SomeKind)(ref SomeKind value) /+ TODO: add array type overload +/ 60 if (Ref.canReferenceType!SomeKind) { 61 version (DigitalMars) pragma(inline, false); // DMD cannot inline 62 mixin(`alias arrayInstance = ` ~ arrayInstanceString!SomeKind ~ `;`); 63 const currentIndex = arrayInstance.length; 64 arrayInstance.insertBackMove(value); 65 return Ref(Ref.nrOfKind!SomeKind, currentIndex); 66 } 67 68 /// ditto 69 void opOpAssign(string op, SomeKind)(SomeKind value) /+ TODO: add array type overload +/ 70 if (op == "~" && 71 Ref.canReferenceType!SomeKind) { 72 pragma(inline, true); 73 insertBackMove(value); // move enables uncopyable types 74 } 75 76 /// Get reference to element of type `SomeKind` at `index`. 77 ref inout(SomeKind) at(SomeKind)(in size_t index) inout return scope 78 if (Ref.canReferenceType!SomeKind) { 79 pragma(inline, true); 80 mixin(`return ` ~ arrayInstanceString!SomeKind ~ `[index];`); 81 } 82 83 /// Get reference to element of type `SomeKind` at `ref_`. 84 scope ref inout(SomeKind) at(SomeKind)(in Ref ref_) inout return 85 if (Ref.canReferenceType!SomeKind) { 86 pragma(inline, true); 87 assert(Ref.nrOfKind!SomeKind == ref_.kindNr, 88 "Ref is not of expected template type " ~ SomeKind.stringof); 89 mixin(`return ` ~ arrayInstanceString!SomeKind ~ `[ref_.index];`); 90 } 91 92 /// Peek at element of type `SomeKind` at `ref_`. 93 inout(SomeKind)* peek(SomeKind)(in Ref ref_) inout return @system 94 if (Ref.canReferenceType!SomeKind) { 95 pragma(inline, true); 96 if (Ref.nrOfKind!SomeKind == ref_._word.kindNr) 97 return &at!SomeKind(ref_._word.index); 98 else 99 return null; 100 } 101 102 /// Constant access to all elements of type `SomeKind`. 103 inout(SomeKind)[] allOf(SomeKind)() inout return scope 104 if (Ref.canReferenceType!SomeKind) { 105 pragma(inline, true); 106 mixin(`return ` ~ arrayInstanceString!SomeKind ~ `[];`); 107 } 108 109 /// Reserve space for `newCapacity` elements of type `SomeKind`. 110 size_t reserve(SomeKind)(in size_t newCapacity) 111 if (Ref.canReferenceType!SomeKind) { 112 pragma(inline, true); 113 mixin(`alias arrayInstance = ` ~ arrayInstanceString!SomeKind ~ `;`); 114 return arrayInstance.reserve(newCapacity); 115 } 116 117 /** Returns: length of store. */ 118 @property size_t length() const 119 { 120 pragma(inline, true); 121 typeof(return) lengthSum = 0; 122 foreach (Type; Types) 123 mixin(`lengthSum += ` ~ arrayInstanceString!Type ~ `.length;`); 124 return lengthSum; 125 } 126 127 /** Check if empty. */ 128 bool empty() const @property 129 { 130 pragma(inline, true); 131 return length == 0; 132 } 133 134 private: 135 // static foreach (const typeIndex, Type; Types) 136 // { 137 // /+ TODO: is it better to use?: mixin VariantArrayOf!(Type); +/ 138 // mixin(arrayTypeStringOfIndex!typeIndex ~ ` ` ~ arrayInstanceString!Type ~ `;`); 139 // } 140 mixin({ 141 string s = ""; 142 foreach (const typeIndex, Type; Types) 143 s ~= arrayTypeStringOfIndex!typeIndex ~ ` ` ~ arrayInstanceString!Type ~ `;`; 144 return s; 145 }()); 146 } 147 148 /** Typed index (reference) into an element in `VariantArrays`. 149 * 150 * TODO: merge with soa.d? 151 */ 152 private struct VariantRef(Size, DefinedTypes...) { 153 import std.meta : staticIndexOf; 154 155 alias Kind = ubyte; // kind code 156 157 import nxt.bit_traits : bitsNeeded; 158 159 /// Used to indicate undefined value. 160 private struct Undefined {} 161 162 import std.meta : AliasSeq; 163 164 alias Types = AliasSeq!(Undefined, DefinedTypes); 165 166 /// Number of bits needed to represent kind. 167 private enum kindBits = bitsNeeded!(Types.length); 168 169 /** Get number kind of kind type `SomeKind`. 170 TODO: make private? 171 */ 172 enum nrOfKind(SomeKind) = staticIndexOf!(SomeKind, Types); /+ TODO: cast to ubyte if Types.length is <= 256 +/ 173 174 /// Is `true` iff an index to a `SomeKind`-kind can be stored. 175 enum canReferenceType(SomeKind) = nrOfKind!SomeKind >= 0; 176 177 /// Comparsion works like for integers. 178 int opCmp(in typeof(this) rhs) const @trusted 179 { 180 version (LDC) pragma(inline, true); 181 if (this._rawWord < rhs._rawWord) 182 return -1; 183 if (this._rawWord > rhs._rawWord) 184 return +1; 185 return 0; 186 } 187 188 pragma(inline, true): 189 190 /// Construct from mutable `that`. 191 this(in typeof(this) that) { 192 this._rawWord = that._rawWord; 193 } 194 /// Construct from constant `that`. 195 this(typeof(this) that) { 196 this._rawWord = that._rawWord; 197 } 198 199 /// Construct. 200 this(Kind kind, Size index) /+ TODO: can ctor inferred by bitfields? +/ 201 { 202 this._word.kindNr = kind; 203 this._word.index = index; 204 } 205 206 /// Construct from raw word representation `rawWord`. 207 private this(Size rawWord) { 208 this._rawWord = rawWord; 209 } 210 211 /// Get kindNr. 212 Kind kindNr() const => _word.kindNr; 213 214 /// Get index. 215 Size index() const => _word.index; 216 217 /// Cast to `size_t`. 218 size_t opCast(T : size_t)() const => _rawWord; 219 220 import core.internal.traits : Unqual; 221 222 /// Allow cast to unqualified. 223 U opCast(U : Unqual!(typeof(this)))() const => U(rawWord); 224 225 /// The index itself is the hash. 226 hash_t toHash() const @property => _rawWord; 227 static assert(hash_t.sizeof == _rawWord.sizeof); 228 229 /// Cast to `bool`, meaning 'true' if defined, `false` otherwise. 230 bool opCast(U : bool)() const => isDefined(); 231 232 /// Returns: `true` iff is defined. 233 bool isDefined() const => _rawWord != 0; 234 235 /// Returns: `true` iff `this` targets a value of type `SomeKind`. 236 public bool isA(SomeKind)() const => nrOfKind!(SomeKind) == _word.kindNr; 237 238 void toString(Sink)(ref scope Sink sink) const @trusted { 239 import std.format : formattedWrite; 240 if (isDefined) 241 sink.formattedWrite!`%s(%s@%s)`(Unqual!(typeof(this)).stringof, _word.index, _word.kindNr); 242 else 243 sink.formattedWrite!`%s(null)`(Unqual!(typeof(this)).stringof); 244 } 245 246 private struct Word { 247 import nxt.dip_traits : hasPreviewBitfields; 248 version (LittleEndian) { 249 static if (hasPreviewBitfields) { 250 /+ TODO: remove mixins when -preview=bitfields is in stable dmd +/ 251 mixin("Kind kindNr:kindBits;"); 252 mixin("Size index:8*Size.sizeof - kindBits;"); 253 } else { 254 import std.bitmanip : bitfields; 255 mixin(bitfields!(Kind, "kindNr", kindBits, 256 Size, "index", 8*Size.sizeof - kindBits)); 257 } 258 } else { 259 static assert(0, "TODO: BigEndian support"); 260 } 261 } 262 263 private union { 264 Word _word; 265 Size _rawWord; // for comparsion 266 } 267 268 // static assert(this.sizeof == Size.sizeof, 269 // `This should haven't any memory overhead compared to size_t`); 270 } 271 272 pure nothrow @safe unittest { 273 alias R = VariantRef!(size_t, int, float); 274 R r; 275 static assert(r.canReferenceType!(int)); 276 static assert(r.canReferenceType!(float)); 277 static assert(!r.canReferenceType!(short)); 278 279 import std.array : Appender; 280 Appender!(const(R)[]) app; 281 assert(app.data.length == 0); 282 283 const R x; 284 R mx = x; 285 assert(x == mx); 286 287 /+ TODO: app ~= x; +/ 288 289 // const y = [R.init, R.init]; 290 /+ TODO: app ~= y; +/ 291 } 292 293 unittest { 294 import std.conv : to; 295 alias R = VariantRef!(size_t, int, float); 296 R r; 297 assert(r.to!string == R.stringof~`(null)`); 298 } 299 300 /** Minimalistic fixed-length (static) array of (`capacity`) number of elements 301 * of type `E` where length only fits in an `ubyte` for compact packing. 302 */ 303 version (unittest) 304 private struct MinimalStaticArray(E, ubyte capacity) { 305 this(in E[] es) { 306 assert(es.length <= capacity, 307 "Length of input parameter `es` is larger than capacity " 308 ~ capacity.stringof); 309 _es[0 .. es.length] = es; 310 _length = cast(typeof(_length))es.length; 311 } 312 313 private: 314 E[capacity] _es; 315 typeof(capacity) _length; 316 } 317 318 pure nothrow @safe @nogc unittest { 319 const ch7 = MinimalStaticArray!(char, 7)(`1234567`); 320 assert(ch7._es[] == `1234567`); 321 } 322 323 /// 324 pure nothrow @safe @nogc unittest { 325 alias Chars(uint capacity) = MinimalStaticArray!(char, capacity); 326 alias Chars7 = Chars!7; 327 alias Chars15 = Chars!15; 328 alias VA = VariantArrays!(ulong, 329 Chars7, 330 Chars15); 331 332 VA data; 333 assert(data.length == 0); 334 assert(data.empty); 335 336 const i0 = data.put(ulong(13)); 337 assert(cast(size_t)i0 == 1); 338 339 assert(i0.isA!ulong); 340 assert(data.at!ulong(0) == ulong(13)); 341 assert(data.length == 1); 342 assert(!data.empty); 343 assert(data.allOf!ulong == [ulong(13)].s); 344 345 const i1 = data.put(Chars7(`1234567`)); 346 assert(cast(size_t)i1 == 2); 347 348 // same order as in `Types` 349 assert(i0 < i1); 350 351 assert(i1.isA!(Chars7)); 352 assert(data.at!(Chars7)(0) == Chars7(`1234567`)); 353 assert(data.allOf!(Chars7) == [Chars7(`1234567`)].s); 354 assert(data.length == 2); 355 356 const i2 = data.put(Chars15(`123`)); 357 assert(cast(size_t)i2 == 3); 358 359 // same order as in `Types` 360 assert(i0 < i2); 361 assert(i1 < i2); 362 363 assert(i2.isA!(Chars15)); 364 assert(data.at!(Chars15)(0) == Chars15(`123`)); 365 /+ TODO: assert(data.allOf!(Chars15) == [Chars15(`123`)].s); +/ 366 assert(data.length == 3); 367 368 const i3 = data.put(Chars15(`1234`)); 369 assert(cast(size_t)i3 == 7); 370 371 // same order as in `Types` 372 assert(i0 < i3); 373 assert(i1 < i3); 374 assert(i2 < i3); // same type, i2 added before i3 375 376 assert(i3.isA!(Chars15)); 377 assert(data.at!(Chars15)(1) == Chars15(`1234`)); 378 assert(data.allOf!(Chars15) == [Chars15(`123`), Chars15(`1234`)].s); 379 assert(data.length == 4); 380 } 381 382 // version = extraTests; 383 384 version (extraTests) { 385 static private: 386 alias S = VariantArrays!(Rel1, Rel2, Int); 387 388 // relations 389 struct Rel1 { S.Ref[1] args; } 390 struct Rel2 { S.Ref[2] args; } 391 392 struct Int { int value; } 393 } 394 395 /// 396 version (extraTests) 397 pure nothrow @safe @nogc unittest { 398 S s; 399 400 const S.Ref top = s.put(Rel1(s.put(Rel1(s.put(Rel2([s.put(Int(42)), 401 s.put(Int(43))])))))); 402 assert(top); 403 assert(s.allOf!Rel1.length == 2); 404 assert(s.allOf!Rel2.length == 1); 405 assert(s.allOf!Int.length == 2); 406 assert(s.length == 5); 407 } 408 409 /// put and peek 410 version (extraTests) 411 @system pure nothrow @nogc unittest { 412 S s; 413 const n = 10; 414 foreach (const i; 0 .. n) { 415 S.Ref lone = s.put(Int(i)); 416 Int* lonePtr = s.peek!Int(lone); 417 assert(lonePtr); 418 assert(*lonePtr == Int(i)); 419 } 420 assert(s.length == 10); 421 } 422 423 version (unittest) { 424 import nxt.array_help : s; 425 } 426 427 version (nxt_benchmark) 428 unittest { 429 import std.stdio : writeln; 430 import std.datetime : MonoTime; 431 import std.meta : AliasSeq; 432 alias E = uint; 433 immutable n = 5_000_000; 434 foreach (A; AliasSeq!(VariantArrays!(E))) { 435 A a; 436 immutable before = MonoTime.currTime(); 437 foreach (uint i; 0 .. n) 438 a ~= i; 439 immutable after = MonoTime.currTime(); 440 writeln("Added ", n, " integer nodes into ", A.stringof, " in ", after - before); 441 } 442 443 } 444 445 /** Store array of `E[]` with different lengths compactly. 446 When length of `E` is known at compile-time store it as a static array. 447 */ 448 private struct HybridArrayArray(E) { /+ TODO: make useful and then public +/ 449 @safe pure: 450 void insertBack(E[] value) { 451 switch (value.length) { 452 /+ TODO: static foreach +/ 453 case 1: _store.insertBack(cast(char[1])value[0 .. 1]); break; 454 case 2: _store.insertBack(cast(char[2])value[0 .. 2]); break; 455 case 3: _store.insertBack(cast(char[3])value[0 .. 3]); break; 456 case 4: _store.insertBack(cast(char[4])value[0 .. 4]); break; 457 case 5: _store.insertBack(cast(char[5])value[0 .. 5]); break; 458 case 6: _store.insertBack(cast(char[6])value[0 .. 6]); break; 459 case 7: _store.insertBack(cast(char[7])value[0 .. 7]); break; 460 case 8: _store.insertBack(cast(char[8])value[0 .. 8]); break; 461 default: 462 _store.insertBack(value); 463 } 464 } 465 private: 466 VariantArrays!(char[1], 467 char[2], 468 char[3], 469 char[4], 470 char[5], 471 char[6], 472 char[7], 473 char[8], 474 E[]) _store; 475 } 476 477 /// 478 pure nothrow @safe @nogc unittest { 479 alias E = char; 480 HybridArrayArray!(E) ss; 481 ss.insertBack(E[].init); 482 }