From 03d94f3762ab576ba0675abcaefde888a9da2c3d Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Fri, 27 Jun 2025 10:13:51 -0700 Subject: Initial commit --- src/game.c | 218 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 src/game.c (limited to 'src/game.c') diff --git a/src/game.c b/src/game.c new file mode 100644 index 0000000..51f5cbe --- /dev/null +++ b/src/game.c @@ -0,0 +1,218 @@ +/* + * Main game module with entry point and game loop. + * + * The game module sets up the window and GL context and defers the core game + * logic to a plugin. + */ +#define _GNU_SOURCE 200112L // For readlink() + +#include "game.h" + +#include "plugins/plugin.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#undef _GNU_SOURCE + +static const int WIDTH = 1350; +static const int HEIGHT = 900; +static const int MAX_FPS = 60; + +typedef struct GfxAppState { + Game game; +} GfxAppState; + +/// Initialize the game's plugin. +static bool init_plugin(Game* game) { + assert(game); + assert(game->plugin); + // Plugin state is allowed to be null, either when the plugin does not + // expose an init() or when init() does not initialize a state. + if (plugin_resolve(game->plugin, plugin_init, "init")) { + State* plugin_state = 0; + if (!plugin_call(game->plugin, plugin_init, "init", game, &plugin_state)) { + return false; + } + set_plugin_state(game->plugin, plugin_state); + } + return true; // Plugin does not need to expose an init(). +} + +/// Shutdown the game's plugin. +/// The game's plugin is allowed to be null in the call to this function. +static void shutdown_plugin(Game* game) { + assert(game); + if (game->plugin && + (plugin_resolve(game->plugin, plugin_shutdown, "shutdown"))) { + void* plugin_state = get_plugin_state(game->plugin); + plugin_call(game->plugin, plugin_shutdown, "shutdown", game, plugin_state); + set_plugin_state(game->plugin, 0); + } +} + +/// Boot the game's plugin. +static bool boot_plugin(Game* game) { + assert(game); + assert(game->plugin); + if (plugin_resolve(game->plugin, plugin_boot, "boot")) { + void* plugin_state = get_plugin_state(game->plugin); + return plugin_call(game->plugin, plugin_boot, "boot", game, plugin_state); + } + return true; // Plugin does not need to expose a boot(). +} + +/// Update the plugin's state. +static void update_plugin(Game* game, double t, double dt) { + assert(game); + assert(game->plugin); + if (plugin_resolve(game->plugin, plugin_update, "update")) { + void* plugin_state = get_plugin_state(game->plugin); + plugin_call( + game->plugin, plugin_update, "update", game, plugin_state, t, dt); + } +} + +/// Plugin render. +static void render_plugin(const Game* game) { + assert(game); + assert(game->plugin); + if (plugin_resolve(game->plugin, plugin_render, "render")) { + void* plugin_state = get_plugin_state(game->plugin); + plugin_call(game->plugin, plugin_render, "render", game, plugin_state); + } +} + +/// Plugin resize. +static void resize_plugin(Game* game, int width, int height) { + assert(game); + assert(game->plugin); + if (plugin_resolve(game->plugin, plugin_resize, "resize")) { + void* plugin_state = get_plugin_state(game->plugin); + plugin_call( + game->plugin, plugin_resize, "resize", game, plugin_state, width, + height); + } +} + +static void Shutdown(Game* game); + +static bool Init(Game* game, int argc, const char** argv) { + assert(game); + + if (argc <= 1) { + LOGE("Usage: %s [plugin args]", argv[0]); + return false; + } + + // Syntax: game [plugin args] + // + // Here we consume the arg so that plugins receive the remainder + // args starting from 0. + game->argc = argc - 1; + game->argv = argv + 1; + + char exe_path_buf[NAME_MAX] = {0}; + if (readlink("/proc/self/exe", exe_path_buf, sizeof(exe_path_buf)) == -1) { + LOGE("readlink(/proc/self/exe) failed"); + goto cleanup; + } + + // Replace the last / with a null terminator to remove the exe file from the + // path. This gets the file's parent directory. + *strrchr(exe_path_buf, '/') = 0; + + const mstring exe_dir = mstring_make(exe_path_buf); + const mstring plugins_path = mstring_concat_cstr(exe_dir, "/src/plugins"); + + if (!(game->plugin_engine = new_plugin_engine( + &(PluginEngineDesc){.plugins_dir = mstring_cstr(&plugins_path)}))) { + goto cleanup; + } + + const char* plugin = argv[1]; + if (!(game->plugin = load_plugin(game->plugin_engine, plugin))) { + goto cleanup; + } + + if (!(game->gfx = gfx_init())) { + goto cleanup; + } + + if (!init_plugin(game)) { + goto cleanup; + } + if (!boot_plugin(game)) { + goto cleanup; + } + + return true; + +cleanup: + LOGE("Gfx error: %s", get_error()); + Shutdown(game); + return false; +} + +static void Shutdown(Game* game) { + assert(game); + shutdown_plugin(game); + if (game->gfx) { + gfx_destroy(&game->gfx); + } + if (game->plugin) { + delete_plugin(&game->plugin); + } + if (game->plugin_engine) { + delete_plugin_engine(&game->plugin_engine); + } +} + +static void Update(Game* game, double t, double dt) { + plugin_engine_update(game->plugin_engine); + if (plugin_reloaded(game->plugin)) { + shutdown_plugin(game); + const bool result = init_plugin(game); + assert(result); // TODO: handle error better. + + // Trigger a resize just like the initial resize that occurs when the gfx + // application starts. + resize_plugin(game, game->width, game->height); + } + + update_plugin(game, t, dt); +} + +static void Render(const Game* game) { + GfxCore* gfxcore = gfx_get_core(game->gfx); + gfx_start_frame(gfxcore); + render_plugin(game); + gfx_end_frame(gfxcore); +} + +static void Resize(Game* game, int width, int height) { + game->width = width; + game->height = height; + + GfxCore* gfxcore = gfx_get_core(game->gfx); + gfx_set_viewport(gfxcore, 0, 0, width, height); + + resize_plugin(game, width, height); +} + +GFX_APP_MAIN(WIDTH, HEIGHT, MAX_FPS, "Game"); -- cgit v1.2.3