From e72bbfff3fff20cdbbee3532d8b15057d23e765a Mon Sep 17 00:00:00 2001 From: Eugene Rossokha Date: Mon, 24 Mar 2025 21:58:46 +0200 Subject: [PATCH] rangemap: implement free_range --- src/util/btree.zig | 2 +- src/util/range.zig | 27 ++++++-- src/util/rangemap.zig | 150 +++++++++++++++++++++++++++--------------- 3 files changed, 120 insertions(+), 59 deletions(-) diff --git a/src/util/btree.zig b/src/util/btree.zig index e00f0b8..fb7b483 100644 --- a/src/util/btree.zig +++ b/src/util/btree.zig @@ -25,7 +25,7 @@ pub fn BTree(comptime N: type, comptime compare_fn: CompareFn(N), comptime deini pub const Iterator = struct { current: ?*Node, - pub fn next(self: *Iterator) ?*const Node { + pub fn next(self: *Iterator) ?*Node { while (self.current) |n| { const v = n; diff --git a/src/util/range.zig b/src/util/range.zig index af70f9d..914e965 100644 --- a/src/util/range.zig +++ b/src/util/range.zig @@ -16,13 +16,32 @@ pub fn Range(comptime T: type) type { /// Range length. len: T, + const Self = @This(); + /// Returns `start + len` of the range. - pub fn end(self: *const @This()) T { + pub fn end(self: Self) T { return self.start + self.len; } + /// Returns a range representing a subtraction of `other` from `self` + pub fn subtract(self: Self, other: Self) ?Self { + // No overlap + if (self.end() <= other.start or other.end() <= self.start) + return self; + + // Self is fully in other + if (other.start <= self.start and other.end() >= self.end()) + return null; + + // Partial overlap + return if (self.start < other.start) + .{ .start = self.start, .len = other.start - self.start } + else + .{ .start = other.end(), .len = self.end() - other.end() }; + } + /// Returns a range representing an intersections of `self` with an`other` range. - pub fn intersect(self: *const @This(), other: *const @This()) ?Range(T) { + pub fn intersect(self: Self, other: Self) ?Self { if (self.start < other.start) { const p = other.start - self.start; if (p < self.len) { @@ -38,11 +57,11 @@ pub fn Range(comptime T: type) type { return null; } - pub fn contains(self: *const @This(), scalar: T) bool { + pub fn contains(self: Self, scalar: T) bool { return scalar >= self.start and scalar - self.start < self.len; } - pub fn compare_disjoint(a: *const @This(), b: *const @This()) std.math.Order { + pub fn compare_disjoint(a: Self, b: Self) std.math.Order { if (a.start >= b.end()) { return .gt; } else if (b.start >= a.end()) { diff --git a/src/util/rangemap.zig b/src/util/rangemap.zig index 33d2eb4..cd59abd 100644 --- a/src/util/rangemap.zig +++ b/src/util/rangemap.zig @@ -11,11 +11,44 @@ pub fn RangeMap( comptime K: type, comptime V: type, comptime ops: struct { - deinit_fn: ?fn(*V) void = null, + deinit_fn: ?fn (*V) void = null, merge_fn: ?fn (*const V, *const V) bool = null, }, ) type { return struct { + btree: Tree, + + const Self = @This(); + + pub const Error = error{ + ScalarOutOfRange, + RangeOutOfBounds, + } || Tree.Error; + + pub const Tree = BTree(Node, compare_fn, deinit_node_fn); + + pub const WalkFn = fn (*const Node) void; + + pub const Iterator = struct { + inner: Tree.Iterator, + + pub fn next(self: *Iterator) ?*Node { + if (self.inner.next()) |n| { + return &n.key; + } else { + return null; + } + } + }; + + pub fn init(gpa: Allocator) @This() { + return .{ .btree = Tree.init(gpa) }; + } + + pub fn deinit(self: *@This()) void { + self.btree.deinit(); + } + pub const Node = struct { key: Range(K), value: V, @@ -25,30 +58,8 @@ pub fn RangeMap( } }; - pub const Error = error{ - ScalarOutOfRange, - RangeOutOfBounds, - } || Tree.Error; - - - pub const WalkFn = fn (*const Node) void; - - pub const Iterator = struct { - inner: Tree.Iterator, - - pub fn next(self: *Iterator) ?*const Node { - if (self.inner.next()) |n| { - return &n.key; - } else { - return null; - } - } - }; - - pub const Tree = BTree(Node, compare_fn, deinit_node_fn); - fn compare_fn(a: *const Node, b: *const Node) Order { - return Range(K).compare_disjoint(&a.key, &b.key); + return Range(K).compare_disjoint(a.key, b.key); } fn deinit_node_fn(n: *Node) void { @@ -57,16 +68,6 @@ pub fn RangeMap( } } - btree: Tree, - - pub fn init(gpa: Allocator) @This() { - return .{ .btree = Tree.init(gpa) }; - } - - pub fn deinit(self: *@This()) void { - self.btree.deinit(); - } - /// Returns the value at a given scalar point, along with the full range it belongs to. pub fn get_scalar(self: *const @This(), scalar: K) ?*Node { return if (self.get_scalar_node(scalar)) |n| &n.key else null; @@ -87,6 +88,27 @@ pub fn RangeMap( }.call); } + /// Frees a range of the range map. + /// If there are any partially freed ranges, they are resized. + pub fn free_range(self: *Self, range: Range(K)) !void { + var it = self.iterator(); + while (it.next()) |node| { + // If the node is fully in range, we can remove it + // If the node partially overlaps, we remove it and reinsert with the new range + // TODO: make the iterator actually work with it + if (range.intersect(node.key)) |intersection| { + if (node.key.subtract(intersection)) |range_resized| { + const value = node.value; + try self.btree.remove(node.*); + _ = try self.btree.insert(.{ .key = range_resized, .value = value }); + } else { + try self.btree.remove(node.*); + } + it = self.iterator(); + } + } + } + /// Splits a given node at a scalar point inside its interval. /// /// The part of the interval before `at` is considered a "left" half, the remaining @@ -245,11 +267,11 @@ test "Range map merging insertion" { { var it = map.iterator(); const n0 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key); + try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key); try std.testing.expectEqual(true, n0.value); const n1 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key); + try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key); try std.testing.expectEqual(false, n1.value); try std.testing.expectEqual(null, it.next()); @@ -262,19 +284,19 @@ test "Range map merging insertion" { { var it = map.iterator(); const n0 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key); + try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key); try std.testing.expectEqual(true, n0.value); const n1 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key); + try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key); try std.testing.expectEqual(false, n1.value); const n2 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key); + try std.testing.expectEqual(Range(u32){ .start = 40, .len = 10 }, n2.key); try std.testing.expectEqual(true, n2.value); const n3 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 50, .len = 10 }, n3.key); + try std.testing.expectEqual(Range(u32){ .start = 50, .len = 10 }, n3.key); try std.testing.expectEqual(false, n3.value); try std.testing.expectEqual(null, it.next()); @@ -287,23 +309,23 @@ test "Range map merging insertion" { { var it = map.iterator(); const n0 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key); + try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key); try std.testing.expectEqual(true, n0.value); const n1 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key); + try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key); try std.testing.expectEqual(false, n1.value); const n2 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key); + try std.testing.expectEqual(Range(u32){ .start = 40, .len = 10 }, n2.key); try std.testing.expectEqual(true, n2.value); const n3 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 50, .len = 20 }, n3.key); + try std.testing.expectEqual(Range(u32){ .start = 50, .len = 20 }, n3.key); try std.testing.expectEqual(false, n3.value); const n4 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 71, .len = 9 }, n4.key); + try std.testing.expectEqual(Range(u32){ .start = 71, .len = 9 }, n4.key); try std.testing.expectEqual(false, n4.value); try std.testing.expectEqual(null, it.next()); @@ -315,19 +337,19 @@ test "Range map merging insertion" { { var it = map.iterator(); const n0 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key); + try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key); try std.testing.expectEqual(true, n0.value); const n1 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key); + try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key); try std.testing.expectEqual(false, n1.value); const n2 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key); + try std.testing.expectEqual(Range(u32){ .start = 40, .len = 10 }, n2.key); try std.testing.expectEqual(true, n2.value); const n3 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 50, .len = 30 }, n3.key); + try std.testing.expectEqual(Range(u32){ .start = 50, .len = 30 }, n3.key); try std.testing.expectEqual(false, n3.value); try std.testing.expectEqual(null, it.next()); @@ -340,23 +362,23 @@ test "Range map merging insertion" { { var it = map.iterator(); const n0 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key); + try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key); try std.testing.expectEqual(true, n0.value); const n1 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key); + try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key); try std.testing.expectEqual(false, n1.value); const n2 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key); + try std.testing.expectEqual(Range(u32){ .start = 40, .len = 10 }, n2.key); try std.testing.expectEqual(true, n2.value); const n3 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 50, .len = 30 }, n3.key); + try std.testing.expectEqual(Range(u32){ .start = 50, .len = 30 }, n3.key); try std.testing.expectEqual(false, n3.value); const n4 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 100, .len = 20 }, n4.key); + try std.testing.expectEqual(Range(u32){ .start = 100, .len = 20 }, n4.key); try std.testing.expectEqual(false, n4.value); try std.testing.expectEqual(null, it.next()); @@ -449,3 +471,23 @@ test "Range map should disallow overflowing ranges" { try std.testing.expectError(error.RangeOutOfBounds, map.insert(0xF0000000, 0x20000000, false)); } + +test "Range map free" { + const Map = RangeMap(u32, bool, .{}); + var map = Map.init(std.testing.allocator); + defer map.deinit(); + + _ = try map.insert(0x1000, 0x1000, true); + _ = try map.insert(0x3000, 0x1000, true); + _ = try map.insert(0x5000, 0x1000, true); + _ = try map.insert(0x7000, 0x1000, true); + + try map.free_range(.{ .start = 0, .len = 0x3800 }); + + try std.testing.expectEqual(null, map.get_scalar(0x1000)); + try std.testing.expectEqual(null, map.get_scalar(0x1800)); + try std.testing.expectEqual(null, map.get_scalar(0x2000)); + try std.testing.expectEqual(null, map.get_scalar(0x3000)); + + try std.testing.expectEqual(Range(u32){ .start = 0x3800, .len = 0x800 }, map.get_scalar(0x3800).?.key); +}