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();
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;

View File

@@ -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 {

View File

@@ -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),
subset,
command_buffer[queued_total..],
prev_dud,
next_dud,
);
queued_sets += 1;
},
else => {},
}
}
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();

View File

@@ -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);
}

View File

@@ -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;
};
}