diff --git a/src/game.zig b/src/game.zig index 4a2d299..534ee7d 100644 --- a/src/game.zig +++ b/src/game.zig @@ -1,48 +1,90 @@ const std = @import("std"); +const builtin = @import("builtin"); const sdl = @import("sdl"); +const Graph = @import("graph.zig"); const Graphics = @import("graphics.zig"); -graphics: Graphics, -running: bool, +// TODO: +// - Do something about deallocating `Resource`s when `Graph` fails + +const RunInfo = struct { running: bool }; + +alloc: std.mem.Allocator, +graph: Graph, const Self = @This(); -pub fn init() GameError!Self { +pub fn init(alloc: std.mem.Allocator) GameError!Self { + var graph = try Graph.init(alloc); + errdefer graph.deinit(); + + const graphics = try Graphics.create(); + + var controller = try graph.getController(); + controller.addResource(graphics); + try graph.freeController(controller); + return Self{ - .graphics = try Graphics.create(), - .running = false, + .alloc = alloc, + .graph = graph, }; } pub fn run(self: *Self) GameError!void { - self.running = true; + { + var controller = try self.graph.getController(); + controller.addResource(RunInfo{ .running = true }); + try self.graph.freeController(controller); + } + while (true) { - try self.processEvents(); - if (!self.running) { + if (!self.graph.getResource(RunInfo).?.running) { break; } - try self.update(); - try self.draw(); + + var controller = try self.graph.getController(); + controller.queue(.{ + .events = processEvents, + .draw = draw, + .ordered = true, + }); + try self.graph.freeController(controller); + + defer self.graph.reset(); + try self.graph.runAllSystems(); } } -fn update(self: *Self) GameError!void { - _ = self; +fn draw(graphics: *Graphics) GameError!void { + try graphics.beginDraw(); + { + errdefer graphics.endDraw() catch {}; + try graphics.drawDebug(); + } + try graphics.endDraw(); } -fn draw(self: *Self) GameError!void { - try self.graphics.beginDraw(); - try self.graphics.drawDebug(); - try self.graphics.endDraw(); +fn clean(graphics: *Graphics) !void { + graphics.destroy(); + // TODO: Also remove the resource } -fn processEvents(self: *Self) GameError!void { +fn processEvents(graphics: *Graphics, run_info: *RunInfo) GameError!void { sdl.PumpEvents(); while (true) { var buffer: [16]sdl.Event = undefined; const count: usize = @intCast(sdl.PeepEvents(&buffer, buffer.len, sdl.GETEVENT, sdl.EVENT_FIRST, sdl.EVENT_LAST)); if (count == -1) return GameError.SdlError; for (buffer[0..count]) |event| { - self.processEvent(event); + switch (event.type) { + sdl.EVENT_QUIT => { + run_info.running = false; + }, + sdl.EVENT_WINDOW_RESIZED => { + if (event.window.windowID != sdl.GetWindowID(graphics.window)) return; + graphics.resize(@intCast(event.window.data1), @intCast(event.window.data2)); + }, + else => {}, + } } if (count < buffer.len) { break; @@ -51,25 +93,21 @@ fn processEvents(self: *Self) GameError!void { sdl.FlushEvents(sdl.EVENT_FIRST, sdl.EVENT_LAST); } -fn processEvent(self: *Self, event: sdl.Event) void { - switch (event.type) { - sdl.EVENT_QUIT => { - self.running = false; - }, - sdl.EVENT_WINDOW_RESIZED => { - if (event.window.windowID != sdl.GetWindowID(self.graphics.window)) return; - self.graphics.resize(@intCast(event.window.data1), @intCast(event.window.data2)); - }, - else => {}, - } -} - pub fn deinit(self: *Self) void { - self.graphics.destroy(); + var controller = self.graph.getController() catch unreachable; + controller.queue(clean); + self.graph.freeController(controller) catch unreachable; + self.graph.runAllSystems() catch unreachable; + + self.graph.deinit(); + sdl.Quit(); } pub const GameError = error{ SdlError, OSError, + OutOfMemory, + MissingResource, + SystemDeadlock, }; diff --git a/src/graph.zig b/src/graph.zig index d5c6c6d..438208e 100644 --- a/src/graph.zig +++ b/src/graph.zig @@ -8,6 +8,9 @@ const System = @import("graph/system.zig"); // - 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 pub const Controller = @import("graph/controller.zig"); @@ -86,12 +89,40 @@ pub fn deinit(self: *Self) void { self.duds.deinit(self.alloc); } +/// Clear all internal data in preparation for the next run cycle +/// Does not clear any `Resource`s +pub fn reset(self: *Self) void { + // Controller cleanup + for (self.controllers.items, 0..) |*controller, i| { + for (controller.commands()) |*command| { + switch (command.*) { + .add_resource => |*resource| resource.deinit(controller.alloc), + .queue_system => |*system| system.deinit(controller.alloc), + } + } + controller.clear(); + controller.setDuds( + i * 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(); + // Duds cleanup + for (self.duds.items) |*dud| { + dud.required_count = 0; + } +} + fn enqueueSystem(self: *Self, system: System) !void { errdefer system.deinit(self.alloc); try self.system_queue.append(self.alloc, system); } -fn runAllSystems(self: *Self) GraphError!void { +pub fn runAllSystems(self: *Self) GraphError!void { while (self.system_queue.items.len > 0) { var swap_with = self.system_queue.items.len - 1; @@ -169,7 +200,7 @@ fn applyCommands(self: *Self, commands: []const Controller.Command) !void { } } -fn getController(self: *Self) !Controller { +pub fn getController(self: *Self) !Controller { if (self.controllers.pop()) |c| { return c; } @@ -185,7 +216,7 @@ fn getController(self: *Self) !Controller { } /// Evaluates and clears the controller (even if errors out) -fn freeController(self: *Self, controller: Controller) !void { +pub fn freeController(self: *Self, controller: Controller) !void { var c = controller; try self.applyCommands(c.commands()); c.clear(); diff --git a/src/graph/controller.zig b/src/graph/controller.zig index c0c2f50..c7fdc32 100644 --- a/src/graph/controller.zig +++ b/src/graph/controller.zig @@ -34,7 +34,7 @@ pub fn create(alloc: std.mem.Allocator) !Controller { /// Returns command queue, caller is responsible for freeing it's data /// Call `clean()` afterwards, to clear the command queue -pub fn commands(self: *Controller) []const Command { +pub fn commands(self: *Controller) []Command { return self.command_buffer.items; } diff --git a/src/graph/system.zig b/src/graph/system.zig index c49179b..fad1829 100644 --- a/src/graph/system.zig +++ b/src/graph/system.zig @@ -8,9 +8,9 @@ requires_dud: ?Dud.Id, submit_dud: ?Dud.Id, pub const Dud = struct { - pub const Id = u16; + pub const Id = usize; - required_count: usize = 0, + required_count: u16 = 0, }; pub const Request = union(enum) { diff --git a/src/graph/utils.zig b/src/graph/utils.zig index 5124c07..fec06fe 100644 --- a/src/graph/utils.zig +++ b/src/graph/utils.zig @@ -32,7 +32,8 @@ pub fn validateResource(comptime resource_type: type) void { 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)); - if (info.@"fn".return_type != void) @compileError("Systems are not allowed to return any value (" ++ @typeName(info.Fn.return_type.?) ++ " returned)"); + if (@typeInfo(info.@"fn".return_type.?) != .void and + @typeInfo(info.@"fn".return_type.?) != .error_union) @compileError("Systems are not allowed to return any value (" ++ @typeName(info.@"fn".return_type.?) ++ " returned)"); if (info.@"fn".is_var_args) @compileError("System cannot be variadic"); if (info.@"fn".is_generic) @compileError("System cannot be generic"); @@ -85,7 +86,13 @@ pub fn generateRunner(comptime system: anytype) fn ([]const *anyopaque) void { inline for (0..@typeInfo(@TypeOf(system)).@"fn".params.len) |index| { args[index] = @alignCast(@ptrCast(resources[index])); } - @call(.always_inline, system, args); + switch (@typeInfo(@typeInfo(@TypeOf(system)).@"fn".return_type.?)) { + .void => @call(.always_inline, system, args), + .error_union => @call(.always_inline, system, args) catch |err| { + std.debug.print("System error: {s}\n", .{@errorName(err)}); + }, + else => unreachable, + } } }; return RunnerImpl.runner; diff --git a/src/main.zig b/src/main.zig index 00fa81a..c3211d5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,9 +1,26 @@ const std = @import("std"); +const builtin = @import("builtin"); const sdl = @import("sdl"); const Game = @import("game.zig"); pub fn runGame() !void { - var game = try Game.init(); + var allocator = switch (builtin.mode) { + .ReleaseSafe, .Debug => std.heap.DebugAllocator(.{}).init, + .ReleaseFast => std.heap.smp_allocator, + .ReleaseSmall => std.heap.c_allocator, + }; + defer switch (builtin.mode) { + .ReleaseSafe, .Debug => std.debug.assert(allocator.deinit() == .ok), + else => {}, + }; + + var game = try Game.init( + switch (builtin.mode) { + .ReleaseSafe, .Debug => allocator.allocator(), + .ReleaseFast => allocator, + .ReleaseSmall => allocator, + }, + ); defer game.deinit(); try game.run(); }