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 = @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
134test 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}