From 7e162221ef62dc53d46295593faebe9f42428255 Mon Sep 17 00:00:00 2001 From: duck Date: Mon, 7 Jul 2025 21:11:55 +0500 Subject: [PATCH] We doing global variables now --- src/debug_scene.zig | 284 ----------------------------- src/entity.zig | 118 ++++++++++++ src/error.zig | 16 ++ src/game.zig | 188 +++++++------------ src/graph.zig | 377 --------------------------------------- src/graph/controller.zig | 200 --------------------- src/graph/resource.zig | 14 -- src/graph/system.zig | 15 -- src/graph/utils.zig | 204 --------------------- src/graphics.zig | 368 +++++++++++++++++--------------------- src/main.zig | 20 +-- src/mouse.zig | 10 +- src/time.zig | 8 + src/world.zig | 144 +++++++++++++++ 14 files changed, 521 insertions(+), 1445 deletions(-) delete mode 100644 src/debug_scene.zig create mode 100644 src/entity.zig create mode 100644 src/error.zig delete mode 100644 src/graph.zig delete mode 100644 src/graph/controller.zig delete mode 100644 src/graph/resource.zig delete mode 100644 src/graph/system.zig delete mode 100644 src/graph/utils.zig create mode 100644 src/world.zig diff --git a/src/debug_scene.zig b/src/debug_scene.zig deleted file mode 100644 index fe6f1d2..0000000 --- a/src/debug_scene.zig +++ /dev/null @@ -1,284 +0,0 @@ -const std = @import("std"); -const sdl = @import("sdl"); -const math = @import("math.zig"); -const Time = @import("time.zig"); -const Transform = @import("graphics/transform.zig"); -const Controller = @import("graph/controller.zig"); -const Graphics = @import("graphics.zig"); -const Game = @import("game.zig"); - -const CUBE_MESH_DATA = [_]f32{ - -0.5, 0.5, -0.5, 0.0, 0.0, - 0.5, 0.5, -0.5, 0.0, 0.0, - -0.5, -0.5, -0.5, 0.0, 0.0, - 0.5, -0.5, -0.5, 0.0, 0.0, - -0.5, -0.5, -0.5, 0.0, 0.0, - 0.5, 0.5, -0.5, 0.0, 0.0, - 0.5, 0.5, -0.5, 0.0, 0.0, - 0.5, 0.5, 0.5, 0.0, 0.0, - 0.5, -0.5, -0.5, 0.0, 0.0, - 0.5, -0.5, 0.5, 0.0, 0.0, - 0.5, -0.5, -0.5, 0.0, 0.0, - 0.5, 0.5, 0.5, 0.0, 0.0, - 0.5, 0.5, 0.5, 0.0, 0.0, - -0.5, 0.5, 0.5, 0.0, 0.0, - 0.5, -0.5, 0.5, 0.0, 0.0, - -0.5, -0.5, 0.5, 0.0, 0.0, - 0.5, -0.5, 0.5, 0.0, 0.0, - -0.5, 0.5, 0.5, 0.0, 0.0, - -0.5, 0.5, 0.5, 0.0, 0.0, - -0.5, 0.5, -0.5, 0.0, 0.0, - -0.5, -0.5, 0.5, 0.0, 0.0, - -0.5, -0.5, -0.5, 0.0, 0.0, - -0.5, -0.5, 0.5, 0.0, 0.0, - -0.5, 0.5, -0.5, 0.0, 0.0, - -0.5, 0.5, 0.5, 0.0, 0.0, - 0.5, 0.5, 0.5, 0.0, 0.0, - -0.5, 0.5, -0.5, 0.0, 0.0, - 0.5, 0.5, -0.5, 0.0, 0.0, - -0.5, 0.5, -0.5, 0.0, 0.0, - 0.5, 0.5, 0.5, 0.0, 0.0, - -0.5, -0.5, -0.5, 0.0, 0.0, - 0.5, -0.5, -0.5, 0.0, 0.0, - -0.5, -0.5, 0.5, 0.0, 0.0, - 0.5, -0.5, 0.5, 0.0, 0.0, - -0.5, -0.5, 0.5, 0.0, 0.0, - 0.5, -0.5, -0.5, 0.0, 0.0, -}; -const PLANE_MESH_DATA = [_]f32{ - -0.5, -0.5, 0, 0.0, 1.0, - 0.5, 0.5, 0, 1.0, 0.0, - -0.5, 0.5, 0, 0.0, 0.0, - 0.5, 0.5, 0, 1.0, 0.0, - -0.5, -0.5, 0, 0.0, 1.0, - 0.5, -0.5, 0, 1.0, 1.0, -}; -// const TEXTURE_DATA = [_]u8{ -// 255, 0, 0, 255, -// 0, 255, 0, 255, -// 0, 0, 255, 255, -// 0, 0, 0, 255, -// }; -const TEXTURE_DATA = [_]u8{ - 255, 0, 0, 255, - 0, 255, 0, 255, - 0, 0, 255, 255, - 0, 0, 0, 255, -}; - -pub const WorldTime = struct { - time: Time, - last_time: Time, - view_unresolved: f32, - view_timescale: f32, -}; - -pub const PlayerState = union(enum) { - idle, - moving: struct { - from_position: @Vector(3, f32), - from_time: Time, - to_time: Time, - }, -}; - -pub const Player = struct { - mesh: Graphics.Mesh, - texture: Graphics.Texture, - transform: Transform, - position: @Vector(2, i32), - state: PlayerState, - - move_units: f32, - idle_timescale: f32, - moving_timescale: f32, -}; - -pub const Environment = struct { - mesh: Graphics.Mesh, - texture: Graphics.Texture, - transform: Transform, -}; - -pub fn init(controller: *Controller, graphics: *Graphics) !void { - controller.addResource(Player{ - .mesh = try graphics.loadMesh(@ptrCast(&CUBE_MESH_DATA)), - .texture = try graphics.loadTexture(2, 2, @ptrCast(&TEXTURE_DATA)), - .transform = .{}, - .position = .{ 0, 0 }, - .state = .idle, - .move_units = 0.125, - .moving_timescale = 1.0, - .idle_timescale = 0.0625, - }); - controller.addResource(Environment{ - .mesh = try graphics.loadMesh(@ptrCast(&PLANE_MESH_DATA)), - .texture = try graphics.loadTexture(2, 2, @ptrCast(&TEXTURE_DATA)), - .transform = .{ - .position = .{ 0, 0, -1 }, - .scale = @splat(5), - }, - }); - controller.addResource(WorldTime{ - .time = Time.ZERO, - .last_time = Time.ZERO, - .view_unresolved = 0.0, - .view_timescale = 0.0625, - }); -} - -pub fn deinit() void {} - -pub fn updateReal( - real_time: *Game.Time, - world_time: *WorldTime, - controller: *Controller, -) void { - world_time.view_unresolved += real_time.delta; - controller.queue(.{ - updateWorld, - updatePlayerTransform, - updateCamera, - Controller.Option.ordered, - }); -} - -pub fn updateWorld( - world_time: *WorldTime, - controller: *Controller, - player: *Player, -) void { - if (world_time.view_unresolved <= 0) return; - - var real_delta = world_time.view_unresolved; - var world_delta = Time.durationFromUnits(real_delta * world_time.view_timescale); - - switch (player.state) { - .moving => |move| { - if (move.to_time.clock > world_time.time.clock) { - world_delta = @min(world_delta, move.to_time.clock - world_time.time.clock); - real_delta = Time.unitsFromDuration(world_delta) / world_time.view_timescale; - } - }, - .idle => {}, - } - - if (world_delta == 0) return; - - world_time.last_time = world_time.time; - - world_time.time.clock += world_delta; - world_time.view_unresolved -= real_delta; - - controller.queue(.{ - updatePlayer, - updateWorld, - Controller.Option.ordered, - }); -} - -pub fn updatePlayer( - player: *Player, - keyboard: *Game.Keyboard, - world_time: *WorldTime, -) void { - switch (player.state) { - .idle => {}, - .moving => |move| { - if (world_time.time.past(move.to_time)) { - player.state = .idle; - world_time.view_timescale = player.idle_timescale; - } else return; - }, - } - - var delta: @Vector(2, i32) = .{ 0, 0 }; - if (keyboard.keys.is_pressed(sdl.SCANCODE_W)) { - delta[1] += 1; - } - if (keyboard.keys.is_pressed(sdl.SCANCODE_S)) { - delta[1] -= 1; - } - if (keyboard.keys.is_pressed(sdl.SCANCODE_D)) { - delta[0] += 1; - } - if (keyboard.keys.is_pressed(sdl.SCANCODE_A)) { - delta[0] -= 1; - } - if (delta[0] != 0 or delta[1] != 0) { - updatePlayerTransform(player, world_time); - player.state = .{ .moving = .{ - .from_position = player.transform.position, - .from_time = world_time.time, - .to_time = world_time.time.offset(player.move_units * math.lengthInt(delta)), - } }; - player.position[0] += delta[0]; - player.position[1] += delta[1]; - world_time.view_timescale = player.moving_timescale; - } -} - -pub fn updateCamera( - graphics: *Graphics, - player: *Player, - real_time: *Game.Time, -) void { - graphics.camera.transform.position = math.lerpTimeLn( - graphics.camera.transform.position, - player.transform.position + @Vector(3, f32){ 0.0, -2.0, 5.0 }, - real_time.delta, - -25, - ); - - const ORIGIN_DIR = @Vector(3, f32){ 0.0, 0.0, -1.0 }; - const INIT_ROTATION = Transform.rotationByAxis(.{ 1.0, 0.0, 0.0 }, std.math.pi * 0.5); - - const ROTATED_DIR = Transform.rotateVector(ORIGIN_DIR, INIT_ROTATION); - - const target_rotation = Transform.combineRotations( - INIT_ROTATION, - Transform.rotationToward( - ROTATED_DIR, - player.transform.position - graphics.camera.transform.position, - .{ .normalize_to = true }, - ), - ); - graphics.camera.transform.rotation = Transform.normalizeRotation(math.slerpTimeLn( - graphics.camera.transform.rotation, - target_rotation, - real_time.delta, - -2, - )); -} - -pub fn updatePlayerTransform(player: *Player, world_time: *WorldTime) void { - switch (player.state) { - .idle => player.transform.position = .{ @floatFromInt(player.position[0]), @floatFromInt(player.position[1]), 0.0 }, - .moving => |move| { - const to_position = @Vector(3, f32){ @floatFromInt(player.position[0]), @floatFromInt(player.position[1]), 0.0 }; - player.transform.position = math.lerp( - move.from_position, - to_position, - world_time.time.progress(move.from_time, move.to_time), - ); - }, - } -} - -pub fn draw( - player: *Player, - env: *Environment, - graphics: *Graphics, - world_time: *WorldTime, -) !void { - env.transform.rotation = Transform.combineRotations( - env.transform.rotation, - Transform.rotationByAxis(.{ 0, 0, 1 }, world_time.time.unitsSince(world_time.last_time) * std.math.pi), - ); - try graphics.drawMesh(env.mesh, env.texture, env.transform); - try graphics.drawMesh(env.mesh, env.texture, Transform{ - .position = .{ 0, 0, -0.5 }, - .scale = @splat(5), - }); - try graphics.drawMesh(player.mesh, player.texture, player.transform); -} diff --git a/src/entity.zig b/src/entity.zig new file mode 100644 index 0000000..c3b40a2 --- /dev/null +++ b/src/entity.zig @@ -0,0 +1,118 @@ +const std = @import("std"); +const sdl = @import("sdl"); +const Game = @import("game.zig"); +const Graphics = @import("graphics.zig"); +const Time = @import("time.zig"); +const World = @import("world.zig"); +const math = @import("math.zig"); + +position: @Vector(2, i32), +player: bool = false, +enemy: bool = false, +controller: Controller = .{}, +next_update: Time = Time.ZERO, + +const Controller = struct { + const Action = union(enum) { + move: @Vector(2, i32), + }; + wanted_action: ?Action = null, + move_units: f32 = 0.125, +}; + +const Self = @This(); +pub fn update(self: *Self) void { + if (!World.time.past(self.next_update)) return; + + if (self.player) self.updatePlayer(); + if (self.enemy) self.updateEnemy(); + self.updateController(); +} + +pub fn updatePlayer(self: *Self) void { + var delta: @Vector(2, i32) = .{ 0, 0 }; + if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_UP)) { + delta[1] += 1; + } + if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_DOWN)) { + delta[1] -= 1; + } + if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_RIGHT)) { + delta[0] += 1; + } + if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_LEFT)) { + delta[0] -= 1; + } + if (@reduce(.Or, delta != @Vector(2, i32){ 0, 0 })) + self.controller.wanted_action = .{ .move = delta } + else + self.controller.wanted_action = null; +} + +fn updateEnemy(self: *Self) void { + if (World.getPlayer()) |player| { + var delta = player.position - self.position; + if (@reduce(.And, @abs(delta) <= @Vector(2, i64){ 1, 1 })) { + self.controller.wanted_action = null; + } else { + delta[0] = @max(-1, @min(1, delta[0])); + delta[1] = @max(-1, @min(1, delta[1])); + self.controller.wanted_action = .{ .move = delta }; + } + } +} + +fn updateController(self: *Self) void { + if (self.controller.wanted_action) |action| { + switch (action) { + .move => |delta| { + const target = self.position + delta; + if (World.isFree(target)) { + self.next_update = World.time.offset(self.controller.move_units * math.lengthInt(delta)); + self.position[0] += delta[0]; + self.position[1] += delta[1]; + } + }, + } + } +} + +pub fn draw(self: *Self, delta: f32) void { + const transform = Graphics.Transform{ + .position = .{ + @floatFromInt(self.position[0]), + @floatFromInt(self.position[1]), + 0.5, + }, + }; + Graphics.drawMesh(World.cube_mesh, World.texture, transform); + + if (!self.player) return; + + Graphics.camera.transform.position = math.lerpTimeLn( + Graphics.camera.transform.position, + transform.position + @Vector(3, f32){ 0.0, -2.0, 5.0 }, + delta, + -25, + ); + + const ORIGIN_DIR = @Vector(3, f32){ 0.0, 0.0, -1.0 }; + const INIT_ROTATION = Graphics.Transform.rotationByAxis(.{ 1.0, 0.0, 0.0 }, std.math.pi * 0.5); + + const ROTATED_DIR = Graphics.Transform.rotateVector(ORIGIN_DIR, INIT_ROTATION); + + const target_rotation = Graphics.Transform.combineRotations( + INIT_ROTATION, + Graphics.Transform.rotationToward( + ROTATED_DIR, + transform.position - Graphics.camera.transform.position, + .{ .normalize_to = true }, + ), + ); + Graphics.camera.transform.rotation = Graphics.Transform.normalizeRotation(math.slerpTimeLn( + Graphics.camera.transform.rotation, + target_rotation, + delta, + -2, + )); +} diff --git a/src/error.zig b/src/error.zig new file mode 100644 index 0000000..a6389a5 --- /dev/null +++ b/src/error.zig @@ -0,0 +1,16 @@ +const std = @import("std"); +const libsdl = @import("sdl"); + +pub fn sdl() noreturn { + std.debug.panic("SDL Error:\n{s}\n", .{libsdl.GetError()}); +} + +pub fn oom() noreturn { + std.debug.panic("Out of memory!\n", .{}); +} + +const FileError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError; + +pub fn file(err: FileError, path: []const u8) noreturn { + std.debug.panic("Error while reading \"{s}\": {any}", .{ path, err }); +} diff --git a/src/game.zig b/src/game.zig index 36f1517..44bd26c 100644 --- a/src/game.zig +++ b/src/game.zig @@ -1,155 +1,106 @@ const std = @import("std"); const builtin = @import("builtin"); const sdl = @import("sdl"); +const err = @import("error.zig"); +const Mouse = @import("mouse.zig"); +const Keyboard = @import("keyboard.zig"); -const debug_scene = @import("debug_scene.zig"); -const Graph = @import("graph.zig"); -const Graphics = @import("graphics.zig"); +pub const Graphics = @import("graphics.zig"); +pub const World = @import("world.zig"); -// TODO: -// - Do something about deallocating `Resource`s when `Graph` fails - -pub const RunInfo = struct { running: bool }; -pub const Mouse = @import("mouse.zig"); -pub const Keyboard = @import("keyboard.zig"); -pub const Time = struct { +const Time = struct { delta: f32, now: sdl.Time, }; -alloc: std.mem.Allocator, -graph: Graph, +var alloc: std.mem.Allocator = undefined; -const Self = @This(); -pub fn init(alloc: std.mem.Allocator) GameError!Self { - var graph = try Graph.init(alloc); - errdefer graph.deinit(); +var running: bool = false; +var time: Time = .{ .delta = 0, .now = 0 }; +pub var keyboard: Keyboard = .{}; +pub var mouse: Mouse = .{}; - const graphics = try Graphics.create(); - - var controller = try graph.getController(); - controller.addResource(graphics); - controller.addResource(Mouse{ - .buttons = .{}, - .x = 0.0, - .y = 0.0, - .dx = 0.0, - .dy = 0.0, - }); - controller.addResource(Keyboard{}); - controller.addResource(Time{ - .delta = 0.0, - .now = 0, - }); - controller.queue(debug_scene.init); - try graph.freeController(controller); - - defer graph.reset(); - try graph.runAllSystems(); - - return Self{ - .alloc = alloc, - .graph = graph, - }; +const Game = @This(); +pub fn init(game_alloc: std.mem.Allocator) void { + Game.alloc = game_alloc; + Game.running = false; + Game.time = Time{ .now = 0, .delta = 0 }; + Game.keyboard = .{}; + Game.mouse = .{ .x = 0, .y = 0, .dx = 0, .dy = 0 }; + Graphics.create(); + World.initDebug(); } -pub fn run(self: *Self) GameError!void { - { - var controller = try self.graph.getController(); - controller.addResource(RunInfo{ .running = true }); - try self.graph.freeController(controller); - } - - while (true) { - if (!self.graph.getResource(RunInfo).?.running) { - break; - } +pub fn run() void { + Game.running = true; + while (Game.running) { var current_time: sdl.Time = undefined; if (sdl.GetCurrentTime(¤t_time)) { - const time = self.graph.getResource(Time).?; - if (time.now != 0) { - time.delta = @as(f32, @floatFromInt(current_time - time.now)) * 0.000000001; + if (Game.time.now != 0) { + Game.time.delta = @as(f32, @floatFromInt(current_time - Game.time.now)) * 0.000000001; } - time.now = current_time; - } else return error.SdlError; + Game.time.now = current_time; + } else err.sdl(); - var controller = try self.graph.getController(); - controller.queue(.{ - processEvents, - debug_scene.updateReal, - beginDraw, - endDraw, - Graph.Controller.Option.ordered, - }); - try self.graph.freeController(controller); - - defer self.graph.reset(); - try self.graph.runAllSystems(); + Game.processEvents(); + World.updateReal(Game.time.delta); + if (Game.beginDraw()) { + World.draw(Game.time.delta); + Game.endDraw(); + } } } -fn beginDraw(graphics: *Graphics, controller: *Graph.Controller) GameError!void { - if (try graphics.beginDraw()) { - controller.queue(debug_scene.draw); - } +fn beginDraw() bool { + return Graphics.beginDraw(); } -fn endDraw(graphics: *Graphics) GameError!void { - try graphics.endDraw(); +fn endDraw() void { + Graphics.endDraw(); } -fn clean(graphics: *Graphics) !void { - graphics.destroy(); - // TODO: Also remove the resource -} - -fn processEvents( - graphics: *Graphics, - run_info: *RunInfo, - mouse: *Mouse, - keyboard: *Keyboard, -) GameError!void { - mouse.dx = 0.0; - mouse.dy = 0.0; - keyboard.keys.reset(); +fn processEvents() void { + Game.mouse.dx = 0.0; + Game.mouse.dy = 0.0; + Game.keyboard.keys.reset(); 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; + if (count == -1) err.sdl(); for (buffer[0..count]) |event| { switch (event.type) { sdl.EVENT_QUIT => { - run_info.running = false; + Game.running = false; }, sdl.EVENT_WINDOW_RESIZED => { - if (event.window.windowID != sdl.GetWindowID(graphics.window)) continue; - graphics.resize(@intCast(event.window.data1), @intCast(event.window.data2)); + if (event.window.windowID != Graphics.windowId()) continue; + Graphics.resize(@intCast(event.window.data1), @intCast(event.window.data2)); }, sdl.EVENT_MOUSE_MOTION => { - if (event.motion.windowID != sdl.GetWindowID(graphics.window)) continue; - mouse.x = event.motion.x; - mouse.y = event.motion.y; - mouse.dx += event.motion.xrel; - mouse.dy += event.motion.yrel; + if (event.motion.windowID != Graphics.windowId()) continue; + Game.mouse.x = event.motion.x; + Game.mouse.y = event.motion.y; + Game.mouse.dx += event.motion.xrel; + Game.mouse.dy += event.motion.yrel; }, sdl.EVENT_KEY_DOWN => { - if (event.key.windowID != sdl.GetWindowID(graphics.window)) continue; - keyboard.keys.press(event.key.scancode); + if (event.key.windowID != Graphics.windowId()) continue; + Game.keyboard.keys.press(event.key.scancode); }, sdl.EVENT_KEY_UP => { - if (event.key.windowID != sdl.GetWindowID(graphics.window)) continue; - keyboard.keys.release(event.key.scancode); + if (event.key.windowID != Graphics.windowId()) continue; + Game.keyboard.keys.release(event.key.scancode); }, sdl.EVENT_MOUSE_BUTTON_DOWN => { - if (event.button.windowID != sdl.GetWindowID(graphics.window)) continue; - mouse.buttons.press(event.button.button); + if (event.button.windowID != Graphics.windowId()) continue; + Game.mouse.buttons.press(event.button.button); }, sdl.EVENT_MOUSE_BUTTON_UP => { - if (event.button.windowID != sdl.GetWindowID(graphics.window)) continue; - mouse.buttons.release(event.button.button); + if (event.button.windowID != Graphics.windowId()) continue; + Game.mouse.buttons.release(event.button.button); }, else => {}, } @@ -161,25 +112,8 @@ fn processEvents( sdl.FlushEvents(sdl.EVENT_FIRST, sdl.EVENT_LAST); } -pub fn deinit(self: *Self) void { - var controller = self.graph.getController() catch unreachable; - controller.queue(.{ - debug_scene.deinit, - clean, - Graph.Controller.Option.ordered, - }); - self.graph.freeController(controller) catch unreachable; - self.graph.runAllSystems() catch unreachable; - - self.graph.deinit(); - +pub fn deinit() void { + World.deinit(); + Graphics.destroy(); sdl.Quit(); } - -pub const GameError = error{ - SdlError, - OSError, - OutOfMemory, - MissingResource, - SystemDeadlock, -}; diff --git a/src/graph.zig b/src/graph.zig deleted file mode 100644 index d3624cf..0000000 --- a/src/graph.zig +++ /dev/null @@ -1,377 +0,0 @@ -const std = @import("std"); -const utils = @import("graph/utils.zig"); -const Resource = @import("graph/resource.zig"); -const System = @import("graph/system.zig"); - -// TODO: -// - Use arena allocator? -// - Resolve missing resource problem -// - Organize a better way to execute single commands on graph -// - Handle system errors -// - Removing of resources - -pub const Controller = @import("graph/controller.zig"); - -const MAX_SYSTEM_REQUESTS = 8; -const DEFAULT_SYSTEM_CAPACITY = 16; -const DEFAULT_CONTROLLERS = 2; -const DEFAULT_DUDS_PER_CONTROLLER = 32; - -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 { - var resources = try ResourceMap.init(alloc, &.{}, &.{}); - errdefer resources.deinit(alloc); - - var system_queue = try SystemQueue.initCapacity(alloc, DEFAULT_SYSTEM_CAPACITY); - errdefer system_queue.deinit(alloc); - - var controllers = try Controllers.initCapacity(alloc, DEFAULT_CONTROLLERS); - errdefer controllers.deinit(alloc); - - errdefer for (controllers.items) |*controller| { - controller.deinit(); - }; - - 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), @intCast(DEFAULT_DUDS_PER_CONTROLLER * (i + 1))); - controllers.appendAssumeCapacity(controller); - } - - return .{ - .alloc = alloc, - .resources = resources, - .system_queue = system_queue, - .controllers = controllers, - .duds = duds, - }; -} - -pub fn deinit(self: *Self) void { - var resource_iter = self.resources.iterator(); - while (resource_iter.next()) |entry| { - entry.value_ptr.deinit(self.alloc); - } - self.resources.clearAndFree(self.alloc); - self.resources.deinit(self.alloc); - - for (self.system_queue.items) |system| { - self.alloc.free(system.requested_types); - } - self.system_queue.deinit(self.alloc); - - for (self.controllers.items) |*controller| { - controller.deinit(); - } - self.controllers.deinit(self.alloc); - - 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 => {}, - } - } - controller.clear(); - controller.setDuds( - i * self.duds.items.len / self.controllers.items.len, - (i + 1) * self.duds.items.len / self.controllers.items.len, - ); - } - self.system_queue.clearRetainingCapacity(); - // Duds cleanup - for (self.duds.items) |*dud| { - dud.required_count = 0; - } -} - -fn enqueueSystem(self: *Self, system: System) !void { - try self.system_queue.append(self.alloc, system); -} - -pub fn runAllSystems(self: *Self) GraphError!void { - 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 > 0) { - 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().?; - - self.runSystem(next_system) catch |err| { - std.debug.print("System run error: {} while running {s}\n", .{ err, next_system.label }); - return err; - }; - } -} - -/// 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(); - - var buffer_len: usize = 0; - for (system.requested_types) |request| { - switch (request) { - .resource => |resource| { - buffer[buffer_len] = self.getAnyopaqueResource(resource) orelse return GraphError.MissingResource; - }, - .controller => { - controller = try self.getController(); - controller.?.submit_dud = system.submit_dud; - buffer[buffer_len] = @ptrCast(&controller.?); - }, - } - 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; - try self.freeController(c); - } -} - -fn applyCommands(self: *Self, commands: []const Controller.Command) !void { - for (commands) |command| { - switch (command) { - .add_resource => |r| try self.addResource(r), - .queue_system => |s| { - if (s.submit_dud) |submit_id| self.duds.items[submit_id].required_count += 1; - try self.enqueueSystem(s); - }, - } - } -} - -pub fn getController(self: *Self) !Controller { - if (self.controllers.pop()) |c| { - return c; - } - 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), @intCast(next_dud_id + DEFAULT_DUDS_PER_CONTROLLER)); - return controller; -} - -/// Evaluates and clears the controller (even if errors out) -pub fn freeController(self: *Self, controller: Controller) !void { - var c = controller; - try self.applyCommands(c.commands()); - c.clear(); - try self.controllers.append(self.alloc, c); - // TODO: Handle controller error state -} - -pub inline fn getResource(self: *Self, comptime resource: type) ?*resource { - utils.validateResource(resource); - if (getAnyopaqueResource(self, utils.hashType(resource))) |ptr| { - return @alignCast(@ptrCast(ptr)); - } - return null; -} - -fn getAnyopaqueResource(self: *Self, resource_hash: utils.Hash) ?*anyopaque { - if (self.resources.get(resource_hash)) |resource| { - return resource.pointer; - } - return null; -} - -/// Discards any previous resource data, resource is assumed to be allocated with `self.alloc` -pub inline fn addResource(self: *Self, resource: Resource) !void { - var previous = try self.resources.fetchPut(self.alloc, resource.hash, resource); - if (previous) |*p| { - p.value.deinit(self.alloc); - } -} - -const GraphError = error{ - MissingResource, - OutOfMemory, - SystemDeadlock, -}; - -test "simple graph smoke test" { - const Graph = @This(); - const TestResource = struct { - number: u32, - - fn addOne(rsc: *@This()) void { - rsc.number += 1; - } - 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.queue(addTen); - cmd.queue(addOne); - - cmd.queue(.{ - .{ - addThousand, - addThousand, - addThousand, - }, - .{ - subThousand, - subThousand, - subThousand, - }, - Controller.Option.ordered, - }); - } - }; - - var graph = try Graph.init(std.testing.allocator); - defer graph.deinit(); - - var controller = try graph.getController(); - controller.addResource(TestResource{ .number = 100 }); - - controller.queue(TestResource.addOne); - controller.queue(TestResource.addOne); - - controller.queue(TestResource.addTen); - - controller.queue(TestResource.addEleven); - - try graph.freeController(controller); - - try graph.runAllSystems(); - - const result = graph.getResource(TestResource); - try std.testing.expectEqual(result.?.number, 123); -} - -test "complex queue graph smoke test" { - const Graph = @This(); - const TestResource = struct { - const Rsc = @This(); - - data1: isize, - data2: isize, - - fn queueManySystems(cmd: *Controller) void { - cmd.queue(.{ - .{ - addTen, - addTen, - addTen, - addTen, - subTwenty, - }, - // `data1` = 20 - // `data2` = 5 - .{ - mulTen, - mulTen, - mulTwo, - mulTwo, - }, - // `data1` = 8000 - // `data2` = 9 - .{ - subTwenty, - }, - // `data1` = 7980 - // `data2` = 10 - Controller.Option.ordered, - }); - } - fn addTen(rsc: *Rsc) void { - rsc.data1 += 10; - rsc.data2 += 1; - } - fn subTwenty(rsc: *Rsc) void { - rsc.data1 -= 20; - rsc.data2 += 1; - } - fn mulTen(rsc: *Rsc) void { - rsc.data1 *= 10; - rsc.data2 += 1; - } - fn mulTwo(rsc: *Rsc) void { - rsc.data1 *= 2; - rsc.data2 += 1; - } - }; - - var graph = try Graph.init(std.testing.allocator); - defer graph.deinit(); - - var controller = try graph.getController(); - - controller.addResource(TestResource{ .data1 = 0, .data2 = 0 }); - controller.queue(TestResource.queueManySystems); - - try graph.freeController(controller); - - try graph.runAllSystems(); - - const result = graph.getResource(TestResource).?; - try std.testing.expectEqual(7980, result.data1); - try std.testing.expectEqual(10, result.data2); -} diff --git a/src/graph/controller.zig b/src/graph/controller.zig deleted file mode 100644 index 0322975..0000000 --- a/src/graph/controller.zig +++ /dev/null @@ -1,200 +0,0 @@ -const std = @import("std"); -const utils = @import("utils.zig"); -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, -command_buffer: std.ArrayListUnmanaged(Command), -error_state: ErrorState, -dud_range: struct { System.Dud.Id, System.Dud.Id }, -submit_dud: ?System.Dud.Id, - -pub const Command = union(enum) { - add_resource: Resource, - queue_system: System, -}; -pub const ErrorState = union(enum) { - ok: void, - recoverable: []const u8, - unrecoverable: void, -}; - -pub fn create(alloc: std.mem.Allocator) !Controller { - return .{ - .alloc = alloc, - .command_buffer = try std.ArrayListUnmanaged(Command).initCapacity(alloc, DEFAULT_CONTROLLER_CAPACITY), - .error_state = .ok, - .dud_range = .{ 0, 0 }, - .submit_dud = null, - }; -} - -/// Returns command queue, caller is responsible for freeing it's data -/// Call `clean()` afterwards, to clear the command queue -pub fn commands(self: *Controller) []Command { - return self.command_buffer.items; -} - -pub fn setDuds(self: *Controller, start_id: System.Dud.Id, end_id: System.Dud.Id) void { - self.dud_range = .{ start_id, end_id }; -} - -fn acquireDud(self: *Controller) ?System.Dud.Id { - if (self.dud_range[0] == self.dud_range[1]) return null; - - defer self.dud_range[0] += 1; - return self.dud_range[0]; -} - -/// 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) { - .ok, .unrecoverable => {}, - .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 -pub inline fn addResource(self: *Controller, resource: anytype) void { - utils.validateResource(@TypeOf(resource)); - - self.addAnyopaqueResource( - @ptrCast(&resource), - utils.hashType(@TypeOf(resource)), - @sizeOf(@TypeOf(resource)), - @alignOf(@TypeOf(resource)), - ) catch |err| self.fail(err); -} - -/// Queues a multitude of functions to be executed either in parallel or in ordered manner -/// `system_set` can be either a `System`-like function or a tuple which may contain other system sets -/// -/// Optional tuple fields that control the execution behavior of functions: -/// -/// `ordered` - ensures that all systems specified in the tuple are executed in provided order -pub fn queue(self: *Controller, comptime system_set: anytype) void { - utils.validateSystemSet(system_set); - - self.queueInternal(system_set) catch |err| self.fail(err); -} - -fn queueInternal(self: *Controller, comptime system_set: anytype) !void { - const prev_count = self.command_buffer.items.len; - - 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(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: utils.SystemSet, - command_buffer: []Command, - requires_dud: ?System.Dud.Id, - submit_dud: ?System.Dud.Id, -) !usize { - 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; - }, - .set => |set| { - var queued_total: usize = 0; - var prev_dud = requires_dud; - var next_dud = submit_dud; - - if (set.ordered) { - next_dud = requires_dud; - } - - var queued_sets: usize = 0; - const total_sets: usize = set.subsets.len; - - inline for (set.subsets) |subset| { - if (set.ordered) { - prev_dud = next_dud; - if (queued_sets == total_sets - 1) { - next_dud = submit_dud; - } else { - // TODO: Soft fail - next_dud = self.acquireDud().?; - } - } - queued_total += try self.createQueueCommands( - subset, - command_buffer[queued_total..], - prev_dud, - next_dud, - ); - queued_sets += 1; - } - return queued_total; - }, - } -} - -/// `previous_output` is expected to be aligned accordingly -fn addAnyopaqueResource( - self: *Controller, - resource: *const anyopaque, - hash: utils.Hash, - size: usize, - align_to: u29, -) !void { - // TODO: Review this shady function - const resource_buffer = try self.alloc.alloc(u8, size + align_to - 1); - errdefer self.alloc.free(resource_buffer); - - const align_offset = std.mem.alignPointerOffset( - @as([*]u8, @ptrCast(resource_buffer)), - align_to, - ) orelse unreachable; - - @memcpy( - resource_buffer[align_offset..size], - @as([*]const u8, @ptrCast(resource))[0..size], - ); - - try self.command_buffer.append(self.alloc, .{ .add_resource = .{ - .pointer = @ptrCast(resource_buffer[align_offset..]), - .buffer = resource_buffer, - .alignment = align_to, - .hash = hash, - } }); -} - -const ControllerError = std.mem.Allocator.Error; -fn fail(self: *Controller, err: ControllerError) void { - if (self.error_state == .unrecoverable) return; - if (self.error_state == .recoverable) self.alloc.free(self.error_state.recoverable); - switch (err) { - error.OutOfMemory => self.error_state = .unrecoverable, - } -} - -pub fn deinit(self: *Controller) void { - for (self.command_buffer.items) |*command| { - switch (command.*) { - .add_resource => |*resource| resource.deinit(self.alloc), - .queue_system => {}, - } - } - self.clear(); - self.command_buffer.deinit(self.alloc); -} diff --git a/src/graph/resource.zig b/src/graph/resource.zig deleted file mode 100644 index 8b67124..0000000 --- a/src/graph/resource.zig +++ /dev/null @@ -1,14 +0,0 @@ -const std = @import("std"); -const utils = @import("utils.zig"); -const Resource = @This(); - -/// Resource data -pointer: *anyopaque, -/// Pointer to the memory allocted for this resource -buffer: []u8, -alignment: u29, -hash: utils.Hash, - -pub fn deinit(self: *Resource, alloc: std.mem.Allocator) void { - alloc.free(self.buffer); -} diff --git a/src/graph/system.zig b/src/graph/system.zig deleted file mode 100644 index fc09a24..0000000 --- a/src/graph/system.zig +++ /dev/null @@ -1,15 +0,0 @@ -const std = @import("std"); -const utils = @import("utils.zig"); -const Controller = @import("controller.zig"); - -function_runner: *const fn ([]const *anyopaque) void, -requested_types: []const utils.SystemRequest, -requires_dud: ?Dud.Id, -submit_dud: ?Dud.Id, -label: []const u8, - -pub const Dud = struct { - pub const Id = usize; - - required_count: u16 = 0, -}; diff --git a/src/graph/utils.zig b/src/graph/utils.zig deleted file mode 100644 index 9bfe9f6..0000000 --- a/src/graph/utils.zig +++ /dev/null @@ -1,204 +0,0 @@ -const std = @import("std"); -const Resource = @import("resource.zig"); -const Controller = @import("controller.zig"); -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)); -} - -pub fn hashString(comptime name: []const u8) Hash { - @setEvalBranchQuota(100000); - var output: [@divExact(@bitSizeOf(Hash), 8)]u8 = undefined; - - HashAlgorithm.hash(name, &output, .{}); - return std.mem.readInt( - Hash, - output[0..], - @import("builtin").cpu.arch.endian(), - ); -} - -pub fn validateResource(comptime resource_type: type) void { - switch (@typeInfo(resource_type)) { - .@"struct", .@"enum", .@"union" => return, - else => @compileError("Invalid resource type \"" ++ @typeName(resource_type) ++ "\""), - } -} - -// 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)); - 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"); - - comptime { - var controller_requests: usize = 0; - for (info.@"fn".params) |param| { - if (@typeInfo(param.type.?) != .pointer) @compileError("Systems can only have pointer parameters"); - switch (@typeInfo(param.type.?).pointer.child) { - Controller => { - controller_requests += 1; - }, - else => |t| validateResource(t), - } - } - if (controller_requests > 1) @compileError("A system cannot request controller more than once"); - } -} - -pub fn validateSystemSet(comptime system_set: anytype) void { - comptime { - @setEvalBranchQuota(1000); - switch (@typeInfo(@TypeOf(system_set))) { - .@"fn" => validateSystem(system_set), - .@"struct" => |struct_info| { - for (struct_info.fields) |field_info| { - switch (@typeInfo(field_info.type)) { - .@"fn", .@"struct" => validateSystemSet(@field(system_set, field_info.name)), - else => { - if (field_info.type == SystemSetOption) continue; - @compileError("Invalid field \"" ++ - field_info.name ++ - "\" of type `" ++ - @typeName(field_info.type) ++ - "` in system set"); - }, - } - } - }, - else => { - @compileError("System set must be either a function or a tuple (got `" ++ @typeName(@TypeOf(system_set)) ++ "`)"); - }, - } - } -} - -pub fn generateRunner(comptime system: anytype) fn ([]const *anyopaque) void { - const RunnerImpl = struct { - fn runner(resources: []const *anyopaque) void { - var args: std.meta.ArgsTuple(@TypeOf(system)) = undefined; - inline for (0..@typeInfo(@TypeOf(system)).@"fn".params.len) |index| { - args[index] = @alignCast(@ptrCast(resources[index])); - } - 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; -} - -pub fn generateRequests(comptime function: anytype) []const SystemRequest { - return comptime blk: { - 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) }, - } - } - const requests_const = requests; - break :blk &requests_const; - }; -} - -pub fn countSystems(comptime tuple: anytype) usize { - return comptime blk: { - var total: usize = 0; - switch (@typeInfo(@TypeOf(tuple))) { - .@"fn" => total += 1, - .@"struct" => |struct_info| { - for (struct_info.fields) |field| { - switch (@typeInfo(field.type)) { - .@"fn", .@"struct" => total += countSystems(@field(tuple, field.name)), - else => {}, - } - } - }, - else => {}, - } - break :blk total; - }; -} diff --git a/src/graphics.zig b/src/graphics.zig index ff33928..6066c13 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -1,7 +1,7 @@ const std = @import("std"); const sdl = @import("sdl"); +const err = @import("error.zig"); const presets = @import("graphics/presets.zig"); -const GameError = @import("game.zig").GameError; pub const Transform = @import("graphics/transform.zig"); pub const Camera = @import("graphics/camera.zig"); @@ -16,74 +16,67 @@ pub const Texture = struct { sampler: *sdl.GPUSampler, }; -window: *sdl.Window, -renderer: *sdl.Renderer, -device: *sdl.GPUDevice, +var window: *sdl.Window = undefined; +var renderer: *sdl.Renderer = undefined; +var device: *sdl.GPUDevice = undefined; /// Only available while drawing -command_buffer: ?*sdl.GPUCommandBuffer, -render_pass: ?*sdl.GPURenderPass, +var command_buffer: ?*sdl.GPUCommandBuffer = null; +var render_pass: ?*sdl.GPURenderPass = null; -shader_vert: *sdl.GPUShader, -shader_frag: *sdl.GPUShader, +var shader_vert: *sdl.GPUShader = undefined; +var shader_frag: *sdl.GPUShader = undefined; -vertex_buffer: *sdl.GPUBuffer, -vertex_buffer_capacity: usize, -vertex_buffer_used: usize, +var vertex_buffer: *sdl.GPUBuffer = undefined; +var vertex_buffer_capacity: usize = undefined; +var vertex_buffer_used: usize = undefined; -transfer_buffer: *sdl.GPUTransferBuffer, -transfer_buffer_capacity: usize, +var transfer_buffer: *sdl.GPUTransferBuffer = undefined; +var transfer_buffer_capacity: usize = undefined; -depth_texture: *sdl.GPUTexture, -msaa_resolve: *sdl.GPUTexture, -pipeline: *sdl.GPUGraphicsPipeline, +var depth_texture: *sdl.GPUTexture = undefined; +var msaa_resolve: *sdl.GPUTexture = undefined; +var pipeline: *sdl.GPUGraphicsPipeline = undefined; -window_size: [2]u32, +var window_size: [2]u32 = undefined; -camera: Camera, +pub var camera: Camera = undefined; -to_resize: ?[2]u32 = null, +var to_resize: ?[2]u32 = null; const VERTEX_BUFFER_DEFAULT_CAPACITY = 1024; const VERTEX_BUFFER_GROWTH_MULTIPLIER = 2; const TRANSFER_BUFFER_DEFAULT_CAPACITY = 1024; const BYTES_PER_VERTEX = 5 * 4; -const Self = @This(); -pub fn create() GameError!Self { +const Graphics = @This(); +pub fn create() void { // Init - if (!sdl.Init(sdl.INIT_VIDEO | sdl.INIT_EVENTS)) return GameError.SdlError; + if (!sdl.Init(sdl.INIT_VIDEO | sdl.INIT_EVENTS)) err.sdl(); // Window and Renderer - var renderer: ?*sdl.Renderer = null; - var window: ?*sdl.Window = null; - if (!sdl.CreateWindowAndRenderer( "", 1600, 900, sdl.WINDOW_VULKAN | sdl.WINDOW_RESIZABLE, - &window, - &renderer, - )) return GameError.SdlError; - errdefer sdl.DestroyRenderer(renderer); - errdefer sdl.DestroyWindow(window); + @ptrCast(&Graphics.window), + @ptrCast(&Graphics.renderer), + )) err.sdl(); + Graphics.window_size = .{ 1600, 900 }; - if (!sdl.SetRenderVSync(renderer, sdl.RENDERER_VSYNC_ADAPTIVE)) return GameError.SdlError; + if (!sdl.SetRenderVSync(renderer, sdl.RENDERER_VSYNC_ADAPTIVE)) err.sdl(); // Device - const device = sdl.CreateGPUDevice( + Graphics.device = sdl.CreateGPUDevice( sdl.GPU_SHADERFORMAT_SPIRV, @import("builtin").mode == .Debug, null, - ) orelse return GameError.SdlError; - errdefer sdl.DestroyGPUDevice(device); + ) orelse err.sdl(); // Claim - if (!sdl.ClaimWindowForGPUDevice(device, window)) return GameError.SdlError; - errdefer sdl.ReleaseWindowFromGPUDevice(device, window); + if (!sdl.ClaimWindowForGPUDevice(Graphics.device, Graphics.window)) err.sdl(); - const shader_vert = try loadShader( - device, + Graphics.shader_vert = loadShader( "data/shaders/basic.vert", .{ .entrypoint = "main", @@ -92,10 +85,8 @@ pub fn create() GameError!Self { .num_uniform_buffers = 2, }, ); - errdefer sdl.ReleaseGPUShader(device, shader_vert); - const shader_frag = try loadShader( - device, + Graphics.shader_frag = loadShader( "data/shaders/basic.frag", .{ .entrypoint = "main", @@ -104,37 +95,34 @@ pub fn create() GameError!Self { .num_samplers = 1, }, ); - errdefer sdl.ReleaseGPUShader(device, shader_frag); - const vertex_buffer = sdl.CreateGPUBuffer(device, &.{ + Graphics.vertex_buffer = sdl.CreateGPUBuffer(Graphics.device, &.{ .usage = sdl.GPU_BUFFERUSAGE_VERTEX, .size = VERTEX_BUFFER_DEFAULT_CAPACITY, - }) orelse return GameError.SdlError; - errdefer sdl.ReleaseGPUBuffer(device, vertex_buffer); + }) orelse err.sdl(); + Graphics.vertex_buffer_capacity = VERTEX_BUFFER_DEFAULT_CAPACITY; + Graphics.vertex_buffer_used = 0; - const transfer_buffer = sdl.CreateGPUTransferBuffer(device, &.{ + Graphics.transfer_buffer = sdl.CreateGPUTransferBuffer(Graphics.device, &.{ .size = TRANSFER_BUFFER_DEFAULT_CAPACITY, .usage = sdl.GPU_TRANSFERBUFFERUSAGE_UPLOAD | sdl.GPU_TRANSFERBUFFERUSAGE_DOWNLOAD, - }) orelse return GameError.SdlError; - errdefer sdl.ReleaseGPUTransferBuffer(device, transfer_buffer); + }) orelse err.sdl(); + Graphics.transfer_buffer_capacity = TRANSFER_BUFFER_DEFAULT_CAPACITY; - const target_format = sdl.GetGPUSwapchainTextureFormat(device, window); - if (target_format == sdl.GPU_TEXTUREFORMAT_INVALID) return GameError.SdlError; + const target_format = sdl.GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window); + if (target_format == sdl.GPU_TEXTUREFORMAT_INVALID) err.sdl(); // TODO: Clean var window_width: c_int = 1; var window_height: c_int = 1; - if (!sdl.GetWindowSizeInPixels(window, &window_width, &window_height)) return GameError.SdlError; + if (!sdl.GetWindowSizeInPixels(Graphics.window, &window_width, &window_height)) err.sdl(); - const depth_texture = try createDepthTexture(device, @intCast(window_width), @intCast(window_height)); - errdefer sdl.ReleaseGPUTexture(device, depth_texture); + Graphics.depth_texture = createDepthTexture(@intCast(window_width), @intCast(window_height)); + Graphics.msaa_resolve = createTexture(@intCast(window_width), @intCast(window_height), target_format); - const msaa_resolve = try createTexture(device, @intCast(window_width), @intCast(window_height), target_format); - errdefer sdl.ReleaseGPUTexture(device, msaa_resolve); - - const pipeline = sdl.CreateGPUGraphicsPipeline(device, &.{ - .vertex_shader = shader_vert, - .fragment_shader = shader_frag, + Graphics.pipeline = sdl.CreateGPUGraphicsPipeline(Graphics.device, &.{ + .vertex_shader = Graphics.shader_vert, + .fragment_shader = Graphics.shader_frag, .vertex_input_state = .{ .vertex_buffer_descriptions = &.{ .slot = 0, @@ -173,68 +161,42 @@ pub fn create() GameError!Self { .num_color_targets = 1, .has_depth_stencil_target = true, }, - }) orelse return GameError.SdlError; - errdefer sdl.ReleaseGPUGraphicsPipeline(pipeline); + }) orelse err.sdl(); - return .{ - .window = window.?, - .renderer = renderer.?, - .device = device, - - .command_buffer = null, - .render_pass = null, - - .shader_vert = shader_vert, - .shader_frag = shader_frag, - - .vertex_buffer = vertex_buffer, - .vertex_buffer_capacity = VERTEX_BUFFER_DEFAULT_CAPACITY, - .vertex_buffer_used = 0, - - .transfer_buffer = transfer_buffer, - .transfer_buffer_capacity = TRANSFER_BUFFER_DEFAULT_CAPACITY, - - .depth_texture = depth_texture, - .msaa_resolve = msaa_resolve, - .pipeline = pipeline, - - .window_size = .{ 1600, 900 }, - - .camera = Camera{ - .transform = .{}, - .near = 1.0, - .far = 1024.0, - .lens = 1.5, - .aspect = 16.0 / 9.0, - }, + Graphics.camera = Camera{ + .transform = .{}, + .near = 1.0, + .far = 1024.0, + .lens = 1.5, + .aspect = 16.0 / 9.0, }; } -pub fn destroy(self: *Self) void { - sdl.ReleaseWindowFromGPUDevice(self.device, self.window); - sdl.DestroyRenderer(self.renderer); - sdl.DestroyWindow(self.window); +pub fn destroy() void { + sdl.ReleaseWindowFromGPUDevice(Graphics.device, Graphics.window); + sdl.DestroyRenderer(Graphics.renderer); + sdl.DestroyWindow(Graphics.window); - sdl.ReleaseGPUGraphicsPipeline(self.device, self.pipeline); - sdl.ReleaseGPUTexture(self.device, self.msaa_resolve); - sdl.ReleaseGPUTexture(self.device, self.depth_texture); - sdl.ReleaseGPUBuffer(self.device, self.vertex_buffer); - sdl.ReleaseGPUTransferBuffer(self.device, self.transfer_buffer); + sdl.ReleaseGPUGraphicsPipeline(Graphics.device, Graphics.pipeline); + sdl.ReleaseGPUTexture(Graphics.device, Graphics.msaa_resolve); + sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture); + sdl.ReleaseGPUBuffer(Graphics.device, Graphics.vertex_buffer); + sdl.ReleaseGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer); - sdl.ReleaseGPUShader(self.device, self.shader_vert); - sdl.ReleaseGPUShader(self.device, self.shader_frag); + sdl.ReleaseGPUShader(Graphics.device, Graphics.shader_vert); + sdl.ReleaseGPUShader(Graphics.device, Graphics.shader_frag); - if (self.command_buffer != null) { - _ = sdl.CancelGPUCommandBuffer(self.command_buffer); - self.command_buffer = null; + if (Graphics.command_buffer != null) { + _ = sdl.CancelGPUCommandBuffer(Graphics.command_buffer); + Graphics.command_buffer = null; } - sdl.DestroyGPUDevice(self.device); + sdl.DestroyGPUDevice(Graphics.device); } -pub fn loadTexture(self: *Self, width: u32, height: u32, texture_bytes: []const u8) GameError!Texture { - const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(self.device, self.window); +pub fn loadTexture(width: u32, height: u32, texture_bytes: []const u8) Texture { + const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window); - const texture = sdl.CreateGPUTexture(self.device, &sdl.GPUTextureCreateInfo{ + const texture = sdl.CreateGPUTexture(Graphics.device, &sdl.GPUTextureCreateInfo{ .format = target_format, .layer_count_or_depth = 1, .width = width, @@ -242,25 +204,22 @@ pub fn loadTexture(self: *Self, width: u32, height: u32, texture_bytes: []const .num_levels = 1, .sample_count = sdl.GPU_SAMPLECOUNT_1, .usage = sdl.GPU_TEXTUREUSAGE_SAMPLER, - }) orelse return GameError.SdlError; - errdefer sdl.ReleaseGPUTexture(self.device, texture); + }) orelse err.sdl(); - const command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError; + const temp_command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl(); { - errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer); - - const copy_pass = sdl.BeginGPUCopyPass(command_buffer) orelse return GameError.SdlError; + const copy_pass = sdl.BeginGPUCopyPass(temp_command_buffer) orelse err.sdl(); defer sdl.EndGPUCopyPass(copy_pass); - const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(self.device, self.transfer_buffer, false) orelse return GameError.SdlError); + const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer, false) orelse err.sdl()); @memcpy(map, texture_bytes); - sdl.UnmapGPUTransferBuffer(self.device, self.transfer_buffer); + sdl.UnmapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer); sdl.UploadToGPUTexture(copy_pass, &sdl.GPUTextureTransferInfo{ .offset = 0, .pixels_per_row = width, .rows_per_layer = height, - .transfer_buffer = self.transfer_buffer, + .transfer_buffer = Graphics.transfer_buffer, }, &sdl.GPUTextureRegion{ .texture = texture, .mip_level = 0, @@ -273,15 +232,15 @@ pub fn loadTexture(self: *Self, width: u32, height: u32, texture_bytes: []const .d = 1, }, false); } - if (!sdl.SubmitGPUCommandBuffer(command_buffer)) return GameError.SdlError; + if (!sdl.SubmitGPUCommandBuffer(temp_command_buffer)) err.sdl(); - const sampler = sdl.CreateGPUSampler(self.device, &sdl.GPUSamplerCreateInfo{ + const sampler = sdl.CreateGPUSampler(Graphics.device, &sdl.GPUSamplerCreateInfo{ .address_mode_u = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, .address_mode_v = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, .address_mode_w = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, .mag_filter = sdl.GPU_FILTER_NEAREST, .min_filter = sdl.GPU_FILTER_LINEAR, - }) orelse return GameError.SdlError; + }) orelse err.sdl(); return Texture{ .texture = texture, @@ -289,44 +248,47 @@ pub fn loadTexture(self: *Self, width: u32, height: u32, texture_bytes: []const }; } -pub fn loadMesh(self: *Self, mesh_bytes: []const u8) GameError!Mesh { - std.debug.assert(mesh_bytes.len < self.transfer_buffer_capacity); +pub fn unloadTexture(texture: Texture) void { + sdl.ReleaseGPUSampler(Graphics.device, texture.sampler); + sdl.ReleaseGPUTexture(Graphics.device, texture.texture); +} + +pub fn loadMesh(mesh_bytes: []const u8) Mesh { + std.debug.assert(mesh_bytes.len < Graphics.transfer_buffer_capacity); var size_mult: usize = 1; - while (self.vertex_buffer_used + mesh_bytes.len > self.vertex_buffer_capacity) { + while (Graphics.vertex_buffer_used + mesh_bytes.len > Graphics.vertex_buffer_capacity * size_mult) { size_mult *= VERTEX_BUFFER_GROWTH_MULTIPLIER; } if (size_mult > 1) { - try self.growVertexBuffer(self.vertex_buffer_capacity * size_mult); + Graphics.growVertexBuffer(Graphics.vertex_buffer_capacity * size_mult); } - const map = sdl.MapGPUTransferBuffer(self.device, self.transfer_buffer, false) orelse return GameError.SdlError; + const map = sdl.MapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer, false) orelse err.sdl(); @memcpy(@as([*]u8, @ptrCast(map)), mesh_bytes); - sdl.UnmapGPUTransferBuffer(self.device, self.transfer_buffer); + sdl.UnmapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer); - const command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError; + const temp_command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl(); const fence = blk: { - errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer); - - const copy_pass = sdl.BeginGPUCopyPass(command_buffer) orelse return GameError.SdlError; + const copy_pass = sdl.BeginGPUCopyPass(temp_command_buffer) orelse err.sdl(); sdl.UploadToGPUBuffer(copy_pass, &.{ - .transfer_buffer = self.transfer_buffer, + .transfer_buffer = Graphics.transfer_buffer, .offset = 0, }, &.{ - .buffer = self.vertex_buffer, - .offset = @intCast(self.vertex_buffer_used), + .buffer = Graphics.vertex_buffer, + .offset = @intCast(Graphics.vertex_buffer_used), .size = @intCast(mesh_bytes.len), }, false); sdl.EndGPUCopyPass(copy_pass); - break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(command_buffer) orelse return GameError.SdlError; + break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(temp_command_buffer) orelse err.sdl(); }; - defer sdl.ReleaseGPUFence(self.device, fence); + defer sdl.ReleaseGPUFence(Graphics.device, fence); - if (!sdl.WaitForGPUFences(self.device, true, &fence, 1)) return GameError.SdlError; + if (!sdl.WaitForGPUFences(Graphics.device, true, &fence, 1)) err.sdl(); - const vertex_start = self.vertex_buffer_used; - self.vertex_buffer_used += mesh_bytes.len; + const vertex_start = Graphics.vertex_buffer_used; + Graphics.vertex_buffer_used += mesh_bytes.len; return Mesh{ .vertex_start = vertex_start / BYTES_PER_VERTEX, @@ -334,38 +296,34 @@ pub fn loadMesh(self: *Self, mesh_bytes: []const u8) GameError!Mesh { }; } -pub fn unloadMesh(self: *Self, mesh: Mesh) void { +pub fn unloadMesh(mesh: Mesh) void { // TODO: free some memory - _ = self; _ = &mesh; } -fn growVertexBuffer(self: *Self, new_size: usize) GameError!void { - const new_buffer = sdl.CreateGPUBuffer(self.device, &.{ +fn growVertexBuffer(new_size: usize) void { + const new_buffer = sdl.CreateGPUBuffer(Graphics.device, &.{ .size = @intCast(new_size), .usage = sdl.GPU_BUFFERUSAGE_VERTEX, - }) orelse return GameError.SdlError; - errdefer sdl.ReleaseGPUBuffer(self.device, new_buffer); + }) orelse err.sdl(); - const command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError; + const temp_command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl(); const fence = blk: { - errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer); - - const copy_pass = sdl.BeginGPUCopyPass(command_buffer); + const copy_pass = sdl.BeginGPUCopyPass(temp_command_buffer); var copied: usize = 0; - while (copied < self.vertex_buffer_used) { - const to_transer = @min(self.vertex_buffer_used - copied, self.transfer_buffer_capacity); + while (copied < Graphics.vertex_buffer_used) { + const to_transer = @min(Graphics.vertex_buffer_used - copied, Graphics.transfer_buffer_capacity); sdl.DownloadFromGPUBuffer(copy_pass, &.{ - .buffer = self.vertex_buffer, + .buffer = Graphics.vertex_buffer, .offset = @intCast(copied), .size = @intCast(to_transer), }, &.{ - .transfer_buffer = self.transfer_buffer, + .transfer_buffer = Graphics.transfer_buffer, .offset = 0, }); sdl.UploadToGPUBuffer(copy_pass, &.{ - .transfer_buffer = self.transfer_buffer, + .transfer_buffer = Graphics.transfer_buffer, .offset = 0, }, &.{ .buffer = new_buffer, @@ -376,36 +334,36 @@ fn growVertexBuffer(self: *Self, new_size: usize) GameError!void { } sdl.EndGPUCopyPass(copy_pass); - break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(command_buffer) orelse return GameError.SdlError; + break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(temp_command_buffer) orelse err.sdl(); }; - defer sdl.ReleaseGPUFence(self.device, fence); + defer sdl.ReleaseGPUFence(Graphics.device, fence); - if (!sdl.WaitForGPUFences(self.device, true, &fence, 1)) return GameError.SdlError; + if (!sdl.WaitForGPUFences(Graphics.device, true, &fence, 1)) err.sdl(); - sdl.ReleaseGPUBuffer(self.device, self.vertex_buffer); - self.vertex_buffer = new_buffer; - self.vertex_buffer_capacity = new_size; + sdl.ReleaseGPUBuffer(Graphics.device, Graphics.vertex_buffer); + Graphics.vertex_buffer = new_buffer; + Graphics.vertex_buffer_capacity = new_size; } /// If window is minimized returns `false`, `render_pass` remains null /// Otherwise `command_buffer` and `render_pass` are both set -pub fn beginDraw(self: *Self) GameError!bool { - self.command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError; - if (self.to_resize) |new_size| { - try self.resetTextures(new_size[0], new_size[1]); - self.camera.aspect = @as(f32, @floatFromInt(new_size[0])) / @as(f32, @floatFromInt(new_size[1])); - self.window_size = new_size; - self.to_resize = null; +pub fn beginDraw() bool { + Graphics.command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl(); + if (Graphics.to_resize) |new_size| { + Graphics.resetTextures(new_size[0], new_size[1]); + Graphics.camera.aspect = @as(f32, @floatFromInt(new_size[0])) / @as(f32, @floatFromInt(new_size[1])); + Graphics.window_size = new_size; + Graphics.to_resize = null; } var render_target: ?*sdl.GPUTexture = null; var width: u32 = 0; var height: u32 = 0; - if (!sdl.WaitAndAcquireGPUSwapchainTexture(self.command_buffer, self.window, &render_target, &width, &height)) return GameError.SdlError; + if (!sdl.WaitAndAcquireGPUSwapchainTexture(Graphics.command_buffer, Graphics.window, &render_target, &width, &height)) err.sdl(); // Hidden if (render_target == null) return false; - self.render_pass = sdl.BeginGPURenderPass(self.command_buffer, &.{ + Graphics.render_pass = sdl.BeginGPURenderPass(Graphics.command_buffer, &.{ .clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 }, .cycle = false, .load_op = sdl.GPU_LOADOP_CLEAR, @@ -413,57 +371,57 @@ pub fn beginDraw(self: *Self) GameError!bool { // .store_op = sdl.GPU_STOREOP_STORE, .resolve_texture = render_target, .mip_level = 0, - .texture = self.msaa_resolve, + .texture = Graphics.msaa_resolve, }, 1, &.{ .clear_depth = 1.0, .load_op = sdl.GPU_LOADOP_CLEAR, .store_op = sdl.GPU_STOREOP_DONT_CARE, .stencil_load_op = sdl.GPU_STOREOP_DONT_CARE, .stencil_store_op = sdl.GPU_STOREOP_DONT_CARE, - .texture = self.depth_texture, - }) orelse return GameError.SdlError; + .texture = Graphics.depth_texture, + }) orelse err.sdl(); - sdl.BindGPUGraphicsPipeline(self.render_pass, self.pipeline); - sdl.BindGPUVertexBuffers(self.render_pass, 0, &.{ .offset = 0, .buffer = self.vertex_buffer }, 1); - sdl.PushGPUVertexUniformData(self.command_buffer, 0, &self.camera.matrix(), 16 * 4); + sdl.BindGPUGraphicsPipeline(Graphics.render_pass, Graphics.pipeline); + sdl.BindGPUVertexBuffers(Graphics.render_pass, 0, &.{ .offset = 0, .buffer = Graphics.vertex_buffer }, 1); + sdl.PushGPUVertexUniformData(Graphics.command_buffer, 0, &Graphics.camera.matrix(), 16 * 4); return true; } -pub fn drawMesh(self: *Self, mesh: Mesh, texture: Texture, transform: Transform) GameError!void { - if (self.render_pass == null) return; +pub fn drawMesh(mesh: Mesh, texture: Texture, transform: Transform) void { + if (Graphics.render_pass == null) return; - sdl.PushGPUVertexUniformData(self.command_buffer, 1, &transform.matrix(), 16 * 4); - sdl.BindGPUFragmentSamplers(self.render_pass, 0, &sdl.GPUTextureSamplerBinding{ + sdl.PushGPUVertexUniformData(Graphics.command_buffer, 1, &transform.matrix(), 16 * 4); + sdl.BindGPUFragmentSamplers(Graphics.render_pass, 0, &sdl.GPUTextureSamplerBinding{ .texture = texture.texture, .sampler = texture.sampler, }, 1); - sdl.DrawGPUPrimitives(self.render_pass, @intCast(mesh.vertex_count), 1, @intCast(mesh.vertex_start), 0); + sdl.DrawGPUPrimitives(Graphics.render_pass, @intCast(mesh.vertex_count), 1, @intCast(mesh.vertex_start), 0); } -pub fn endDraw(self: *Self) GameError!void { - defer self.command_buffer = null; - defer self.render_pass = null; - if (self.render_pass) |render_pass| { - sdl.EndGPURenderPass(render_pass); +pub fn endDraw() void { + defer Graphics.command_buffer = null; + defer Graphics.render_pass = null; + if (Graphics.render_pass) |pass| { + sdl.EndGPURenderPass(pass); } - if (!sdl.SubmitGPUCommandBuffer(self.command_buffer)) return GameError.SdlError; + if (!sdl.SubmitGPUCommandBuffer(Graphics.command_buffer)) err.sdl(); } -fn loadShader(device: *sdl.GPUDevice, path: []const u8, info: sdl.GPUShaderCreateInfo) GameError!*sdl.GPUShader { - const file = std.fs.cwd().openFile(path, .{}) catch return GameError.OSError; +fn loadShader(path: []const u8, info: sdl.GPUShaderCreateInfo) *sdl.GPUShader { + const file = std.fs.cwd().openFile(path, .{}) catch |e| err.file(e, path); defer file.close(); - const code = file.readToEndAllocOptions(std.heap.c_allocator, 1024 * 1024 * 1024, null, .@"1", 0) catch return GameError.OSError; + const code = file.readToEndAllocOptions(std.heap.c_allocator, std.math.maxInt(usize), null, .@"1", 0) catch |e| err.file(e, path); defer std.heap.c_allocator.free(code); var updated_info = info; updated_info.code = code; updated_info.code_size = code.len; - return sdl.CreateGPUShader(device, &updated_info) orelse return GameError.SdlError; + return sdl.CreateGPUShader(device, &updated_info) orelse err.sdl(); } -fn createDepthTexture(device: *sdl.GPUDevice, width: u32, height: u32) GameError!*sdl.GPUTexture { +fn createDepthTexture(width: u32, height: u32) *sdl.GPUTexture { return sdl.CreateGPUTexture(device, &.{ .format = sdl.GPU_TEXTUREFORMAT_D16_UNORM, .layer_count_or_depth = 1, @@ -472,10 +430,10 @@ fn createDepthTexture(device: *sdl.GPUDevice, width: u32, height: u32) GameError .num_levels = 1, .sample_count = sdl.GPU_SAMPLECOUNT_4, .usage = sdl.GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, - }) orelse return GameError.SdlError; + }) orelse err.sdl(); } -fn createTexture(device: *sdl.GPUDevice, width: u32, height: u32, format: c_uint) GameError!*sdl.GPUTexture { +fn createTexture(width: u32, height: u32, format: c_uint) *sdl.GPUTexture { return sdl.CreateGPUTexture(device, &.{ .format = format, .layer_count_or_depth = 1, @@ -484,19 +442,23 @@ fn createTexture(device: *sdl.GPUDevice, width: u32, height: u32, format: c_uint .num_levels = 1, .sample_count = sdl.GPU_SAMPLECOUNT_4, .usage = sdl.GPU_TEXTUREUSAGE_COLOR_TARGET, - }) orelse return GameError.SdlError; + }) orelse err.sdl(); } -fn resetTextures(self: *Self, width: u32, height: u32) GameError!void { - sdl.ReleaseGPUTexture(self.device, self.depth_texture); - self.depth_texture = try createDepthTexture(self.device, width, height); +fn resetTextures(width: u32, height: u32) void { + sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture); + Graphics.depth_texture = createDepthTexture(width, height); - const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(self.device, self.window); + const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window); - sdl.ReleaseGPUTexture(self.device, self.msaa_resolve); - self.msaa_resolve = try createTexture(self.device, width, height, target_format); + sdl.ReleaseGPUTexture(Graphics.device, Graphics.msaa_resolve); + Graphics.msaa_resolve = createTexture(width, height, target_format); } -pub fn resize(self: *Self, width: u32, height: u32) void { - self.to_resize = .{ width, height }; +pub fn resize(width: u32, height: u32) void { + Graphics.to_resize = .{ width, height }; +} + +pub fn windowId() sdl.WindowID { + return sdl.GetWindowID(Graphics.window); } diff --git a/src/main.zig b/src/main.zig index c3211d5..7b43c2c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3,7 +3,7 @@ const builtin = @import("builtin"); const sdl = @import("sdl"); const Game = @import("game.zig"); -pub fn runGame() !void { +pub fn main() void { var allocator = switch (builtin.mode) { .ReleaseSafe, .Debug => std.heap.DebugAllocator(.{}).init, .ReleaseFast => std.heap.smp_allocator, @@ -14,25 +14,13 @@ pub fn runGame() !void { else => {}, }; - var game = try Game.init( + Game.init( switch (builtin.mode) { .ReleaseSafe, .Debug => allocator.allocator(), .ReleaseFast => allocator, .ReleaseSmall => allocator, }, ); - defer game.deinit(); - try game.run(); -} - -pub fn main() !void { - runGame() catch |err| { - switch (err) { - error.SdlError => { - std.debug.print("SDL Error:\n---\n{s}\n---\n", .{sdl.SDL_GetError()}); - }, - else => unreachable, - } - return err; - }; + defer Game.deinit(); + Game.run(); } diff --git a/src/mouse.zig b/src/mouse.zig index 83cf9c9..ce870a2 100644 --- a/src/mouse.zig +++ b/src/mouse.zig @@ -1,9 +1,9 @@ const sdl = @import("sdl"); const key_store = @import("data/keystore.zig"); -buttons: key_store.KeyStore(@TypeOf(sdl.BUTTON_LEFT), 4, 0), +buttons: key_store.KeyStore(@TypeOf(sdl.BUTTON_LEFT), 4, 0) = .{}, -x: f32, -y: f32, -dx: f32, -dy: f32, +x: f32 = 0, +y: f32 = 0, +dx: f32 = 0, +dy: f32 = 0, diff --git a/src/time.zig b/src/time.zig index baa4a7d..ff505d5 100644 --- a/src/time.zig +++ b/src/time.zig @@ -41,3 +41,11 @@ pub fn unitsFromDuration(duration: TimeType) f32 { pub fn durationFromUnits(units: f32) TimeType { return @intFromFloat(@as(f32, @floatFromInt(TIME_UNIT)) * units); } + +pub fn earliest(a: Time, b: Time) Time { + return .{ .clock = @min(a.clock, b.clock) }; +} + +pub fn plus(time: Time, ticks: TimeType) Time { + return .{ .clock = time.clock + ticks }; +} diff --git a/src/world.zig b/src/world.zig new file mode 100644 index 0000000..1570fd2 --- /dev/null +++ b/src/world.zig @@ -0,0 +1,144 @@ +const Graphics = @import("graphics.zig"); +const Entity = @import("entity.zig"); +const Time = @import("time.zig"); + +pub var time: Time = undefined; +var next_stop: Time = undefined; +var entities: [16]?Entity = undefined; + +pub var plane_mesh: Graphics.Mesh = undefined; +pub var cube_mesh: Graphics.Mesh = undefined; +pub var texture: Graphics.Texture = undefined; + +const World = @This(); +pub fn initDebug() void { + entities = .{null} ** 16; + entities[0] = .{ + .position = .{ 0, 0 }, + .player = true, + }; + entities[1] = .{ + .position = .{ 2, 0 }, + .enemy = true, + .controller = .{ + .move_units = 0.25, + }, + }; + entities[2] = .{ + .position = .{ 3, 0 }, + .enemy = true, + .controller = .{ + .move_units = 0.25, + }, + }; + time = Time.ZERO; + World.plane_mesh = Graphics.loadMesh(@ptrCast(&PLANE_MESH_DATA)); + World.cube_mesh = Graphics.loadMesh(@ptrCast(&CUBE_MESH_DATA)); + World.texture = Graphics.loadTexture(2, 2, @ptrCast(&TEXTURE_DATA)); +} + +pub fn deinit() void { + Graphics.unloadMesh(World.plane_mesh); + Graphics.unloadMesh(World.cube_mesh); + Graphics.unloadTexture(World.texture); +} + +pub fn updateReal(delta: f32) void { + const update_until = World.time.plus(Time.durationFromUnits(delta)); + while (!World.time.past(update_until)) { + const current = Time.earliest(World.next_stop, update_until); + defer World.time = current; + + for (&World.entities) |*entity| { + if (entity.*) |*e| e.update(); + } + } +} + +pub fn draw(delta: f32) void { + Graphics.drawMesh(World.plane_mesh, World.texture, .{ .scale = @splat(5) }); + for (&World.entities) |*entity| { + if (entity.*) |*e| e.draw(delta); + } +} + +pub fn requestUpdate(at: Time) void { + World.next_stop = Time.earliest(at, World.next_stop); +} + +pub fn entityAt(position: @Vector(2, i32)) ?*Entity { + for (&World.entities) |*maybe_entity| { + if (maybe_entity.*) |*entity| { + if (@reduce(.And, entity.position == position)) + return entity; + } + } + return null; +} + +pub fn isFree(position: @Vector(2, i32)) bool { + return World.entityAt(position) == null; +} + +pub fn getPlayer() ?*Entity { + for (&World.entities) |*maybe_entity| { + if (maybe_entity.*) |*entity| { + if (entity.player) + return entity; + } + } + return null; +} + +const CUBE_MESH_DATA = [_]f32{ + -0.5, 0.5, -0.5, 0.0, 0.0, + 0.5, 0.5, -0.5, 0.0, 0.0, + -0.5, -0.5, -0.5, 0.0, 0.0, + 0.5, -0.5, -0.5, 0.0, 0.0, + -0.5, -0.5, -0.5, 0.0, 0.0, + 0.5, 0.5, -0.5, 0.0, 0.0, + 0.5, 0.5, -0.5, 0.0, 0.0, + 0.5, 0.5, 0.5, 0.0, 0.0, + 0.5, -0.5, -0.5, 0.0, 0.0, + 0.5, -0.5, 0.5, 0.0, 0.0, + 0.5, -0.5, -0.5, 0.0, 0.0, + 0.5, 0.5, 0.5, 0.0, 0.0, + 0.5, 0.5, 0.5, 0.0, 0.0, + -0.5, 0.5, 0.5, 0.0, 0.0, + 0.5, -0.5, 0.5, 0.0, 0.0, + -0.5, -0.5, 0.5, 0.0, 0.0, + 0.5, -0.5, 0.5, 0.0, 0.0, + -0.5, 0.5, 0.5, 0.0, 0.0, + -0.5, 0.5, 0.5, 0.0, 0.0, + -0.5, 0.5, -0.5, 0.0, 0.0, + -0.5, -0.5, 0.5, 0.0, 0.0, + -0.5, -0.5, -0.5, 0.0, 0.0, + -0.5, -0.5, 0.5, 0.0, 0.0, + -0.5, 0.5, -0.5, 0.0, 0.0, + -0.5, 0.5, 0.5, 0.0, 0.0, + 0.5, 0.5, 0.5, 0.0, 0.0, + -0.5, 0.5, -0.5, 0.0, 0.0, + 0.5, 0.5, -0.5, 0.0, 0.0, + -0.5, 0.5, -0.5, 0.0, 0.0, + 0.5, 0.5, 0.5, 0.0, 0.0, + -0.5, -0.5, -0.5, 0.0, 0.0, + 0.5, -0.5, -0.5, 0.0, 0.0, + -0.5, -0.5, 0.5, 0.0, 0.0, + 0.5, -0.5, 0.5, 0.0, 0.0, + -0.5, -0.5, 0.5, 0.0, 0.0, + 0.5, -0.5, -0.5, 0.0, 0.0, +}; +const PLANE_MESH_DATA = [_]f32{ + -0.5, -0.5, 0, 0.0, 1.0, + 0.5, 0.5, 0, 1.0, 0.0, + -0.5, 0.5, 0, 0.0, 0.0, + 0.5, 0.5, 0, 1.0, 0.0, + -0.5, -0.5, 0, 0.0, 1.0, + 0.5, -0.5, 0, 1.0, 1.0, +}; +const TEXTURE_DATA = [_]u8{ + 255, 64, 64, 255, + 64, 255, 64, 255, + 64, 64, 255, 255, + 64, 64, 64, 255, +};