diff --git a/src/main.zig b/src/main.zig index 36c492f2e..557aa85fb 100644 --- a/src/main.zig +++ b/src/main.zig @@ -27,6 +27,7 @@ test { _ = @import("TempDir.zig"); _ = @import("terminal/Terminal.zig"); - // TEMP + // Libraries + _ = @import("segmented_pool.zig"); _ = @import("libuv/main.zig"); } diff --git a/src/segmented_pool.zig b/src/segmented_pool.zig new file mode 100644 index 000000000..ae866f729 --- /dev/null +++ b/src/segmented_pool.zig @@ -0,0 +1,95 @@ +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const testing = std.testing; + +/// A data structure where you can get stable (never copied) pointers to +/// a type that automatically grows if necessary. The values can be "put back" +/// but are expected to be put back IN ORDER. +/// +/// This is implemented specifically for libuv write requests, since the +/// write requests must have a stable pointer and are guaranteed to be processed +/// in order for a single stream. +/// +/// This is NOT thread safe. +pub fn SegmentedPool(comptime T: type, comptime prealloc: usize) type { + return struct { + const Self = @This(); + + i: usize = 0, + available: usize = prealloc, + list: std.SegmentedList(T, prealloc) = .{ .len = prealloc }, + + pub fn deinit(self: *Self, alloc: Allocator) void { + self.list.deinit(alloc); + self.* = undefined; + } + + /// Get the next available value out of the list. This will not + /// grow the list. + pub fn get(self: *Self) !*T { + // Error to not have any + if (self.available == 0) return error.OutOfValues; + + // The index we grab is just i % len, so we wrap around to the front. + const i = @mod(self.i, self.list.len); + self.i +%= 1; // Wrapping addition to swe go back to 0 + self.available -= 1; + return self.list.at(i); + } + + /// Get the next available value out of the list and grow the list + /// if necessary. + pub fn getGrow(self: *Self, alloc: Allocator) !*T { + if (self.available == 0) try self.grow(alloc); + return try self.get(); + } + + fn grow(self: *Self, alloc: Allocator) !void { + try self.list.growCapacity(alloc, self.list.len * 2); + self.i = self.list.len; + self.available = self.list.len; + self.list.len *= 2; + } + + /// Put a value back. The value put back is expected to be the + /// in order of get. + pub fn put(self: *Self) void { + self.available += 1; + assert(self.available <= self.list.len); + } + }; +} + +test "SegmentedPool" { + var list: SegmentedPool(u8, 2) = .{}; + defer list.deinit(testing.allocator); + try testing.expectEqual(@as(usize, 2), list.available); + + // Get to capacity + var v1 = try list.get(); + var v2 = try list.get(); + try testing.expect(v1 != v2); + try testing.expectError(error.OutOfValues, list.get()); + + // Test writing for later + v1.* = 42; + + // Put a value back + list.put(); + var temp = try list.get(); + try testing.expect(v1 == temp); + try testing.expect(temp.* == 42); + try testing.expectError(error.OutOfValues, list.get()); + + // Grow + var v3 = try list.getGrow(testing.allocator); + try testing.expect(v1 != v3 and v2 != v3); + _ = try list.get(); + try testing.expectError(error.OutOfValues, list.get()); + + // Put a value back + list.put(); + try testing.expect(v1 == try list.get()); + try testing.expectError(error.OutOfValues, list.get()); +}