diff --git a/src/game.zig b/src/game.zig index 7ae0045..34f6872 100644 --- a/src/game.zig +++ b/src/game.zig @@ -71,11 +71,11 @@ pub fn run(self: *Self) GameError!void { var controller = try self.graph.getController(); controller.queue(.{ - .events = processEvents, - .update = debug_scene.update, - .begin_draw = beginDraw, - .end_draw = endDraw, - .ordered = true, + processEvents, + debug_scene.update, + beginDraw, + endDraw, + Graph.Controller.Option.ordered, }); try self.graph.freeController(controller); @@ -159,9 +159,9 @@ fn processEvents( pub fn deinit(self: *Self) void { var controller = self.graph.getController() catch unreachable; controller.queue(.{ - .deinit = debug_scene.deinit, - .clean = clean, - .ordered = true, + debug_scene.deinit, + clean, + Graph.Controller.Option.ordered, }); self.graph.freeController(controller) catch unreachable; self.graph.runAllSystems() catch unreachable; diff --git a/src/graph.zig b/src/graph.zig index 1ea4641..31cd387 100644 --- a/src/graph.zig +++ b/src/graph.zig @@ -6,8 +6,6 @@ const System = @import("graph/system.zig"); // TODO: // - Use arena allocator? // - Resolve missing resource problem -// - Parse system sets into a properly defined data structure instead of relying on `@typeInfo` -// - Find a better way to represent system sets // - Organize a better way to execute single commands on graph // - Handle system errors // - Removing of resources @@ -97,7 +95,7 @@ pub fn reset(self: *Self) void { for (controller.commands()) |*command| { switch (command.*) { .add_resource => |*resource| resource.deinit(controller.alloc), - .queue_system => |*system| system.deinit(controller.alloc), + .queue_system => {}, } } controller.clear(); @@ -106,10 +104,6 @@ pub fn reset(self: *Self) void { (i + 1) * self.duds.items.len / self.controllers.items.len, ); } - // System cleanup - for (self.system_queue.items) |*system| { - system.deinit(self.alloc); - } self.system_queue.clearRetainingCapacity(); // Duds cleanup for (self.duds.items) |*dud| { @@ -118,7 +112,6 @@ pub fn reset(self: *Self) void { } fn enqueueSystem(self: *Self, system: System) !void { - errdefer system.deinit(self.alloc); try self.system_queue.append(self.alloc, system); } @@ -148,7 +141,6 @@ pub fn runAllSystems(self: *Self) GraphError!void { const next_system = self.system_queue.pop().?; - defer next_system.deinit(self.alloc); self.runSystem(next_system) catch |err| { std.debug.print("System run error: {} while running {s}\n", .{ err, next_system.label }); return err; @@ -278,17 +270,17 @@ test "simple graph smoke test" { cmd.queue(addOne); cmd.queue(.{ - .first = .{ + .{ addThousand, addThousand, addThousand, }, - .second = .{ + .{ subThousand, subThousand, subThousand, }, - .ordered = true, + Controller.Option.ordered, }); } }; @@ -324,7 +316,7 @@ test "complex queue graph smoke test" { fn queueManySystems(cmd: *Controller) void { cmd.queue(.{ - .@"0" = .{ + .{ addTen, addTen, addTen, @@ -333,7 +325,7 @@ test "complex queue graph smoke test" { }, // `data1` = 20 // `data2` = 5 - .@"1" = .{ + .{ mulTen, mulTen, mulTwo, @@ -341,12 +333,12 @@ test "complex queue graph smoke test" { }, // `data1` = 8000 // `data2` = 9 - .@"2" = .{ + .{ subTwenty, }, - .ordered = true, // `data1` = 7980 // `data2` = 10 + Controller.Option.ordered, }); } fn addTen(rsc: *Rsc) void { diff --git a/src/graph/controller.zig b/src/graph/controller.zig index c7fdc32..0322975 100644 --- a/src/graph/controller.zig +++ b/src/graph/controller.zig @@ -4,6 +4,8 @@ const System = @import("system.zig"); const Resource = @import("resource.zig"); const Controller = @This(); +pub const Option = utils.SystemSetOption; + const DEFAULT_CONTROLLER_CAPACITY = 8; alloc: std.mem.Allocator, @@ -90,50 +92,42 @@ fn queueInternal(self: *Controller, comptime system_set: anytype) !void { const command_buffer = try self.command_buffer.addManyAsSlice(self.alloc, utils.countSystems(system_set)); errdefer self.command_buffer.shrinkRetainingCapacity(prev_count); - const commands_created = try self.createQueueCommands(system_set, command_buffer, null, self.submit_dud); + const commands_created = try self.createQueueCommands(utils.SystemSet.fromAny(system_set), command_buffer, null, self.submit_dud); std.debug.assert(commands_created == command_buffer.len); } fn createQueueCommands( self: *Controller, - comptime system_set: anytype, + comptime system_set: utils.SystemSet, command_buffer: []Command, requires_dud: ?System.Dud.Id, submit_dud: ?System.Dud.Id, ) !usize { - switch (@typeInfo(@TypeOf(system_set))) { - .@"fn" => { - var system = try System.fromFunction(system_set, self.alloc); - system.requires_dud = requires_dud; - system.submit_dud = submit_dud; - command_buffer[0] = .{ .queue_system = system }; + switch (system_set) { + .single => |single| { + command_buffer[0] = .{ .queue_system = .{ + .function_runner = single.runner, + .requested_types = single.requests, + .requires_dud = requires_dud, + .submit_dud = submit_dud, + .label = single.label, + } }; return 1; }, - .@"struct" => { - const ordered = utils.getOptionalTupleField(system_set, "ordered", false); + .set => |set| { var queued_total: usize = 0; var prev_dud = requires_dud; var next_dud = submit_dud; - errdefer for (command_buffer[0..queued_total]) |command| { - command.queue_system.deinit(self.alloc); - }; - - if (ordered) { + if (set.ordered) { next_dud = requires_dud; } var queued_sets: usize = 0; - var total_sets: usize = 0; - inline for (@typeInfo(@TypeOf(system_set)).@"struct".fields) |field| { - switch (@typeInfo(field.type)) { - .@"fn", .@"struct" => total_sets += 1, - else => {}, - } - } + const total_sets: usize = set.subsets.len; - inline for (@typeInfo(@TypeOf(system_set)).@"struct".fields) |field| { - if (ordered) { + inline for (set.subsets) |subset| { + if (set.ordered) { prev_dud = next_dud; if (queued_sets == total_sets - 1) { next_dud = submit_dud; @@ -142,22 +136,16 @@ fn createQueueCommands( next_dud = self.acquireDud().?; } } - switch (@typeInfo(field.type)) { - .@"fn", .@"struct" => { - queued_total += try self.createQueueCommands( - @field(system_set, field.name), - command_buffer[queued_total..], - prev_dud, - next_dud, - ); - queued_sets += 1; - }, - else => {}, - } + queued_total += try self.createQueueCommands( + subset, + command_buffer[queued_total..], + prev_dud, + next_dud, + ); + queued_sets += 1; } return queued_total; }, - else => @compileError("System set must be either a single function or a tuple of other system sets"), } } @@ -204,7 +192,7 @@ pub fn deinit(self: *Controller) void { for (self.command_buffer.items) |*command| { switch (command.*) { .add_resource => |*resource| resource.deinit(self.alloc), - .queue_system => |*system| system.deinit(self.alloc), + .queue_system => {}, } } self.clear(); diff --git a/src/graph/system.zig b/src/graph/system.zig index 9233e3d..fc09a24 100644 --- a/src/graph/system.zig +++ b/src/graph/system.zig @@ -3,7 +3,7 @@ const utils = @import("utils.zig"); const Controller = @import("controller.zig"); function_runner: *const fn ([]const *anyopaque) void, -requested_types: []const Request, +requested_types: []const utils.SystemRequest, requires_dud: ?Dud.Id, submit_dud: ?Dud.Id, label: []const u8, @@ -13,35 +13,3 @@ pub const Dud = struct { required_count: u16 = 0, }; - -pub const Request = union(enum) { - resource: utils.Hash, - controller: void, - // TODO: - // - Params -}; - -const Self = @This(); -pub fn fromFunction(comptime function: anytype, alloc: std.mem.Allocator) !Self { - utils.validateSystem(function); - - var requests: [@typeInfo(@TypeOf(function)).@"fn".params.len]Request = undefined; - inline for (0.., @typeInfo(@TypeOf(function)).@"fn".params) |i, param| { - switch (@typeInfo(param.type.?).pointer.child) { - Controller => requests[i] = .controller, - else => |resource_type| requests[i] = .{ .resource = utils.hashType(resource_type) }, - } - } - - return Self{ - .requested_types = try alloc.dupe(Request, &requests), - .function_runner = utils.generateRunner(function), - .requires_dud = null, - .submit_dud = null, - .label = @typeName(@TypeOf(function)), - }; -} - -pub fn deinit(self: *const Self, alloc: std.mem.Allocator) void { - alloc.free(self.requested_types); -} diff --git a/src/graph/utils.zig b/src/graph/utils.zig index fec06fe..9bfe9f6 100644 --- a/src/graph/utils.zig +++ b/src/graph/utils.zig @@ -6,6 +6,77 @@ const System = @import("system.zig"); pub const Hash = u32; const HashAlgorithm = std.crypto.hash.blake2.Blake2s(@bitSizeOf(Hash)); +pub const SystemSetOption = enum { + ordered, +}; + +pub const SystemRequest = union(enum) { + resource: Hash, + controller: void, +}; + +pub const SystemSet = union(enum) { + single: struct { + runner: *const fn ([]const *anyopaque) void, + requests: []const SystemRequest, + label: []const u8, + }, + set: struct { + subsets: []const SystemSet, + ordered: bool, + }, + + pub fn fromAny(comptime any: anytype) SystemSet { + return comptime switch (@typeInfo(@TypeOf(any))) { + .@"struct" => fromStruct(any), + .@"fn" => fromFunction(any), + else => @compileError("System set must be either a tuple or a function, got " ++ @typeName(@TypeOf(any))), + }; + } + pub fn fromStruct(comptime set: anytype) SystemSet { + return comptime blk: { + var subset_count = 0; + for (@typeInfo(@TypeOf(set)).@"struct".fields) |field| { + const info = @typeInfo(field.type); + if (info == .@"fn" or info == .@"struct") { + subset_count += 1; + } + } + var subsets: [subset_count]SystemSet = undefined; + var ordered = false; + var i = 0; + for (@typeInfo(@TypeOf(set)).@"struct".fields) |field| { + const info = @typeInfo(field.type); + if (info == .@"fn" or info == .@"struct") { + subsets[i] = SystemSet.fromAny(@field(set, field.name)); + i += 1; + continue; + } + if (field.type == SystemSetOption) { + switch (@field(set, field.name)) { + SystemSetOption.ordered => ordered = true, + } + i += 1; + continue; + } + @compileError("System set contains extraneous elements"); + } + const subsets_const = subsets; + break :blk SystemSet{ .set = .{ + .subsets = &subsets_const, + .ordered = ordered, + } }; + }; + } + pub fn fromFunction(comptime function: anytype) SystemSet { + return comptime SystemSet{ .single = .{ + .runner = generateRunner(function), + .requests = generateRequests(function), + .label = @typeName(@TypeOf(function)), + } }; + } +}; + pub inline fn hashType(comptime h_type: type) Hash { return hashString(@typeName(h_type)); } @@ -29,6 +100,7 @@ pub fn validateResource(comptime resource_type: type) void { } } +// TODO: Make validators print helpful errors so I don't have to check reference trace all the time pub fn validateSystem(comptime system: anytype) void { const info = @typeInfo(@TypeOf(system)); if (info != .@"fn") @compileError("System can only be a function, got " ++ @typeName(system)); @@ -62,7 +134,7 @@ pub fn validateSystemSet(comptime system_set: anytype) void { switch (@typeInfo(field_info.type)) { .@"fn", .@"struct" => validateSystemSet(@field(system_set, field_info.name)), else => { - if (checkIsField(field_info, "ordered", bool)) continue; + if (field_info.type == SystemSetOption) continue; @compileError("Invalid field \"" ++ field_info.name ++ "\" of type `" ++ @@ -98,27 +170,17 @@ pub fn generateRunner(comptime system: anytype) fn ([]const *anyopaque) void { return RunnerImpl.runner; } -pub fn checkIsField(field: std.builtin.Type.StructField, field_name: []const u8, comptime field_type: type) bool { - if (!std.mem.eql(u8, field.name, field_name)) return false; - if (field.type != field_type) return false; - return true; -} - -pub fn getOptionalTupleField(tuple: anytype, comptime field_name: []const u8, comptime default: anytype) @TypeOf(default) { +pub fn generateRequests(comptime function: anytype) []const SystemRequest { return comptime blk: { - for (@typeInfo(@TypeOf(tuple)).@"struct".fields) |field| { - if (!std.mem.eql(u8, field.name, field_name)) continue; - if (@TypeOf(default) != field.type) - @compileError("Cannot get tuple field `" ++ - field_name ++ - "` with type `" ++ - @typeName(@TypeOf(default)) ++ - "` (tuple field has type `" ++ - @typeName(field.type) ++ - "`)"); - break :blk @field(tuple, field.name); + var requests: [@typeInfo(@TypeOf(function)).@"fn".params.len]SystemRequest = undefined; + for (0.., @typeInfo(@TypeOf(function)).@"fn".params) |i, param| { + switch (@typeInfo(param.type.?).pointer.child) { + Controller => requests[i] = .controller, + else => |resource_type| requests[i] = .{ .resource = hashType(resource_type) }, + } } - break :blk default; + const requests_const = requests; + break :blk &requests_const; }; }