const std = @import("std"); const sdl = @import("sdl.zig").c; const gs = @import("./game_state.zig"); const assert = std.debug.assert; const SCREEN_WIDTH = 640; const SCREEN_HEIGHT = 480; 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; const Timer = @import("./utils/timer.zig").Timer; var window: *sdl.struct_SDL_Window = undefined; // var screen_surface: *sdl.struct_SDL_Surface = undefined; var renderer: *sdl.struct_SDL_Renderer = undefined; const Offset = struct { x: i32, y: i32, }; var img_pos: Offset = .{ .x = 0, .y = 0 }; pub fn main() !void { errdefer |err| if (err == error.SdlError) std.log.err("SDL error: {s}", .{sdl.SDL_GetError()}); try init(); defer close(); var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); var game_state_controller = try gs.GameStateController.init(allocator, renderer); defer game_state_controller.deinit(); var quit = false; var game_timer = Timer{}; game_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) { // double newTime = time(); // double frameTime = newTime - currentTime; // if ( frameTime > 0.25 ) // frameTime = 0.25; // currentTime = newTime; // accumulator += frameTime; // while ( accumulator >= dt ) // { // previousState = currentState; // integrate( currentState, t, dt ); // t += dt; // accumulator -= dt; // } // const double alpha = accumulator / dt; // State state = currentState * alpha + // previousState * ( 1.0 - alpha ); // 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 game_time_ms: u32 = @intFromFloat(game_timer.getTicks()); const phys_done_at_ticks: u32 = game_time_ms + total_phys_ticks; var phys_ticks_used: f64 = 0; std.debug.print("Phys ticks/upd: {d}\nAcc: {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) { sdl.SDLK_RETURN => {}, 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; accumulator -= phys_ticks_this_update; // 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); // const ticksf: f64 = @floatFromInt(ticks); // accumulator -= (phys_ticks_this_update + ticksf); // } else { // // std.debug.print("PHYSICS JANK: Physics 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}); } // 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 = 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); // } } return; } fn init() !void { // Init SDL with video subsystem flag if (sdl.SDL_Init(sdl.SDL_INIT_VIDEO) < 0) { log("Unable to initialize SDL: %s\n", sdl.SDL_GetError()); return error.SDLInitializationFailed; } const opt_window = sdl.SDL_CreateWindow("Game Window", sdl.SDL_WINDOWPOS_UNDEFINED, sdl.SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, sdl.SDL_WINDOW_SHOWN); if (opt_window == null) { log("Unable to initialize SDL window: %s\n", sdl.SDL_GetError()); return error.SDLInitializationFailed; } window = opt_window.?; renderer = sdl.SDL_CreateRenderer(window, -1, sdl.SDL_RENDERER_ACCELERATED) orelse { log("Unable to initialize SDL window: %s\n", sdl.SDL_GetError()); return error.SDLInitializationFailed; }; _ = sdl.SDL_SetRenderDrawColor(renderer, 0xff, 0xff, 0xff, 0xff); // screen_surface = sdl.SDL_GetWindowSurface(window); const img_flags = sdl.IMG_INIT_PNG; if (sdl.IMG_Init(img_flags) != img_flags) { // NOTE remember that errors with SDL_Image will be in IMG_GetError() not SDL_GetError() log("Unable to initialize SDL Image: %s\n", sdl.IMG_GetError()); return error.SDLInitializationFailed; } if (sdl.TTF_Init() < 0) { log("Unable to initialize SDL TTF: %s\n", sdl.TTF_GetError()); return error.SDLInitializationFailed; } } fn close() void { sdl.SDL_DestroyRenderer(renderer); sdl.SDL_DestroyWindow(window); sdl.IMG_Quit(); sdl.TTF_Quit(); sdl.SDL_Quit(); }