const debug = @import("./debug.zig"); const io = @import("./io.zig"); const list = @import("./list.zig"); const math = @import("./math.zig"); pub const Stacking = struct { base_allocator: io.Allocator, min_page_size: usize, allocations: list.Stack(usize) = .{}, pages: list.Stack(Page) = .{}, const Page = struct { buffer: []u8, used: usize, const Self = @This(); fn available(self: Self) usize { return self.buffer.len - self.used; } }; pub fn allocate(self: *Stacking, allocation_size: usize) io.AllocationError![]u8 { const alignment = @as(usize, 4); const aligned_allocation_size = (allocation_size + alignment - 1) & ~(alignment - 1); if (self.pages.values.len == 0) { const page = try self.allocate_page(math.max(self.min_page_size, aligned_allocation_size)); page.used = allocation_size; return page.buffer[0 .. allocation_size]; } var page = self.current_page() orelse unreachable; if (page.available() <= aligned_allocation_size) { page = try self.allocate_page(math.max(self.min_page_size, aligned_allocation_size)); } debug.assert(page.available() >= allocation_size); defer page.used += aligned_allocation_size; return page.buffer[page.used .. (page.used + allocation_size)]; } fn allocate_page(self: *Stacking, page_size: usize) io.AllocationError!*Page { var buffer = try io.allocate_many(self.base_allocator, page_size, u8); errdefer io.deallocate(self.base_allocator, buffer); try self.pages.push_one(self.base_allocator, .{ .buffer = buffer, .used = 0, }); return (self.current_page() orelse unreachable); } pub fn as_allocator(self: *Stacking) io.Allocator { return io.Allocator.bind(Stacking, self, struct { fn reallocate(stacking: *Stacking, options: io.AllocationOptions) ?[]u8 { const allocation = options.allocation orelse { return stacking.allocate(options.size) catch null; }; if (allocation.len == 0) { return null; } const reallocation = stacking.allocate(allocation.len) catch { return null; }; io.copy(reallocation, allocation); return reallocation; } }.reallocate); } pub fn clear_allocations(self: *Stacking) void { for (self.pages.values) |page| { io.deallocate(self.base_allocator, page.buffer); } self.pages.deinit(self.base_allocator); self.allocations.deinit(self.base_allocator); } fn current_page(self: Stacking) ?*Page { if (self.pages.values.len == 0) { return null; } return &self.pages.values[self.pages.values.len - 1]; } };