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}