ghostty/src/segmented_pool.zig
Mitchell Hashimoto a2a22791ee SegmentedPool
2022-04-26 16:18:34 -07:00

96 lines
3.1 KiB
Zig

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());
}