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