| 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 | const Type = std.builtin.Type; |
| 7 | |
| 8 | /// This is useful for saving memory when allocating an object that has many |
| 9 | /// optional components. The optional objects are allocated sequentially in |
| 10 | /// memory, and a single integer is used to represent each optional object |
| 11 | /// and whether it is present based on each corresponding bit. |
| 12 | pub fn TrailerFlags(comptime Fields: type) type { |
| 13 | return struct { |
| 14 | bits: Int, |
| 15 | |
| 16 | pub const Int = @Int(.unsigned, bit_count); |
| 17 | pub const bit_count = @typeInfo(Fields).@"struct".field_names.len; |
| 18 | |
| 19 | pub const FieldEnum = std.meta.FieldEnum(Fields); |
| 20 | |
| 21 | pub const ActiveFields = std.enums.EnumFieldStruct(FieldEnum, bool, false); |
| 22 | pub const FieldValues = blk: { |
| 23 | var field_names: [bit_count][]const u8 = undefined; |
| 24 | var field_types: [bit_count]type = undefined; |
| 25 | var field_attrs: [bit_count]std.builtin.Type.Struct.FieldAttributes = undefined; |
| 26 | const fields_info = @typeInfo(Fields).@"struct"; |
| 27 | for ( |
| 28 | fields_info.field_names, |
| 29 | fields_info.field_types, |
| 30 | &field_names, |
| 31 | &field_types, |
| 32 | &field_attrs, |
| 33 | ) |field_name, field_type, *new_name, *NewType, *new_attrs| { |
| 34 | new_name.* = field_name; |
| 35 | NewType.* = ?field_type; |
| 36 | const default: ?field_type = null; |
| 37 | new_attrs.* = .{ .default_value_ptr = &default }; |
| 38 | } |
| 39 | break :blk @Struct(.auto, null, &field_names, &field_types, &field_attrs); |
| 40 | }; |
| 41 | |
| 42 | const Self = @This(); |
| 43 | |
| 44 | pub fn has(self: Self, comptime field: FieldEnum) bool { |
| 45 | const field_index = @backingInt(field); |
| 46 | return (self.bits & (1 << field_index)) != 0; |
| 47 | } |
| 48 | |
| 49 | pub fn get(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime field: FieldEnum) ?Field(field) { |
| 50 | if (!self.has(field)) |
| 51 | return null; |
| 52 | return self.ptrConst(p, field).*; |
| 53 | } |
| 54 | |
| 55 | pub fn setFlag(self: *Self, comptime field: FieldEnum) void { |
| 56 | const field_index = @backingInt(field); |
| 57 | self.bits |= 1 << field_index; |
| 58 | } |
| 59 | |
| 60 | /// `fields` is a boolean struct where each active field is set to `true` |
| 61 | pub fn init(fields: ActiveFields) Self { |
| 62 | var self: Self = .{ .bits = 0 }; |
| 63 | inline for (@typeInfo(Fields).@"struct".field_names, 0..) |field_name, i| { |
| 64 | if (@field(fields, field_name)) |
| 65 | self.bits |= 1 << i; |
| 66 | } |
| 67 | return self; |
| 68 | } |
| 69 | |
| 70 | /// `fields` is a struct with each field set to an optional value |
| 71 | pub fn setMany(self: Self, p: [*]align(@alignOf(Fields)) u8, fields: FieldValues) void { |
| 72 | inline for (@typeInfo(Fields).@"struct".field_names, 0..) |field_name, i| { |
| 73 | if (@field(fields, field_name)) |value| |
| 74 | self.set(p, @as(FieldEnum, @fromBackingInt(@intCast(i))), value); |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | pub fn set( |
| 79 | self: Self, |
| 80 | p: [*]align(@alignOf(Fields)) u8, |
| 81 | comptime field: FieldEnum, |
| 82 | value: Field(field), |
| 83 | ) void { |
| 84 | self.ptr(p, field).* = value; |
| 85 | } |
| 86 | |
| 87 | pub fn ptr(self: Self, p: [*]align(@alignOf(Fields)) u8, comptime field: FieldEnum) *Field(field) { |
| 88 | if (@sizeOf(Field(field)) == 0) |
| 89 | return undefined; |
| 90 | const off = self.offset(field); |
| 91 | return @ptrCast(@alignCast(p + off)); |
| 92 | } |
| 93 | |
| 94 | pub fn ptrConst(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime field: FieldEnum) *const Field(field) { |
| 95 | if (@sizeOf(Field(field)) == 0) |
| 96 | return undefined; |
| 97 | const off = self.offset(field); |
| 98 | return @ptrCast(@alignCast(p + off)); |
| 99 | } |
| 100 | |
| 101 | pub fn offset(self: Self, comptime field: FieldEnum) usize { |
| 102 | var off: usize = 0; |
| 103 | inline for (@typeInfo(Fields).@"struct".field_types, 0..) |field_type, i| { |
| 104 | const active = (self.bits & (1 << i)) != 0; |
| 105 | if (i == @backingInt(field)) { |
| 106 | assert(active); |
| 107 | return mem.alignForward(usize, off, @alignOf(field_type)); |
| 108 | } else if (active) { |
| 109 | off = mem.alignForward(usize, off, @alignOf(field_type)); |
| 110 | off += @sizeOf(field_type); |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | pub fn Field(comptime field: FieldEnum) type { |
| 116 | return @typeInfo(Fields).@"struct".field_types[@backingInt(field)]; |
| 117 | } |
| 118 | |
| 119 | pub fn sizeInBytes(self: Self) usize { |
| 120 | var off: usize = 0; |
| 121 | inline for (@typeInfo(Fields).@"struct".field_types, 0..) |field_type, i| { |
| 122 | if (@sizeOf(field_type) == 0) |
| 123 | continue; |
| 124 | if ((self.bits & (1 << i)) != 0) { |
| 125 | off = mem.alignForward(usize, off, @alignOf(field_type)); |
| 126 | off += @sizeOf(field_type); |
| 127 | } |
| 128 | } |
| 129 | return off; |
| 130 | } |
| 131 | }; |
| 132 | } |
| 133 | |
| 134 | test TrailerFlags { |
| 135 | const Flags = TrailerFlags(struct { |
| 136 | a: i32, |
| 137 | b: bool, |
| 138 | c: u64, |
| 139 | }); |
| 140 | try testing.expectEqual(u2, meta.Tag(Flags.FieldEnum)); |
| 141 | |
| 142 | var flags = Flags.init(.{ |
| 143 | .b = true, |
| 144 | .c = true, |
| 145 | }); |
| 146 | const slice = try testing.allocator.alignedAlloc(u8, .@"8", flags.sizeInBytes()); |
| 147 | defer testing.allocator.free(slice); |
| 148 | |
| 149 | flags.set(slice.ptr, .b, false); |
| 150 | flags.set(slice.ptr, .c, 12345678); |
| 151 | |
| 152 | try testing.expect(flags.get(slice.ptr, .a) == null); |
| 153 | try testing.expect(!flags.get(slice.ptr, .b).?); |
| 154 | try testing.expect(flags.get(slice.ptr, .c).? == 12345678); |
| 155 | |
| 156 | flags.setMany(slice.ptr, .{ |
| 157 | .b = true, |
| 158 | .c = 5678, |
| 159 | }); |
| 160 | |
| 161 | try testing.expect(flags.get(slice.ptr, .a) == null); |
| 162 | try testing.expect(flags.get(slice.ptr, .b).?); |
| 163 | try testing.expect(flags.get(slice.ptr, .c).? == 5678); |
| 164 | } |