#include "common.h" typedef struct game { time_t mod_time; memory_t memory; const char* lib_name; void* lib_ptr; void (*startup)(memory_t*); void (*shutdown)(void); void (*update)(memory_t*, float, input_t, bool*); void (*render)(void); void (*debug)(uint64_t, uint64_t, uint64_t); void (*audio_callback)(void* userdata, uint8_t* stream, int len); } game_t; typedef struct keystate { int w; int a; int s; int d; int up; int left; int down; int right; int e; int enter; int space; int esc; } keystate_t; int main(int argc, char* argv[]) { (void)argc; (void)argv; { int ret; ret = SDL_InitSubSystem(SDL_INIT_VIDEO); if (ret != 0) { fprintf(stderr, "Error initializing SDL Video subsystem: %s\n", SDL_GetError()); exit(EXIT_FAILURE); } ret = SDL_InitSubSystem(SDL_INIT_AUDIO); if (ret != 0) { fprintf(stderr, "Error initializing SDL Audio subsystem: %s\n", SDL_GetError()); exit(EXIT_FAILURE); } } SDL_Window* window = NULL; SDL_GLContext* context = NULL; { window = SDL_CreateWindow( WINDOW_TITLE, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL); if (window == NULL) { fprintf(stderr, "Error creating SDL window: %s\n", SDL_GetError()); exit(EXIT_FAILURE); } SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); context = SDL_GL_CreateContext(window); if (context == NULL) { fprintf(stderr, "Error creating SDL GL context: %s\n", SDL_GetError()); exit(EXIT_FAILURE); } SDL_GL_SetSwapInterval(0); gladLoadGL((GLADloadfunc)SDL_GL_GetProcAddress); SDL_SetRelativeMouseMode(SDL_TRUE); SDL_ShowCursor(SDL_DISABLE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } game_t game = {0}; { const char* platform = SDL_GetPlatform(); if (strcmp(platform, "Windows") == 0) { game.lib_name = ".\\libgame.dll"; } else { game.lib_name = "./libgame.so"; } struct stat attrib; stat(game.lib_name, &attrib); game.mod_time = attrib.st_mtime; game.lib_ptr = SDL_LoadObject(game.lib_name); if (game.lib_ptr == NULL) { fprintf(stderr, "Error loading game library: %s\n", SDL_GetError()); exit(EXIT_FAILURE); } game.startup = SDL_LoadFunction(game.lib_ptr, "game_startup"); if (game.startup == NULL) { fprintf(stderr, "Error loading game function pointer from game lib: %s\n", SDL_GetError()); exit(EXIT_FAILURE); } game.shutdown = SDL_LoadFunction(game.lib_ptr, "game_shutdown"); if (game.shutdown == NULL) { fprintf(stderr, "Error loading game function pointer from game lib: %s\n", SDL_GetError()); exit(EXIT_FAILURE); } game.update = SDL_LoadFunction(game.lib_ptr, "game_update"); if (game.update == NULL) { fprintf(stderr, "Error loading game function pointer from game lib: %s\n", SDL_GetError()); exit(EXIT_FAILURE); } game.render = SDL_LoadFunction(game.lib_ptr, "game_render"); if (game.render == NULL) { fprintf(stderr, "Error loading game function pointer from game lib: %s\n", SDL_GetError()); exit(EXIT_FAILURE); } game.debug = SDL_LoadFunction(game.lib_ptr, "game_debug"); if (game.debug == NULL) { fprintf(stderr, "Error loading game function pointer from game lib: %s\n", SDL_GetError()); exit(EXIT_FAILURE); } game.audio_callback = SDL_LoadFunction(game.lib_ptr, "game_audio_callback"); if (game.audio_callback == NULL) { fprintf(stderr, "Error loading game function pointer from game lib: %s\n", SDL_GetError()); exit(EXIT_FAILURE); } game.memory.engine.begin = calloc(ENGINE_MEMORY_SIZE, 1); game.memory.engine.end = game.memory.engine.begin + ENGINE_MEMORY_SIZE; game.memory.engine.current = game.memory.engine.begin; game.memory.engine.size = ENGINE_MEMORY_SIZE; if (game.memory.engine.begin == NULL) { fprintf(stderr, "Error requesting %lu bytes for engine memory\n", ENGINE_MEMORY_SIZE); exit(EXIT_FAILURE); } game.memory.game.begin = calloc(GAME_MEMORY_SIZE, 1); game.memory.game.end = game.memory.game.begin + GAME_MEMORY_SIZE; game.memory.game.current = game.memory.game.begin; game.memory.game.size = GAME_MEMORY_SIZE; if (game.memory.game.begin == NULL) { fprintf(stderr, "Error requesting %lu bytes for game memory\n", GAME_MEMORY_SIZE); exit(EXIT_FAILURE); } game.memory.scratch.begin = calloc(SCRATCH_MEMORY_SIZE, 1); game.memory.scratch.end = game.memory.scratch.begin + SCRATCH_MEMORY_SIZE; game.memory.scratch.current = game.memory.scratch.begin ; game.memory.scratch.size = SCRATCH_MEMORY_SIZE; if (game.memory.scratch.begin == NULL) { fprintf(stderr, "Error requesting %lu bytes for scratch memory\n", SCRATCH_MEMORY_SIZE); exit(EXIT_FAILURE); } } // Set up audio SDL_AudioDeviceID audio_device; SDL_AudioSpec audio_specs = {0}; { audio_specs.freq = 8000; audio_specs.format = AUDIO_F32; audio_specs.channels = 2; audio_specs.samples = 256; audio_specs.callback = game.audio_callback; audio_specs.userdata = &game.memory; SDL_AudioSpec have; audio_device = SDL_OpenAudioDevice(NULL, 0, &audio_specs, &have, 0); // Un-pause SDL_PauseAudioDevice(audio_device, 0); } game.startup(&game.memory); bool is_running = true; keystate_t keystate = {0}; uint64_t previous_time = SDL_GetTicks64(); while (is_running) { float dt; uint16_t frame_time; { uint64_t current_time = SDL_GetTicks64(); frame_time = current_time - previous_time; if (frame_time < TARGET_FRAME_TIME) { uint64_t sleep_time = TARGET_FRAME_TIME - frame_time; SDL_Delay(sleep_time); current_time = SDL_GetTicks64(); frame_time = current_time - previous_time; } previous_time = current_time; dt = frame_time / 1000.0f; } { SDL_Event e; while (SDL_PollEvent(&e)) { if (e.type == SDL_QUIT) { is_running = false; } else if (e.type == SDL_KEYDOWN || e.type == SDL_KEYUP) { switch (e.key.keysym.sym) { case SDLK_w: { keystate.w = e.type; } break; case SDLK_a: { keystate.a = e.type; } break; case SDLK_s: { keystate.s = e.type; } break; case SDLK_d: { keystate.d = e.type; } break; case SDLK_UP: { keystate.up = e.type; } break; case SDLK_LEFT: { keystate.left = e.type; } break; case SDLK_DOWN: { keystate.down = e.type; } break; case SDLK_RIGHT: { keystate.right = e.type; } break; case SDLK_e: { keystate.e = e.type; } break; case SDLK_RETURN: { keystate.enter = e.type; } break; case SDLK_SPACE: { keystate.space= e.type; } break; case SDLK_ESCAPE: { keystate.esc = e.type; } break; } } } } input_t input = {0}; if (keystate.w == SDL_KEYDOWN || keystate.up == SDL_KEYDOWN) { input.up = true; } if (keystate.a == SDL_KEYDOWN || keystate.left == SDL_KEYDOWN) { input.left = true; } if (keystate.s == SDL_KEYDOWN || keystate.down == SDL_KEYDOWN) { input.down = true; } if (keystate.d == SDL_KEYDOWN || keystate.right == SDL_KEYDOWN) { input.right = true; } if (keystate.e == SDL_KEYDOWN) { input.interact = true; } if (keystate.enter == SDL_KEYDOWN || keystate.space == SDL_KEYDOWN) { input.select = true; } if (keystate.esc == SDL_KEYDOWN) { input.pause = true; } SDL_GetRelativeMouseState(&input.mouse_x_delta, &input.mouse_y_delta); bool quit_requested = false; #ifdef BUILD_RELEASE game.update(&game.memory, dt, input, &quit_requested); game.render(&game.memory); #else { struct stat attrib; stat(game.lib_name, &attrib); time_t mod_time = attrib.st_mtime; if (mod_time != game.mod_time) { SDL_CloseAudioDevice(audio_device); SDL_UnloadObject(game.lib_ptr); game.lib_ptr = NULL; game.update = NULL; game.render = NULL; game.debug = NULL; game.audio_callback = NULL; game.shutdown = NULL; // Failure here is likely the first few times while it attempts to load a library // that has not finished compiling, so we let it fail and try again next time game.lib_ptr = SDL_LoadObject(game.lib_name); if (game.lib_ptr) { game.mod_time = mod_time; game.update = SDL_LoadFunction(game.lib_ptr, "game_update"); game.render = SDL_LoadFunction(game.lib_ptr, "game_render"); game.shutdown = SDL_LoadFunction(game.lib_ptr, "game_shutdown"); game.debug = SDL_LoadFunction(game.lib_ptr, "game_debug"); game.audio_callback = SDL_LoadFunction(game.lib_ptr, "game_audio_callback"); if (game.audio_callback) { // Need to re-initialize the audio device so it gets the updated callback pointer audio_specs.callback = game.audio_callback; SDL_AudioSpec have; audio_device = SDL_OpenAudioDevice(NULL, 0, &audio_specs, &have, 0); SDL_PauseAudioDevice(audio_device, 0); } } } } if (game.update && game.render && game.debug) { uint64_t update_time; { uint64_t pre_time = SDL_GetTicks64(); game.update(&game.memory, dt, input, &quit_requested); uint64_t post_time = SDL_GetTicks64(); update_time = post_time - pre_time; } uint16_t render_time; { uint64_t pre_time = SDL_GetTicks64(); game.render(); uint64_t post_time = SDL_GetTicks64(); render_time = post_time - pre_time; } game.debug(update_time, render_time, frame_time); } #endif is_running = !quit_requested; SDL_GL_SwapWindow(window); } game.shutdown(); SDL_UnloadObject(game.lib_ptr); SDL_QuitSubSystem(SDL_INIT_AUDIO); SDL_QuitSubSystem(SDL_INIT_VIDEO); SDL_Quit(); return EXIT_SUCCESS; }