/* * 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 #undef _GNU_SOURCE // Plugin to load if no plugin is provided. static const char* DEFAULT_PLUGIN = "texture_view"; static bool validate_plugin(const Plugin* plugin) { #define CHECK_FUNCTION(name, signature) \ if (!plugin_resolve(plugin, signature, name)) { \ LOGE("Plugin is missing function: " #name); \ return false; \ } CHECK_FUNCTION("init", plugin_init); CHECK_FUNCTION("boot", plugin_boot); CHECK_FUNCTION("update", plugin_update); CHECK_FUNCTION("render", plugin_render); return true; } bool game_new(Game* game, int argc, const char** argv) { assert(game); // Syntax: game [plugin] // // Here we consume the [plugin] 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 = argc > 1 ? argv[1] : DEFAULT_PLUGIN; if (!(game->plugin = load_plugin(game->plugin_engine, plugin))) { goto cleanup; } if (!validate_plugin(game->plugin)) { goto cleanup; } if (!(game->gfx = gfx_init())) { goto cleanup; } if (!(game->scene = gfx_make_scene())) { goto cleanup; } if (!(game->camera = gfx_make_camera())) { goto cleanup; } void* plugin_state = plugin_call(game->plugin, plugin_init, "init", game); if (!plugin_state) { goto cleanup; } set_plugin_state(game->plugin, plugin_state); bool boot_success = plugin_call(game->plugin, plugin_boot, "boot", plugin_state, game); if (!boot_success) { goto cleanup; } return true; cleanup: LOGE("Gfx error: %s", get_error()); game_end(game); return false; } void game_end(Game* game) { assert(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); } } void game_update(Game* game, double t, double dt) { plugin_engine_update(game->plugin_engine); if (plugin_reloaded(game->plugin)) { void* plugin_state = plugin_call(game->plugin, plugin_init, "init", game); assert(plugin_state); // TODO: handle error better. set_plugin_state(game->plugin, plugin_state); } void* plugin_state = get_plugin_state(game->plugin); assert(plugin_state); plugin_call(game->plugin, plugin_update, "update", plugin_state, game, t, dt); } void game_render(const Game* game) { Renderer* renderer = gfx_get_renderer(game->gfx); gfx_render_scene( renderer, &(RenderSceneParams){ .mode = RenderDefault, .scene = game->scene, .camera = game->camera}); void* plugin_state = get_plugin_state(game->plugin); assert(plugin_state); plugin_call(game->plugin, plugin_render, "render", plugin_state, game); } void game_set_viewport(Game* game, int width, int height) { RenderBackend* render_backend = gfx_get_render_backend(game->gfx); gfx_set_viewport(render_backend, width, height); const R fovy = 90 * TO_RAD; const R aspect = (R)width / (R)height; const R near = 0.1; const R far = 1000; const mat4 projection = mat4_perspective(fovy, aspect, near, far); Camera* camera = gfx_get_camera_camera(game->camera); camera->projection = projection; }