From 7716ddf26a7832e9417c2f60f85b9af92d7eefd7 Mon Sep 17 00:00:00 2001 From: Nathan Anderson <n8r@tuta.io> Date: Thu, 9 Jan 2025 14:35:52 -0700 Subject: [PATCH] Leaky mess rn... --- src/game_state.zig | 151 ++++++++++++++++++++++++++++++++------- src/main.zig | 94 +++++++++++++++++-------- src/slime_factory.zig | 44 +++++++++--- src/text.zig | 160 ++++++++++++++++++++++++++---------------- src/utils/offset.zig | 4 +- src/utils/timer.zig | 1 + 6 files changed, 327 insertions(+), 127 deletions(-) diff --git a/src/game_state.zig b/src/game_state.zig index 6edc9ca..dd5e42a 100644 --- a/src/game_state.zig +++ b/src/game_state.zig @@ -2,30 +2,64 @@ const sdl = @import("./sdl.zig").c; const std = @import("std"); const sf = @import("./slime_factory.zig"); const atils = @import("./asset_utils.zig"); -const GameText = @import("./text.zig").GameText; +const text = @import("./text.zig"); const RGBAColor = @import("./utils/rgb_color.zig").RGBAColor; +const Timer = @import("./utils/timer.zig").Timer; -pub const GameState = struct { - r: u8 = 0xff, - g: u8 = 0xff, - b: u8 = 0xff, +const SCREEN_FPS: f64 = 30.0; +const SCREEN_TICKS_PER_FRAME: f64 = 1000 / SCREEN_FPS; +const PHYS_UPS: f64 = 310.0; +const PHYS_TICKS_PER_UPDATE: f64 = 1000 / PHYS_UPS; +const PHYS_UPDATES_PER_RENDER: f64 = PHYS_UPS / SCREEN_FPS; +const phys_updates_per_render: u32 = @intFromFloat(PHYS_UPDATES_PER_RENDER); + +pub const GameStateController = struct { SCREEN_WIDTH: u32 = 640, SCREEN_HEIGHT: u32 = 480, - slime_factory: sf.SlimeFactory = undefined, + slime_factory_controller: sf.SlimeFactoryController = undefined, + game_text_factory_controller: text.GameTextFactoryController = undefined, + prev_game_state: *GameState = undefined, + current_game_state: *GameState = undefined, renderer: *sdl.struct_SDL_Renderer = undefined, + rng: std.Random = undefined, + allocator: std.mem.Allocator = undefined, + fps_timer: Timer, + phys_timer: Timer, + frames: u32 = 0, + phys_updates_since_last_render: u16 = 0, - pub fn init(allocator: std.mem.Allocator, renderer: *sdl.struct_SDL_Renderer) !GameState { - var slime_factory = try sf.SlimeFactory.init(allocator, renderer); + pub fn init(allocator: std.mem.Allocator, renderer: *sdl.struct_SDL_Renderer) !GameStateController { + var slime_factory = try sf.SlimeFactoryController.init(allocator, renderer); + const text_factory = try text.GameTextFactoryController.init(allocator); + var xosh = std.rand.DefaultPrng.init(0); try slime_factory.add(.{}); - return .{ .slime_factory = slime_factory, .renderer = renderer }; + var timer = Timer{}; + timer.start(); + var phys_timer = Timer{}; + phys_timer.start(); + + return .{ + .slime_factory_controller = slime_factory, + .game_text_factory_controller = text_factory, + .renderer = renderer, + .rng = xosh.random(), + .allocator = allocator, + .fps_timer = timer, + .phys_timer = phys_timer, + }; } - pub fn deinit(self: *GameState) void { - self.slime_factory.deinit(); + pub fn deinit(self: *GameStateController) void { + self.slime_factory_controller.deinit(); } // Physics main loop that processes variable time steps - pub fn update_tick(self: *GameState) !void { + pub fn update_tick(self: *GameStateController) !*const GameState { + // DEBUG random wait time + // std.debug.print("getting rng\n", .{}); + // const wait_time_ms: u32 = 6; //self.rng.int(u32); + // sdl.SDL_Delay(wait_time_ms % 15); + // DEFINITELY REMOVE BEFORE PROD const bg_texture = try atils.loadTexture(self.renderer, "assets/background.png"); defer sdl.SDL_DestroyTexture(bg_texture); @@ -33,16 +67,16 @@ pub const GameState = struct { _ = sdl.SDL_RenderClear(self.renderer); _ = sdl.SDL_RenderCopy(self.renderer, bg_texture, null, null); - var text = try GameText.loadFromRenderedText("Press S to start / stop", RGBAColor.whiteSmoke().tosdl()); - try text.render( - self, - .{ .x = @intCast(self.SCREEN_WIDTH - text.w), .y = 2 }, - ); - text = try GameText.loadFromRenderedText("Press P to pause / unpause", RGBAColor.whiteSmoke().tosdl()); - try text.render( - self, - .{ .x = @intCast(self.SCREEN_WIDTH - text.w), .y = 24 }, - ); + // var text = try GameText.loadFromRenderedText("Press S to start / stop", RGBAColor.whiteSmoke().tosdl()); + // try text.render( + // self, + // .{ .x = @intCast(self.SCREEN_WIDTH - text.w), .y = 2 }, + // ); + // text = try GameText.loadFromRenderedText("Press P to pause / unpause", RGBAColor.whiteSmoke().tosdl()); + // try text.render( + // self, + // .{ .x = @intCast(self.SCREEN_WIDTH - text.w), .y = 24 }, + // ); // var buf: [16]u8 = undefined; // const timer_ms = timer.getTicks(); @@ -79,11 +113,78 @@ pub const GameState = struct { // _ = sdl.SDL_UpdateWindowSurface(window); // sdl.SDL_Delay(100); - self.slime_factory.render(self); + var game_state = try self.allocator.create(GameState); + game_state.allocator = self.allocator; + game_state.slime_factory = try self.slime_factory_controller.spawnFactory(self.allocator); + game_state.fps_timer = &self.fps_timer; + game_state.phys_ticks = @floatFromInt(self.phys_timer.getTicks()); + game_state.phys_updates_since_render = self.phys_updates_since_last_render; + + // self.allocator.destroy(self.prev_game_state); + self.prev_game_state = self.current_game_state; + self.current_game_state = game_state; + self.phys_updates_since_last_render += 1; + + return game_state; } // Renders the current frame to screen - pub fn render(self: *GameState) void { - _ = sdl.SDL_RenderPresent(self.renderer); + pub fn render(self: *GameStateController, game_state: *const GameState) !void { + try self.game_text_factory_controller.addText(.{ .text = "Test new text factory", .offset = .{ .x = 15, .y = 15 } }); + var buf: [8]u8 = undefined; + const fps_text = try self.getFpsText(&buf); + try self.game_text_factory_controller.addText(.{ .text = fps_text, .offset = .{ .x = 400, .y = 15 } }); + + try self.game_text_factory_controller.render(self.renderer, game_state); + try self.slime_factory_controller.render(self.renderer); + + sdl.SDL_RenderPresent(self.renderer); + self.frames += 1; + self.phys_updates_since_last_render = 0; + self.phys_timer.reset(); + } + + fn getFpsText(self: *GameStateController, buf: []u8) ![:0]const u8 { + const ticks_f: f64 = @floatFromInt(self.fps_timer.getTicks()); + const frames_f: f64 = @floatFromInt(self.frames); + const avg_fps: f64 = @round(frames_f / (ticks_f / 1000.0)); + return try std.fmt.bufPrintZ(buf, "{d} fps", .{avg_fps}); + } +}; + +pub const GameState = struct { + allocator: std.mem.Allocator, + slime_factory: sf.SlimeFactory = undefined, + fps_timer: *Timer, + phys_ticks: f64, + phys_updates_since_render: u32, + // Checks all struct fields in GameState and calls deinit + pub fn deinit(self: *const GameState) void { + const game_state_info = @typeInfo(GameState); + switch (game_state_info) { + .Struct => { + inline for (game_state_info.Struct.fields) |field| { + switch (@typeInfo(field.type)) { + .Struct => { + // const inner_struct_info = @typeInfo(field.type); + + // Check if struct has `deinit` method + if (@hasDecl(field.type, "deinit")) { + const field_ptr = @field(self, field.name); + field_ptr.deinit(); + std.debug.print("Deinit called on {s}\n", .{field.name}); + } + }, + else => {}, + } + } + }, + + else => {}, + } + + // Loop over fields of `GameState` + + self.allocator.destroy(self); } }; diff --git a/src/main.zig b/src/main.zig index 1190d79..37ed154 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,8 +4,13 @@ const gs = @import("./game_state.zig"); const assert = std.debug.assert; const SCREEN_WIDTH = 640; const SCREEN_HEIGHT = 480; -const SCREEN_FPS: f64 = 60.0; +const SCREEN_FPS: f64 = 30.0; const SCREEN_TICKS_PER_FRAME: f64 = 1000 / SCREEN_FPS; +const PHYS_UPS: f64 = 310.0; +const PHYS_TICKS_PER_UPDATE: f64 = 1000 / PHYS_UPS; +const PHYS_UPDATES_PER_RENDER: f64 = PHYS_UPS / SCREEN_FPS; +const phys_updates_per_render: u32 = @intFromFloat(PHYS_UPDATES_PER_RENDER); + const log = sdl.SDL_Log; const GameText = @import("./text.zig").GameText; const RGBAColor = @import("./utils/rgb_color.zig").RGBAColor; @@ -28,36 +33,20 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); - try GameText.initFont(); - defer GameText.deinitFont(); - - var game_state = try gs.GameState.init(allocator, renderer); - defer game_state.deinit(); + var game_state_controller = try gs.GameStateController.init(allocator, renderer); + defer game_state_controller.deinit(); var quit = false; - var fps_timer = Timer{}; - fps_timer.start(); + var frame_time = Timer{}; + frame_time.start(); + var prev_frame_ms: f64 = 16.0; var accumulator: f64 = 0.0; + var prev_game_state: ?*const gs.GameState = null; + var current_game_state: ?*const gs.GameState = undefined; while (!quit) { - frame_time.reset(); - var event: sdl.SDL_Event = undefined; - while (sdl.SDL_PollEvent(&event) != 0) { - switch (event.type) { - sdl.SDL_QUIT => { - quit = true; - }, - sdl.SDL_KEYDOWN => { - switch (event.key.keysym.sym) { - else => {}, - } - log("Got key event: %i\n", event.key.keysym.sym); - }, - else => {}, - } - } // double newTime = time(); // double frameTime = newTime - currentTime; @@ -80,17 +69,60 @@ pub fn main() !void { // State state = currentState * alpha + // previousState * ( 1.0 - alpha ); - // render( state ); - accumulator = accumulator + prev_frame_ms; - while (accumulator >= SCREEN_TICKS_PER_FRAME) { - try game_state.update_tick(); - accumulator = accumulator - SCREEN_TICKS_PER_FRAME; - std.debug.print("Tick update done. Acc: {d}\n", .{accumulator}); + // Wait for time to render screen, keep running physics + accumulator = accumulator + SCREEN_TICKS_PER_FRAME; + const total_phys_ticks: u32 = @intFromFloat(PHYS_TICKS_PER_UPDATE * PHYS_UPDATES_PER_RENDER); + const phys_done_at_ticks: u32 = sdl.SDL_GetTicks() + total_phys_ticks; + var phys_ticks_used: f64 = 0; + std.debug.print("phys ticks/upd: {d}, Acc: {d}\nTotal phys ticks {d}ms\nPhys done at {d}ms\n", .{ PHYS_TICKS_PER_UPDATE, accumulator, total_phys_ticks, phys_done_at_ticks }); + // Run a physics update + while (accumulator >= PHYS_TICKS_PER_UPDATE) { + // Control loop here + var event: sdl.SDL_Event = undefined; + while (sdl.SDL_PollEvent(&event) != 0) { + switch (event.type) { + sdl.SDL_QUIT => { + quit = true; + }, + sdl.SDL_KEYDOWN => { + switch (event.key.keysym.sym) { + else => {}, + } + log("Got key event: %i\n", event.key.keysym.sym); + }, + else => {}, + } + } + prev_game_state = current_game_state; + current_game_state = try game_state_controller.update_tick(); + const phys_ticks_this_update: f64 = current_game_state.?.phys_ticks - phys_ticks_used; + + std.debug.print(" PU in {d}ms ", .{phys_ticks_this_update}); + std.debug.print(" Acc:{d} - ", .{accumulator}); + + // TODO dont turn it into an int, take the extra phys_frames and do a partial render + if (phys_ticks_this_update < PHYS_TICKS_PER_UPDATE) { + const ticks: u32 = @intFromFloat(PHYS_TICKS_PER_UPDATE - phys_ticks_this_update); + sdl.SDL_Delay(ticks); + accumulator -= PHYS_TICKS_PER_UPDATE; + } else { + std.debug.print("\nPHYSICS JANK\nPhysics Update took {d}ms, target: {d}ms\n", .{ phys_ticks_this_update, PHYS_TICKS_PER_UPDATE }); + accumulator -= phys_ticks_this_update; + } + phys_ticks_used = current_game_state.?.phys_ticks; + std.debug.print(" PhyTicks this render: {d} ", .{current_game_state.?.phys_ticks}); } - game_state.render(); + // TODO lerp together prev and current states + // Otherwise will have physics jank + + // Update screen frame + std.debug.print("Took {d} phys frames\n", .{current_game_state.?.phys_updates_since_render}); + try game_state_controller.render(current_game_state.?); prev_frame_ms = @floatFromInt(frame_time.getTicks()); + std.debug.print("Rendered to screen in {d}ms\n***\n***\n", .{prev_frame_ms}); + frame_time.reset(); // const frame_time_ticks = frame_time.getTicks(); // if (frame_time_ticks < SCREEN_TICKS_PER_FRAME) { // sdl.SDL_Delay(SCREEN_TICKS_PER_FRAME - frame_time_ticks); diff --git a/src/slime_factory.zig b/src/slime_factory.zig index 6f2df38..759fc75 100644 --- a/src/slime_factory.zig +++ b/src/slime_factory.zig @@ -18,11 +18,11 @@ pub const Slime = struct { }, }; -pub const SlimeFactory = struct { +pub const SlimeFactoryController = struct { slimes: std.ArrayList(Slime) = undefined, slime_sprite_sheet_texture: *sdl.struct_SDL_Texture = undefined, - pub fn init(allocator: std.mem.Allocator, renderer: *sdl.struct_SDL_Renderer) !SlimeFactory { + pub fn init(allocator: std.mem.Allocator, renderer: *sdl.struct_SDL_Renderer) !SlimeFactoryController { const texture = try atils.loadTexture(renderer, slime_sprite_sheet_texture_path); const val = sdl.SDL_SetTextureBlendMode(texture, sdl.SDL_BLENDMODE_BLEND); if (val != 0) { @@ -35,33 +35,61 @@ pub const SlimeFactory = struct { }; } - pub fn deinit(sf: *SlimeFactory) void { + pub fn deinit(sf: *SlimeFactoryController) void { sdl.SDL_DestroyTexture(sf.slime_sprite_sheet_texture); sf.slimes.deinit(); } - pub fn add(sf: *SlimeFactory, slime: Slime) !void { + pub fn add(sf: *SlimeFactoryController, slime: Slime) !void { try sf.slimes.append(slime); } - pub fn render(sf: *SlimeFactory, game_state: *GameState) void { + pub fn render(sf: *SlimeFactoryController, renderer: *sdl.SDL_Renderer) !void { if (sf.slimes.items.len == 0) return; const slime_color = sf.slimes.items[0].color; - _ = sdl.SDL_SetTextureColorMod( + var res = sdl.SDL_SetTextureColorMod( sf.slime_sprite_sheet_texture, slime_color.r, slime_color.g, slime_color.b, ); - _ = sdl.SDL_SetTextureAlphaMod(sf.slime_sprite_sheet_texture, slime_color.a); + if (res > 0) { + sdl.SDL_Log("Error setting slime texture color: %s\n", sdl.SDL_GetError()); + return error.FailedToRenderSlimes; + } + + res = sdl.SDL_SetTextureAlphaMod(sf.slime_sprite_sheet_texture, slime_color.a); + if (res > 0) { + sdl.SDL_Log("Error setting slime texture alpha: %s\n", sdl.SDL_GetError()); + return error.FailedToRenderSlimes; + } for (sf.slimes.items, 0..) |slime, s_idx| { const sprite_frame_x_dim: u16 = slime.f * 64; const sprite_frame_rect: sdl.struct_SDL_Rect = .{ .x = sprite_frame_x_dim, .y = 0, .w = 64, .h = 64 }; const dest_rect: sdl.struct_SDL_Rect = .{ .x = slime.x, .y = slime.y, .w = 64, .h = 64 }; - _ = sdl.SDL_RenderCopy(game_state.renderer, sf.slime_sprite_sheet_texture, &sprite_frame_rect, &dest_rect); + if (sdl.SDL_RenderCopy(renderer, sf.slime_sprite_sheet_texture, &sprite_frame_rect, &dest_rect) > 0) { + sdl.SDL_Log("Error copying slime texture to renderer: %s\n", sdl.SDL_GetError()); + return error.FailedToRenderSlimes; + } sf.slimes.items[s_idx].f = (sf.slimes.items[s_idx].f + 1) % sf.slimes.items[s_idx].total_frames; } } + + pub fn spawnFactory(sfc: *SlimeFactoryController, allocator: std.mem.Allocator) !SlimeFactory { + const slimes = sfc.slimes.items; + const slimes_copy = try allocator.alloc(Slime, slimes.len); + errdefer allocator.free(slimes_copy); + + std.mem.copyForwards(Slime, slimes_copy, slimes); + + return .{ + .slimes = &slimes_copy, + }; + } +}; + +pub const SlimeFactory = struct { + slimes: *const []Slime, }; diff --git a/src/text.zig b/src/text.zig index 60b8714..106a430 100644 --- a/src/text.zig +++ b/src/text.zig @@ -2,38 +2,109 @@ const sdl = @import("./sdl.zig").c; const std = @import("std"); const GameState = @import("./game_state.zig").GameState; const Offset = @import("./utils/offset.zig").Offset; +const RGBAColor = @import("./utils/rgb_color.zig").RGBAColor; const FONT_SIZE = 24; var font: ?*sdl.TTF_Font = null; -var text_texture: ?*sdl.SDL_Texture = null; var text_init = false; +pub const GameTextFactoryController = struct { + texts: std.ArrayList(GameText), + + pub fn init(allocator: std.mem.Allocator) !GameTextFactoryController { + try initFont(); + return .{ + .texts = std.ArrayList(GameText).init(allocator), + }; + } + + fn initFont() !void { + const loaded_font = sdl.TTF_OpenFont("./assets/fonts/DepartureMonoNF-Regular.ttf", FONT_SIZE) orelse { + sdl.SDL_Log("Error loading ttf font: %s\n", sdl.TTF_GetError()); + return error.FailedToLoadFont; + }; + + font = loaded_font; + text_init = true; + } + + pub fn deinit(self: *GameTextFactoryController) void { + self.texts.deinit(); + deinitFont(); + } + + fn deinitFont() void { + sdl.TTF_CloseFont(font); + text_init = false; + } + + pub fn addText(self: *GameTextFactoryController, text_ctx: GameTextContext) !void { + try self.texts.append(GameText.initFromContext(text_ctx)); + } + + pub fn render(self: *GameTextFactoryController, renderer: *sdl.SDL_Renderer, game_state: *const GameState) !void { + _ = game_state; + for (self.texts.items) |text| { + const c_text: [*c]const u8 = @ptrCast(text.text); + // TODO dont create new surface if unchanged + const text_surface: [*c]sdl.SDL_Surface = sdl.TTF_RenderText_Solid(font.?, c_text, text.color) orelse { + sdl.SDL_Log("Error loading text surface: %s\n", sdl.SDL_GetError()); + return error.FailedToRenderSurface; + }; + defer sdl.SDL_FreeSurface(text_surface); + + if (!text_init) { + return error.TextNotInitialized; + } + + // TODO dont create new texture if unchanged + const text_texture = sdl.SDL_CreateTextureFromSurface(renderer, text_surface) orelse { + sdl.SDL_Log("Error loading text texture: %s\n", sdl.SDL_GetError()); + return error.FailedToRenderTexture; + }; + defer sdl.SDL_DestroyTexture(text_texture); + + const w: c_int = @intCast(text_surface.*.w); + const h: c_int = @intCast(text_surface.*.h); + const srcr = sdl.SDL_Rect{ .x = 0, .y = 0, .w = w, .h = h }; + const destr = sdl.SDL_Rect{ .x = @intCast(text.offset.x), .y = @intCast(text.offset.y), .w = w, .h = h }; + _ = sdl.SDL_RenderCopy(renderer, text_texture, &srcr, &destr); + } + } + + pub fn spawnFactory(self: *GameTextFactoryController, allocator: std.mem.Allocator) void { + const texts = self.texts.items; + var texts_copy = try allocator.alloc(GameText, texts.len); + errdefer allocator.free(texts_copy); + + std.mem.copyForwards(GameText, texts_copy, texts); + + return .{ + .texts = &texts_copy, + }; + } +}; + +pub const GameTextFactory = struct { + texts: []GameText, + text_surfaces: [*c]sdl.SDL_Surface, +}; + +pub const GameTextContext = struct { + text: [*:0]const u8, + color: sdl.SDL_Color = RGBAColor.whiteSmoke().tosdl(), + // TODO Constraints?? + offset: Offset = .{ .x = 0, .y = 0 }, +}; + pub const GameText = struct { text: [*:0]const u8, color: sdl.SDL_Color, - surface: *sdl.SDL_Surface, - w: u32, - h: u32, + offset: Offset, - pub fn loadFromRenderedText(text: [*:0]const u8, color: sdl.SDL_Color) !GameText { - const c_text: [*c]const u8 = @ptrCast(text); - const text_surface: [*c]sdl.SDL_Surface = sdl.TTF_RenderText_Solid(font.?, c_text, color) orelse { - sdl.SDL_Log("Error loading text surface: %s\n", sdl.SDL_GetError()); - return error.FailedToRenderSurface; - }; - - return .{ - .surface = text_surface, - .text = text, - .color = color, - .w = @intCast(text_surface.*.w), - .h = @intCast(text_surface.*.h), - }; - } - - pub fn deinit(self: *GameText) void { - sdl.SDL_FreeSurface(self.text_surface); - } + // pub fn deinit(self: *GameText) void { + // sdl.SDL_FreeSurface(self.text_surface); + // } pub fn setBlendMode(self: *GameText, blend: sdl.SDL_BlendMode) !void { _ = self; @@ -47,44 +118,11 @@ pub const GameText = struct { return error.Unimplemented; } - pub fn render( - self: *GameText, - game_state: *GameState, - offset: Offset, - ) !void { - if (!text_init) { - return error.TextNotInitialized; - } - - if (text_texture != null) { - sdl.SDL_DestroyTexture(text_texture); - text_texture = null; - } - - // TODO dont create new texture if unchanged - text_texture = sdl.SDL_CreateTextureFromSurface(game_state.renderer, self.surface) orelse { - sdl.SDL_Log("Error loading text texture: %s\n", sdl.SDL_GetError()); - return error.FailedToRenderTexture; + pub fn initFromContext(ctx: GameTextContext) GameText { + return .{ + .text = ctx.text, + .color = ctx.color, + .offset = ctx.offset, }; - - const srcr = sdl.SDL_Rect{ .x = 0, .y = 0, .w = @intCast(self.w), .h = @intCast(self.h) }; - const destr = sdl.SDL_Rect{ .x = @intCast(offset.x), .y = @intCast(offset.y), .w = @intCast(self.w), .h = @intCast(self.h) }; - _ = sdl.SDL_RenderCopy(game_state.renderer, text_texture, &srcr, &destr); - } - - pub fn initFont() !void { - const loaded_font = sdl.TTF_OpenFont("./assets/fonts/DepartureMonoNF-Regular.ttf", FONT_SIZE) orelse { - sdl.SDL_Log("Error loading ttf font: %s\n", sdl.TTF_GetError()); - return error.FailedToLoadFont; - }; - - font = loaded_font; - text_init = true; - } - - pub fn deinitFont() void { - sdl.SDL_DestroyTexture(text_texture); - sdl.TTF_CloseFont(font); - text_init = false; } }; diff --git a/src/utils/offset.zig b/src/utils/offset.zig index 386bc5f..6f9e808 100644 --- a/src/utils/offset.zig +++ b/src/utils/offset.zig @@ -1,4 +1,4 @@ pub const Offset = struct { - x: i32, - y: i32, + x: i32 = 0, + y: i32 = 0, }; diff --git a/src/utils/timer.zig b/src/utils/timer.zig index 8abe812..f1a2be6 100644 --- a/src/utils/timer.zig +++ b/src/utils/timer.zig @@ -41,6 +41,7 @@ pub const Timer = struct { self.pause_ticks_ms = 0; } + // Gets the current tick/ms of the timer pub fn getTicks(self: *Timer) u32 { switch (self.status) { .stopped => {