mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
SegmentedPool
This commit is contained in:
@ -27,6 +27,7 @@ test {
|
||||
_ = @import("TempDir.zig");
|
||||
_ = @import("terminal/Terminal.zig");
|
||||
|
||||
// TEMP
|
||||
// Libraries
|
||||
_ = @import("segmented_pool.zig");
|
||||
_ = @import("libuv/main.zig");
|
||||
}
|
||||
|
95
src/segmented_pool.zig
Normal file
95
src/segmented_pool.zig
Normal file
@ -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());
|
||||
}
|
Reference in New Issue
Block a user