master
  1const std = @import("../std.zig");
  2const meta = std.meta;
  3const testing = std.testing;
  4const mem = std.mem;
  5const assert = std.debug.assert;
  6const 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.
 12pub fn TrailerFlags(comptime Fields: type) type {
 13    return struct {
 14        bits: Int,
 15
 16        pub const Int = meta.Int(.unsigned, bit_count);
 17        pub const bit_count = @typeInfo(Fields).@"struct".fields.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.StructField.Attributes = undefined;
 26            for (@typeInfo(Fields).@"struct".fields, &field_names, &field_types, &field_attrs) |field, *new_name, *NewType, *new_attrs| {
 27                new_name.* = field.name;
 28                NewType.* = ?field.type;
 29                const default: ?field.type = null;
 30                new_attrs.* = .{ .default_value_ptr = &default };
 31            }
 32            break :blk @Struct(.auto, null, &field_names, &field_types, &field_attrs);
 33        };
 34
 35        pub const Self = @This();
 36
 37        pub fn has(self: Self, comptime field: FieldEnum) bool {
 38            const field_index = @intFromEnum(field);
 39            return (self.bits & (1 << field_index)) != 0;
 40        }
 41
 42        pub fn get(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime field: FieldEnum) ?Field(field) {
 43            if (!self.has(field))
 44                return null;
 45            return self.ptrConst(p, field).*;
 46        }
 47
 48        pub fn setFlag(self: *Self, comptime field: FieldEnum) void {
 49            const field_index = @intFromEnum(field);
 50            self.bits |= 1 << field_index;
 51        }
 52
 53        /// `fields` is a boolean struct where each active field is set to `true`
 54        pub fn init(fields: ActiveFields) Self {
 55            var self: Self = .{ .bits = 0 };
 56            inline for (@typeInfo(Fields).@"struct".fields, 0..) |field, i| {
 57                if (@field(fields, field.name))
 58                    self.bits |= 1 << i;
 59            }
 60            return self;
 61        }
 62
 63        /// `fields` is a struct with each field set to an optional value
 64        pub fn setMany(self: Self, p: [*]align(@alignOf(Fields)) u8, fields: FieldValues) void {
 65            inline for (@typeInfo(Fields).@"struct".fields, 0..) |field, i| {
 66                if (@field(fields, field.name)) |value|
 67                    self.set(p, @as(FieldEnum, @enumFromInt(i)), value);
 68            }
 69        }
 70
 71        pub fn set(
 72            self: Self,
 73            p: [*]align(@alignOf(Fields)) u8,
 74            comptime field: FieldEnum,
 75            value: Field(field),
 76        ) void {
 77            self.ptr(p, field).* = value;
 78        }
 79
 80        pub fn ptr(self: Self, p: [*]align(@alignOf(Fields)) u8, comptime field: FieldEnum) *Field(field) {
 81            if (@sizeOf(Field(field)) == 0)
 82                return undefined;
 83            const off = self.offset(field);
 84            return @ptrCast(@alignCast(p + off));
 85        }
 86
 87        pub fn ptrConst(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime field: FieldEnum) *const 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 offset(self: Self, comptime field: FieldEnum) usize {
 95            var off: usize = 0;
 96            inline for (@typeInfo(Fields).@"struct".fields, 0..) |field_info, i| {
 97                const active = (self.bits & (1 << i)) != 0;
 98                if (i == @intFromEnum(field)) {
 99                    assert(active);
100                    return mem.alignForward(usize, off, @alignOf(field_info.type));
101                } else if (active) {
102                    off = mem.alignForward(usize, off, @alignOf(field_info.type));
103                    off += @sizeOf(field_info.type);
104                }
105            }
106        }
107
108        pub fn Field(comptime field: FieldEnum) type {
109            return @typeInfo(Fields).@"struct".fields[@intFromEnum(field)].type;
110        }
111
112        pub fn sizeInBytes(self: Self) usize {
113            var off: usize = 0;
114            inline for (@typeInfo(Fields).@"struct".fields, 0..) |field, i| {
115                if (@sizeOf(field.type) == 0)
116                    continue;
117                if ((self.bits & (1 << i)) != 0) {
118                    off = mem.alignForward(usize, off, @alignOf(field.type));
119                    off += @sizeOf(field.type);
120                }
121            }
122            return off;
123        }
124    };
125}
126
127test TrailerFlags {
128    const Flags = TrailerFlags(struct {
129        a: i32,
130        b: bool,
131        c: u64,
132    });
133    try testing.expectEqual(u2, meta.Tag(Flags.FieldEnum));
134
135    var flags = Flags.init(.{
136        .b = true,
137        .c = true,
138    });
139    const slice = try testing.allocator.alignedAlloc(u8, .@"8", flags.sizeInBytes());
140    defer testing.allocator.free(slice);
141
142    flags.set(slice.ptr, .b, false);
143    flags.set(slice.ptr, .c, 12345678);
144
145    try testing.expect(flags.get(slice.ptr, .a) == null);
146    try testing.expect(!flags.get(slice.ptr, .b).?);
147    try testing.expect(flags.get(slice.ptr, .c).? == 12345678);
148
149    flags.setMany(slice.ptr, .{
150        .b = true,
151        .c = 5678,
152    });
153
154    try testing.expect(flags.get(slice.ptr, .a) == null);
155    try testing.expect(flags.get(slice.ptr, .b).?);
156    try testing.expect(flags.get(slice.ptr, .c).? == 5678);
157}