Initial commit
This commit is contained in:
177
src/engine/core.c
Normal file
177
src/engine/core.c
Normal file
@@ -0,0 +1,177 @@
|
||||
#include "engine/core.h"
|
||||
#include "engine/input.h"
|
||||
#include "engine/renderer.h"
|
||||
#include "engine/audio.h"
|
||||
#include "engine/assets.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
Engine g_engine = {0};
|
||||
|
||||
static GameCallbacks s_callbacks = {0};
|
||||
|
||||
void engine_set_callbacks(GameCallbacks cb) {
|
||||
s_callbacks = cb;
|
||||
}
|
||||
|
||||
bool engine_init(void) {
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER) != 0) {
|
||||
fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
int win_w = SCREEN_WIDTH * WINDOW_SCALE;
|
||||
int win_h = SCREEN_HEIGHT * WINDOW_SCALE;
|
||||
|
||||
g_engine.window = SDL_CreateWindow(
|
||||
WINDOW_TITLE,
|
||||
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||
win_w, win_h,
|
||||
SDL_WINDOW_SHOWN
|
||||
);
|
||||
if (!g_engine.window) {
|
||||
fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
g_engine.renderer = SDL_CreateRenderer(
|
||||
g_engine.window, -1,
|
||||
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
|
||||
);
|
||||
if (!g_engine.renderer) {
|
||||
fprintf(stderr, "SDL_CreateRenderer failed: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Set logical size for pixel-perfect scaling */
|
||||
SDL_RenderSetLogicalSize(g_engine.renderer, SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); /* nearest neighbor */
|
||||
|
||||
/* Initialize subsystems */
|
||||
input_init();
|
||||
renderer_init(g_engine.renderer);
|
||||
audio_init();
|
||||
assets_init(g_engine.renderer);
|
||||
|
||||
g_engine.running = true;
|
||||
g_engine.tick = 0;
|
||||
g_engine.fps = 0.0f;
|
||||
|
||||
printf("Engine initialized: %dx%d (x%d scale)\n",
|
||||
SCREEN_WIDTH, SCREEN_HEIGHT, WINDOW_SCALE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ── Frame-loop state (file-scope so engine_frame can access it) ── */
|
||||
static uint64_t s_freq;
|
||||
static uint64_t s_prev_time;
|
||||
static float s_accumulator;
|
||||
static float s_fps_timer;
|
||||
static int s_fps_frames;
|
||||
|
||||
/*
|
||||
* engine_frame -- execute exactly one iteration of the game loop.
|
||||
*
|
||||
* Called repeatedly by the platform layer:
|
||||
* Native : tight while-loop in engine_run()
|
||||
* Browser : emscripten_set_main_loop() via requestAnimationFrame
|
||||
*/
|
||||
static void engine_frame(void) {
|
||||
uint64_t current_time = SDL_GetPerformanceCounter();
|
||||
float frame_time = (float)(current_time - s_prev_time) / (float)s_freq;
|
||||
s_prev_time = current_time;
|
||||
|
||||
/* Clamp frame time to avoid spiral of death */
|
||||
if (frame_time > MAX_FRAME_SKIP * DT) {
|
||||
frame_time = MAX_FRAME_SKIP * DT;
|
||||
}
|
||||
|
||||
/* FPS counter */
|
||||
s_fps_timer += frame_time;
|
||||
s_fps_frames++;
|
||||
if (s_fps_timer >= 1.0f) {
|
||||
g_engine.fps = (float)s_fps_frames / s_fps_timer;
|
||||
s_fps_timer = 0.0f;
|
||||
s_fps_frames = 0;
|
||||
}
|
||||
|
||||
/* ── Input ────────────────────────────── */
|
||||
input_poll();
|
||||
if (input_quit_requested()) {
|
||||
g_engine.running = false;
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if (s_callbacks.shutdown) {
|
||||
s_callbacks.shutdown();
|
||||
}
|
||||
emscripten_cancel_main_loop();
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
/* ── Fixed timestep update ────────────── */
|
||||
s_accumulator += frame_time;
|
||||
while (s_accumulator >= DT) {
|
||||
if (s_callbacks.update) {
|
||||
s_callbacks.update(DT);
|
||||
}
|
||||
input_consume();
|
||||
g_engine.tick++;
|
||||
s_accumulator -= DT;
|
||||
}
|
||||
|
||||
/* ── Render ───────────────────────────── */
|
||||
g_engine.interpolation = s_accumulator / DT;
|
||||
renderer_clear();
|
||||
if (s_callbacks.render) {
|
||||
s_callbacks.render(g_engine.interpolation);
|
||||
}
|
||||
renderer_present();
|
||||
}
|
||||
|
||||
void engine_run(void) {
|
||||
if (s_callbacks.init) {
|
||||
s_callbacks.init();
|
||||
}
|
||||
|
||||
/* Initialise frame-loop timing state */
|
||||
s_freq = SDL_GetPerformanceFrequency();
|
||||
s_prev_time = SDL_GetPerformanceCounter();
|
||||
s_accumulator = 0.0f;
|
||||
s_fps_timer = 0.0f;
|
||||
s_fps_frames = 0;
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
/*
|
||||
* Hand control to the browser's requestAnimationFrame loop.
|
||||
* fps=0 : match display refresh rate
|
||||
* simulate_infinite_loop=1 : don't return from this function
|
||||
* (so the rest of engine_run never executes until shutdown)
|
||||
*/
|
||||
emscripten_set_main_loop(engine_frame, 0, 1);
|
||||
#else
|
||||
while (g_engine.running) {
|
||||
engine_frame();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (s_callbacks.shutdown) {
|
||||
s_callbacks.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void engine_shutdown(void) {
|
||||
assets_free_all();
|
||||
audio_shutdown();
|
||||
renderer_shutdown();
|
||||
input_shutdown();
|
||||
|
||||
if (g_engine.renderer) SDL_DestroyRenderer(g_engine.renderer);
|
||||
if (g_engine.window) SDL_DestroyWindow(g_engine.window);
|
||||
SDL_Quit();
|
||||
|
||||
printf("Engine shut down cleanly.\n");
|
||||
}
|
||||
Reference in New Issue
Block a user