From 0d93da5f3055935345fb00fa8cf3c01d5e90052e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 12 Mar 2023 21:34:06 -0700 Subject: [PATCH] config: changed() to test if a specific key has changed --- src/config.zig | 67 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/src/config.zig b/src/config.zig index 310212176..262a59a8b 100644 --- a/src/config.zig +++ b/src/config.zig @@ -172,18 +172,24 @@ pub const Config = struct { pub const Key = key: { const field_infos = std.meta.fields(Config); var enumFields: [field_infos.len]std.builtin.Type.EnumField = undefined; - var decls = [_]std.builtin.Type.Declaration{}; - inline for (field_infos, 0..) |field, i| { + var i: usize = 0; + inline for (field_infos) |field| { + // Ignore fields starting with "_" since they're internal and + // not copied ever. + if (field.name[0] == '_') continue; + enumFields[i] = .{ .name = field.name, .value = i, }; + i += 1; } + var decls = [_]std.builtin.Type.Declaration{}; break :key @Type(.{ .Enum = .{ .tag_type = std.math.IntFittingRange(0, field_infos.len - 1), - .fields = &enumFields, + .fields = enumFields[0..i], .decls = &decls, .is_exhaustive = true, }, @@ -635,10 +641,7 @@ pub const Config = struct { const alloc = result._arena.?.allocator(); inline for (@typeInfo(Config).Struct.fields) |field| { - // Ignore fields starting with "_" since they're internal and - // not copied ever. - if (field.name[0] == '_') continue; - + if (!@hasField(Key, field.name)) continue; @field(result, field.name) = try cloneValue( alloc, field.type, @@ -680,7 +683,7 @@ pub const Config = struct { } /// Returns an iterator that goes through each changed field from - /// old to new. + /// old to new. The order of old or new do not matter. pub fn changeIterator(old: *const Config, new: *const Config) ChangeIterator { return .{ .old = old, @@ -688,6 +691,26 @@ pub const Config = struct { }; } + /// Returns true if the given key has changed from old to new. This + /// requires the key to be comptime known to make this more efficient. + pub fn changed(self: *const Config, new: *const Config, comptime key: Key) bool { + // Get the field at comptime + const field = comptime field: { + const fields = std.meta.fields(Config); + for (fields) |field| { + if (@field(Key, field.name) == key) { + break :field field; + } + } + + unreachable; + }; + + const old_value = @field(self, field.name); + const new_value = @field(new, field.name); + return !equal(field.type, old_value, new_value); + } + fn equal(comptime T: type, old: T, new: T) bool { // Do known named types first switch (T) { @@ -719,27 +742,21 @@ pub const Config = struct { } } + /// This yields a key for every changed field between old and new. pub const ChangeIterator = struct { old: *const Config, new: *const Config, i: usize = 0, pub fn next(self: *ChangeIterator) ?Key { - const fields = comptime std.meta.fields(Config); - + const fields = comptime std.meta.fields(Key); while (self.i < fields.len) { switch (self.i) { inline 0...(fields.len - 1) => |i| { const field = fields[i]; + const key = @field(Key, field.name); self.i += 1; - - if (field.name[0] == '_') return self.next(); - - const old_value = @field(self.old, field.name); - const new_value = @field(self.new, field.name); - if (!equal(field.type, old_value, new_value)) { - return @field(Key, field.name); - } + if (self.old.changed(self.new, key)) return key; }, else => unreachable, @@ -766,6 +783,20 @@ pub const Config = struct { // I want to do this but this doesn't work (the API doesn't work) // try testing.expectEqualDeep(dest, source); } + + test "changed" { + const testing = std.testing; + const alloc = testing.allocator; + + var source = try Config.default(alloc); + defer source.deinit(); + var dest = try source.clone(alloc); + defer dest.deinit(); + dest.@"font-family" = "something else"; + + try testing.expect(source.changed(&dest, .@"font-family")); + try testing.expect(!source.changed(&dest, .@"font-size")); + } }; /// Color represents a color using RGB.