From 4cb0c6aee98f07b1a6485a5d5f2c26bf50f7591f Mon Sep 17 00:00:00 2001 From: duck Date: Sun, 4 May 2025 21:05:17 +0500 Subject: [PATCH] `Controller` queueOrdered functionality --- src/graph.zig | 80 ++++++++++++++++++++++++++++++++++++++---- src/graph/resource.zig | 67 ++++++++++++++++++++++++++++++++++- src/graph/system.zig | 10 ++++++ 3 files changed, 150 insertions(+), 7 deletions(-) diff --git a/src/graph.zig b/src/graph.zig index 11b4bed..8187d92 100644 --- a/src/graph.zig +++ b/src/graph.zig @@ -12,16 +12,19 @@ pub const Controller = Resource.Controller; const MAX_SYSTEM_REQUESTS = 8; const DEFAULT_SYSTEM_CAPACITY = 16; const DEFAULT_CONTROLLERS = 2; +const DEFAULT_DUDS_PER_CONTROLLER = 4; const ResourceMap = std.AutoArrayHashMapUnmanaged(utils.Hash, Resource); const SystemQueue = std.ArrayListUnmanaged(System); const Controllers = std.ArrayListUnmanaged(Controller); +const Duds = std.ArrayListUnmanaged(System.Dud); /// Assumed to be thread-safe alloc: std.mem.Allocator, resources: ResourceMap, system_queue: SystemQueue, controllers: Controllers, +duds: Duds, const Self = @This(); pub fn init(alloc: std.mem.Allocator) !Self { @@ -38,8 +41,16 @@ pub fn init(alloc: std.mem.Allocator) !Self { controller.deinit(); }; - for (0..DEFAULT_CONTROLLERS) |_| { - const controller = try Controller.create(alloc); + var duds = try Duds.initCapacity(alloc, DEFAULT_CONTROLLERS * DEFAULT_DUDS_PER_CONTROLLER); + errdefer duds.deinit(alloc); + + for (0..DEFAULT_CONTROLLERS * DEFAULT_DUDS_PER_CONTROLLER) |_| { + duds.appendAssumeCapacity(.{}); + } + + for (0..DEFAULT_CONTROLLERS) |i| { + var controller = try Controller.create(alloc); + controller.setDuds(@intCast(DEFAULT_DUDS_PER_CONTROLLER * i), duds.items[DEFAULT_DUDS_PER_CONTROLLER * i .. DEFAULT_DUDS_PER_CONTROLLER * (i + 1)]); controllers.appendAssumeCapacity(controller); } @@ -48,6 +59,7 @@ pub fn init(alloc: std.mem.Allocator) !Self { .resources = resources, .system_queue = system_queue, .controllers = controllers, + .duds = duds, }; } @@ -68,6 +80,8 @@ pub fn deinit(self: *Self) void { controller.deinit(); } self.controllers.deinit(self.alloc); + + self.duds.deinit(self.alloc); } fn enqueueSystem(self: *Self, system: System) !void { @@ -76,15 +90,41 @@ fn enqueueSystem(self: *Self, system: System) !void { } fn runAllSystems(self: *Self) GraphError!void { - while (self.system_queue.pop()) |next_system| { - defer next_system.deinit(self.alloc); + while (self.system_queue.items.len > 0) { + var swap_with = self.system_queue.items.len - 1; + while (true) { + const system = &self.system_queue.items[self.system_queue.items.len - 1]; + if (system.requires_dud) |dud_id| { + if (self.duds.items[dud_id].required_count == 0) { + break; + } + } else break; + if (swap_with > 1) { + swap_with -= 1; + std.mem.swap( + System, + &self.system_queue.items[self.system_queue.items.len - 1], + &self.system_queue.items[swap_with], + ); + } else { + return GraphError.SystemDeadlock; + } + } + + const next_system = self.system_queue.pop().?; + + defer next_system.deinit(self.alloc); try self.runSystem(next_system); } } /// Does not deallocate the system fn runSystem(self: *Self, system: System) GraphError!void { + if (system.requires_dud) |dud_id| { + std.debug.assert(self.duds.items[dud_id].required_count == 0); + } + var buffer: [MAX_SYSTEM_REQUESTS]*anyopaque = undefined; var controller: ?Controller = null; errdefer if (controller) |*c| c.deinit(); @@ -103,6 +143,9 @@ fn runSystem(self: *Self, system: System) GraphError!void { buffer_len += 1; } system.function_runner(buffer[0..buffer_len]); + if (system.submit_dud) |dud_id| { + self.duds.items[dud_id].required_count -= 1; + } if (controller) |c| { defer controller = null; @@ -123,7 +166,15 @@ fn getController(self: *Self) !Controller { if (self.controllers.pop()) |c| { return c; } - return Controller.create(self.alloc); + const next_dud_id = self.duds.items.len; + for (try self.duds.addManyAsSlice(self.alloc, DEFAULT_DUDS_PER_CONTROLLER)) |*dud| { + dud.required_count = 0; + } + errdefer self.duds.shrinkRetainingCapacity(self.duds.items.len - DEFAULT_DUDS_PER_CONTROLLER); + + var controller = try Controller.create(self.alloc); + controller.setDuds(@intCast(next_dud_id), self.duds.items[next_dud_id .. next_dud_id + DEFAULT_DUDS_PER_CONTROLLER]); + return controller; } /// Evaluates and clears the controller (even if errors out) @@ -161,6 +212,7 @@ pub inline fn addResource(self: *Self, resource: Resource) !void { const GraphError = error{ MissingResource, OutOfMemory, + SystemDeadlock, }; test { @@ -174,9 +226,25 @@ test { fn addTen(rsc: *@This()) void { rsc.number += 10; } + fn addThousand(rsc: *@This()) void { + rsc.number += 1000; + } + fn subThousand(rsc: *@This()) void { + rsc.number -= 1000; + } fn addEleven(cmd: *Controller) void { cmd.queueSystem(addTen); - cmd.queuesystem(addOne); + cmd.queueSystem(addOne); + + cmd.queueOrdered(.{ + addThousand, + addThousand, + addThousand, + }, .{ + subThousand, + subThousand, + subThousand, + }); } }; diff --git a/src/graph/resource.zig b/src/graph/resource.zig index 7fae214..7192d3f 100644 --- a/src/graph/resource.zig +++ b/src/graph/resource.zig @@ -20,6 +20,9 @@ pub const Controller = struct { alloc: std.mem.Allocator, command_buffer: std.ArrayListUnmanaged(Command), error_state: ErrorState, + duds: [*]System.Dud, + dud_range: struct { System.Dud.Id, System.Dud.Id }, + submit_dud: ?System.Dud.Id, pub const Command = union(enum) { add_resource: Resource, @@ -36,6 +39,9 @@ pub const Controller = struct { .alloc = alloc, .command_buffer = try std.ArrayListUnmanaged(Command).initCapacity(alloc, DEFAULT_CONTROLLER_CAPACITY), .error_state = .ok, + .duds = &[0]System.Dud{}, + .dud_range = .{ 0, 0 }, + .submit_dud = null, }; } @@ -45,7 +51,12 @@ pub const Controller = struct { return self.command_buffer.items; } - /// Clears the command buffer, but does not deallocate it's contents + pub fn setDuds(self: *Controller, start_id: System.Dud.Id, buffer: []System.Dud) void { + self.dud_range = .{ start_id, start_id + @as(System.Dud.Id, @intCast(buffer.len)) }; + self.duds = @ptrCast(buffer); + } + + /// Clears the command buffer for the next use (does not deallocate it's contents) pub fn clear(self: *Controller) void { self.command_buffer.clearRetainingCapacity(); switch (self.error_state) { @@ -53,6 +64,7 @@ pub const Controller = struct { .recoverable => |msg| self.alloc.free(msg), } self.error_state = .ok; + self.submit_dud = null; } /// Adds resource to the global storage, discarding any previously existing data @@ -73,6 +85,59 @@ pub const Controller = struct { self.queueSystemInternal(function) catch |err| self.fail(err); } + /// Function sets are expected to be tuples of system functions + pub fn queueOrdered( + self: *Controller, + comptime function_set_first: anytype, + comptime function_set_second: anytype, + ) void { + self.queueOrderedInternal(function_set_first, function_set_second) catch |err| self.fail(err); + } + + pub fn queueOrderedInternal( + self: *Controller, + comptime function_set_first: anytype, + comptime function_set_second: anytype, + ) !void { + if (self.dud_range[0] == self.dud_range[1]) { + // TODO: Make `Controller` request more ids + self.error_state = .unrecoverable; + return; + } + const commands_first = @typeInfo(@TypeOf(function_set_first)).@"struct".fields.len; + const commands_second = @typeInfo(@TypeOf(function_set_second)).@"struct".fields.len; + var new_commands: [commands_first + commands_second]Command = undefined; + var i: usize = 0; + + errdefer for (0..i) |del_i| { + new_commands[del_i].queue_system.deinit(self.alloc); + }; + + std.debug.assert(self.duds[0].required_count == 0); + self.duds[0].required_count = commands_first; + + const dud_id = self.dud_range[0]; + self.duds += 1; + self.dud_range[0] += 1; + + inline for (function_set_first) |fn_first| { + var system = try System.fromFunction(fn_first, self.alloc); + system.submit_dud = dud_id; + new_commands[i] = Command{ .queue_system = system }; + i += 1; + } + inline for (function_set_second) |fn_second| { + var system = try System.fromFunction(fn_second, self.alloc); + system.requires_dud = dud_id; + system.submit_dud = self.submit_dud; + new_commands[i] = Command{ .queue_system = system }; + i += 1; + } + std.debug.assert(i == new_commands.len); + + try self.command_buffer.appendSlice(self.alloc, &new_commands); + } + fn queueSystemInternal(self: *Controller, comptime function: anytype) !void { var system = try System.fromFunction(function, self.alloc); errdefer system.deinit(self.alloc); diff --git a/src/graph/system.zig b/src/graph/system.zig index 5914e41..3642f03 100644 --- a/src/graph/system.zig +++ b/src/graph/system.zig @@ -4,6 +4,14 @@ const Controller = @import("resource.zig").Controller; function_runner: *const fn ([]const *anyopaque) void, requested_types: []const Request, +requires_dud: ?Dud.Id, +submit_dud: ?Dud.Id, + +pub const Dud = struct { + pub const Id = u16; + + required_count: usize = 0, +}; pub const Request = union(enum) { resource: utils.Hash, @@ -27,6 +35,8 @@ pub fn fromFunction(comptime function: anytype, alloc: std.mem.Allocator) !Self return Self{ .requested_types = try alloc.dupe(Request, &requests), .function_runner = utils.generateRunner(function), + .requires_dud = null, + .submit_dud = null, }; }