const std = @import("std"); fn SinglyLinkedList(comptime T: type, comptime Parent: type) type { return struct { const Self = @This(); /// Node inside the linked list wrapping the actual data. pub const Node = struct { next: ?*Node = null, allocator: std.mem.Allocator, data: T, pub const Data = T; /// Insert a new node after the current one. /// /// Arguments: /// new_node: Pointer to the new node to insert. pub fn insertAfter(node: *Node, new_node: *Node) void { new_node.next = node.next; node.next = new_node; } /// Remove a node from the list. /// /// Arguments: /// node: Pointer to the node to be removed. /// Returns: /// node removed pub fn removeNext(node: *Node) ?*Node { const next_node = node.next orelse return null; node.next = next_node.next; return next_node; } /// Iterate over the singly-linked list from this node, until the final node is found. /// This operation is O(N). pub fn findLast(node: *Node) *Node { var it = node; while (true) { it = it.next orelse return it; } } /// Iterate over each next node, returning the count of all nodes except the starting one. /// This operation is O(N). pub fn countChildren(node: *const Node) usize { var count: usize = 0; var it: ?*const Node = node.next; while (it) |n| : (it = n.next) { count += 1; } return count; } pub inline fn release(node: *Node) void { Parent.release(node); } }; first: ?*Node = null, /// Insert a new node at the head. /// /// Arguments: /// new_node: Pointer to the new node to insert. pub fn prepend(list: *Self, new_node: *Node) void { new_node.next = list.first; list.first = new_node; } /// Remove a node from the list. /// /// Arguments: /// node: Pointer to the node to be removed. pub fn remove(list: *Self, node: *Node) void { if (list.first == node) { list.first = node.next; } else { var current_elm = list.first.?; while (current_elm.next != node) { current_elm = current_elm.next.?; } current_elm.next = node.next; } } /// Remove and return the first node in the list. /// /// Returns: /// A pointer to the first node in the list. pub fn popFirst(list: *Self) ?*Node { const first = list.first orelse return null; list.first = first.next; return first; } /// Iterate over all nodes, returning the count. /// This operation is O(N). pub fn len(list: Self) usize { if (list.first) |n| { return 1 + n.countChildren(); } else { return 0; } } }; } const log_allocations = false; pub fn ObjectPool( comptime Type: type, comptime Init: (?fn (allocator: std.mem.Allocator) anyerror!Type), comptime threadsafe: bool, comptime max_count: comptime_int, ) type { return struct { const Pool = @This(); const LinkedList = SinglyLinkedList(Type, Pool); pub const List = LinkedList; pub const Node = LinkedList.Node; const MaxCountInt = std.math.IntFittingRange(0, max_count); const DataStruct = struct { list: LinkedList = undefined, loaded: bool = false, count: MaxCountInt = 0, }; // We want this to be global // but we don't want to create 3 global variables per pool // instead, we create one global variable per pool const DataStructNonThreadLocal = if (threadsafe) void else DataStruct; const DataStructThreadLocal = if (!threadsafe) void else DataStruct; threadlocal var data_threadlocal: DataStructThreadLocal = DataStructThreadLocal{}; var data__: DataStructNonThreadLocal = DataStructNonThreadLocal{}; inline fn data() *DataStruct { if (comptime threadsafe) { return &data_threadlocal; } if (comptime !threadsafe) { return &data__; } unreachable; } pub fn full() bool { if (comptime max_count == 0) return false; return data().loaded and data().count >= max_count; } pub fn has() bool { return data().loaded and data().list.first != null; } pub fn push(allocator: std.mem.Allocator, pooled: Type) void { if (comptime @import("./env.zig").allow_assert) std.debug.assert(!full()); var new_node = allocator.create(LinkedList.Node) catch unreachable; new_node.* = LinkedList.Node{ .allocator = allocator, .data = pooled, }; release(new_node); } pub fn getIfExists() ?*LinkedList.Node { if (!data().loaded) { return null; } var node = data().list.popFirst() orelse return null; if (comptime std.meta.trait.isContainer(Type) and @hasDecl(Type, "reset")) node.data.reset(); if (comptime max_count > 0) data().count -|= 1; return node; } pub fn first(allocator: std.mem.Allocator) *Type { return &get(allocator).data; } pub fn get(allocator: std.mem.Allocator) *LinkedList.Node { if (data().loaded) { if (data().list.popFirst()) |node| { if (comptime std.meta.trait.isContainer(Type) and @hasDecl(Type, "reset")) node.data.reset(); if (comptime max_count > 0) data().count -|= 1; return node; } } if (comptime log_allocations) std.io.getStdErr().writeAll(comptime std.fmt.comptimePrint("Allocate {s} - {d} bytes\n", .{ @typeName(Type), @sizeOf(Type) })) catch {}; var new_node = allocator.create(LinkedList.Node) catch unreachable; new_node.* = LinkedList.Node{ .allocator = allocator, .data = if (comptime Init) |init_| (init_( allocator, ) catch unreachable) else undefined, }; return new_node; } pub fn releaseValue(value: *Type) void { @fieldParentPtr(LinkedList.Node, "data", value).release(); } pub fn release(node: *LinkedList.Node) void { if (comptime max_count > 0) { if (data().count >= max_count) { if (comptime log_allocations) std.io.getStdErr().writeAll(comptime std.fmt.comptimePrint("Free {s} - {d} bytes\n", .{ @typeName(Type), @sizeOf(Type) })) catch {}; if (comptime std.meta.trait.isContainer(Type) and @hasDecl(Type, "deinit")) node.data.deinit(); node.allocator.destroy(node); return; } } if (comptime max_count > 0) data().count +|= 1; if (data().loaded) { data().list.prepend(node); return; } data().list = LinkedList{ .first = node }; data().loaded = true; } }; }