780 lines
24 KiB
C
780 lines
24 KiB
C
#include "twn_loop.h"
|
|
#include "twn_engine_context_c.h"
|
|
#include "twn_input_c.h"
|
|
#include "twn_util.h"
|
|
#include "twn_game_object_c.h"
|
|
#include "twn_audio_c.h"
|
|
#include "twn_textures_c.h"
|
|
|
|
#include <SDL2/SDL.h>
|
|
#include <physfs.h>
|
|
#include <stb_ds.h>
|
|
#include <toml.h>
|
|
|
|
#ifdef EMSCRIPTEN
|
|
#include <GLES2/gl2.h>
|
|
#else
|
|
#include <glad/glad.h>
|
|
#endif
|
|
|
|
#include <stdbool.h>
|
|
#include <limits.h>
|
|
|
|
|
|
static int event_callback(void *userdata, SDL_Event *event) {
|
|
switch (event->type) {
|
|
case SDL_WINDOWEVENT:
|
|
if (event->window.windowID != ctx.window_id)
|
|
break;
|
|
|
|
switch (event->window.event) {
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
ctx.game.window_w = event->window.data1;
|
|
ctx.game.window_h = event->window.data2;
|
|
ctx.resync_flag = true;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void poll_events(void) {
|
|
SDL_Event e;
|
|
|
|
ctx.game.window_size_has_changed = false;
|
|
|
|
while (SDL_PollEvent(&e)) {
|
|
switch (e.type) {
|
|
case SDL_QUIT:
|
|
ctx.game.is_running = false;
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT:
|
|
if (e.window.windowID != ctx.window_id)
|
|
break;
|
|
|
|
switch (e.window.event) {
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
ctx.game.window_size_has_changed = true;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#ifndef EMSCRIPTEN
|
|
|
|
static void APIENTRY opengl_log(GLenum source,
|
|
GLenum type,
|
|
GLuint id,
|
|
GLenum severity,
|
|
GLsizei length,
|
|
const GLchar* message,
|
|
const void* userParam)
|
|
{
|
|
(void)source;
|
|
(void)type;
|
|
(void)id;
|
|
(void)severity;
|
|
(void)userParam;
|
|
|
|
log_info("OpenGL: %.*s\n", length, message);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
static 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;
|
|
ctx.game.delta_time = delta_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 */
|
|
int frames = 0;
|
|
while (ctx.frame_accumulator >= ctx.desired_frametime * ctx.game.update_multiplicity) {
|
|
frames += 1;
|
|
for (size_t i = 0; i < ctx.game.update_multiplicity; ++i) {
|
|
/* TODO: disable rendering pushes on not-last ? */
|
|
render_queue_clear();
|
|
|
|
poll_events();
|
|
|
|
input_state_update(&ctx.game.input);
|
|
|
|
game_object_tick();
|
|
|
|
ctx.frame_accumulator -= ctx.desired_frametime;
|
|
ctx.game.tick_count = (ctx.game.tick_count % ULLONG_MAX) + 1;
|
|
ctx.game.initialization_needed = false;
|
|
|
|
}
|
|
}
|
|
|
|
/* TODO: in some cases machine might want to assume frames will be fed as much as possible */
|
|
/* which for now is broken as glBufferData with NULL is used all over right after render */
|
|
if (frames != 0)
|
|
render();
|
|
}
|
|
|
|
|
|
/* TODO: cache and deny duplicates */
|
|
/* TODO: ability to redefine mounting point for a dependency */
|
|
/* TODO: ability to load external sources over HTTP if url is given */
|
|
static void resolve_pack_dependencies(const char *pack_name) {
|
|
char *path;
|
|
if (SDL_asprintf(&path, "/packs/%s.toml", pack_name) == -1) {
|
|
CRY("resolve_pack_dependencies()", "Allocation error");
|
|
goto ERR_PACK_MANIFEST_PATH_ALLOC_FAIL;
|
|
}
|
|
|
|
if (!PHYSFS_exists(path))
|
|
/* no package manifest provided, abort */
|
|
goto OK_NO_MANIFEST;
|
|
|
|
char *manifest_file = file_to_str(path);
|
|
if (!manifest_file) {
|
|
CRY_PHYSFS("Pack manifest file loading failed");
|
|
goto ERR_PACK_MANIFEST_FILE_LOADING;
|
|
}
|
|
|
|
char errbuf[256]; /* tomlc99 example implies that this is enough... */
|
|
toml_table_t *manifest = toml_parse(manifest_file, errbuf, sizeof errbuf);
|
|
SDL_free(manifest_file);
|
|
|
|
if (!manifest) {
|
|
CRY("Pack manifest decoding failed", errbuf);
|
|
goto ERR_PACK_MANIFEST_DECODING;
|
|
}
|
|
|
|
toml_array_t *deps = toml_array_in(manifest, "deps");
|
|
if (!deps)
|
|
goto OK_NO_DEPS;
|
|
|
|
/* iterate over entries in [[deps]] array */
|
|
/* TODO: use sub procedures for failable loops? so that error mechanism cleans well */
|
|
toml_table_t *cur_dep = toml_table_at(deps, 0);
|
|
for (int idx = 0; cur_dep; ++idx, cur_dep = toml_table_at(deps, idx)) {
|
|
toml_datum_t dep_name = toml_string_in(cur_dep, "name");
|
|
if (!dep_name.ok) {
|
|
log_warn("No 'name' is present in pack dependency, skipping it");
|
|
continue;
|
|
}
|
|
|
|
char *dep_pack_name;
|
|
if (SDL_asprintf(&dep_pack_name, "%s%s.btw", ctx.base_dir, dep_name.u.s) == -1) {
|
|
CRY("resolve_pack_dependencies()", "Allocation error");
|
|
SDL_free(dep_name.u.s);
|
|
goto ERR_DEP_PATH_ALLOC_FAIL;
|
|
}
|
|
|
|
/* try loading pack */
|
|
if (!PHYSFS_mount(dep_pack_name, "/", true)) {
|
|
/* if it failes, try going for source */
|
|
toml_datum_t dep_source = toml_string_in(cur_dep, "source");
|
|
if (!dep_source.ok) {
|
|
log_warn("No 'source' is present in pack %s, while %s.btw isn't present, skipping it", dep_name.u.s, dep_name.u.s);
|
|
SDL_free(dep_name.u.s);
|
|
SDL_free(dep_pack_name);
|
|
continue;
|
|
}
|
|
|
|
char *dep_source_path;
|
|
if (SDL_asprintf(&dep_source_path, "%s%s", ctx.base_dir, dep_source.u.s) == -1) {
|
|
CRY("resolve_pack_dependencies()", "Allocation error");
|
|
SDL_free(dep_name.u.s);
|
|
SDL_free(dep_pack_name);
|
|
goto ERR_DEP_PATH_ALLOC_FAIL;
|
|
}
|
|
|
|
if (!PHYSFS_mount(dep_source_path, "/", true))
|
|
CRY("Cannot load pack", "Nothing is given to work with");
|
|
|
|
log_info("Pack loaded: %s\n", dep_source.u.s);
|
|
|
|
SDL_free(dep_source.u.s);
|
|
SDL_free(dep_source_path);
|
|
} else
|
|
log_info("Pack loaded: %s\n", dep_pack_name);
|
|
|
|
SDL_free(dep_pack_name);
|
|
|
|
/* recursive resolution */
|
|
resolve_pack_dependencies(dep_name.u.s);
|
|
|
|
SDL_free(dep_name.u.s);
|
|
}
|
|
|
|
ERR_DEP_PATH_ALLOC_FAIL:
|
|
OK_NO_DEPS:
|
|
toml_free(manifest);
|
|
ERR_PACK_MANIFEST_DECODING:
|
|
ERR_PACK_MANIFEST_FILE_LOADING:
|
|
OK_NO_MANIFEST:
|
|
SDL_free(path);
|
|
ERR_PACK_MANIFEST_PATH_ALLOC_FAIL:
|
|
return;
|
|
}
|
|
|
|
|
|
static bool initialize(void) {
|
|
if (SDL_Init(SDL_INIT_EVERYTHING & ~SDL_INIT_HAPTIC) == -1) {
|
|
CRY_SDL("SDL initialization failed.");
|
|
return false;
|
|
}
|
|
|
|
/* first things first, most things here will be loaded from the config file */
|
|
/* it's expected to be present in the data directory, no matter what */
|
|
/* that is why PhysicsFS is initialized before anything else */
|
|
toml_set_memutil(SDL_malloc, SDL_free);
|
|
|
|
/* time to orderly resolve any dependencies present */
|
|
resolve_pack_dependencies("data");
|
|
|
|
/* load the config file into an opaque table */
|
|
{
|
|
char *config_file = file_to_str("/twn.toml");
|
|
if (config_file == NULL) {
|
|
CRY("Configuration file loading failed", "Cannot access /twn.toml");
|
|
goto fail;
|
|
}
|
|
|
|
char errbuf[256]; /* tomlc99 example implies that this is enough... */
|
|
ctx.config_table = toml_parse(config_file, errbuf, sizeof errbuf);
|
|
SDL_free(config_file);
|
|
|
|
if (ctx.config_table == NULL) {
|
|
CRY("Configuration file pasing failed", errbuf);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
/* at this point we can save references to the tables within the parent one for later */
|
|
toml_table_t *about = toml_table_in(ctx.config_table, "about");
|
|
if (about == NULL) {
|
|
CRY("Initialization failed", "[about] table expected in configuration file");
|
|
goto fail;
|
|
}
|
|
|
|
toml_table_t *game = toml_table_in(ctx.config_table, "game");
|
|
if (game == NULL) {
|
|
CRY("Initialization failed", "[game] table expected in configuration file");
|
|
goto fail;
|
|
}
|
|
|
|
toml_table_t *engine = toml_table_in(ctx.config_table, "engine");
|
|
if (engine == NULL) {
|
|
CRY("Initialization failed", "[engine] table expected in configuration file");
|
|
goto fail;
|
|
}
|
|
|
|
/* configure physicsfs write dir */
|
|
{
|
|
toml_datum_t datum_dev_id = toml_string_in(about, "dev_id");
|
|
if (!datum_dev_id.ok) {
|
|
CRY("Initialization failed", "Valid about.dev_id expected in configuration file");
|
|
goto fail;
|
|
}
|
|
toml_datum_t datum_app_id = toml_string_in(about, "app_id");
|
|
if (!datum_app_id.ok) {
|
|
CRY("Initialization failed", "Valid about.app_id expected in configuration file");
|
|
goto fail;
|
|
}
|
|
|
|
if (!PHYSFS_setSaneConfig(datum_dev_id.u.s, datum_app_id.u.s, PACKAGE_EXTENSION, false, true)) {
|
|
CRY_PHYSFS("Filesystem initialization failed");
|
|
goto fail;
|
|
}
|
|
|
|
SDL_free(datum_dev_id.u.s);
|
|
SDL_free(datum_app_id.u.s);
|
|
}
|
|
|
|
/* debug mode defaults to being enabled */
|
|
/* pass --debug or --release to force a mode, ignoring configuration */
|
|
toml_datum_t datum_debug = toml_bool_in(game, "debug");
|
|
if (!datum_debug.ok) {
|
|
ctx.game.debug = true;
|
|
} else {
|
|
ctx.game.debug = datum_debug.u.b;
|
|
}
|
|
|
|
#ifdef EMSCRIPTEN
|
|
/* emscripten interpretes those as GL ES version against WebGL */
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
|
#else
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5);
|
|
|
|
if (ctx.game.debug)
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
|
|
else
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_NO_ERROR);
|
|
#endif
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
|
|
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
|
|
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
|
|
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
|
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
|
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
|
|
|
|
/* init got far enough to create a window */
|
|
{
|
|
toml_datum_t datum_title = toml_string_in(about, "title");
|
|
if (!datum_title.ok) {
|
|
CRY("Initialization failed", "Valid about.title expected in configuration file");
|
|
goto fail;
|
|
}
|
|
/* not yet used
|
|
toml_datum_t datum_developer = toml_string_in(about, "developer");
|
|
if (!datum_developer.ok) {
|
|
CRY("Initialization failed", "Valid about.developer expected in configuration file");
|
|
goto fail;
|
|
}
|
|
*/
|
|
toml_datum_t datum_base_render_width = toml_int_in(game, "base_render_width");
|
|
if (!datum_base_render_width.ok) {
|
|
CRY("Initialization failed", "Valid game.base_render_width expected in configuration file");
|
|
goto fail;
|
|
}
|
|
ctx.base_render_width = datum_base_render_width.u.i;
|
|
ctx.game.base_draw_w = (int)ctx.base_render_width;
|
|
|
|
toml_datum_t datum_base_render_height = toml_int_in(game, "base_render_height");
|
|
if (!datum_base_render_height.ok) {
|
|
CRY("Initialization failed", "Valid game.base_render_height expected in configuration file");
|
|
goto fail;
|
|
}
|
|
ctx.base_render_height = datum_base_render_height.u.i;
|
|
ctx.game.base_draw_h = (int)ctx.base_render_height;
|
|
|
|
ctx.window = SDL_CreateWindow(datum_title.u.s,
|
|
SDL_WINDOWPOS_CENTERED,
|
|
SDL_WINDOWPOS_CENTERED,
|
|
(int)datum_base_render_width.u.i,
|
|
(int)datum_base_render_height.u.i,
|
|
//SDL_WINDOW_ALLOW_HIGHDPI |
|
|
SDL_WINDOW_RESIZABLE |
|
|
SDL_WINDOW_OPENGL);
|
|
|
|
SDL_free(datum_title.u.s);
|
|
//SDL_free(datum_developer.u.s);
|
|
}
|
|
|
|
if (ctx.window == NULL) {
|
|
CRY_SDL("Window creation failed.");
|
|
goto fail;
|
|
}
|
|
|
|
ctx.gl_context = SDL_GL_CreateContext(ctx.window);
|
|
if (!ctx.gl_context) {
|
|
CRY_SDL("GL context creation failed.");
|
|
goto fail;
|
|
}
|
|
|
|
if (SDL_GL_MakeCurrent(ctx.window, ctx.gl_context)) {
|
|
CRY_SDL("GL context binding failed.");
|
|
goto fail;
|
|
}
|
|
|
|
if (SDL_GL_SetSwapInterval(-1))
|
|
SDL_GL_SetSwapInterval(1);
|
|
|
|
#ifndef EMSCRIPTEN
|
|
if (gladLoadGL() == 0) {
|
|
CRY("Init", "GLAD failed");
|
|
goto fail;
|
|
}
|
|
#endif
|
|
|
|
log_info("OpenGL context: %s\n", glGetString(GL_VERSION));
|
|
|
|
#ifndef EMSCRIPTEN
|
|
glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);
|
|
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
|
|
glHint(GL_FOG_HINT, GL_FASTEST);
|
|
#endif
|
|
|
|
/* might need this to have multiple windows */
|
|
ctx.window_id = SDL_GetWindowID(ctx.window);
|
|
|
|
glViewport(0, 0, ctx.base_render_width, ctx.base_render_height);
|
|
|
|
/* TODO: */
|
|
// SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h);
|
|
ctx.game.window_w = (int)ctx.base_render_width;
|
|
ctx.game.window_h = (int)ctx.base_render_height;
|
|
|
|
/* add a watcher for immediate updates on window size */
|
|
SDL_AddEventWatch(event_callback, NULL);
|
|
|
|
/* audio initialization */
|
|
{
|
|
|
|
SDL_AudioSpec request, got;
|
|
SDL_zero(request);
|
|
|
|
request.freq = AUDIO_FREQUENCY;
|
|
request.format = AUDIO_F32;
|
|
request.channels = 2;
|
|
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_assert_always(got.format == AUDIO_F32);
|
|
SDL_assert_always(got.channels == 2);
|
|
|
|
SDL_PauseAudioDevice(ctx.audio_device, 0);
|
|
|
|
}
|
|
|
|
/* you could change this at runtime if you wanted */
|
|
ctx.game.update_multiplicity = 1;
|
|
|
|
#ifndef EMSCRIPTEN
|
|
/* hook up opengl debugging callback */
|
|
if (ctx.game.debug) {
|
|
glEnable(GL_DEBUG_OUTPUT);
|
|
glDebugMessageCallback(opengl_log, NULL);
|
|
}
|
|
#endif
|
|
|
|
/* random seeding */
|
|
/* SDL_GetPerformanceCounter returns some platform-dependent number. */
|
|
/* it should vary between game instances. i checked! random enough for me. */
|
|
ctx.game.random_seed = SDL_GetPerformanceCounter();
|
|
srand((unsigned int)ctx.game.random_seed);
|
|
stbds_rand_seed(ctx.game.random_seed);
|
|
|
|
/* main loop machinery */
|
|
toml_datum_t datum_ticks_per_second = toml_int_in(engine, "ticks_per_second");
|
|
if (!datum_ticks_per_second.ok) {
|
|
ctx.ticks_per_second = TICKS_PER_SECOND_DEFAULT;
|
|
} else {
|
|
if (datum_ticks_per_second.u.i < 8) {
|
|
ctx.ticks_per_second = TICKS_PER_SECOND_DEFAULT;
|
|
} else {
|
|
ctx.ticks_per_second = datum_ticks_per_second.u.i;
|
|
}
|
|
}
|
|
|
|
ctx.game.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 / ctx.ticks_per_second;
|
|
ctx.frame_accumulator = 0;
|
|
ctx.game.tick_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 */
|
|
/* configuration */
|
|
{
|
|
toml_datum_t datum_texture_atlas_size = toml_int_in(engine, "texture_atlas_size");
|
|
if (!datum_texture_atlas_size.ok) {
|
|
ctx.texture_atlas_size = TEXTURE_ATLAS_SIZE_DEFAULT;
|
|
} else {
|
|
if (datum_texture_atlas_size.u.i < 32) {
|
|
ctx.texture_atlas_size = TEXTURE_ATLAS_SIZE_DEFAULT;
|
|
} else {
|
|
ctx.texture_atlas_size = datum_texture_atlas_size.u.i;
|
|
}
|
|
}
|
|
|
|
toml_datum_t datum_font_texture_size = toml_int_in(engine, "font_texture_size");
|
|
if (!datum_font_texture_size.ok) {
|
|
ctx.font_texture_size = TEXT_FONT_TEXTURE_SIZE_DEFAULT;
|
|
} else {
|
|
if (datum_font_texture_size.u.i < 1024) {
|
|
ctx.font_texture_size = TEXT_FONT_TEXTURE_SIZE_DEFAULT;
|
|
} else {
|
|
ctx.font_texture_size = datum_font_texture_size.u.i;
|
|
}
|
|
}
|
|
|
|
toml_datum_t datum_font_oversampling = toml_int_in(engine, "font_oversampling");
|
|
if (!datum_font_oversampling.ok) {
|
|
ctx.font_oversampling = TEXT_FONT_OVERSAMPLING_DEFAULT;
|
|
} else {
|
|
if (datum_font_oversampling.u.i < 0) {
|
|
ctx.font_oversampling = TEXT_FONT_OVERSAMPLING_DEFAULT;
|
|
} else {
|
|
ctx.font_oversampling = datum_font_oversampling.u.i;
|
|
}
|
|
}
|
|
|
|
toml_datum_t datum_font_filtering = toml_string_in(engine, "font_filtering");
|
|
if (!datum_font_filtering.ok) {
|
|
ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT;
|
|
} else {
|
|
if (SDL_strcmp(datum_font_filtering.u.s, "nearest") == 0) {
|
|
ctx.font_filtering = TEXTURE_FILTER_NEAREAST;
|
|
} else if (SDL_strcmp(datum_font_filtering.u.s, "linear") == 0) {
|
|
ctx.font_filtering = TEXTURE_FILTER_LINEAR;
|
|
} else {
|
|
ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT;
|
|
}
|
|
}
|
|
SDL_free(datum_font_filtering.u.s);
|
|
}
|
|
|
|
/* these are dynamic arrays and will be allocated lazily by stb_ds */
|
|
ctx.render_queue_2d = NULL;
|
|
ctx.uncolored_mesh_batches = NULL;
|
|
|
|
textures_cache_init(&ctx.texture_cache, ctx.window);
|
|
text_cache_init(&ctx.text_cache);
|
|
|
|
/* input */
|
|
toml_datum_t datum_keybind_slots = toml_int_in(engine, "keybind_slots");
|
|
if (!datum_keybind_slots.ok) {
|
|
ctx.keybind_slots = KEYBIND_SLOTS_DEFAULT;
|
|
} else {
|
|
if (datum_keybind_slots.u.i < 1) {
|
|
ctx.keybind_slots = KEYBIND_SLOTS_DEFAULT;
|
|
} else {
|
|
ctx.keybind_slots = datum_keybind_slots.u.i;
|
|
}
|
|
}
|
|
input_state_init(&ctx.game.input);
|
|
|
|
/* 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.game.input);
|
|
text_cache_deinit(&ctx.text_cache);
|
|
textures_cache_deinit(&ctx.texture_cache);
|
|
|
|
textures_reset_state();
|
|
|
|
arrfree(ctx.render_queue_2d);
|
|
|
|
toml_free(ctx.config_table);
|
|
PHYSFS_deinit();
|
|
|
|
SDL_free(ctx.base_dir);
|
|
SDL_GL_DeleteContext(ctx.gl_context);
|
|
SDL_Quit();
|
|
}
|
|
|
|
|
|
static void reset_state(void) {
|
|
input_reset_state(&ctx.game.input);
|
|
textures_reset_state();
|
|
}
|
|
|
|
|
|
int enter_loop(int argc, char **argv) {
|
|
ctx.argc = argc;
|
|
ctx.argv = argv;
|
|
ctx.base_dir = SDL_GetBasePath();
|
|
|
|
/* needs to be done before anything else so config can be loaded */
|
|
/* TODO: ANDROID: see the warning in physicsfs PHYSFS_init docs/header */
|
|
if (!PHYSFS_init(ctx.argv[0])) {
|
|
CRY_PHYSFS("Filesystem initialization failed");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* process arguments */
|
|
bool force_debug = false;
|
|
bool force_release = false;
|
|
bool data_dir_mounted = false;
|
|
|
|
for (int i = 1; i < argc; ++i) {
|
|
/* override data directory */
|
|
if (SDL_strcmp(argv[i], "--data-dir") == 0) {
|
|
if (argv[i+1] == NULL || SDL_strncmp(argv[i+1], "--", 2) == 0) {
|
|
CRY("Data dir mount override failed.", "No arguments passed (expected 1).");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (!PHYSFS_mount(argv[i+1], NULL, true)) {
|
|
CRY_PHYSFS("Data dir mount override failed.");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
data_dir_mounted = true;
|
|
|
|
continue;
|
|
}
|
|
|
|
/* force debug mode */
|
|
if (SDL_strcmp(argv[i], "--debug") == 0) {
|
|
force_debug = true;
|
|
continue;
|
|
}
|
|
|
|
/* force release mode */
|
|
if (SDL_strcmp(argv[i], "--release") == 0) {
|
|
force_release = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* data path not explicitly specified, look into convention defined places */
|
|
if (!data_dir_mounted) {
|
|
/* try mouning data folder first, relative to executable root */
|
|
char *full_path;
|
|
SDL_asprintf(&full_path, "%sdata", ctx.base_dir);
|
|
if (!PHYSFS_mount(full_path, NULL, true)) {
|
|
SDL_free(full_path);
|
|
SDL_asprintf(&full_path, "%sdata.btw", ctx.base_dir);
|
|
if (!PHYSFS_mount(full_path, NULL, true))
|
|
SDL_free(full_path);
|
|
CRY_PHYSFS("Cannot find data.btw or data directory in root. Please create them or specify with --data-dir parameter.");
|
|
return EXIT_FAILURE;
|
|
}
|
|
SDL_free(full_path);
|
|
}
|
|
|
|
if (!initialize())
|
|
return EXIT_FAILURE;
|
|
|
|
/* good time to override anything that was set in initialize() */
|
|
if (force_debug) {
|
|
ctx.game.debug = true;
|
|
}
|
|
if (force_release) {
|
|
ctx.game.debug = false;
|
|
}
|
|
|
|
/* now we can actually start doing stuff */
|
|
game_object_load();
|
|
|
|
ctx.was_successful = true;
|
|
ctx.game.initialization_needed = true;
|
|
|
|
while (ctx.game.is_running) {
|
|
if (game_object_try_reloading()) {
|
|
ctx.game.initialization_needed = true;
|
|
reset_state();
|
|
}
|
|
|
|
main_loop();
|
|
}
|
|
|
|
/* loop is over */
|
|
game_object_unload();
|
|
|
|
clean_up();
|
|
|
|
return ctx.was_successful ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
}
|