1 module nxt.container.variant_arrays; 2 3 // version = nxt_benchmark; 4 5 /** Typed index (reference) into an element in `VariantArrays`. 6 * 7 * TODO: merge with soa.d? 8 */ 9 private struct VariantRef(Size, DefinedTypes...) { 10 import std.meta : staticIndexOf; 11 12 alias Kind = ubyte; // kind code 13 14 import nxt.bit_traits : bitsNeeded; 15 16 /// Used to indicate undefined value. 17 private struct Undefined {} 18 19 import std.meta : AliasSeq; 20 21 alias Types = AliasSeq!(Undefined, DefinedTypes); 22 23 /// Number of bits needed to represent kind. 24 private enum kindBits = bitsNeeded!(Types.length); 25 26 /** Get number kind of kind type `SomeKind`. 27 TODO: make private? 28 */ 29 enum nrOfKind(SomeKind) = staticIndexOf!(SomeKind, Types); /+ TODO: cast to ubyte if Types.length is <= 256 +/ 30 31 /// Is `true` iff an index to a `SomeKind`-kind can be stored. 32 enum canReferenceType(SomeKind) = nrOfKind!SomeKind >= 0; 33 34 /// Comparsion works like for integers. 35 int opCmp(in typeof(this) rhs) const @trusted 36 { 37 version (LDC) pragma(inline, true); 38 if (this._rawWord < rhs._rawWord) 39 return -1; 40 if (this._rawWord > rhs._rawWord) 41 return +1; 42 return 0; 43 } 44 45 pragma(inline, true): 46 47 /// Construct from mutable `that`. 48 this(in typeof(this) that) { 49 this._rawWord = that._rawWord; 50 } 51 /// Construct from constant `that`. 52 this(typeof(this) that) { 53 this._rawWord = that._rawWord; 54 } 55 56 /// Construct. 57 this(Kind kind, Size index) /+ TODO: can ctor inferred by bitfields? +/ 58 { 59 this._word.kindNr = kind; 60 this._word.index = index; 61 } 62 63 /// Construct from raw word representation `rawWord`. 64 private this(Size rawWord) { 65 this._rawWord = rawWord; 66 } 67 68 /// Get kindNr. 69 Kind kindNr() const => _word.kindNr; 70 71 /// Get index. 72 Size index() const => _word.index; 73 74 /// Cast to `size_t`. 75 size_t opCast(T : size_t)() const => _rawWord; 76 77 import core.internal.traits : Unqual; 78 79 /// Allow cast to unqualified. 80 U opCast(U : Unqual!(typeof(this)))() const => U(rawWord); 81 82 /// The index itself is the hash. 83 hash_t toHash() const @property => _rawWord; 84 static assert(hash_t.sizeof == _rawWord.sizeof); 85 86 /// Cast to `bool`, meaning 'true' if defined, `false` otherwise. 87 bool opCast(U : bool)() const => isDefined(); 88 89 /// Returns: `true` iff is defined. 90 bool isDefined() const => _rawWord != 0; 91 92 /// Returns: `true` iff `this` targets a value of type `SomeKind`. 93 public bool isA(SomeKind)() const => nrOfKind!(SomeKind) == _word.kindNr; 94 95 void toString(Sink)(ref scope Sink sink) const @trusted { 96 import std.format : formattedWrite; 97 if (isDefined) 98 sink.formattedWrite!`%s(%s@%s)`(Unqual!(typeof(this)).stringof, _word.index, _word.kindNr); 99 else 100 sink.formattedWrite!`%s(null)`(Unqual!(typeof(this)).stringof); 101 } 102 103 private struct Word { 104 import nxt.dip_traits : hasPreviewBitfields; 105 version (LittleEndian) { 106 static if (hasPreviewBitfields) { 107 /+ TODO: remove mixins when -preview=bitfields is in stable dmd +/ 108 mixin("Kind kindNr:kindBits;"); 109 mixin("Size index:8*Size.sizeof - kindBits;"); 110 } else { 111 import std.bitmanip : bitfields; 112 mixin(bitfields!(Kind, "kindNr", kindBits, 113 Size, "index", 8*Size.sizeof - kindBits)); 114 } 115 } else { 116 static assert(0, "TODO: BigEndian support"); 117 } 118 } 119 120 private union { 121 Word _word; 122 Size _rawWord; // for comparsion 123 } 124 125 // static assert(this.sizeof == Size.sizeof, 126 // `This should haven't any memory overhead compared to size_t`); 127 } 128 129 pure nothrow @safe unittest { 130 alias R = VariantRef!(size_t, int, float); 131 R r; 132 static assert(r.canReferenceType!(int)); 133 static assert(r.canReferenceType!(float)); 134 static assert(!r.canReferenceType!(short)); 135 136 import std.array : Appender; 137 Appender!(const(R)[]) app; 138 assert(app.data.length == 0); 139 140 const R x; 141 R mx = x; 142 assert(x == mx); 143 144 /+ TODO: app ~= x; +/ 145 146 // const y = [R.init, R.init]; 147 /+ TODO: app ~= y; +/ 148 } 149 150 unittest { 151 import std.conv : to; 152 alias R = VariantRef!(size_t, int, float); 153 R r; 154 assert(r.to!string == R.stringof~`(null)`); 155 } 156 157 // private mixin template VariantArrayOf(Type) 158 // { 159 // import nxt.container.dynamic_array : DynamicArray; 160 // DynamicArray!Type store; 161 // } 162 163 /** Stores set of variants. 164 165 Enables lightweight storage of polymorphic objects. 166 167 Each element is indexed by a corresponding `VariantRef`. 168 */ 169 struct VariantArrays(Types...) { /+ TODO: Change to AliasSeq TypesTuple, Allocatar = GCAllocator +/ 170 alias Size = size_t; 171 alias Ref = VariantRef!(Size, Types); 172 173 import nxt.container.dynamic_array : DynamicArray; 174 import std.experimental.allocator.mallocator : Mallocator; 175 176 /// Returns: array type (as a string) of `Type`. 177 private static immutable(string) arrayTypeStringOfIndex(uint typeIndex)() { 178 pragma(inline, true); 179 return `DynamicArray!(Types[` ~ typeIndex.stringof ~ `], Mallocator)`; /+ TODO: Make Mallocator a parameter +/ 180 } 181 182 /** Returns: array instance (as a strinng) storing `SomeKind`. 183 * TODO: make this a template mixin 184 */ 185 private static immutable(string) arrayInstanceString(SomeKind)() 186 if (Ref.canReferenceType!SomeKind) { 187 pragma(inline, true); 188 return `_store` ~ Ref.nrOfKind!(SomeKind).stringof; // previously `SomeKind.mangleof` 189 } 190 191 /// Make reference to type `SomeKind` at offset `index`. 192 static Ref makeRef(SomeKind)(Ref.Size index) 193 if (Ref.canReferenceType!SomeKind) { 194 pragma(inline, true); 195 return Ref(Ref.nrOfKind!SomeKind, index); 196 } 197 198 /** Insert `value` at back. 199 */ 200 Ref insertBack(SomeKind)(SomeKind value) /+ TODO: add array type overload +/ 201 if (Ref.canReferenceType!SomeKind) { 202 mixin(`alias arrayInstance = ` ~ arrayInstanceString!SomeKind ~ `;`); 203 const currentIndex = arrayInstance.length; 204 arrayInstance.insertBackMove(value); 205 return Ref(Ref.nrOfKind!SomeKind, currentIndex); 206 } 207 alias put = insertBack; // polymorphic `OutputRange` support 208 209 /** Move (emplace) `value` into back. 210 */ 211 Ref insertBackMove(SomeKind)(ref SomeKind value) /+ TODO: add array type overload +/ 212 if (Ref.canReferenceType!SomeKind) { 213 version (DigitalMars) pragma(inline, false); // DMD cannot inline 214 mixin(`alias arrayInstance = ` ~ arrayInstanceString!SomeKind ~ `;`); 215 const currentIndex = arrayInstance.length; 216 arrayInstance.insertBackMove(value); 217 return Ref(Ref.nrOfKind!SomeKind, currentIndex); 218 } 219 220 /// ditto 221 void opOpAssign(string op, SomeKind)(SomeKind value) /+ TODO: add array type overload +/ 222 if (op == "~" && 223 Ref.canReferenceType!SomeKind) { 224 pragma(inline, true); 225 insertBackMove(value); // move enables uncopyable types 226 } 227 228 /// Get reference to element of type `SomeKind` at `index`. 229 ref inout(SomeKind) at(SomeKind)(in size_t index) inout return scope 230 if (Ref.canReferenceType!SomeKind) { 231 pragma(inline, true); 232 mixin(`return ` ~ arrayInstanceString!SomeKind ~ `[index];`); 233 } 234 235 /// Get reference to element of type `SomeKind` at `ref_`. 236 scope ref inout(SomeKind) at(SomeKind)(in Ref ref_) inout return 237 if (Ref.canReferenceType!SomeKind) { 238 pragma(inline, true); 239 assert(Ref.nrOfKind!SomeKind == ref_.kindNr, 240 "Ref is not of expected template type " ~ SomeKind.stringof); 241 mixin(`return ` ~ arrayInstanceString!SomeKind ~ `[ref_.index];`); 242 } 243 244 /// Peek at element of type `SomeKind` at `ref_`. 245 inout(SomeKind)* peek(SomeKind)(in Ref ref_) inout return @system 246 if (Ref.canReferenceType!SomeKind) { 247 pragma(inline, true); 248 if (Ref.nrOfKind!SomeKind == ref_._word.kindNr) 249 return &at!SomeKind(ref_._word.index); 250 else 251 return null; 252 } 253 254 /// Constant access to all elements of type `SomeKind`. 255 inout(SomeKind)[] allOf(SomeKind)() inout return scope 256 if (Ref.canReferenceType!SomeKind) { 257 pragma(inline, true); 258 mixin(`return ` ~ arrayInstanceString!SomeKind ~ `[];`); 259 } 260 261 /// Reserve space for `newCapacity` elements of type `SomeKind`. 262 size_t reserve(SomeKind)(in size_t newCapacity) 263 if (Ref.canReferenceType!SomeKind) { 264 pragma(inline, true); 265 mixin(`alias arrayInstance = ` ~ arrayInstanceString!SomeKind ~ `;`); 266 return arrayInstance.reserve(newCapacity); 267 } 268 269 /** Returns: length of store. */ 270 @property size_t length() const 271 { 272 pragma(inline, true); 273 typeof(return) lengthSum = 0; 274 foreach (Type; Types) 275 mixin(`lengthSum += ` ~ arrayInstanceString!Type ~ `.length;`); 276 return lengthSum; 277 } 278 279 /** Check if empty. */ 280 bool empty() const @property 281 { 282 pragma(inline, true); 283 return length == 0; 284 } 285 286 private: 287 // static foreach (const typeIndex, Type; Types) 288 // { 289 // /+ TODO: is it better to use?: mixin VariantArrayOf!(Type); +/ 290 // mixin(arrayTypeStringOfIndex!typeIndex ~ ` ` ~ arrayInstanceString!Type ~ `;`); 291 // } 292 mixin({ 293 string s = ""; 294 foreach (const typeIndex, Type; Types) 295 s ~= arrayTypeStringOfIndex!typeIndex ~ ` ` ~ arrayInstanceString!Type ~ `;`; 296 return s; 297 }()); 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 }