Parse system set into SystemSet

This commit is contained in:
duck
2025-06-15 23:36:22 +05:00
parent 52e9c62eeb
commit 2d010644c7
5 changed files with 125 additions and 115 deletions

View File

@@ -71,11 +71,11 @@ pub fn run(self: *Self) GameError!void {
var controller = try self.graph.getController(); var controller = try self.graph.getController();
controller.queue(.{ controller.queue(.{
.events = processEvents, processEvents,
.update = debug_scene.update, debug_scene.update,
.begin_draw = beginDraw, beginDraw,
.end_draw = endDraw, endDraw,
.ordered = true, Graph.Controller.Option.ordered,
}); });
try self.graph.freeController(controller); try self.graph.freeController(controller);
@@ -159,9 +159,9 @@ fn processEvents(
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
var controller = self.graph.getController() catch unreachable; var controller = self.graph.getController() catch unreachable;
controller.queue(.{ controller.queue(.{
.deinit = debug_scene.deinit, debug_scene.deinit,
.clean = clean, clean,
.ordered = true, Graph.Controller.Option.ordered,
}); });
self.graph.freeController(controller) catch unreachable; self.graph.freeController(controller) catch unreachable;
self.graph.runAllSystems() catch unreachable; self.graph.runAllSystems() catch unreachable;

View File

@@ -6,8 +6,6 @@ const System = @import("graph/system.zig");
// TODO: // TODO:
// - Use arena allocator? // - Use arena allocator?
// - Resolve missing resource problem // - 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 // - Organize a better way to execute single commands on graph
// - Handle system errors // - Handle system errors
// - Removing of resources // - Removing of resources
@@ -97,7 +95,7 @@ pub fn reset(self: *Self) void {
for (controller.commands()) |*command| { for (controller.commands()) |*command| {
switch (command.*) { switch (command.*) {
.add_resource => |*resource| resource.deinit(controller.alloc), .add_resource => |*resource| resource.deinit(controller.alloc),
.queue_system => |*system| system.deinit(controller.alloc), .queue_system => {},
} }
} }
controller.clear(); controller.clear();
@@ -106,10 +104,6 @@ pub fn reset(self: *Self) void {
(i + 1) * self.duds.items.len / self.controllers.items.len, (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(); self.system_queue.clearRetainingCapacity();
// Duds cleanup // Duds cleanup
for (self.duds.items) |*dud| { for (self.duds.items) |*dud| {
@@ -118,7 +112,6 @@ pub fn reset(self: *Self) void {
} }
fn enqueueSystem(self: *Self, system: System) !void { fn enqueueSystem(self: *Self, system: System) !void {
errdefer system.deinit(self.alloc);
try self.system_queue.append(self.alloc, system); 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().?; const next_system = self.system_queue.pop().?;
defer next_system.deinit(self.alloc);
self.runSystem(next_system) catch |err| { self.runSystem(next_system) catch |err| {
std.debug.print("System run error: {} while running {s}\n", .{ err, next_system.label }); std.debug.print("System run error: {} while running {s}\n", .{ err, next_system.label });
return err; return err;
@@ -278,17 +270,17 @@ test "simple graph smoke test" {
cmd.queue(addOne); cmd.queue(addOne);
cmd.queue(.{ cmd.queue(.{
.first = .{ .{
addThousand, addThousand,
addThousand, addThousand,
addThousand, addThousand,
}, },
.second = .{ .{
subThousand, subThousand,
subThousand, subThousand,
subThousand, subThousand,
}, },
.ordered = true, Controller.Option.ordered,
}); });
} }
}; };
@@ -324,7 +316,7 @@ test "complex queue graph smoke test" {
fn queueManySystems(cmd: *Controller) void { fn queueManySystems(cmd: *Controller) void {
cmd.queue(.{ cmd.queue(.{
.@"0" = .{ .{
addTen, addTen,
addTen, addTen,
addTen, addTen,
@@ -333,7 +325,7 @@ test "complex queue graph smoke test" {
}, },
// `data1` = 20 // `data1` = 20
// `data2` = 5 // `data2` = 5
.@"1" = .{ .{
mulTen, mulTen,
mulTen, mulTen,
mulTwo, mulTwo,
@@ -341,12 +333,12 @@ test "complex queue graph smoke test" {
}, },
// `data1` = 8000 // `data1` = 8000
// `data2` = 9 // `data2` = 9
.@"2" = .{ .{
subTwenty, subTwenty,
}, },
.ordered = true,
// `data1` = 7980 // `data1` = 7980
// `data2` = 10 // `data2` = 10
Controller.Option.ordered,
}); });
} }
fn addTen(rsc: *Rsc) void { fn addTen(rsc: *Rsc) void {

View File

@@ -4,6 +4,8 @@ const System = @import("system.zig");
const Resource = @import("resource.zig"); const Resource = @import("resource.zig");
const Controller = @This(); const Controller = @This();
pub const Option = utils.SystemSetOption;
const DEFAULT_CONTROLLER_CAPACITY = 8; const DEFAULT_CONTROLLER_CAPACITY = 8;
alloc: std.mem.Allocator, 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)); const command_buffer = try self.command_buffer.addManyAsSlice(self.alloc, utils.countSystems(system_set));
errdefer self.command_buffer.shrinkRetainingCapacity(prev_count); 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); std.debug.assert(commands_created == command_buffer.len);
} }
fn createQueueCommands( fn createQueueCommands(
self: *Controller, self: *Controller,
comptime system_set: anytype, comptime system_set: utils.SystemSet,
command_buffer: []Command, command_buffer: []Command,
requires_dud: ?System.Dud.Id, requires_dud: ?System.Dud.Id,
submit_dud: ?System.Dud.Id, submit_dud: ?System.Dud.Id,
) !usize { ) !usize {
switch (@typeInfo(@TypeOf(system_set))) { switch (system_set) {
.@"fn" => { .single => |single| {
var system = try System.fromFunction(system_set, self.alloc); command_buffer[0] = .{ .queue_system = .{
system.requires_dud = requires_dud; .function_runner = single.runner,
system.submit_dud = submit_dud; .requested_types = single.requests,
command_buffer[0] = .{ .queue_system = system }; .requires_dud = requires_dud,
.submit_dud = submit_dud,
.label = single.label,
} };
return 1; return 1;
}, },
.@"struct" => { .set => |set| {
const ordered = utils.getOptionalTupleField(system_set, "ordered", false);
var queued_total: usize = 0; var queued_total: usize = 0;
var prev_dud = requires_dud; var prev_dud = requires_dud;
var next_dud = submit_dud; var next_dud = submit_dud;
errdefer for (command_buffer[0..queued_total]) |command| { if (set.ordered) {
command.queue_system.deinit(self.alloc);
};
if (ordered) {
next_dud = requires_dud; next_dud = requires_dud;
} }
var queued_sets: usize = 0; var queued_sets: usize = 0;
var total_sets: usize = 0; const total_sets: usize = set.subsets.len;
inline for (@typeInfo(@TypeOf(system_set)).@"struct".fields) |field| {
switch (@typeInfo(field.type)) {
.@"fn", .@"struct" => total_sets += 1,
else => {},
}
}
inline for (@typeInfo(@TypeOf(system_set)).@"struct".fields) |field| { inline for (set.subsets) |subset| {
if (ordered) { if (set.ordered) {
prev_dud = next_dud; prev_dud = next_dud;
if (queued_sets == total_sets - 1) { if (queued_sets == total_sets - 1) {
next_dud = submit_dud; next_dud = submit_dud;
@@ -142,22 +136,16 @@ fn createQueueCommands(
next_dud = self.acquireDud().?; next_dud = self.acquireDud().?;
} }
} }
switch (@typeInfo(field.type)) {
.@"fn", .@"struct" => {
queued_total += try self.createQueueCommands( queued_total += try self.createQueueCommands(
@field(system_set, field.name), subset,
command_buffer[queued_total..], command_buffer[queued_total..],
prev_dud, prev_dud,
next_dud, next_dud,
); );
queued_sets += 1; queued_sets += 1;
},
else => {},
}
} }
return queued_total; 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| { for (self.command_buffer.items) |*command| {
switch (command.*) { switch (command.*) {
.add_resource => |*resource| resource.deinit(self.alloc), .add_resource => |*resource| resource.deinit(self.alloc),
.queue_system => |*system| system.deinit(self.alloc), .queue_system => {},
} }
} }
self.clear(); self.clear();

View File

@@ -3,7 +3,7 @@ const utils = @import("utils.zig");
const Controller = @import("controller.zig"); const Controller = @import("controller.zig");
function_runner: *const fn ([]const *anyopaque) void, function_runner: *const fn ([]const *anyopaque) void,
requested_types: []const Request, requested_types: []const utils.SystemRequest,
requires_dud: ?Dud.Id, requires_dud: ?Dud.Id,
submit_dud: ?Dud.Id, submit_dud: ?Dud.Id,
label: []const u8, label: []const u8,
@@ -13,35 +13,3 @@ pub const Dud = struct {
required_count: u16 = 0, 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);
}

View File

@@ -6,6 +6,77 @@ const System = @import("system.zig");
pub const Hash = u32; pub const Hash = u32;
const HashAlgorithm = std.crypto.hash.blake2.Blake2s(@bitSizeOf(Hash)); 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 { pub inline fn hashType(comptime h_type: type) Hash {
return hashString(@typeName(h_type)); 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 { pub fn validateSystem(comptime system: anytype) void {
const info = @typeInfo(@TypeOf(system)); const info = @typeInfo(@TypeOf(system));
if (info != .@"fn") @compileError("System can only be a function, got " ++ @typeName(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)) { switch (@typeInfo(field_info.type)) {
.@"fn", .@"struct" => validateSystemSet(@field(system_set, field_info.name)), .@"fn", .@"struct" => validateSystemSet(@field(system_set, field_info.name)),
else => { else => {
if (checkIsField(field_info, "ordered", bool)) continue; if (field_info.type == SystemSetOption) continue;
@compileError("Invalid field \"" ++ @compileError("Invalid field \"" ++
field_info.name ++ field_info.name ++
"\" of type `" ++ "\" of type `" ++
@@ -98,27 +170,17 @@ pub fn generateRunner(comptime system: anytype) fn ([]const *anyopaque) void {
return RunnerImpl.runner; return RunnerImpl.runner;
} }
pub fn checkIsField(field: std.builtin.Type.StructField, field_name: []const u8, comptime field_type: type) bool { pub fn generateRequests(comptime function: anytype) []const SystemRequest {
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) {
return comptime blk: { return comptime blk: {
for (@typeInfo(@TypeOf(tuple)).@"struct".fields) |field| { var requests: [@typeInfo(@TypeOf(function)).@"fn".params.len]SystemRequest = undefined;
if (!std.mem.eql(u8, field.name, field_name)) continue; for (0.., @typeInfo(@TypeOf(function)).@"fn".params) |i, param| {
if (@TypeOf(default) != field.type) switch (@typeInfo(param.type.?).pointer.child) {
@compileError("Cannot get tuple field `" ++ Controller => requests[i] = .controller,
field_name ++ else => |resource_type| requests[i] = .{ .resource = hashType(resource_type) },
"` with type `" ++
@typeName(@TypeOf(default)) ++
"` (tuple field has type `" ++
@typeName(field.type) ++
"`)");
break :blk @field(tuple, field.name);
} }
break :blk default; }
const requests_const = requests;
break :blk &requests_const;
}; };
} }