#include "context.h" #include "rendering.h" #include "input.h" #include "util.h" #include "textures.h" #include "game/game.h" #include "private/audio.h" #include #include #include #include #include #include #include #include #include #include static void poll_events(void) { SDL_Event e; while (SDL_PollEvent(&e)) { switch (e.type) { case SDL_QUIT: ctx.is_running = false; break; case SDL_WINDOWEVENT: if (e.window.windowID != ctx.window_id) break; switch (e.window.event) { case SDL_WINDOWEVENT_RESIZED: break; } break; } } } void main_loop(void) { /* if (!ctx.is_running) { end(ctx); clean_up(ctx); emscripten_cancel_main_loop(); } */ /* frame timer */ int64_t current_frame_time = SDL_GetPerformanceCounter(); int64_t delta_time = current_frame_time - ctx.prev_frame_time; ctx.prev_frame_time = current_frame_time; /* handle unexpected timer anomalies (overflow, extra slow frames, etc) */ if (delta_time > ctx.desired_frametime * 8) { /* ignore extra-slow frames */ delta_time = ctx.desired_frametime; } delta_time = MAX(0, delta_time); /* vsync time snapping */ /* get the refresh rate of the current display (it might not always be the same one) */ int display_framerate = 60; /* a reasonable guess */ SDL_DisplayMode current_display_mode; int current_display_index = SDL_GetWindowDisplayIndex(ctx.window); if (SDL_GetCurrentDisplayMode(current_display_index, ¤t_display_mode) == 0) display_framerate = current_display_mode.refresh_rate; int64_t snap_hz = display_framerate; if (snap_hz <= 0) snap_hz = 60; /* these are to snap delta time to vsync values if it's close enough */ int64_t vsync_maxerror = (int64_t)((double)ctx.clocks_per_second * 0.0002); size_t snap_frequency_count = 8; for (size_t i = 0; i < snap_frequency_count; ++i) { int64_t frequency = (ctx.clocks_per_second / snap_hz) * (i+1); if (llabs(delta_time - frequency) < vsync_maxerror) { delta_time = frequency; break; } } /* delta time averaging */ size_t time_averager_count = SDL_arraysize(ctx.time_averager); for (size_t i = 0; i < time_averager_count - 1; ++i) { ctx.time_averager[i] = ctx.time_averager[i+1]; } ctx.time_averager[time_averager_count - 1] = delta_time; int64_t averager_sum = 0; for (size_t i = 0; i < time_averager_count; ++i) { averager_sum += ctx.time_averager[i]; } delta_time = averager_sum / time_averager_count; ctx.delta_averager_residual += averager_sum % time_averager_count; delta_time += ctx.delta_averager_residual / time_averager_count; ctx.delta_averager_residual %= time_averager_count; /* add to the accumulator */ ctx.frame_accumulator += delta_time; /* spiral of death protection */ if (ctx.frame_accumulator > ctx.desired_frametime * 8) { ctx.resync_flag = true; } /* timer resync if requested */ if (ctx.resync_flag) { ctx.frame_accumulator = 0; delta_time = ctx.desired_frametime; ctx.resync_flag = false; } /* finally, let's get to work */ poll_events(); input_state_update(&ctx.input); /* do we _always_ have to be dry? */ if (ctx.frame_accumulator >= ctx.desired_frametime * ctx.update_multiplicity) { while (ctx.frame_accumulator >= ctx.desired_frametime * ctx.update_multiplicity) { for (size_t i = 0; i < ctx.update_multiplicity; ++i) { render_queue_clear(); game_tick(); ctx.frame_accumulator -= ctx.desired_frametime; ctx.tick_count = (ctx.tick_count % ULLONG_MAX) + 1; } } } else { render_queue_clear(); } /* the "step" will always run as long as there is time to spare. */ /* without interpolation, this is preferable to fixed ticks if the additional */ /* delta time calculations (like in physics code) are acceptable */ game_step(delta_time); ctx.step_count = (ctx.step_count % ULLONG_MAX) + 1; render(); } static bool initialize(void) { if (SDL_Init(SDL_INIT_EVERYTHING) == -1) { CRY_SDL("SDL initialization failed."); return false; } /* init got far enough to create a window */ ctx.window = SDL_CreateWindow("emerald", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT, SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE); if (ctx.window == NULL) { CRY_SDL("Window creation failed."); goto fail; } /* might need this to have multiple windows */ ctx.window_id = SDL_GetWindowID(ctx.window); /* now that we have a window, we know a renderer can be created */ ctx.renderer = SDL_CreateRenderer(ctx.window, -1, SDL_RENDERER_PRESENTVSYNC); /* SDL_SetHint(SDL_HINT_RENDER_LOGICAL_SIZE_MODE, "overscan"); */ /* SDL_RenderSetIntegerScale(ctx.renderer, SDL_TRUE); */ SDL_RenderSetLogicalSize(ctx.renderer, RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT); SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h); /* audio initialization */ { SDL_AudioSpec request, got; SDL_zero(request); request.freq = AUDIO_FREQUENCY; request.format = AUDIO_S16; request.channels = AUDIO_N_CHANNELS; request.callback = audio_callback; /* TODO: check for errors */ ctx.audio_device = SDL_OpenAudioDevice(NULL, 0, &request, &got, 0); ctx.audio_stream_format = got.format; ctx.audio_stream_frequency = got.freq; ctx.audio_stream_channel_count = got.channels; SDL_PauseAudioDevice(ctx.audio_device, 0); } /* images */ if (IMG_Init(IMG_INIT_PNG) == 0) { CRY_SDL("SDL_image initialization failed."); goto fail; } /* filesystem time */ /* TODO: ANDROID: see the warning in physicsfs PHYSFS_init docs/header */ if (!PHYSFS_init(ctx.argv[0]) || !PHYSFS_setSaneConfig(ORGANIZATION_NAME, APP_NAME, PACKAGE_EXTENSION, false, true)) { CRY_PHYSFS("Filesystem initialization failed."); goto fail; } /* you could change this at runtime if you wanted */ ctx.update_multiplicity = 1; /* debug mode _defaults_ to being enabled on debug builds. */ /* you should be able to enable it at runtime on any build */ #ifndef NDEBUG ctx.debug = true; #else ctx.debug = false; #endif /* random seeding */ /* SDL_GetPerformanceCounter returns some platform-dependent number. */ /* it should vary between game instances. i checked! random enough for me. */ ctx.random_seed = SDL_GetPerformanceCounter(); srand((unsigned int)ctx.random_seed); stbds_rand_seed(ctx.random_seed); /* main loop machinery */ ctx.is_running = true; ctx.resync_flag = true; ctx.clocks_per_second = SDL_GetPerformanceFrequency(); ctx.prev_frame_time = SDL_GetPerformanceCounter(); ctx.desired_frametime = ctx.clocks_per_second / TICKS_PER_SECOND; ctx.frame_accumulator = 0; ctx.tick_count = 0; ctx.step_count = 0; /* delta time averaging */ ctx.delta_averager_residual = 0; for (size_t i = 0; i < SDL_arraysize(ctx.time_averager); ++i) { ctx.time_averager[i] = ctx.desired_frametime; } /* rendering */ /* these are dynamic arrays and will be allocated lazily by stb_ds */ ctx.render_queue_sprites = NULL; ctx.render_queue_rectangles = NULL; ctx.render_queue_circles = NULL; ctx.circle_radius_hash = NULL; textures_cache_init(&ctx.texture_cache, ctx.window); if (TTF_Init() < 0) { CRY_SDL("SDL_ttf initialization failed."); goto fail; } /* input */ input_state_init(&ctx.input, ctx.renderer); /* scripting */ /* if (!scripting_init(ctx)) { goto fail; } */ return true; fail: SDL_Quit(); return false; } /* will not be called on an abnormal exit */ static void clean_up(void) { /* scripting_deinit(ctx); */ input_state_deinit(&ctx.input); arrfree(ctx.render_queue_sprites); arrfree(ctx.render_queue_rectangles); textures_cache_deinit(&ctx.texture_cache); PHYSFS_deinit(); TTF_Quit(); IMG_Quit(); SDL_Quit(); } int main(int argc, char **argv) { ctx.argc = argc; ctx.argv = argv; if (!initialize()) return EXIT_FAILURE; for (int i = 1; i < (argc - 1); ++i) { if (strcmp(argv[i], "--data-dir") == 0) { if (!PHYSFS_mount(argv[i+1], NULL, true)) { CRY_PHYSFS("Data dir mount override failed."); return EXIT_FAILURE; } } } ctx.was_successful = true; while (ctx.is_running) main_loop(); game_end(); clean_up(); return ctx.was_successful ? EXIT_SUCCESS : EXIT_FAILURE; }