| ... | ... | @@ -8,6 +8,7 @@ const meta = std.meta; |
| 8 | 8 | const testing = std.testing; |
| 9 | 9 | const mem = std.mem; |
| 10 | 10 | const assert = std.debug.assert; |
| 11 | const TypeInfo = std.builtin.TypeInfo; |
| 11 | 12 | |
| 12 | 13 | /// This is useful for saving memory when allocating an object that has many |
| 13 | 14 | /// optional components. The optional objects are allocated sequentially in |
| ... | ... | @@ -17,91 +18,127 @@ pub fn TrailerFlags(comptime Fields: type) type { |
| 17 | 18 | return struct { |
| 18 | 19 | bits: Int, |
| 19 | 20 | |
| 20 | | pub const Int = @Type(.{ .Int = .{ .bits = bit_count, .is_signed = false } }); |
| 21 | pub const Int = meta.Int(false, bit_count); |
| 21 | 22 | pub const bit_count = @typeInfo(Fields).Struct.fields.len; |
| 22 | 23 | |
| 24 | pub const FieldEnum = blk: { |
| 25 | comptime var fields: []const TypeInfo.EnumField = &[_]TypeInfo.EnumField{}; |
| 26 | inline for (@typeInfo(Fields).Struct.fields) |struct_field, i| { |
| 27 | const field = TypeInfo.EnumField{ .name = struct_field.name, .value = i }; |
| 28 | fields = fields ++ [_]TypeInfo.EnumField{field}; |
| 29 | } |
| 30 | break :blk @Type(.{ |
| 31 | .Enum = .{ |
| 32 | .layout = .Auto, |
| 33 | .tag_type = std.math.IntFittingRange(0, bit_count - 1), |
| 34 | .fields = fields, |
| 35 | .decls = &[_]TypeInfo.Declaration{}, |
| 36 | .is_exhaustive = true, |
| 37 | }, |
| 38 | }); |
| 39 | }; |
| 40 | |
| 41 | pub const InitStruct = blk: { |
| 42 | comptime var fields: []const TypeInfo.StructField = &[_]TypeInfo.StructField{}; |
| 43 | inline for (@typeInfo(Fields).Struct.fields) |struct_field, i| { |
| 44 | const field = TypeInfo.StructField{ |
| 45 | .name = struct_field.name, |
| 46 | .field_type = ?struct_field.field_type, |
| 47 | .default_value = @as(??struct_field.field_type, @as(?struct_field.field_type, null)), |
| 48 | }; |
| 49 | fields = fields ++ [_]TypeInfo.StructField{field}; |
| 50 | } |
| 51 | break :blk @Type(.{ |
| 52 | .Struct = .{ |
| 53 | .layout = .Auto, |
| 54 | .fields = fields, |
| 55 | .decls = &[_]TypeInfo.Declaration{}, |
| 56 | .is_tuple = false, |
| 57 | }, |
| 58 | }); |
| 59 | }; |
| 60 | |
| 23 | 61 | pub const Self = @This(); |
| 24 | 62 | |
| 25 | | pub fn has(self: Self, comptime name: []const u8) bool { |
| 26 | | const field_index = meta.fieldIndex(Fields, name).?; |
| 63 | pub fn has(self: Self, comptime field: FieldEnum) bool { |
| 64 | const field_index = @enumToInt(field); |
| 27 | 65 | return (self.bits & (1 << field_index)) != 0; |
| 28 | 66 | } |
| 29 | 67 | |
| 30 | | pub fn get(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime name: []const u8) ?Field(name) { |
| 31 | | if (!self.has(name)) |
| 68 | pub fn get(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime field: FieldEnum) ?Field(field) { |
| 69 | if (!self.has(field)) |
| 32 | 70 | return null; |
| 33 | | return self.ptrConst(p, name).*; |
| 71 | return self.ptrConst(p, field).*; |
| 34 | 72 | } |
| 35 | 73 | |
| 36 | | pub fn setFlag(self: *Self, comptime name: []const u8) void { |
| 37 | | const field_index = meta.fieldIndex(Fields, name).?; |
| 74 | pub fn setFlag(self: *Self, comptime field: FieldEnum) void { |
| 75 | const field_index = @enumToInt(field); |
| 38 | 76 | self.bits |= 1 << field_index; |
| 39 | 77 | } |
| 40 | 78 | |
| 41 | 79 | /// `fields` is a struct with each field set to an optional value. |
| 42 | 80 | /// Missing fields are assumed to be `null`. |
| 43 | 81 | /// Only the non-null bits are observed and are used to set the flag bits. |
| 44 | | pub fn init(fields: anytype) Self { |
| 82 | pub fn init(fields: InitStruct) Self { |
| 45 | 83 | var self: Self = .{ .bits = 0 }; |
| 46 | | inline for (@typeInfo(@TypeOf(fields)).Struct.fields) |field| { |
| 47 | | const opt: ?Field(field.name) = @field(fields, field.name); |
| 48 | | const field_index = meta.fieldIndex(Fields, field.name).?; |
| 49 | | self.bits |= @as(Int, @boolToInt(opt != null)) << field_index; |
| 84 | inline for (@typeInfo(Fields).Struct.fields) |field, i| { |
| 85 | if (@field(fields, field.name)) |_| |
| 86 | self.bits |= 1 << i; |
| 50 | 87 | } |
| 51 | 88 | return self; |
| 52 | 89 | } |
| 53 | 90 | |
| 54 | 91 | /// `fields` is a struct with each field set to an optional value (same as `init`). |
| 55 | 92 | /// Missing fields are assumed to be `null`. |
| 56 | | pub fn setMany(self: Self, p: [*]align(@alignOf(Fields)) u8, fields: anytype) void { |
| 57 | | inline for (@typeInfo(@TypeOf(fields)).Struct.fields) |field| { |
| 58 | | const opt: ?Field(field.name) = @field(fields, field.name); |
| 59 | | if (opt) |value| { |
| 60 | | self.set(p, field.name, value); |
| 61 | | } |
| 93 | pub fn setMany(self: Self, p: [*]align(@alignOf(Fields)) u8, fields: InitStruct) void { |
| 94 | inline for (@typeInfo(Fields).Struct.fields) |field, i| { |
| 95 | if (@field(fields, field.name)) |value| |
| 96 | self.set(p, @intToEnum(FieldEnum, i), value); |
| 62 | 97 | } |
| 63 | 98 | } |
| 64 | 99 | |
| 65 | 100 | pub fn set( |
| 66 | 101 | self: Self, |
| 67 | 102 | p: [*]align(@alignOf(Fields)) u8, |
| 68 | | comptime name: []const u8, |
| 69 | | value: Field(name), |
| 103 | comptime field: FieldEnum, |
| 104 | value: Field(field), |
| 70 | 105 | ) void { |
| 71 | | self.ptr(p, name).* = value; |
| 106 | self.ptr(p, field).* = value; |
| 72 | 107 | } |
| 73 | 108 | |
| 74 | | pub fn ptr(self: Self, p: [*]align(@alignOf(Fields)) u8, comptime name: []const u8) *Field(name) { |
| 75 | | if (@sizeOf(Field(name)) == 0) |
| 109 | pub fn ptr(self: Self, p: [*]align(@alignOf(Fields)) u8, comptime field: FieldEnum) *Field(field) { |
| 110 | if (@sizeOf(Field(field)) == 0) |
| 76 | 111 | return undefined; |
| 77 | | const off = self.offset(p, name); |
| 78 | | return @ptrCast(*Field(name), @alignCast(@alignOf(Field(name)), p + off)); |
| 112 | const off = self.offset(p, field); |
| 113 | return @ptrCast(*Field(field), @alignCast(@alignOf(Field(field)), p + off)); |
| 79 | 114 | } |
| 80 | 115 | |
| 81 | | pub fn ptrConst(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime name: []const u8) *const Field(name) { |
| 82 | | if (@sizeOf(Field(name)) == 0) |
| 116 | pub fn ptrConst(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime field: FieldEnum) *const Field(field) { |
| 117 | if (@sizeOf(Field(field)) == 0) |
| 83 | 118 | return undefined; |
| 84 | | const off = self.offset(p, name); |
| 85 | | return @ptrCast(*const Field(name), @alignCast(@alignOf(Field(name)), p + off)); |
| 119 | const off = self.offset(p, field); |
| 120 | return @ptrCast(*const Field(field), @alignCast(@alignOf(Field(field)), p + off)); |
| 86 | 121 | } |
| 87 | 122 | |
| 88 | | pub fn offset(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime name: []const u8) usize { |
| 123 | pub fn offset(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime field: FieldEnum) usize { |
| 89 | 124 | var off: usize = 0; |
| 90 | | inline for (@typeInfo(Fields).Struct.fields) |field, i| { |
| 125 | inline for (@typeInfo(Fields).Struct.fields) |field_info, i| { |
| 91 | 126 | const active = (self.bits & (1 << i)) != 0; |
| 92 | | if (comptime mem.eql(u8, field.name, name)) { |
| 127 | if (i == @enumToInt(field)) { |
| 93 | 128 | assert(active); |
| 94 | | return mem.alignForwardGeneric(usize, off, @alignOf(field.field_type)); |
| 129 | return mem.alignForwardGeneric(usize, off, @alignOf(field_info.field_type)); |
| 95 | 130 | } else if (active) { |
| 96 | | off = mem.alignForwardGeneric(usize, off, @alignOf(field.field_type)); |
| 97 | | off += @sizeOf(field.field_type); |
| 131 | off = mem.alignForwardGeneric(usize, off, @alignOf(field_info.field_type)); |
| 132 | off += @sizeOf(field_info.field_type); |
| 98 | 133 | } |
| 99 | 134 | } |
| 100 | | @compileError("no field named " ++ name ++ " in type " ++ @typeName(Fields)); |
| 101 | 135 | } |
| 102 | 136 | |
| 103 | | pub fn Field(comptime name: []const u8) type { |
| 104 | | return meta.fieldInfo(Fields, name).field_type; |
| 137 | pub fn Field(comptime field: FieldEnum) type { |
| 138 | inline for (@typeInfo(Fields).Struct.fields) |field_info, i| { |
| 139 | if (i == @enumToInt(field)) |
| 140 | return field_info.field_type; |
| 141 | } |
| 105 | 142 | } |
| 106 | 143 | |
| 107 | 144 | pub fn sizeInBytes(self: Self) usize { |
| ... | ... | @@ -125,6 +162,8 @@ test "TrailerFlags" { |
| 125 | 162 | b: bool, |
| 126 | 163 | c: u64, |
| 127 | 164 | }); |
| 165 | testing.expectEqual(u2, @TagType(Flags.FieldEnum)); |
| 166 | |
| 128 | 167 | var flags = Flags.init(.{ |
| 129 | 168 | .b = true, |
| 130 | 169 | .c = 1234, |
| ... | ... | @@ -132,19 +171,19 @@ test "TrailerFlags" { |
| 132 | 171 | const slice = try testing.allocator.allocAdvanced(u8, 8, flags.sizeInBytes(), .exact); |
| 133 | 172 | defer testing.allocator.free(slice); |
| 134 | 173 | |
| 135 | | flags.set(slice.ptr, "b", false); |
| 136 | | flags.set(slice.ptr, "c", 12345678); |
| 174 | flags.set(slice.ptr, .b, false); |
| 175 | flags.set(slice.ptr, .c, 12345678); |
| 137 | 176 | |
| 138 | | testing.expect(flags.get(slice.ptr, "a") == null); |
| 139 | | testing.expect(!flags.get(slice.ptr, "b").?); |
| 140 | | testing.expect(flags.get(slice.ptr, "c").? == 12345678); |
| 177 | testing.expect(flags.get(slice.ptr, .a) == null); |
| 178 | testing.expect(!flags.get(slice.ptr, .b).?); |
| 179 | testing.expect(flags.get(slice.ptr, .c).? == 12345678); |
| 141 | 180 | |
| 142 | 181 | flags.setMany(slice.ptr, .{ |
| 143 | 182 | .b = true, |
| 144 | 183 | .c = 5678, |
| 145 | 184 | }); |
| 146 | 185 | |
| 147 | | testing.expect(flags.get(slice.ptr, "a") == null); |
| 148 | | testing.expect(flags.get(slice.ptr, "b").?); |
| 149 | | testing.expect(flags.get(slice.ptr, "c").? == 5678); |
| 186 | testing.expect(flags.get(slice.ptr, .a) == null); |
| 187 | testing.expect(flags.get(slice.ptr, .b).?); |
| 188 | testing.expect(flags.get(slice.ptr, .c).? == 5678); |
| 150 | 189 | } |