From 450e33b2e327b8add80ab17c67e203d8d3136bdc Mon Sep 17 00:00:00 2001 From: Sebastian Krzyszkowiak Date: Sat, 9 Sep 2017 00:11:43 +0200 Subject: [PATCH] support of loading in separate thread; thread-safe console; various fixes from clang code model warnings --- cmake/libsuperderpy.cmake | 4 + src/gamestate.c | 20 +--- src/internal.c | 153 +++++++++++++++++++++++------- src/internal.h | 13 +++ src/libsuperderpy.c | 189 +++++++++++++++++--------------------- src/libsuperderpy.h | 46 ++++++---- src/timeline.c | 6 +- src/utils.c | 63 +++++-------- src/utils.h | 3 + 9 files changed, 285 insertions(+), 212 deletions(-) diff --git a/cmake/libsuperderpy.cmake b/cmake/libsuperderpy.cmake index b10cc1e..2106cec 100644 --- a/cmake/libsuperderpy.cmake +++ b/cmake/libsuperderpy.cmake @@ -71,6 +71,10 @@ if(EMSCRIPTEN) endif() +if(ANDROID OR EMSCRIPTEN) + add_definitions(-DLIBSUPERDERPY_SINGLE_THREAD=1) +endif() + find_package(Allegro5 REQUIRED) find_package(Allegro5Font REQUIRED) find_package(Allegro5TTF REQUIRED) diff --git a/src/gamestate.c b/src/gamestate.c index 75944e7..8a205d7 100644 --- a/src/gamestate.c +++ b/src/gamestate.c @@ -22,34 +22,22 @@ #include "utils.h" #include "gamestate.h" -SYMBOL_INTERNAL struct Gamestate* AddNewGamestate(struct Game *game, const char* name) { +static struct Gamestate* AddNewGamestate(struct Game *game, const char* name) { struct Gamestate *tmp = game->_priv.gamestates; if (!tmp) { - game->_priv.gamestates = malloc(sizeof(struct Gamestate)); + game->_priv.gamestates = AllocateGamestate(game, name); tmp = game->_priv.gamestates; } else { while (tmp->next) { tmp = tmp->next; } - tmp->next = malloc(sizeof(struct Gamestate)); + tmp->next = AllocateGamestate(game, name); tmp = tmp->next; } - tmp->name = strdup(name); - tmp->handle = NULL; - tmp->loaded = false; - tmp->paused = false; - tmp->frozen = false; - tmp->started = false; - tmp->pending_load = false; - tmp->pending_start = false; - tmp->pending_stop = false; - tmp->pending_unload = false; - tmp->next = NULL; - tmp->api = NULL; return tmp; } -SYMBOL_INTERNAL struct Gamestate* FindGamestate(struct Game *game, const char* name) { +static struct Gamestate* FindGamestate(struct Game *game, const char* name) { struct Gamestate *tmp = game->_priv.gamestates; while (tmp) { if (!strcmp(name, tmp->name)) { diff --git a/src/internal.c b/src/internal.c index 00c0d23..b07ed79 100644 --- a/src/internal.c +++ b/src/internal.c @@ -18,9 +18,11 @@ */ #include +#include #include #include "internal.h" #include "libsuperderpy.h" +#include "3rdparty/valgrind.h" SYMBOL_INTERNAL void DrawGamestates(struct Game *game) { ClearScreen(game); @@ -99,22 +101,40 @@ SYMBOL_INTERNAL void DrawConsole(struct Game *game) { al_get_clipping_rectangle(&clipX, &clipY, &clipWidth, &clipHeight); al_use_transform(&trans); - al_draw_bitmap(game->_priv.console, clipX, clipY, 0); - double game_time = al_get_time(); - if(game_time - game->_priv.fps_count.old_time >= 1.0) { - game->_priv.fps_count.fps = game->_priv.fps_count.frames_done / (game_time - game->_priv.fps_count.old_time); - game->_priv.fps_count.frames_done = 0; - game->_priv.fps_count.old_time = game_time; + int width = (al_get_display_width(game->display) / game->viewport.width) * game->viewport.width; + if (!game->viewport.integer_scaling) { + width = (al_get_display_width(game->display) / (float)game->viewport.width) * game->viewport.width; } + + int size = sizeof(game->_priv.console) / sizeof(game->_priv.console[0]); + for (int i=0; i_priv.font_console)*(size-i), al_map_rgba(0,0,0,80)); + } + int cur = game->_priv.console_pos + size; + for (int i=0; i= size) { + cur -= size; + } + al_draw_text(game->_priv.font_console, al_map_rgb(255,255,255), clipX + (int)(game->viewport.width*0.005), clipY + al_get_font_line_height(game->_priv.font_console)*i, ALLEGRO_ALIGN_LEFT, game->_priv.console[cur]); + cur++; + } + char sfps[6] = { }; snprintf(sfps, 6, "%.0f", game->_priv.fps_count.fps); DrawTextWithShadow(game->_priv.font_console, al_map_rgb(255,255,255), clipX + clipWidth, clipY, ALLEGRO_ALIGN_RIGHT, sfps); al_use_transform(&game->projection); + DrawTimelines(game); + } + + double game_time = al_get_time(); + if (game_time - game->_priv.fps_count.old_time >= 1.0) { + game->_priv.fps_count.fps = game->_priv.fps_count.frames_done / (game_time - game->_priv.fps_count.old_time); + game->_priv.fps_count.frames_done = 0; + game->_priv.fps_count.old_time = game_time; } game->_priv.fps_count.frames_done++; - DrawTimelines(game); } SYMBOL_INTERNAL void Console_Load(struct Game *game) { @@ -124,35 +144,106 @@ SYMBOL_INTERNAL void Console_Load(struct Game *game) { } else { game->_priv.font_bsod = al_load_ttf_font(GetDataFilePath(game, "fonts/DejaVuSansMono.ttf"), al_get_display_height(game->display)*0.025,0 ); } - int width = (al_get_display_width(game->display) / game->viewport.width) * game->viewport.width; - if (!game->viewport.integer_scaling) { - width = (al_get_display_width(game->display) / (float)game->viewport.width) * game->viewport.width; - } - game->_priv.console = CreateNotPreservedBitmap(width, al_get_font_line_height(game->_priv.font_console)*5); - game->_priv.console_tmp = CreateNotPreservedBitmap(width, al_get_font_line_height(game->_priv.font_console)*5); - al_set_target_bitmap(game->_priv.console); - al_clear_to_color(al_map_rgba(0,0,0,80)); - al_set_target_bitmap(al_get_backbuffer(game->display)); } SYMBOL_INTERNAL void Console_Unload(struct Game *game) { - al_destroy_font(game->_priv.font_console); - al_destroy_bitmap(game->_priv.console); - al_destroy_bitmap(game->_priv.console_tmp); + if (game->_priv.font_console) { + al_destroy_font(game->_priv.font_console); + } +} + +SYMBOL_INTERNAL void* GamestateLoadingThread(void *arg) { + struct GamestateLoadingThreadData *data = arg; + data->game->_priv.loading.inProgress = true; + al_set_new_bitmap_flags(data->bitmap_flags); + GamestateProgress(data->game); + data->gamestate->data = (*data->gamestate->api->Gamestate_Load)(data->game, &GamestateProgress); + data->bitmap_flags = al_get_new_bitmap_flags(); + data->game->_priv.loading.inProgress = false; + return NULL; } SYMBOL_INTERNAL void GamestateProgress(struct Game *game) { - struct Gamestate *tmp = game->_priv.tmp_gamestate.tmp; - game->_priv.tmp_gamestate.p++; - DrawGamestates(game); + struct Gamestate *tmp = game->_priv.loading.current; + game->_priv.loading.progress++; float progressCount = *(tmp->api->Gamestate_ProgressCount) ? (float)*(tmp->api->Gamestate_ProgressCount) : 1; - float progress = ((game->_priv.tmp_gamestate.p / progressCount) / (float)game->_priv.tmp_gamestate.toLoad) + (game->_priv.tmp_gamestate.loaded/(float)game->_priv.tmp_gamestate.toLoad); - if (game->config.debug) PrintConsole(game, "[%s] Progress: %d% (%d/%d)", tmp->name, (int)(progress*100), game->_priv.tmp_gamestate.p, *(tmp->api->Gamestate_ProgressCount)); - if (tmp->showLoading) (*game->_priv.loading.Draw)(game, game->_priv.loading.data, progress); + float progress = ((game->_priv.loading.progress / progressCount) / (float)game->_priv.loading.toLoad) + (game->_priv.loading.loaded/(float)game->_priv.loading.toLoad); + game->loading_progress = progress; + if (game->config.debug) PrintConsole(game, "[%s] Progress: %d% (%d/%d)", tmp->name, (int)(progress*100), game->_priv.loading.progress, *(tmp->api->Gamestate_ProgressCount)); +#ifdef LIBSUPERDERPY_SINGLE_THREAD + DrawGamestates(game); + if (tmp->showLoading) (*game->_priv.loading.gamestate->api->Gamestate_Draw)(game, game->_priv.loading.gamestate->data); DrawConsole(game); - if (al_get_time() - game->_priv.tmp_gamestate.t >= 1/60.0) { - al_flip_display(); - game->_priv.tmp_gamestate.t = al_get_time(); + al_flip_display(); +#endif +} + +SYMBOL_INTERNAL bool OpenGamestate(struct Game *game, struct Gamestate *gamestate) { + PrintConsole(game, "Opening gamestate \"%s\"...", gamestate->name); + char libname[1024]; + snprintf(libname, 1024, "libsuperderpy-%s-%s" LIBRARY_EXTENSION, game->name, gamestate->name); + gamestate->handle = dlopen(libname, RTLD_NOW); + if (!gamestate->handle) { + FatalError(game, false, "Error while opening gamestate \"%s\": %s", gamestate->name, dlerror()); // TODO: move out + return false; + } + return true; +} + +SYMBOL_INTERNAL bool LinkGamestate(struct Game *game, struct Gamestate *gamestate) { + gamestate->api = malloc(sizeof(struct Gamestate_API)); + +#define GS_ERROR FatalError(game, false, "Error on resolving gamestate's %s symbol: %s", gamestate->name, dlerror()); /* TODO: move out */ \ + free(gamestate->api); \ + return false; + + if (!(gamestate->api->Gamestate_Draw = dlsym(gamestate->handle, "Gamestate_Draw"))) { GS_ERROR; } + if (!(gamestate->api->Gamestate_Logic = dlsym(gamestate->handle, "Gamestate_Logic"))) { GS_ERROR; } + + if (!(gamestate->api->Gamestate_Load = dlsym(gamestate->handle, "Gamestate_Load"))) { GS_ERROR; } + if (!(gamestate->api->Gamestate_Start = dlsym(gamestate->handle, "Gamestate_Start"))) { GS_ERROR; } + if (!(gamestate->api->Gamestate_Pause = dlsym(gamestate->handle, "Gamestate_Pause"))) { GS_ERROR; } + if (!(gamestate->api->Gamestate_Resume = dlsym(gamestate->handle, "Gamestate_Resume"))) { GS_ERROR; } + if (!(gamestate->api->Gamestate_Stop = dlsym(gamestate->handle, "Gamestate_Stop"))) { GS_ERROR; } + if (!(gamestate->api->Gamestate_Unload = dlsym(gamestate->handle, "Gamestate_Unload"))) { GS_ERROR; } + + if (!(gamestate->api->Gamestate_ProcessEvent = dlsym(gamestate->handle, "Gamestate_ProcessEvent"))) { GS_ERROR; } + if (!(gamestate->api->Gamestate_Reload = dlsym(gamestate->handle, "Gamestate_Reload"))) { GS_ERROR; } + + if (!(gamestate->api->Gamestate_ProgressCount = dlsym(gamestate->handle, "Gamestate_ProgressCount"))) { GS_ERROR; } + +#undef GS_ERROR + + return true; +} + +SYMBOL_INTERNAL struct Gamestate* AllocateGamestate(struct Game *game, const char* name) { + struct Gamestate *tmp = malloc(sizeof(struct Gamestate)); + tmp->name = strdup(name); + tmp->handle = NULL; + tmp->loaded = false; + tmp->paused = false; + tmp->frozen = false; + tmp->started = false; + tmp->pending_load = false; + tmp->pending_start = false; + tmp->pending_stop = false; + tmp->pending_unload = false; + tmp->next = NULL; + tmp->api = NULL; + return tmp; +} + +SYMBOL_INTERNAL void CloseGamestate(struct Game *game, struct Gamestate *gamestate) { + if (gamestate->handle && !RUNNING_ON_VALGRIND) { +#ifndef LEAK_SANITIZER + PrintConsole(game, "Closing gamestate \"%s\"...", gamestate->name); + dlclose(gamestate->handle); +#endif + } + free(gamestate->name); + if (gamestate->api) { + free(gamestate->api); } } @@ -245,7 +336,7 @@ SYMBOL_INTERNAL void ClearScreen(struct Game *game) { al_set_clipping_rectangle(clipX, clipY, clipWidth, clipHeight); } -SYMBOL_INTERNAL void DrawQueue(struct Game *game, struct TM_Action* queue, int clipX, int clipY) { +static void DrawQueue(struct Game *game, struct TM_Action* queue, int clipX, int clipY) { int pos = clipX; @@ -270,7 +361,7 @@ SYMBOL_INTERNAL void DrawQueue(struct Game *game, struct TM_Action* queue, int c } } -SYMBOL_INTERNAL void DrawTimeline(struct Game *game, struct Timeline* timeline, int pos) { +static void DrawTimeline(struct Game *game, struct Timeline* timeline, int pos) { al_set_target_backbuffer(game->display); ALLEGRO_TRANSFORM trans; al_identity_transform(&trans); @@ -289,7 +380,7 @@ SYMBOL_INTERNAL void DrawTimeline(struct Game *game, struct Timeline* timeline, } SYMBOL_INTERNAL void DrawTimelines(struct Game *game) { - if ((!game->_priv.showconsole) || (!game->_priv.showtimeline)) { + if (!game->_priv.showtimeline) { return; } struct libsuperderpy_list *tmp = game->_priv.timelines; diff --git a/src/internal.h b/src/internal.h index bcffb8c..3cbd3a6 100644 --- a/src/internal.h +++ b/src/internal.h @@ -42,6 +42,12 @@ struct libsuperderpy_list { struct libsuperderpy_list *next; }; +struct GamestateLoadingThreadData { + struct Game *game; + struct Gamestate *gamestate; + int bitmap_flags; +}; + void DrawGamestates(struct Game *game); void LogicGamestates(struct Game *game); void EventGamestates(struct Game *game, ALLEGRO_EVENT *ev); @@ -51,12 +57,19 @@ void UnfreezeGamestates(struct Game *game); void DrawConsole(struct Game *game); void Console_Load(struct Game *game); void Console_Unload(struct Game *game); +void* GamestateLoadingThread(void *arg); void GamestateProgress(struct Game *game); void* AddGarbage(struct Game *game, void* data); void ClearGarbage(struct Game *game); void ClearScreen(struct Game *game); +struct libsuperderpy_list* AddToList(struct libsuperderpy_list *list, void* data); +struct libsuperderpy_list* RemoveFromList(struct libsuperderpy_list **list, bool (*identity)(struct libsuperderpy_list* elem, void* data), void* data); void AddTimeline(struct Game *game, struct Timeline *timeline); void RemoveTimeline(struct Game *game, struct Timeline *timeline); void DrawTimelines(struct Game *game); +bool OpenGamestate(struct Game *game, struct Gamestate *gamestate); +bool LinkGamestate(struct Game *game, struct Gamestate *gamestate); +void CloseGamestate(struct Game *game, struct Gamestate *gamestate); +struct Gamestate* AllocateGamestate(struct Game *game, const char* name); #endif diff --git a/src/libsuperderpy.c b/src/libsuperderpy.c index be7e82f..3013d13 100644 --- a/src/libsuperderpy.c +++ b/src/libsuperderpy.c @@ -34,7 +34,6 @@ #include #include "internal.h" #include "libsuperderpy.h" -#include "3rdparty/valgrind.h" #ifdef ALLEGRO_MACOSX #include #endif @@ -67,15 +66,18 @@ SYMBOL_EXPORT struct Game* libsuperderpy_init(int argc, char** argv, const char* game->_priv.fps_count.fps = 0; game->_priv.fps_count.old_time = 0; + game->_priv.font_console = NULL; game->_priv.font_bsod = NULL; - game->_priv.console = NULL; - game->_priv.console_tmp = NULL; + game->_priv.console_pos = 0; + for (unsigned int i=0; i<(sizeof(game->_priv.console)/sizeof(game->_priv.console[0])); i++) { + game->_priv.console[i][0] = '\0'; + } game->_priv.garbage = NULL; game->_priv.timelines = NULL; - game->eventHandler = NULL; - game->destroyHandler = NULL; + game->handlers.event = NULL; + game->handlers.destroy = NULL; game->config.fullscreen = atoi(GetConfigOptionDefault(game, "SuperDerpy", "fullscreen", "1")); game->config.music = atoi(GetConfigOptionDefault(game, "SuperDerpy", "music", "10")); @@ -143,7 +145,7 @@ SYMBOL_EXPORT struct Game* libsuperderpy_init(int argc, char** argv, const char* al_install_joystick(); - al_set_new_display_flags(ALLEGRO_PROGRAMMABLE_PIPELINE | (game->config.fullscreen ? ALLEGRO_FULLSCREEN_WINDOW : ALLEGRO_WINDOWED) | ALLEGRO_RESIZABLE | ALLEGRO_OPENGL ); // TODO: make ALLEGRO_PROGRAMMABLE_PIPELINE game-optional + al_set_new_display_flags(ALLEGRO_OPENGL_ES_PROFILE | ALLEGRO_PROGRAMMABLE_PIPELINE | (game->config.fullscreen ? ALLEGRO_FULLSCREEN_WINDOW : ALLEGRO_WINDOWED) | ALLEGRO_RESIZABLE | ALLEGRO_OPENGL); #ifdef __EMSCRIPTEN__ al_set_new_display_flags((al_get_new_display_flags() | ALLEGRO_WINDOWED) ^ ALLEGRO_FULLSCREEN_WINDOW); #endif @@ -183,7 +185,7 @@ SYMBOL_EXPORT struct Game* libsuperderpy_init(int argc, char** argv, const char* if (game->config.fullscreen) al_hide_mouse_cursor(game->display); al_inhibit_screensaver(true); - al_set_new_bitmap_flags(ALLEGRO_MIN_LINEAR); + al_add_new_bitmap_flag(ALLEGRO_MIN_LINEAR | ALLEGRO_MAG_LINEAR); game->_priv.gamestates = NULL; game->_priv.gamestate_scheduled = false; @@ -197,9 +199,15 @@ SYMBOL_EXPORT struct Game* libsuperderpy_init(int argc, char** argv, const char* return NULL; } - game->audio.v = al_create_voice(44100, ALLEGRO_AUDIO_DEPTH_FLOAT32, ALLEGRO_CHANNEL_CONF_2); + ALLEGRO_AUDIO_DEPTH depth = ALLEGRO_AUDIO_DEPTH_FLOAT32; +#ifdef ALLEGRO_ANDROID + depth = ALLEGRO_AUDIO_DEPTH_INT16; +#endif + game->audio.v = al_create_voice(44100, depth, ALLEGRO_CHANNEL_CONF_2); if (!game->audio.v) { - game->audio.v = al_create_voice(44100, ALLEGRO_AUDIO_DEPTH_INT16, ALLEGRO_CHANNEL_CONF_2); + // fallback + depth = (depth == ALLEGRO_AUDIO_DEPTH_FLOAT32) ? ALLEGRO_AUDIO_DEPTH_INT16 : ALLEGRO_AUDIO_DEPTH_FLOAT32; + game->audio.v = al_create_voice(44100, depth, ALLEGRO_CHANNEL_CONF_2); } game->audio.mixer = al_create_mixer(44100, ALLEGRO_AUDIO_DEPTH_FLOAT32, ALLEGRO_CHANNEL_CONF_2); game->audio.fx = al_create_mixer(44100, ALLEGRO_AUDIO_DEPTH_FLOAT32, ALLEGRO_CHANNEL_CONF_2); @@ -220,11 +228,13 @@ SYMBOL_EXPORT struct Game* libsuperderpy_init(int argc, char** argv, const char* game->data = NULL; - game->shuttingdown = false; + game->shutting_down = false; game->restart = false; game->show_loading_on_launch = false; + game->loading_progress = 0; + return game; } @@ -259,24 +269,13 @@ SYMBOL_EXPORT int libsuperderpy_run(struct Game *game) { tmp = tmp->next; } - char libname[1024] = {}; - snprintf(libname, 1024, "libsuperderpy-%s-loading" LIBRARY_EXTENSION, game->name); - void *handle = dlopen(libname, RTLD_NOW); - if (!handle) { - FatalError(game, true, "Error while initializing loading screen: %s", dlerror()); + game->_priv.loading.gamestate = AllocateGamestate(game, "loading"); + if (!OpenGamestate(game, game->_priv.loading.gamestate) || !LinkGamestate(game, game->_priv.loading.gamestate)) { + // TODO: support loading-less scenario return 2; - } else { - -#define GS_LOADINGERROR FatalError(game, true, "Error on resolving loading symbol: %s", dlerror()); return 3; - - if (!(game->_priv.loading.Draw = dlsym(handle, "Draw"))) { GS_LOADINGERROR; } - if (!(game->_priv.loading.Load = dlsym(handle, "Load"))) { GS_LOADINGERROR; } - if (!(game->_priv.loading.Start = dlsym(handle, "Start"))) { GS_LOADINGERROR; } - if (!(game->_priv.loading.Stop = dlsym(handle, "Stop"))) { GS_LOADINGERROR; } - if (!(game->_priv.loading.Unload = dlsym(handle, "Unload"))) { GS_LOADINGERROR; } } - - game->_priv.loading.data = (*game->_priv.loading.Load)(game); + game->_priv.loading.gamestate->data = (*game->_priv.loading.gamestate->api->Gamestate_Load)(game, NULL); + PrintConsole(game, "Loading screen registered."); game->_priv.draw = true; @@ -311,10 +310,11 @@ SYMBOL_INTERNAL void libsuperderpy_mainloop(void *g) { game->_priv.gamestate_scheduled = false; struct Gamestate *tmp = game->_priv.gamestates; - game->_priv.tmp_gamestate.toLoad = 0; - game->_priv.tmp_gamestate.loaded = 0; + game->_priv.loading.toLoad = 0; + game->_priv.loading.loaded = 0; + game->loading_progress = 0; - // TODO: support gamestate dependences + // TODO: support gamestate dependences/ordering while (tmp) { if (tmp->pending_stop) { PrintConsole(game, "Stopping gamestate \"%s\"...", tmp->name); @@ -324,14 +324,12 @@ SYMBOL_INTERNAL void libsuperderpy_mainloop(void *g) { tmp->pending_stop = false; } - if (tmp->pending_load) game->_priv.tmp_gamestate.toLoad++; + if (tmp->pending_load) game->_priv.loading.toLoad++; tmp=tmp->next; } tmp = game->_priv.gamestates; - game->_priv.tmp_gamestate.t = -1; - while (tmp) { if (tmp->pending_unload) { PrintConsole(game, "Unloading gamestate \"%s\"...", tmp->name); @@ -344,60 +342,48 @@ SYMBOL_INTERNAL void libsuperderpy_mainloop(void *g) { } if (tmp->pending_load) { al_stop_timer(game->_priv.timer); + if (tmp->showLoading) (*game->_priv.loading.gamestate->api->Gamestate_Start)(game, game->_priv.loading.gamestate->data); if (!tmp->api) { - PrintConsole(game, "Opening gamestate \"%s\"...", tmp->name); - char libname[1024]; - snprintf(libname, 1024, "libsuperderpy-%s-%s" LIBRARY_EXTENSION, game->name, tmp->name); - tmp->handle = dlopen(libname,RTLD_NOW); - if (!tmp->handle) { - FatalError(game, false, "Error while opening gamestate \"%s\": %s", tmp->name, dlerror()); - + if (!OpenGamestate(game, tmp) || !LinkGamestate(game, tmp)) { tmp->pending_load = false; tmp->pending_start = false; - } else { - - tmp->api = malloc(sizeof(struct Gamestate_API)); - -#define GS_ERROR FatalError(game, false, "Error on resolving gamestate symbol: %s", dlerror()); tmp->pending_load = false; tmp->pending_start = false; tmp=tmp->next; continue; - - if (!(tmp->api->Gamestate_Draw = dlsym(tmp->handle, "Gamestate_Draw"))) { GS_ERROR; } - if (!(tmp->api->Gamestate_Logic = dlsym(tmp->handle, "Gamestate_Logic"))) { GS_ERROR; } - - if (!(tmp->api->Gamestate_Load = dlsym(tmp->handle, "Gamestate_Load"))) { GS_ERROR; } - if (!(tmp->api->Gamestate_Start = dlsym(tmp->handle, "Gamestate_Start"))) { GS_ERROR; } - if (!(tmp->api->Gamestate_Pause = dlsym(tmp->handle, "Gamestate_Pause"))) { GS_ERROR; } - if (!(tmp->api->Gamestate_Resume = dlsym(tmp->handle, "Gamestate_Resume"))) { GS_ERROR; } - if (!(tmp->api->Gamestate_Stop = dlsym(tmp->handle, "Gamestate_Stop"))) { GS_ERROR; } - if (!(tmp->api->Gamestate_Unload = dlsym(tmp->handle, "Gamestate_Unload"))) { GS_ERROR; } - - if (!(tmp->api->Gamestate_ProcessEvent = dlsym(tmp->handle, "Gamestate_ProcessEvent"))) { GS_ERROR; } - if (!(tmp->api->Gamestate_Reload = dlsym(tmp->handle, "Gamestate_Reload"))) { GS_ERROR; } - - if (!(tmp->api->Gamestate_ProgressCount = dlsym(tmp->handle, "Gamestate_ProgressCount"))) { GS_ERROR; } + tmp->next = tmp; + continue; } } if (tmp->api) { PrintConsole(game, "Loading gamestate \"%s\"...", tmp->name); - game->_priv.tmp_gamestate.p = 0; + game->_priv.loading.progress = -1; - DrawGamestates(game); - if (tmp->showLoading) { - (*game->_priv.loading.Draw)(game, game->_priv.loading.data, game->_priv.tmp_gamestate.loaded/(float)game->_priv.tmp_gamestate.toLoad); - } - DrawConsole(game); - if (al_get_time() - game->_priv.tmp_gamestate.t >= 1/60.0) { - al_flip_display(); - game->_priv.tmp_gamestate.t = al_get_time(); - } - game->_priv.tmp_gamestate.tmp = tmp; + game->_priv.loading.current = tmp; game->_priv.current_gamestate = tmp; - tmp->data = (*tmp->api->Gamestate_Load)(game, &GamestateProgress); - game->_priv.tmp_gamestate.loaded++; + + struct GamestateLoadingThreadData data = { .game = game, .gamestate = tmp, .bitmap_flags = al_get_new_bitmap_flags() }; + game->_priv.loading.inProgress = true; + +#ifndef LIBSUPERDERPY_SINGLE_THREAD + al_run_detached_thread(GamestateLoadingThread, &data); + while (game->_priv.loading.inProgress) { + DrawGamestates(game); + if (tmp->showLoading) (*game->_priv.loading.gamestate->api->Gamestate_Draw)(game, game->_priv.loading.gamestate->data); + DrawConsole(game); + al_flip_display(); + } +#else + GamestateLoadingThread(&data); +#endif + + al_set_new_bitmap_flags(data.bitmap_flags); + // TODO: compile shaders + al_convert_memory_bitmaps(); + game->_priv.loading.loaded++; tmp->loaded = true; tmp->pending_load = false; + } + if (tmp->showLoading) (*game->_priv.loading.gamestate->api->Gamestate_Stop)(game, game->_priv.loading.gamestate->data); al_resume_timer(game->_priv.timer); } @@ -430,6 +416,8 @@ SYMBOL_INTERNAL void libsuperderpy_mainloop(void *g) { break; } + al_convert_memory_bitmaps(); + DrawGamestates(game); DrawConsole(game); al_flip_display(); @@ -445,8 +433,8 @@ SYMBOL_INTERNAL void libsuperderpy_mainloop(void *g) { al_wait_for_event(game->_priv.event_queue, &ev); - if (game->eventHandler) { - if ((*game->eventHandler)(game, &ev)) { + if (game->handlers.event) { + if ((*game->handlers.event)(game, &ev)) { continue; } } @@ -467,13 +455,11 @@ SYMBOL_INTERNAL void libsuperderpy_mainloop(void *g) { al_stop_timer(game->_priv.timer); al_detach_voice(game->audio.v); FreezeGamestates(game); - if (game->_priv.console) Console_Unload(game); al_acknowledge_drawing_halt(game->display); } else if(ev.type == ALLEGRO_EVENT_DISPLAY_RESUME_DRAWING) { game->_priv.draw = true; al_acknowledge_drawing_resume(game->display); - Console_Load(game); PrintConsole(game, "Engine resumed."); ReloadGamestates(game); UnfreezeGamestates(game); @@ -529,7 +515,7 @@ SYMBOL_INTERNAL void libsuperderpy_mainloop(void *g) { } #ifndef __EMSCRIPTEN__ - if (game->destroyHandler) { + if (game->handlers.destroy) { libsuperderpy_destroy(game); } return 0; @@ -537,7 +523,7 @@ SYMBOL_INTERNAL void libsuperderpy_mainloop(void *g) { } SYMBOL_EXPORT void libsuperderpy_destroy(struct Game *game) { - game->shuttingdown = true; + game->shutting_down = true; ClearGarbage(game); @@ -555,41 +541,37 @@ SYMBOL_EXPORT void libsuperderpy_destroy(struct Game *game) { (*tmp->api->Gamestate_Unload)(game, tmp->data); tmp->loaded = false; } - if (tmp->handle && !RUNNING_ON_VALGRIND) { -#ifndef LEAK_SANITIZER - PrintConsole(game, "Closing gamestate \"%s\"...", tmp->name); - dlclose(tmp->handle); -#endif - } - free(tmp->name); - if (tmp->api) { - free(tmp->api); - } + CloseGamestate(game, tmp); pom = tmp->next; free(tmp); tmp=pom; } - if (game->destroyHandler) { - (*game->destroyHandler)(game); + (*game->_priv.loading.gamestate->api->Gamestate_Unload)(game, game->_priv.loading.gamestate->data); + CloseGamestate(game, game->_priv.loading.gamestate); + free(game->_priv.loading.gamestate); + + if (game->handlers.destroy) { + (*game->handlers.destroy)(game); } ClearScreen(game); #ifdef __EMSCRIPTEN__ -{ - ALLEGRO_BITMAP *bmp = al_create_bitmap(320, 180); - al_set_target_bitmap(bmp); - al_clear_to_color(al_map_rgb(0,0,0)); - ALLEGRO_FONT *font = al_create_builtin_font(); - al_draw_text(font, al_map_rgb(228,127,59), 320/2, 180/2 - 8 - 6, ALLEGRO_ALIGN_CENTER, "It's now safe to turn off"); - al_draw_text(font, al_map_rgb(228,127,59), 320/2, 180/2 - 8 + 6, ALLEGRO_ALIGN_CENTER, "your browser."); - al_set_target_backbuffer(game->display); - al_draw_scaled_bitmap(bmp, 0, 0, 320, 180, 0, -game->viewport.height*0.2, game->viewport.width, game->viewport.height*1.4, 0); - al_flip_display(); - al_destroy_bitmap(bmp); - al_destroy_font(font); -} + { + ALLEGRO_BITMAP *bmp = al_create_bitmap(320, 180); + al_set_target_bitmap(bmp); + al_clear_to_color(al_map_rgb(0,0,0)); + ALLEGRO_FONT *font = al_create_builtin_font(); + al_draw_text(font, al_map_rgb(228,127,59), 320/2, 180/2 - 8 - 6, ALLEGRO_ALIGN_CENTER, "It's now safe to turn off"); + al_draw_text(font, al_map_rgb(228,127,59), 320/2, 180/2 - 8 + 6, ALLEGRO_ALIGN_CENTER, "your browser."); + al_set_target_backbuffer(game->display); + al_draw_scaled_bitmap(bmp, 0, 0, 320, 180, 0, -game->viewport.height*0.2, game->viewport.width, game->viewport.height*1.4, 0); + al_flip_display(); + al_destroy_bitmap(bmp); + al_destroy_font(font); + } #endif + PrintConsole(game, "Shutting down..."); DrawConsole(game); al_flip_display(); @@ -597,7 +579,6 @@ SYMBOL_EXPORT void libsuperderpy_destroy(struct Game *game) { free(game->_priv.garbage->data); game->_priv.garbage = game->_priv.garbage->next; } - (*game->_priv.loading.Unload)(game, game->_priv.loading.data); al_destroy_timer(game->_priv.timer); Console_Unload(game); al_destroy_display(game->display); diff --git a/src/libsuperderpy.h b/src/libsuperderpy.h index cbc6518..3ea1d11 100644 --- a/src/libsuperderpy.h +++ b/src/libsuperderpy.h @@ -87,8 +87,8 @@ struct Game { bool gamestate_scheduled; /*!< Whether there's some gamestate lifecycle management work to do. */ ALLEGRO_FONT *font_console; /*!< Font used in game console. */ ALLEGRO_FONT *font_bsod; /*!< Font used in Blue Screens of Derp. */ - ALLEGRO_BITMAP *console; /*!< Bitmap with game console. */ - ALLEGRO_BITMAP *console_tmp; /*!< Bitmap used for drawing game console. */ + char console[5][1024]; + unsigned int console_pos; ALLEGRO_EVENT_QUEUE *event_queue; /*!< Main event queue. */ ALLEGRO_TIMER *timer; /*!< Main LPS (logic) timer. */ bool showconsole; /*!< If true, game console is rendered on screen. */ @@ -101,25 +101,16 @@ struct Game { ALLEGRO_CONFIG *config; /*!< Configuration file interface. */ - struct { - void (*Draw)(struct Game *game, void* data, float p); - void* (*Load)(struct Game *game); - void (*Start)(struct Game *game, void* data); - void (*Stop)(struct Game *game, void* data); - void (*Unload)(struct Game *game, void* data); - - void* data; - } loading; /*!< Interface for accessing loading screen functions. */ - int argc; char** argv; struct { - int p; - struct Gamestate *tmp; - double t; + struct Gamestate *gamestate; + struct Gamestate *current; + int progress; int loaded, toLoad; - } tmp_gamestate; + bool inProgress; + } loading; struct Gamestate *current_gamestate; @@ -133,7 +124,7 @@ struct Game { } _priv; /*!< Private resources. Do not use in gamestates! */ - bool shuttingdown; /*!< If true then shut down of the game is pending. */ + bool shutting_down; /*!< If true then shut down of the game is pending. */ bool restart; /*!< If true then restart of the game is pending. */ bool touch; @@ -143,8 +134,12 @@ struct Game { ALLEGRO_EVENT_SOURCE event_source; - bool (*eventHandler)(struct Game *game, ALLEGRO_EVENT *ev); - void (*destroyHandler)(struct Game *game); + float loading_progress; + + struct { + bool (*event)(struct Game *game, ALLEGRO_EVENT *ev); + void (*destroy)(struct Game *game); + } handlers; LIBSUPERDERPY_DATA_TYPE *data; @@ -154,4 +149,17 @@ struct Game* libsuperderpy_init(int argc, char **argv, const char* name, struct int libsuperderpy_run(struct Game* game); void libsuperderpy_destroy(struct Game* game); +struct GamestateResources; +extern int Gamestate_ProgressCount; +void Gamestate_ProcessEvent(struct Game *game, struct GamestateResources *data, ALLEGRO_EVENT *ev); +void Gamestate_Logic(struct Game *game, struct GamestateResources *data); +void Gamestate_Draw(struct Game *game, struct GamestateResources *data); +void* Gamestate_Load(struct Game *game, void (*progress)(struct Game*)); +void Gamestate_Unload(struct Game *game, struct GamestateResources *data); +void Gamestate_Start(struct Game *game, struct GamestateResources *data); +void Gamestate_Stop(struct Game *game, struct GamestateResources *data); +void Gamestate_Reload(struct Game *game, struct GamestateResources* data); +void Gamestate_Pause(struct Game *game, struct GamestateResources* data); +void Gamestate_Resume(struct Game *game, struct GamestateResources* data); + #endif diff --git a/src/timeline.c b/src/timeline.c index 16081a3..9469742 100644 --- a/src/timeline.c +++ b/src/timeline.c @@ -128,7 +128,7 @@ SYMBOL_EXPORT void TM_Process(struct Timeline* timeline) { } } -SYMBOL_INTERNAL void PauseTimers(struct Timeline* timeline, bool pause) { +static void PauseTimers(struct Timeline* timeline, bool pause) { if (timeline->queue) { if (timeline->queue->timer) { if (pause) { @@ -147,7 +147,7 @@ SYMBOL_INTERNAL void PauseTimers(struct Timeline* timeline, bool pause) { } } -SYMBOL_EXPORT void TM_Propagate(struct Timeline* timeline, enum TM_ActionState action) { +static void TM_Propagate(struct Timeline* timeline, enum TM_ActionState action) { if (timeline->queue) { if ((*timeline->queue->function) && (timeline->queue->active)) { (*timeline->queue->function)(timeline->game, timeline->queue, action); @@ -273,7 +273,7 @@ SYMBOL_EXPORT struct TM_Action* TM_AddBackgroundAction(struct Timeline* timeline } /*! \brief Predefined action used by TM_AddQueuedBackgroundAction */ -SYMBOL_INTERNAL bool runinbackground(struct Game* game, struct TM_Action* action, enum TM_ActionState state) { +static bool runinbackground(struct Game* game, struct TM_Action* action, enum TM_ActionState state) { int* delay = (int*) TM_GetArg(action->arguments, 1); char* name = (char*) TM_GetArg(action->arguments, 2); struct Timeline *timeline = (struct Timeline*) TM_GetArg(action->arguments, 3); diff --git a/src/utils.c b/src/utils.c index 9fe69af..f893d1e 100644 --- a/src/utils.c +++ b/src/utils.c @@ -187,20 +187,12 @@ SYMBOL_EXPORT ALLEGRO_BITMAP* LoadScaledBitmap(struct Game *game, char* filename SYMBOL_EXPORT void FatalError(struct Game *game, bool fatal, char* format, ...) { char text[1024] = {}; - if (!game->_priv.console) { - va_list vl; - va_start(vl, format); - vsnprintf(text, 1024, format, vl); - va_end(vl); - printf("%s\n", text); - } else { - PrintConsole(game, "Fatal Error, displaying Blue Screen of Derp..."); - va_list vl; - va_start(vl, format); - vsnprintf(text, 1024, format, vl); - va_end(vl); - PrintConsole(game, text); - } + PrintConsole(game, "Fatal Error, displaying Blue Screen of Derp..."); + va_list vl; + va_start(vl, format); + vsnprintf(text, 1024, format, vl); + va_end(vl); + PrintConsole(game, text); ALLEGRO_TRANSFORM trans; al_identity_transform(&trans); @@ -270,7 +262,7 @@ SYMBOL_EXPORT void FatalError(struct Game *game, bool fatal, char* format, ...) al_use_transform(&game->projection); } -SYMBOL_INTERNAL void TestPath(char* filename, char* subpath, char** result) { +static void TestPath(char* filename, char* subpath, char** result) { if (*result) return; //already found ALLEGRO_PATH *tail = al_create_path(filename); ALLEGRO_PATH *path = al_get_standard_path(ALLEGRO_RESOURCES_PATH); @@ -293,7 +285,7 @@ SYMBOL_EXPORT char* GetGameName(struct Game *game, char* format) { } -SYMBOL_INTERNAL char* TestDataFilePath(struct Game *game, char* filename) { +static char* TestDataFilePath(struct Game *game, char* filename) { char *result = NULL; if (al_filename_exists(filename)) { @@ -339,11 +331,11 @@ SYMBOL_EXPORT char* GetDataFilePath(struct Game *game, char* filename) { char* file = AddGarbage(game, strdup(filename)); char* sub = strstr(file, ".flac"); if (sub) { - sub[0] = '.'; - sub[1] = 'o'; - sub[2] = 'g'; - sub[3] = 'g'; - sub[4] = 0; + sub[0] = '.'; + sub[1] = 'o'; + sub[2] = 'g'; + sub[3] = 'g'; + sub[4] = 0; } result = TestDataFilePath(game, file); if (result) { @@ -368,8 +360,8 @@ ALLEGRO_DEBUG_CHANNEL("libsuperderpy") SYMBOL_EXPORT void PrintConsole(struct Game *game, char* format, ...) { va_list vl; va_start(vl, format); - char text[1024] = {}; - vsnprintf(text, 1024, format, vl); + char* text = game->_priv.console[game->_priv.console_pos]; + vsnprintf(text, (sizeof(game->_priv.console[0])/sizeof(game->_priv.console[0][0])), format, vl); va_end(vl); ALLEGRO_DEBUG("%s", text); #ifndef __EMSCRIPTEN__ @@ -379,18 +371,11 @@ SYMBOL_EXPORT void PrintConsole(struct Game *game, char* format, ...) { printf("%s\n", text); fflush(stdout); } - if (!game->_priv.draw) return; - if (!game->_priv.console) return; - if ((!game->config.debug) && (!game->_priv.showconsole)) return; - ALLEGRO_BITMAP *target = al_get_target_bitmap(); - al_set_target_bitmap(game->_priv.console_tmp); - al_clear_to_color(al_map_rgba(0,0,0,80)); - al_draw_bitmap_region(game->_priv.console, 0, (int)(al_get_bitmap_height(game->_priv.console)*0.2), al_get_bitmap_width(game->_priv.console), (int)(al_get_bitmap_height(game->_priv.console)*0.8), 0, 0, 0); - al_draw_text(game->_priv.font_console, al_map_rgb(255,255,255), (int)(game->viewport.width*0.005), (int)(al_get_bitmap_height(game->_priv.console)*0.81), ALLEGRO_ALIGN_LEFT, text); - al_set_target_bitmap(game->_priv.console); - al_clear_to_color(al_map_rgba(0,0,0,0)); - al_draw_bitmap(game->_priv.console_tmp, 0, 0, 0); - al_set_target_bitmap(target); + game->_priv.console_pos++; + if (game->_priv.console_pos >= (sizeof(game->_priv.console)/sizeof(game->_priv.console[0]))) { + game->_priv.console_pos = 0; + } + return; } SYMBOL_EXPORT void SetupViewport(struct Game *game, struct Viewport config) { @@ -418,6 +403,9 @@ SYMBOL_EXPORT void SetupViewport(struct Game *game, struct Viewport config) { } if (game->viewport.integer_scaling) { resolution = floor(resolution); + if (floor(resolution) == 0) { + resolution = 1; + } } if ((!atoi(GetConfigOptionDefault(game, "SuperDerpy", "downscale", "1"))) && (resolution < 1)) { resolution = 1; @@ -425,9 +413,6 @@ SYMBOL_EXPORT void SetupViewport(struct Game *game, struct Viewport config) { if (!atoi(GetConfigOptionDefault(game, "SuperDerpy", "scaling", "1"))) { resolution = 1; } - if (resolution == 0) { - resolution = 1; - } int clipWidth = game->viewport.width * resolution; int clipHeight = game->viewport.height * resolution; @@ -440,7 +425,7 @@ SYMBOL_EXPORT void SetupViewport(struct Game *game, struct Viewport config) { al_build_transform(&game->projection, 0, 0, al_get_display_width(game->display) / (float)game->viewport.width, al_get_display_height(game->display) / (float)game->viewport.height, 0.0f); } al_use_transform(&game->projection); - if (game->_priv.console) Console_Unload(game); + Console_Unload(game); Console_Load(game); } diff --git a/src/utils.h b/src/utils.h index 7a61491..1fdd98c 100644 --- a/src/utils.h +++ b/src/utils.h @@ -51,6 +51,9 @@ void DrawTextWithShadow(ALLEGRO_FONT *font, ALLEGRO_COLOR color, float x, float int DrawWrappedText(ALLEGRO_FONT *font, ALLEGRO_COLOR color, float x, float y, int width, int flags, char const *text); int DrawWrappedTextWithShadow(ALLEGRO_FONT *font, ALLEGRO_COLOR color, float x, float y, int width, int flags, char const *text); +ALLEGRO_COLOR InterpolateColor(ALLEGRO_COLOR c1, ALLEGRO_COLOR c2, float frac); +void ScaleBitmap(ALLEGRO_BITMAP* source, int width, int height); + /*! \brief Loads bitmap into memory and scales it with software linear filtering. */ ALLEGRO_BITMAP* LoadScaledBitmap(struct Game *game, char* filename, int width, int height);