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