| ... | @@ -0,0 +1,118 @@ |
| 1 | const std = @import("../std.zig"); |
| 2 | const meta = std.meta; |
| 3 | const testing = std.testing; |
| 4 | const mem = std.mem; |
| 5 | const assert = std.debug.assert; |
| 6 | |
| 7 | /// This is useful for saving memory when allocating an object that has many |
| 8 | /// optional components. The optional objects are allocated sequentially in |
| 9 | /// memory, and a single integer is used to represent each optional object |
| 10 | /// and whether it is present based on each corresponding bit. |
| 11 | pub fn TrailerFlags(comptime Fields: type) type { |
| 12 | return struct { |
| 13 | bits: Int, |
| 14 | |
| 15 | pub const Int = @Type(.{ .Int = .{ .bits = bit_count, .is_signed = false } }); |
| 16 | pub const bit_count = @typeInfo(Fields).Struct.fields.len; |
| 17 | |
| 18 | pub const Self = @This(); |
| 19 | |
| 20 | pub fn has(self: Self, comptime name: []const u8) bool { |
| 21 | const field_index = meta.fieldIndex(Fields, name).?; |
| 22 | return (self.bits & (1 << field_index)) != 0; |
| 23 | } |
| 24 | |
| 25 | pub fn get(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime name: []const u8) ?Field(name) { |
| 26 | if (!self.has(name)) |
| 27 | return null; |
| 28 | return self.ptrConst(p, name).*; |
| 29 | } |
| 30 | |
| 31 | pub fn setFlag(self: *Self, comptime name: []const u8) void { |
| 32 | const field_index = meta.fieldIndex(Fields, name).?; |
| 33 | self.bits |= 1 << field_index; |
| 34 | } |
| 35 | |
| 36 | pub fn init(comptime names: anytype) Self { |
| 37 | var self: Self = .{ .bits = 0 }; |
| 38 | inline for (@typeInfo(@TypeOf(names)).Struct.fields) |field| { |
| 39 | if (@field(names, field.name)) { |
| 40 | const field_index = meta.fieldIndex(Fields, field.name).?; |
| 41 | self.bits |= 1 << field_index; |
| 42 | } |
| 43 | } |
| 44 | return self; |
| 45 | } |
| 46 | |
| 47 | pub fn set( |
| 48 | self: Self, |
| 49 | p: [*]align(@alignOf(Fields)) u8, |
| 50 | comptime name: []const u8, |
| 51 | value: Field(name), |
| 52 | ) void { |
| 53 | self.ptr(p, name).* = value; |
| 54 | } |
| 55 | |
| 56 | pub fn ptr(self: Self, p: [*]align(@alignOf(Fields)) u8, comptime name: []const u8) *Field(name) { |
| 57 | const off = self.offset(p, name); |
| 58 | return @ptrCast(*Field(name), @alignCast(@alignOf(Field(name)), p + off)); |
| 59 | } |
| 60 | |
| 61 | pub fn ptrConst(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime name: []const u8) *const Field(name) { |
| 62 | const off = self.offset(p, name); |
| 63 | return @ptrCast(*const Field(name), @alignCast(@alignOf(Field(name)), p + off)); |
| 64 | } |
| 65 | |
| 66 | pub fn offset(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime name: []const u8) usize { |
| 67 | var off: usize = 0; |
| 68 | inline for (@typeInfo(Fields).Struct.fields) |field, i| { |
| 69 | const active = (self.bits & (1 << i)) != 0; |
| 70 | if (comptime mem.eql(u8, field.name, name)) { |
| 71 | assert(active); |
| 72 | return mem.alignForwardGeneric(usize, off, @alignOf(field.field_type)); |
| 73 | } else if (active) { |
| 74 | off = mem.alignForwardGeneric(usize, off, @alignOf(field.field_type)); |
| 75 | off += @sizeOf(field.field_type); |
| 76 | } |
| 77 | } |
| 78 | @compileError("no field named " ++ name ++ " in type " ++ @typeName(Fields)); |
| 79 | } |
| 80 | |
| 81 | pub fn Field(comptime name: []const u8) type { |
| 82 | return meta.fieldInfo(Fields, name).field_type; |
| 83 | } |
| 84 | |
| 85 | pub fn sizeInBytes(self: Self) usize { |
| 86 | var off: usize = 0; |
| 87 | inline for (@typeInfo(Fields).Struct.fields) |field, i| { |
| 88 | if ((self.bits & (1 << i)) != 0) { |
| 89 | off = mem.alignForwardGeneric(usize, off, @alignOf(field.field_type)); |
| 90 | off += @sizeOf(field.field_type); |
| 91 | } |
| 92 | } |
| 93 | return off; |
| 94 | } |
| 95 | }; |
| 96 | } |
| 97 | |
| 98 | test "TrailerFlags" { |
| 99 | const Flags = TrailerFlags(struct { |
| 100 | a: i32, |
| 101 | b: bool, |
| 102 | c: u64, |
| 103 | }); |
| 104 | var flags = Flags.init(.{ |
| 105 | .b = true, |
| 106 | .c = true, |
| 107 | }); |
| 108 | testing.expect(flags.sizeInBytes() == 16); |
| 109 | const slice = try testing.allocator.allocAdvanced(u8, 8, flags.sizeInBytes(), .exact); |
| 110 | defer testing.allocator.free(slice); |
| 111 | |
| 112 | flags.set(slice.ptr, "b", false); |
| 113 | flags.set(slice.ptr, "c", 12345678); |
| 114 | |
| 115 | testing.expect(flags.get(slice.ptr, "a") == null); |
| 116 | testing.expect(!flags.get(slice.ptr, "b").?); |
| 117 | testing.expect(flags.get(slice.ptr, "c").? == 12345678); |
| 118 | } |