From 50dea84f1df2f59602d099fb23588add165cb61f Mon Sep 17 00:00:00 2001 From: cozis Date: Fri, 19 Jun 2026 15:51:13 +0200 Subject: [PATCH] partition: add broken-link tracking --- src/partition.zig | 129 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 src/partition.zig diff --git a/src/partition.zig b/src/partition.zig new file mode 100644 index 0000000..c75f816 --- /dev/null +++ b/src/partition.zig @@ -0,0 +1,129 @@ +const std = @import("std"); + +const Allocator = std.mem.Allocator; + +pub const EndpointID = u32; + +pub const Link = struct { + a: EndpointID, + b: EndpointID, + + pub fn init(a: EndpointID, b: EndpointID) Link { + return if (a <= b) + .{ .a = a, .b = b } + else + .{ .a = b, .b = a }; + } + + pub fn eql(self: Link, other: Link) bool { + return self.a == other.a and self.b == other.b; + } +}; + +gpa: Allocator, +broken_links: std.ArrayList(Link), + +pub fn init(self: *@This(), gpa: Allocator) void { + self.gpa = gpa; + self.broken_links = .empty; +} + +pub fn deinit(self: *@This()) void { + self.broken_links.deinit(self.gpa); +} + +pub fn breakLink(self: *@This(), a: EndpointID, b: EndpointID) Allocator.Error!void { + if (a == b) + return; + + const link = Link.init(a, b); + if (self.contains(link)) + return; + + try self.broken_links.append(self.gpa, link); +} + +pub fn healLink(self: *@This(), a: EndpointID, b: EndpointID) void { + const link = Link.init(a, b); + for (self.broken_links.items, 0..) |item, i| { + if (item.eql(link)) { + _ = self.broken_links.swapRemove(i); + return; + } + } +} + +pub fn clear(self: *@This()) void { + self.broken_links.clearRetainingCapacity(); +} + +pub fn isBroken(self: *const @This(), a: EndpointID, b: EndpointID) bool { + if (a == b) + return false; + + return self.contains(Link.init(a, b)); +} + +pub fn links(self: *const @This()) []const Link { + return self.broken_links.items; +} + +fn contains(self: *const @This(), link: Link) bool { + for (self.broken_links.items) |item| { + if (item.eql(link)) + return true; + } + return false; +} + +test "broken links are symmetric" { + var partition: @This() = undefined; + partition.init(std.testing.allocator); + defer partition.deinit(); + + try partition.breakLink(1, 2); + + try std.testing.expect(partition.isBroken(1, 2)); + try std.testing.expect(partition.isBroken(2, 1)); + try std.testing.expect(!partition.isBroken(1, 1)); +} + +test "breaking the same link twice does not duplicate it" { + var partition: @This() = undefined; + partition.init(std.testing.allocator); + defer partition.deinit(); + + try partition.breakLink(1, 2); + try partition.breakLink(2, 1); + + try std.testing.expectEqual(@as(usize, 1), partition.links().len); +} + +test "healing a link removes only that link" { + var partition: @This() = undefined; + partition.init(std.testing.allocator); + defer partition.deinit(); + + try partition.breakLink(1, 2); + try partition.breakLink(2, 3); + + partition.healLink(2, 1); + + try std.testing.expect(!partition.isBroken(1, 2)); + try std.testing.expect(partition.isBroken(2, 3)); +} + +test "clear heals every link" { + var partition: @This() = undefined; + partition.init(std.testing.allocator); + defer partition.deinit(); + + try partition.breakLink(1, 2); + try partition.breakLink(3, 4); + + partition.clear(); + + try std.testing.expectEqual(@as(usize, 0), partition.links().len); + try std.testing.expect(!partition.isBroken(1, 2)); + try std.testing.expect(!partition.isBroken(3, 4)); +}