From ef67a16867bbae5a80b505da677ee6e0bc0baff0 Mon Sep 17 00:00:00 2001 From: Andrei Alexeyev <0x416b617269@gmail.com> Date: Fri, 29 Sep 2017 22:03:49 +0300 Subject: [PATCH] WIP event system rewrite. text input missing --- src/credits.c | 2 +- src/ending.c | 2 +- src/events.c | 516 ++++++++++++++++++++++++++++------------ src/events.h | 122 ++++++---- src/gamepad.c | 133 +++++------ src/global.c | 7 + src/global.h | 3 +- src/list.c | 62 ++++- src/list.h | 15 +- src/main.c | 3 +- src/menu/charselect.c | 22 +- src/menu/menu.c | 22 +- src/menu/menu.h | 4 +- src/menu/options.c | 252 +++++++++----------- src/menu/savereplay.c | 21 +- src/resource/resource.c | 37 +-- src/resource/resource.h | 4 - src/stage.c | 52 ++-- src/video.c | 47 ++-- src/video.h | 1 - 20 files changed, 818 insertions(+), 509 deletions(-) diff --git a/src/credits.c b/src/credits.c index d48d56fa..6ed96357 100644 --- a/src/credits.c +++ b/src/credits.c @@ -265,7 +265,7 @@ void credits_loop(void) { credits_preload(); credits_init(); while(credits.end) { - handle_events(NULL, 0, NULL); + events_poll(NULL, 0); credits_process(); credits_draw(); global.frames++; diff --git a/src/ending.c b/src/ending.c index 0d45fafd..264a0792 100644 --- a/src/ending.c +++ b/src/ending.c @@ -145,7 +145,7 @@ void ending_loop(void) { set_ortho(); while(e.pos < e.count-1) { - handle_events(NULL, 0, NULL); + events_poll(NULL, 0); ending_draw(&e); global.frames++; diff --git a/src/events.c b/src/events.c index 1f412c5d..f2bf7c64 100644 --- a/src/events.c +++ b/src/events.c @@ -12,37 +12,197 @@ #include "video.h" #include "gamepad.h" -struct sdl_custom_events_s sdl_custom_events; static uint32_t keyrepeat_paused_until; +static ListContainer *global_handlers; + +uint32_t sdl_first_user_event; + +static void events_register_default_handlers(void); +static void events_unregister_default_handlers(void); + +/* + * Public API + */ void events_init(void) { - uint32_t *events = (uint32_t*)&sdl_custom_events; - uint32_t id = SDL_RegisterEvents(NUM_CUSTOM_EVENTS); + sdl_first_user_event = SDL_RegisterEvents(NUM_TAISEI_EVENTS); - for(int i = 0; i < NUM_CUSTOM_EVENTS; ++i, ++id) { - events[i] = id; - log_debug("User event registered: %u", events[i]); + if(sdl_first_user_event == (uint32_t)-1) { + char *s = + "You have exhausted the SDL userevent pool. " + "How you managed that is beyond me, but congratulations. " +#if (LOG_DEFAULT_LEVELS_BACKTRACE & LOG_FATAL) + "Here's your prize stack trace." +#endif + ; + log_fatal("%s", s); + } + + SDL_EventState(SDL_MOUSEMOTION, SDL_DISABLE); + + events_register_default_handlers(); +} + +void events_shutdown(void) { + events_unregister_default_handlers(); + +#ifdef DEBUG + if(global_handlers) { + log_warn( + "Someone didn't unregister their handler. " + "Clean up after yourself, I'm not your personal maid. " + "Hint: ASan or valgrind can probably determine the culprit." + ); + } +#endif +} + +static bool events_invoke_handler(SDL_Event *event, EventHandler *handler) { + assert(handler->proc != NULL); + + if(!handler->event_type || handler->event_type == event->type) { + bool result = handler->proc(event, handler->arg); + + return result; + } + + return false; +} + +static int handler_container_prio_func(void *h) { + return ((EventHandler*)((ListContainer*)h)->data)->priority; +} + +static EventPriority real_priority(EventPriority prio) { + if(prio == EPRIO_DEFAULT) { + return EPRIO_DEFAULT_REMAP; + } + + assert(prio < NUM_EPRIOS); + return prio; +} + +static void events_print_handlers(ListContainer *list) { + for(ListContainer *c = list; c; c = c->next) { + EventHandler *h = c->data; + log_debug(" * %p %u", *(void**)&h->proc, h->priority); } } -void events_pause_keyrepeat(void) { - // for whatever stupid bizarre reason, keyrepeat skips the delay after the window toggles fullscreen on some systems - // this ugly hack is a workaround for that - keyrepeat_paused_until = SDL_GetTicks() + 250; +static bool events_invoke_handlers(SDL_Event *event, ListContainer *h_list, EventHandler *h_array) { + // invoke handlers from two sources (a list and an array) in the correct order according to priority + // list items take precedence + // + // assumptions: + // h_list is sorted by priority + // h_array is in arbitrary order and terminated with a NULL-proc entry + + bool result = false; + + if(h_list && !h_array) { + // case 1 (simplest): we have a list and no custom handlers + + for(ListContainer *c = h_list; c; c = c->next) { + if(result = events_invoke_handler(event, (EventHandler*)c->data)) { + break; + } + } + + return result; + } + + if(h_list && h_array) { + // case 2 (suboptimal): we have both a list and a disordered array; need to do some actual work + // if you want to optimize this be my guest + + ListContainer *merged_list = NULL; + ListContainer *prevc = NULL; + + // copy the list + for(ListContainer *c = h_list; c; c = c->next) { + ListContainer *newc = calloc(1, sizeof(ListContainer)); + newc->data = c->data; + newc->prev = prevc; + + if(prevc) { + prevc->next = newc; + } + + if(!merged_list) { + merged_list = newc; + } + + prevc = newc; + } + + // merge the array into the list copy, respecting priority + for(EventHandler *h = h_array; h->proc; ++h) { + create_container_at_priority( + &merged_list, + real_priority(h->priority), + handler_container_prio_func + )->data = h; + } + + // iterate over the merged list + for(ListContainer *c = merged_list; c; c = c->next) { + if(result = events_invoke_handler(event, (EventHandler*)c->data)) { + break; + } + } + + delete_all_elements((void**)&merged_list, delete_element); + return result; + } + + if(!h_list && h_array) { + // case 3 (unlikely): we don't have a list for some reason (no global handlers?), but there are custom handlers + + for(EventHandler *h = h_array; h->proc; ++h) { + if(result = events_invoke_handler(event, h)) { + break; + } + } + + return result; + } + + // case 4 (unlikely): huh? okay then + return result; } -void handle_events(EventHandler handler, EventFlags flags, void *arg) { - SDL_Event event; +void events_register_handler(EventHandler *handler) { + assert(handler->proc != NULL); + EventHandler *handler_alloc = malloc(sizeof(EventHandler)); + memcpy(handler_alloc, handler, sizeof(EventHandler)); - bool kbd = flags & EF_Keyboard; - bool text = flags & EF_Text; - bool menu = flags & EF_Menu; - bool game = flags & EF_Game; - bool video = flags & EF_Video; + if(handler_alloc->priority == EPRIO_DEFAULT) { + handler_alloc->priority = EPRIO_DEFAULT_REMAP; + } - // TODO: rewrite text input handling to properly support multibyte characters and IMEs + assert(handler_alloc->priority > EPRIO_DEFAULT); + create_container_at_priority(&global_handlers, handler_alloc->priority, handler_container_prio_func)->data = handler_alloc; - if(text) { + log_debug("Registered handler: %p %u", *(void**)&handler_alloc->proc, handler_alloc->priority); + events_print_handlers(global_handlers); +} + +void events_unregister_handler(EventHandlerProc proc) { + ListContainer *next; + for(ListContainer *c = global_handlers; c; c = next) { + EventHandler *h = c->data; + next = c->next; + + if(h->proc == proc) { + free(c->data); + delete_element((void**)&global_handlers, c); + return; + } + } +} + +static void events_apply_flags(EventFlags flags) { + if(flags & EFLAG_TEXT) { if(!SDL_IsTextInputActive()) { SDL_StartTextInput(); } @@ -52,147 +212,189 @@ void handle_events(EventHandler handler, EventFlags flags, void *arg) { } } - while(SDL_PollEvent(&event)) { - if(IS_CUSTOM_EVENT(event.type)) { - log_debug("Custom event %u: %"PRIxMAX" %"PRIxMAX, event.type, (uintmax_t)event.user.data1, (uintmax_t)event.user.data2); + TaiseiEvent type; - if(event.type == sdl_custom_events.video_mode_changed) { - if(video) { - handler(E_VideoModeChanged, 0, arg); - } - } + for(type = TE_MENU_FIRST; type <= TE_MENU_LAST; ++type) { + SDL_EventState(MAKE_TAISEI_EVENT(type), (bool)(flags & EFLAG_MENU)); + } - resource_sdl_event(&event); - continue; - } - - SDL_Scancode scan = event.key.keysym.scancode; - SDL_Keymod mod = event.key.keysym.mod; - bool repeat = event.key.repeat; - uint32_t timenow; - - if((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) && repeat && (timenow = SDL_GetTicks()) < keyrepeat_paused_until) { - log_debug("Prevented a potentially bogus key repeat: %i %i %i %i %i", event.type - SDL_KEYDOWN, scan, mod, repeat, keyrepeat_paused_until - timenow); - continue; - } - - switch(event.type) { - case SDL_KEYDOWN: - if(text) { - if(scan == SDL_SCANCODE_ESCAPE) - handler(E_CancelText, 0, arg); - else if(scan == SDL_SCANCODE_RETURN) - handler(E_SubmitText, 0, arg); - else if(scan == SDL_SCANCODE_BACKSPACE) - handler(E_CharErased, 0, arg); - } else if(!repeat) { - if(scan == config_get_int(CONFIG_KEY_SCREENSHOT)) { - video_take_screenshot(); - break; - } - - if((scan == SDL_SCANCODE_RETURN && (mod & KMOD_ALT)) || scan == config_get_int(CONFIG_KEY_FULLSCREEN)) { - config_set_int(CONFIG_FULLSCREEN, !config_get_int(CONFIG_FULLSCREEN)); - break; - } - } - - if(kbd) { - handler(E_KeyDown, scan, arg); - } - - if(menu && (!repeat || transition.state == TRANS_IDLE)) { - struct eventmap_t { int scancode; int event; } map[] = { - // order matters - // handle all the hardcoded controls first to prevent accidentally overriding them with unusable ones - - { SDL_SCANCODE_DOWN, E_CursorDown }, - { SDL_SCANCODE_UP, E_CursorUp }, - { SDL_SCANCODE_RIGHT, E_CursorRight }, - { SDL_SCANCODE_LEFT, E_CursorLeft }, - { SDL_SCANCODE_RETURN, E_MenuAccept }, - { SDL_SCANCODE_ESCAPE, E_MenuAbort }, - - { config_get_int(CONFIG_KEY_DOWN), E_CursorDown }, - { config_get_int(CONFIG_KEY_UP), E_CursorUp }, - { config_get_int(CONFIG_KEY_RIGHT), E_CursorRight }, - { config_get_int(CONFIG_KEY_LEFT), E_CursorLeft }, - { config_get_int(CONFIG_KEY_SHOT), E_MenuAccept }, - { config_get_int(CONFIG_KEY_BOMB), E_MenuAbort }, - - {SDL_SCANCODE_UNKNOWN, -1} - }; - - for(struct eventmap_t *m = map; m->scancode != SDL_SCANCODE_UNKNOWN; ++m) { - if(scan == m->scancode) { - handler(m->event, 0, arg); - break; - } - } - } - - if(game && !repeat) { - if(scan == config_get_int(CONFIG_KEY_PAUSE) || scan == SDL_SCANCODE_ESCAPE) { - handler(E_Pause, 0, arg); - } else { - int key = config_key_from_scancode(scan); - if(key >= 0) - handler(E_PlrKeyDown, key, arg); - } - } - - break; - - case SDL_KEYUP: - if(kbd) { - handler(E_KeyUp, scan, arg); - } - - if(game && !repeat) { - int key = config_key_from_scancode(scan); - if(key >= 0) - handler(E_PlrKeyUp, key, arg); - } - - break; - - case SDL_TEXTINPUT: { - char *c; - - for(c = event.text.text; *c; ++c) { - handler(E_CharTyped, *c, arg); - } - - break; - } - - case SDL_WINDOWEVENT: - switch(event.window.event) { - case SDL_WINDOWEVENT_RESIZED: - video_resize(event.window.data1, event.window.data2); - break; - - case SDL_WINDOWEVENT_FOCUS_LOST: - if(game) { - handler(E_Pause, 0, arg); - } - break; - } - break; - - case SDL_QUIT: - exit(0); - break; - - default: - gamepad_event(&event, handler, flags, arg); - break; - } + for(type = TE_GAME_FIRST; type <= TE_GAME_LAST; ++type) { + SDL_EventState(MAKE_TAISEI_EVENT(type), (bool)(flags & EFLAG_GAME)); } } -// Inputdevice-agnostic method of checking whether a game control is pressed. -// ALWAYS use this instead of SDL_GetKeyState if you need it. -bool gamekeypressed(KeyIndex key) { - return SDL_GetKeyboardState(NULL)[config_get_int(KEYIDX_TO_CFGIDX(key))] || gamepad_gamekeypressed(key); +void events_poll(EventHandler *handlers, EventFlags flags) { + SDL_Event event; + events_apply_flags(flags); + + while(SDL_PollEvent(&event)) { + events_invoke_handlers(&event, global_handlers, handlers); + } +} + +void events_emit(TaiseiEvent type, int32_t code, void *data1, void *data2) { + assert(TAISEI_EVENT_VALID(type)); + uint32_t sdltype = MAKE_TAISEI_EVENT(type); + assert(IS_TAISEI_EVENT(sdltype)); + + if(!SDL_EventState(sdltype, SDL_QUERY)) { + return; + } + + SDL_Event event = { 0 }; + event.type = sdltype; + event.user.code = code; + event.user.data1 = data1; + event.user.data2 = data2; + + SDL_PushEvent(&event); +} + +void events_pause_keyrepeat(void) { + // for whatever stupid bizarre reason, keyrepeat skips the delay after the window toggles fullscreen on some systems + // this ugly hack is a workaround for that + keyrepeat_paused_until = SDL_GetTicks() + 250; +} + +/* + * Default handlers + */ + +static bool events_handler_quit(SDL_Event *event, void *arg); +static bool events_handler_keyrepeat_workaround(SDL_Event *event, void *arg); +static bool events_handler_hotkeys(SDL_Event *event, void *arg); +static bool events_handler_key_down(SDL_Event *event, void *arg); +static bool events_handler_key_up(SDL_Event *event, void *arg); +// static bool events_handler_text_input(SDL_Event *event, void *arg); + +static EventHandler default_handlers[] = { + { .proc = events_handler_quit, .priority = EPRIO_SYSTEM, .event_type = SDL_QUIT}, + { .proc = events_handler_keyrepeat_workaround, .priority = EPRIO_CAPTURE, .event_type = SDL_KEYDOWN}, + { .proc = events_handler_hotkeys, .priority = EPRIO_HOTKEYS, .event_type = SDL_KEYDOWN}, + { .proc = events_handler_key_down, .priority = EPRIO_TRANSLATION, .event_type = SDL_KEYDOWN}, + { .proc = events_handler_key_up, .priority = EPRIO_TRANSLATION, .event_type = SDL_KEYUP}, + + {NULL} +}; + +static void events_register_default_handlers(void) { + for(EventHandler *h = default_handlers; h->proc; ++h) { + events_register_handler(h); + } +} + +static void events_unregister_default_handlers(void) { + for(EventHandler *h = default_handlers; h->proc; ++h) { + events_unregister_handler(h->proc); + } +} + +static bool events_handler_quit(SDL_Event *event, void *arg) { + exit(0); +} + +static bool events_handler_keyrepeat_workaround(SDL_Event *event, void *arg) { + uint32_t timenow; + + if(event->key.repeat && (timenow = SDL_GetTicks()) < keyrepeat_paused_until) { + log_debug("Prevented a potentially bogus key repeat: %i %i %u", + event->key.keysym.scancode, event->key.keysym.mod, keyrepeat_paused_until - timenow); + return true; + } + + return false; +} + +static bool events_handler_key_down(SDL_Event *event, void *arg) { + SDL_Scancode scan = event->key.keysym.scancode; + bool repeat = event->key.repeat; + + /* + * Emit menu events + */ + + struct eventmap_s { int scancode; TaiseiEvent event; } menu_event_map[] = { + // order matters + // handle all the hardcoded controls first to prevent accidentally overriding them with unusable ones + + { SDL_SCANCODE_DOWN, TE_MENU_CURSOR_DOWN }, + { SDL_SCANCODE_UP, TE_MENU_CURSOR_UP }, + { SDL_SCANCODE_RIGHT, TE_MENU_CURSOR_RIGHT }, + { SDL_SCANCODE_LEFT, TE_MENU_CURSOR_LEFT }, + { SDL_SCANCODE_RETURN, TE_MENU_ACCEPT }, + { SDL_SCANCODE_ESCAPE, TE_MENU_ABORT }, + + { config_get_int(CONFIG_KEY_DOWN), TE_MENU_CURSOR_DOWN }, + { config_get_int(CONFIG_KEY_UP), TE_MENU_CURSOR_UP }, + { config_get_int(CONFIG_KEY_RIGHT), TE_MENU_CURSOR_RIGHT }, + { config_get_int(CONFIG_KEY_LEFT), TE_MENU_CURSOR_LEFT }, + { config_get_int(CONFIG_KEY_SHOT), TE_MENU_ACCEPT }, + { config_get_int(CONFIG_KEY_BOMB), TE_MENU_ABORT }, + + {SDL_SCANCODE_UNKNOWN, -1} + }; + + if(!repeat || transition.state == TRANS_IDLE) { + for(struct eventmap_s *m = menu_event_map; m->scancode != SDL_SCANCODE_UNKNOWN; ++m) { + if(scan == m->scancode) { + events_emit(m->event, 0, NULL, NULL); + break; + } + } + } + + /* + * Emit game events + */ + + if(!repeat) { + if(scan == config_get_int(CONFIG_KEY_PAUSE) || scan == SDL_SCANCODE_ESCAPE) { + events_emit(TE_GAME_PAUSE, 0, NULL, NULL); + } else { + int key = config_key_from_scancode(scan); + + if(key >= 0) { + events_emit(TE_GAME_KEY_DOWN, key, NULL, NULL); + } + } + } + + return false; +} + +static bool events_handler_key_up(SDL_Event *event, void *arg) { + SDL_Scancode scan = event->key.keysym.scancode; + + /* + * Emit game events + */ + + int key = config_key_from_scancode(scan); + + if(key >= 0) { + events_emit(TE_GAME_KEY_UP, key, NULL, NULL); + } + + return false; +} + +static bool events_handler_hotkeys(SDL_Event *event, void *arg) { + if(event->key.repeat) { + return false; + } + + SDL_Scancode scan = event->key.keysym.scancode; + SDL_Keymod mod = event->key.keysym.mod; + + if(scan == config_get_int(CONFIG_KEY_SCREENSHOT)) { + video_take_screenshot(); + return true; + } + + if((scan == SDL_SCANCODE_RETURN && (mod & KMOD_ALT)) || scan == config_get_int(CONFIG_KEY_FULLSCREEN)) { + config_set_int(CONFIG_FULLSCREEN, !config_get_int(CONFIG_FULLSCREEN)); + return true; + } + + return false; } diff --git a/src/events.h b/src/events.h index c74d768d..c78be56f 100644 --- a/src/events.h +++ b/src/events.h @@ -8,68 +8,88 @@ #pragma once -#include "config.h" +#include "util.h" typedef enum { - EF_Keyboard = 1, - EF_Text = 2, - EF_Menu = 4, - EF_Game = 8, - EF_Gamepad = 16, - EF_Video = 32, + TE_RESOURCE_ASYNC_LOADED, + + #define TE_MENU_FIRST TE_MENU_CURSOR_UP + TE_MENU_CURSOR_UP, + TE_MENU_CURSOR_DOWN, + TE_MENU_CURSOR_LEFT, + TE_MENU_CURSOR_RIGHT, + TE_MENU_ACCEPT, + TE_MENU_ABORT, + #define TE_MENU_LAST TE_MENU_ABORT + + #define TE_GAME_FIRST TE_GAME_KEY_DOWN + TE_GAME_KEY_DOWN, + TE_GAME_KEY_UP, + TE_GAME_AXIS_UD, + TE_GAME_AXIS_LR, + TE_GAME_PAUSE, + #define TE_GAME_LAST TE_GAME_PAUSE + + TE_GAMEPAD_BUTTON_DOWN, + TE_GAMEPAD_BUTTON_UP, + TE_GAMEPAD_AXIS, + + TE_VIDEO_MODE_CHANGED, + + NUM_TAISEI_EVENTS +} TaiseiEvent; + +typedef enum { + EPRIO_DEFAULT = 0, + + // from highest to lowest + // feel free to add new prios as needed, just don't randomly reorder stuff + + EPRIO_SYSTEM, // for events not associated with user input + EPRIO_CAPTURE, // for capturing raw user input before it's further processed + EPRIO_HOTKEYS, // for global keybindings + EPRIO_TRANSLATION, // for translating raw input events into higher level Taisei events + EPRIO_NORMAL, // for everything else + + NUM_EPRIOS +} EventPriority; + +typedef enum { + EFLAG_MENU = (1 << 0), + EFLAG_GAME = (1 << 1), + EFLAG_TEXT = (1 << 2), } EventFlags; -typedef enum { - // EF_Keyboard - E_KeyDown, - E_KeyUp, +#define EPRIO_DEFAULT_REMAP EPRIO_NORMAL - // EF_Text - E_CharTyped, - E_CharErased, - E_SubmitText, - E_CancelText, +// if the this returns true, the event won't be passed down to lower priority handlers +typedef bool (*EventHandlerProc)(SDL_Event *event, void *arg); - // EF_Menu - E_CursorUp, - E_CursorDown, - E_CursorLeft, - E_CursorRight, - E_MenuAccept, - E_MenuAbort, +typedef struct EventHandler { + EventHandlerProc proc; + void *arg; + EventPriority priority; + uint32_t event_type; // if 0, this handler gets all events +} EventHandler; - // EF_Game - E_PlrKeyDown, - E_PlrKeyUp, - E_PlrAxisUD, - E_PlrAxisLR, - E_Pause, +extern uint32_t sdl_first_user_event; - // EF_Gamepad - E_GamepadKeyDown, - E_GamepadKeyUp, - E_GamepadAxis, - E_GamepadAxisValue, +// Convert a TaiseiEvent code to an SDL event type +#define MAKE_TAISEI_EVENT(te_code) (sdl_first_user_event + (te_code)) - // EF_Video - E_VideoModeChanged, -} EventType; +// Convert an SDL event type to a TaiseiEvent code +#define TAISEI_EVENT(sdl_etype) ((sdl_etype) - sdl_first_user_event) -extern struct sdl_custom_events_s { - uint32_t resource_load_finished; - uint32_t video_mode_changed; -} sdl_custom_events; +// Check if argument is a valid TaiseiEvent code +#define TAISEI_EVENT_VALID(te_code) ((unsigned)(te_code) < NUM_TAISEI_EVENTS) -#define NUM_CUSTOM_EVENTS (sizeof(struct sdl_custom_events_s)/sizeof(uint32_t)) -#define FIRST_CUSTOM_EVENT (((uint32_t*)&sdl_custom_events)[0]) -#define LAST_CUSTOM_EVENT (((uint32_t*)&sdl_custom_events)[NUM_CUSTOM_EVENTS - 1]) -#define IS_CUSTOM_EVENT(e) ((e) >= FIRST_CUSTOM_EVENT && (e) <= LAST_CUSTOM_EVENT) - -typedef void(*EventHandler)(EventType, int, void*); +// Check if an SDL event type is a Taisei event +#define IS_TAISEI_EVENT(sdl_etype) (TAISEI_EVENT_VALID(TAISEI_EVENT(sdl_etype))) void events_init(void); +void events_shutdown(void); void events_pause_keyrepeat(void); -void handle_events(EventHandler handler, EventFlags flags, void *arg); -bool gamekeypressed(KeyIndex key); - - +void events_register_handler(EventHandler *handler); +void events_unregister_handler(EventHandlerProc proc); +void events_poll(EventHandler *handlers, EventFlags flags); +void events_emit(TaiseiEvent type, int32_t code, void *data1, void *data2); diff --git a/src/gamepad.c b/src/gamepad.c index c4cf0a55..6cb8f488 100644 --- a/src/gamepad.c +++ b/src/gamepad.c @@ -24,6 +24,8 @@ static struct { } devices; } gamepad; +static bool gamepad_event_handler(SDL_Event *event, void *arg); + static int gamepad_load_mappings(const char *vpath, int warn_noexist) { char *repr = vfs_repr(vpath, true); char *errstr = NULL; @@ -240,6 +242,12 @@ void gamepad_init(void) { SDL_GameControllerEventState(SDL_ENABLE); config_set_str(CONFIG_GAMEPAD_DEVICE, guid); + + events_register_handler(&(EventHandler){ + .proc = gamepad_event_handler, + .priority = EPRIO_TRANSLATION, + }); + gamepad.initialized = true; } @@ -261,6 +269,7 @@ void gamepad_shutdown(void) { SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); memset(&gamepad, 0, sizeof(gamepad)); + events_unregister_handler(gamepad_event_handler); } void gamepad_restart(void) { @@ -304,22 +313,22 @@ int gamepad_gamekey2axisval(KeyIndex key) { } } -int gamepad_axis2menuevt(SDL_GameControllerAxis id, int val) { +static int gamepad_axis2menuevt(SDL_GameControllerAxis id, int val) { if(id == SDL_CONTROLLER_AXIS_LEFTX || id == SDL_CONTROLLER_AXIS_RIGHTX) - return val == AXISVAL_LEFT ? E_CursorLeft : E_CursorRight; + return val == AXISVAL_LEFT ? TE_MENU_CURSOR_LEFT : TE_MENU_CURSOR_RIGHT; if(id == SDL_CONTROLLER_AXIS_LEFTY || id == SDL_CONTROLLER_AXIS_RIGHTY) - return val == AXISVAL_UP ? E_CursorUp : E_CursorDown; + return val == AXISVAL_UP ? TE_MENU_CURSOR_UP : TE_MENU_CURSOR_DOWN; return -1; } -int gamepad_axis2gameevt(SDL_GameControllerAxis id) { +static int gamepad_axis2gameevt(SDL_GameControllerAxis id) { if(id == config_get_int(CONFIG_GAMEPAD_AXIS_LR)) - return E_PlrAxisLR; + return TE_GAME_AXIS_LR; if(id == config_get_int(CONFIG_GAMEPAD_AXIS_UD)) - return E_PlrAxisUD; + return TE_GAME_AXIS_UD; return -1; } @@ -386,125 +395,113 @@ int gamepad_get_player_axis_value(GamepadPlrAxis paxis) { return gamepad_axis_process_value(id, SDL_GameControllerGetAxis(gamepad.device, id)); } -void gamepad_axis(SDL_GameControllerAxis id, int raw, EventHandler handler, EventFlags flags, void *arg) { +void gamepad_axis(SDL_GameControllerAxis id, int raw) { signed char *a = gamepad.axis; signed char val = AXISVAL(gamepad_axis_process_value_deadzone(raw)); - bool free = config_get_int(CONFIG_GAMEPAD_AXIS_FREE); + bool restricted = !config_get_int(CONFIG_GAMEPAD_AXIS_FREE); - bool menu = flags & EF_Menu; - bool game = flags & EF_Game; - bool gp = flags & EF_Gamepad; + events_emit(TE_GAMEPAD_AXIS, id, (void*)(intptr_t)raw, NULL); - if(game && free) { + if(!restricted) { int evt = gamepad_axis2gameevt(id); if(evt >= 0) { - handler(evt, gamepad_axis_process_value(id, raw), arg); + events_emit(evt, gamepad_axis_process_value(id, raw), NULL, NULL); } } if(val) { // simulate press if(!a[id]) { a[id] = val; + int key = gamepad_axis2gamekey(id, val); - if(game && !free) { - int key = gamepad_axis2gamekey(id, val); - if(key >= 0) { - handler(E_PlrKeyDown, key, arg); + if(key >= 0) { + if(restricted) { + events_emit(TE_GAME_KEY_DOWN, key, NULL, NULL); } - } - if(menu) { int evt = gamepad_axis2menuevt(id, val); if(evt >= 0) { - handler(evt, 0, arg); + events_emit(evt, 0, NULL, NULL); } } + } } else if(a[id]) { // simulate release - if(game) { + if(restricted) { int key = gamepad_axis2gamekey(id, a[id]); - handler(E_PlrKeyUp, key, arg); + + if(key >= 0) { + events_emit(TE_GAME_KEY_UP, key, NULL, NULL); + } } a[id] = AXISVAL_NULL; } - - if(gp) { - // we probably need a better way to pass more than an int to the handler... - handler(E_GamepadAxis, id, arg); - handler(E_GamepadAxisValue, raw, arg); - } } -void gamepad_button(SDL_GameControllerButton button, int state, EventHandler handler, EventFlags flags, void *arg) { - int menu = flags & EF_Menu; - int game = flags & EF_Game; - int gpad = flags & EF_Gamepad; - +void gamepad_button(SDL_GameControllerButton button, int state) { int gpkey = config_gamepad_key_from_gamepad_button(button); int key = config_key_from_gamepad_key(gpkey); if(state == SDL_PRESSED) { - if(game) switch(button) { + events_emit(TE_GAMEPAD_BUTTON_DOWN, button, NULL, NULL); + + switch(button) { case SDL_CONTROLLER_BUTTON_START: + events_emit(TE_MENU_ACCEPT, 0, NULL, NULL); + events_emit(TE_GAME_PAUSE, 0, NULL, NULL); + break; + case SDL_CONTROLLER_BUTTON_BACK: - handler(E_Pause, 0, arg); break; + events_emit(TE_MENU_ABORT, 0, NULL, NULL); + events_emit(TE_GAME_PAUSE, 0, NULL, NULL); + break; + + case SDL_CONTROLLER_BUTTON_DPAD_UP: events_emit(TE_MENU_CURSOR_UP, 0, NULL, NULL); break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: events_emit(TE_MENU_CURSOR_DOWN, 0, NULL, NULL); break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: events_emit(TE_MENU_CURSOR_LEFT, 0, NULL, NULL); break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: events_emit(TE_MENU_CURSOR_RIGHT, 0, NULL, NULL); break; + + case SDL_CONTROLLER_BUTTON_A: + events_emit(TE_MENU_ACCEPT, 0, NULL, NULL); + + case SDL_CONTROLLER_BUTTON_B: + events_emit(TE_MENU_ABORT, 0, NULL, NULL); default: if(key >= 0) { - handler(E_PlrKeyDown, key, arg); + events_emit(TE_GAME_KEY_DOWN, key, NULL, NULL); } break; } - if(menu) switch(button) { - case SDL_CONTROLLER_BUTTON_DPAD_UP: handler(E_CursorUp, 0, arg); break; - case SDL_CONTROLLER_BUTTON_DPAD_DOWN: handler(E_CursorDown, 0, arg); break; - case SDL_CONTROLLER_BUTTON_DPAD_LEFT: handler(E_CursorLeft, 0, arg); break; - case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: handler(E_CursorRight, 0, arg); break; - - case SDL_CONTROLLER_BUTTON_A: - case SDL_CONTROLLER_BUTTON_START: - handler(E_MenuAccept, 0, arg); break; - - case SDL_CONTROLLER_BUTTON_B: - case SDL_CONTROLLER_BUTTON_BACK: - handler(E_MenuAbort, 0, arg); break; - - default: break; - } - - if(gpad) { - handler(E_GamepadKeyDown, button, arg); - } } else { - if(game && key >= 0) { - handler(E_PlrKeyUp, key, arg); - } + events_emit(TE_GAMEPAD_BUTTON_UP, button, NULL, NULL); - if(gpad) { - handler(E_GamepadKeyUp, button, arg); + if(key >= 0) { + events_emit(TE_GAME_KEY_UP, key, NULL, NULL); } } } -void gamepad_event(SDL_Event *event, EventHandler handler, EventFlags flags, void *arg) { - if(!gamepad.initialized) - return; +static bool gamepad_event_handler(SDL_Event *event, void *arg) { + assert(gamepad.initialized); switch(event->type) { case SDL_CONTROLLERAXISMOTION: if(event->caxis.which == gamepad.instance) { - gamepad_axis(event->caxis.axis, event->caxis.value, handler, flags, arg); + gamepad_axis(event->caxis.axis, event->caxis.value); } - break; + return true; case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: if(event->cbutton.which == gamepad.instance) { - gamepad_button(event->cbutton.button, event->cbutton.state, handler, flags, arg); + gamepad_button(event->cbutton.button, event->cbutton.state); } - break; + return true; } + + return false; } int gamepad_devicecount(void) { diff --git a/src/global.c b/src/global.c index 744fbb85..38fd46a4 100644 --- a/src/global.c +++ b/src/global.c @@ -27,3 +27,10 @@ void init_global(CLIAction *cli) { log_warn("FPS limiter disabled. Gotta go fast! (frameskip = %i)", global.frameskip); } } + +// Inputdevice-agnostic method of checking whether a game control is pressed. +// ALWAYS use this instead of SDL_GetKeyState if you need it. +// XXX: Move this somewhere? +bool gamekeypressed(KeyIndex key) { + return SDL_GetKeyboardState(NULL)[config_get_int(KEYIDX_TO_CFGIDX(key))] || gamepad_gamekeypressed(key); +} diff --git a/src/global.h b/src/global.h index 9994bb27..582a2782 100644 --- a/src/global.h +++ b/src/global.h @@ -124,4 +124,5 @@ extern Global global; void init_global(CLIAction *cli); - +// XXX: Move this somewhere? +bool gamekeypressed(KeyIndex key); diff --git a/src/list.c b/src/list.c index ef94b15a..1ebfd95e 100644 --- a/src/list.c +++ b/src/list.c @@ -12,24 +12,21 @@ #include #include "global.h" -typedef struct { - void *next; - void *prev; -} List; - void *_FREEREF; -void *create_element(void **dest, int size) { - List *e = malloc(size); +void *create_element(void **dest, size_t size) { + assert(dest != NULL); + assert(size > 0); + + List *e = calloc(1, size); List **d = (List **)dest; - e->next = NULL; e->prev = *d; if(*d != NULL) { e->next = (*d)->next; if((*d)->next) - ((List *)(*d)->next)->prev = e; + (*d)->next->prev = e; (*d)->next = e; } else { @@ -39,10 +36,57 @@ void *create_element(void **dest, int size) { return e; } +void* create_element_at_priority(void **list_head, size_t size, int prio, int (*prio_func)(void*)) { + assert(list_head != NULL); + assert(size > 0); + assert(prio_func != NULL); + + List *elem = calloc(1, size); + + if(!*list_head) { + *list_head = elem; + log_debug("PRIO: %i", prio); + return elem; + } + + List *dest = *list_head; + + for(List *e = dest; e && prio_func(e) <= prio; e = e->next) { + dest = e; + } + + if(dest == *list_head && prio_func(dest) > prio) { + elem->next = dest; + elem->prev = dest->prev; + + if(elem->prev) { + elem->prev->next = elem; + } + + dest->prev = elem; + *list_head = elem; + } else { + elem->prev = dest; + elem->next = dest->next; + + if(dest->next) { + dest->next->prev = elem; + } + + dest->next = elem; + } + + return elem; +} + ListContainer* create_container(ListContainer **dest) { return create_element((void**)dest, sizeof(ListContainer)); } +ListContainer* create_container_at_priority(ListContainer **list_head, int prio, int (*prio_func)(void*)) { + return create_element_at_priority((void**)list_head, sizeof(ListContainer), prio, prio_func); +} + void delete_element(void **dest, void *e) { if(((List *)e)->prev != NULL) ((List *)((List *)e)->prev)->next = ((List *)e)->next; diff --git a/src/list.h b/src/list.h index 3c0f1f06..9e138008 100644 --- a/src/list.h +++ b/src/list.h @@ -12,18 +12,27 @@ * so i do some void-magic here to save the lines. */ -void *create_element(void **dest, int size); +#include + +void* create_element(void **dest, size_t size); +void* create_element_at_priority(void **list_head, size_t size, int prio, int (*prio_func)(void*)); void delete_element(void **dest, void *e); void delete_all_elements(void **dest, void (callback)(void **, void *)); void delete_all_elements_witharg(void **dest, void (callback)(void **, void *, void *), void *arg); +typedef struct List { + struct List *next; + struct List *prev; +} List; + typedef struct ListContainer { - void *next; - void *prev; + struct ListContainer *next; + struct ListContainer *prev; void *data; } ListContainer; ListContainer* create_container(ListContainer **dest); +ListContainer* create_container_at_priority(ListContainer **list_head, int prio, int (*prio_func)(void*)); typedef struct { void *ptr; diff --git a/src/main.c b/src/main.c index 4f2c09b5..940d685a 100644 --- a/src/main.c +++ b/src/main.c @@ -38,6 +38,7 @@ static void taisei_shutdown(void) { stage_free_array(); config_uninit(); vfs_uninit(); + events_shutdown(); log_info("Good bye"); SDL_Quit(); @@ -182,8 +183,8 @@ int main(int argc, char **argv) { init_sdl(); init_global(&a); - init_fonts(); events_init(); + init_fonts(); video_init(); init_resources(); draw_loading_screen(); diff --git a/src/menu/charselect.c b/src/menu/charselect.c index a0cd83ae..49ff029e 100644 --- a/src/menu/charselect.c +++ b/src/menu/charselect.c @@ -134,23 +134,24 @@ void draw_char_menu(MenuData *menu) { glColor3f(1,1,1); } -void char_menu_input_event(EventType type, int state, void *arg) { +bool char_menu_input_handler(SDL_Event *event, void *arg) { MenuData *menu = arg; MenuData *mod = menu->context; + TaiseiEvent type = TAISEI_EVENT(event->type); - if(type == E_CursorRight) { + if(type == TE_MENU_CURSOR_RIGHT) { play_ui_sound("generic_shot"); menu->cursor++; - } else if(type == E_CursorLeft) { + } else if(type == TE_MENU_CURSOR_LEFT) { play_ui_sound("generic_shot"); menu->cursor--; - } else if(type == E_CursorDown) { + } else if(type == TE_MENU_CURSOR_DOWN) { play_ui_sound("generic_shot"); mod->cursor++; - } else if(type == E_CursorUp) { + } else if(type == TE_MENU_CURSOR_UP) { play_ui_sound("generic_shot"); mod->cursor--; - } else if(type == E_MenuAccept) { + } else if(type == TE_MENU_ACCEPT) { play_ui_sound("shot_special1"); mod->selected = mod->cursor; close_menu(mod); @@ -159,7 +160,7 @@ void char_menu_input_event(EventType type, int state, void *arg) { // XXX: This needs a better fix set_shotmode(mod, mod->entries[mod->selected].arg); - } else if(type == E_MenuAbort) { + } else if(type == TE_MENU_ABORT) { play_ui_sound("hit"); close_menu(menu); close_menu(mod); @@ -167,10 +168,15 @@ void char_menu_input_event(EventType type, int state, void *arg) { menu->cursor = (menu->cursor % menu->ecount) + menu->ecount*(menu->cursor < 0); mod->cursor = (mod->cursor % mod->ecount) + mod->ecount*(mod->cursor < 0); + + return false; } void char_menu_input(MenuData *menu) { - handle_events(char_menu_input_event, EF_Menu, menu); + events_poll((EventHandler[]){ + { .proc = char_menu_input_handler, .arg = menu }, + {NULL} + }, EFLAG_MENU); } void free_char_menu(MenuData *menu) { diff --git a/src/menu/menu.c b/src/menu/menu.c index 2a750215..09dfc1f0 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -86,11 +86,12 @@ float menu_fade(MenuData *menu) { return transition.fade; } -void menu_event(EventType type, int state, void *arg) { +bool menu_input_handler(SDL_Event *event, void *arg) { MenuData *menu = arg; + TaiseiEvent te = TAISEI_EVENT(event->type); - switch(type) { - case E_CursorDown: + switch(te) { + case TE_MENU_CURSOR_DOWN: play_ui_sound("generic_shot"); do { if(++menu->cursor >= menu->ecount) @@ -98,7 +99,7 @@ void menu_event(EventType type, int state, void *arg) { } while(menu->entries[menu->cursor].action == NULL); break; - case E_CursorUp: + case TE_MENU_CURSOR_UP: play_ui_sound("generic_shot"); do { if(--menu->cursor < 0) @@ -106,7 +107,7 @@ void menu_event(EventType type, int state, void *arg) { } while(menu->entries[menu->cursor].action == NULL); break; - case E_MenuAccept: + case TE_MENU_ACCEPT: play_ui_sound("shot_special1"); if(menu->entries[menu->cursor].action) { menu->selected = menu->cursor; @@ -114,7 +115,7 @@ void menu_event(EventType type, int state, void *arg) { } break; - case E_MenuAbort: + case TE_MENU_ABORT: play_ui_sound("hit"); if(menu->flags & MF_Abortable) { menu->selected = -1; @@ -124,14 +125,19 @@ void menu_event(EventType type, int state, void *arg) { default: break; } + + return false; } void menu_input(MenuData *menu) { - handle_events(menu_event, EF_Menu, (void*)menu); + events_poll((EventHandler[]){ + { .proc = menu_input_handler, .arg = menu }, + {NULL} + }, EFLAG_MENU); } void menu_no_input(MenuData *menu) { - handle_events(NULL, 0, NULL); + events_poll(NULL, 0); } void menu_logic(MenuData *menu) { diff --git a/src/menu/menu.h b/src/menu/menu.h index c70eb6b9..70f7d169 100644 --- a/src/menu/menu.h +++ b/src/menu/menu.h @@ -94,6 +94,4 @@ int menu_loop(MenuData *menu); float menu_fade(MenuData *menu); -void menu_event(EventType type, int state, void *arg); - - +bool menu_input_handler(SDL_Event *event, void *arg); diff --git a/src/menu/options.c b/src/menu/options.c index 37aceeb4..d446097c 100644 --- a/src/menu/options.c +++ b/src/menu/options.c @@ -879,12 +879,8 @@ void draw_options_menu(MenuData *menu) { // --- Input/event processing --- // -static void notify_bindings(EventType type, int state, MenuData *menu) { - // FIXME: this won't be called when input is blocked (we need a better event system) - - if(type != E_VideoModeChanged) { - return; - } +static bool options_vidmode_change_handler(SDL_Event *event, void *arg) { + MenuData *menu = arg; for(int i = 0; i < menu->ecount; ++i) { OptionBinding *bind = bind_get(menu, i); @@ -908,153 +904,145 @@ static void notify_bindings(EventType type, int state, MenuData *menu) { break; } } + + return false; } -static void bind_input_event(EventType type, int state, void *arg) { +static bool options_input_handler_for_binding(SDL_Event *event, void *arg) { + if(!arg) { + return false; + } + OptionBinding *b = arg; + uint32_t t = event->type; - int scan = state; - char c = (char)(((Uint16)state) & 0x7F); - char *dest = b->type == BT_StrValue? *b->values : NULL; + if(t == SDL_KEYDOWN) { + SDL_Scancode scan = event->key.keysym.scancode; + bool esc = scan == SDL_SCANCODE_ESCAPE; - switch(type) { - case E_KeyDown: { - int esc = scan == SDL_SCANCODE_ESCAPE; - if(b->type == BT_GamepadKeyBinding || b->type == BT_GamepadAxisBinding) { - if(esc) - b->blockinput = false; - break; + if(b->type != BT_KeyBinding) { + if(esc) { + b->blockinput = false; } - if(!esc) { - for(int i = CONFIG_KEY_FIRST; i <= CONFIG_KEY_LAST; ++i) { - if(config_get_int(i) == scan) { - config_set_int(i, config_get_int(b->configentry)); - } - } - - config_set_int(b->configentry, scan); - } - - b->blockinput = false; - break; + return true; } - case E_GamepadKeyDown: { - if(b->type != BT_GamepadKeyBinding) { - if(b->type == BT_GamepadAxisBinding) - b->blockinput = false; - break; - } else if(scan == SDL_CONTROLLER_BUTTON_BACK || scan == SDL_CONTROLLER_BUTTON_START) { - b->blockinput = false; - break; - } - - for(int i = CONFIG_GAMEPAD_KEY_FIRST; i <= CONFIG_GAMEPAD_KEY_LAST; ++i) { + if(!esc) { + for(int i = CONFIG_KEY_FIRST; i <= CONFIG_KEY_LAST; ++i) { if(config_get_int(i) == scan) { config_set_int(i, config_get_int(b->configentry)); } } config_set_int(b->configentry, scan); - b->blockinput = false; - break; } - case E_GamepadAxis: { - if(b->type == BT_GamepadAxisBinding) { - if(b->configentry == CONFIG_GAMEPAD_AXIS_UD) { - if(config_get_int(CONFIG_GAMEPAD_AXIS_LR) == state) { - config_set_int(CONFIG_GAMEPAD_AXIS_LR, config_get_int(CONFIG_GAMEPAD_AXIS_UD)); - } - } else if(b->configentry == CONFIG_GAMEPAD_AXIS_LR) { - if(config_get_int(CONFIG_GAMEPAD_AXIS_UD) == state) { - config_set_int(CONFIG_GAMEPAD_AXIS_UD, config_get_int(CONFIG_GAMEPAD_AXIS_LR)); - } - } + b->blockinput = false; + return true; + } - config_set_int(b->configentry, state); + if(t == MAKE_TAISEI_EVENT(TE_GAMEPAD_BUTTON_DOWN)) { + SDL_GameControllerButton button = event->user.code; + + if(b->type != BT_GamepadKeyBinding) { + if(b->type == BT_GamepadAxisBinding) { b->blockinput = false; } - break; + return true; } - case E_CharTyped: { - if(c != ':') { - char s[] = {c, 0}; - strlcat(dest, s, OPTIONS_TEXT_INPUT_BUFSIZE); + if(button == SDL_CONTROLLER_BUTTON_BACK || button == SDL_CONTROLLER_BUTTON_START) { + b->blockinput = false; + return true; + } + + for(int i = CONFIG_GAMEPAD_KEY_FIRST; i <= CONFIG_GAMEPAD_KEY_LAST; ++i) { + if(config_get_int(i) == button) { + config_set_int(i, config_get_int(b->configentry)); } - break; } - case E_CharErased: { - if(dest != NULL && strlen(dest)) - dest[strlen(dest)-1] = 0; - break; - } - - case E_SubmitText: { - if(dest != NULL && strlen(dest)) - config_set_str(b->configentry, dest); - else - strlcpy(dest, config_get_str(b->configentry), OPTIONS_TEXT_INPUT_BUFSIZE); - b->blockinput = false; - break; - } - - case E_CancelText: { - strlcpy(dest, config_get_str(b->configentry), OPTIONS_TEXT_INPUT_BUFSIZE); - b->blockinput = false; - break; - } - - default: break; + config_set_int(b->configentry, button); + b->blockinput = false; + return true; } + + if(t == MAKE_TAISEI_EVENT(TE_GAMEPAD_AXIS)) { + SDL_GameControllerAxis axis = event->user.code; + + if(b->type == BT_GamepadAxisBinding) { + if(b->configentry == CONFIG_GAMEPAD_AXIS_UD) { + if(config_get_int(CONFIG_GAMEPAD_AXIS_LR) == axis) { + config_set_int(CONFIG_GAMEPAD_AXIS_LR, config_get_int(CONFIG_GAMEPAD_AXIS_UD)); + } + } else if(b->configentry == CONFIG_GAMEPAD_AXIS_LR) { + if(config_get_int(CONFIG_GAMEPAD_AXIS_UD) == axis) { + config_set_int(CONFIG_GAMEPAD_AXIS_UD, config_get_int(CONFIG_GAMEPAD_AXIS_LR)); + } + } + + config_set_int(b->configentry, axis); + b->blockinput = false; + } + + return true; + } + + // FIXME: Implement text input + + return true; } // raw access to arg is safe there after the bind_get check -#define SHOULD_SKIP (!menu->entries[menu->cursor].action || (bind_get(menu, menu->cursor) && !bind_isactive(menu->entries[menu->cursor].arg))) +#define SHOULD_SKIP -static void options_input_event(EventType type, int state, void *arg) { +static bool options_input_handler(SDL_Event *event, void *arg) { MenuData *menu = arg; OptionBinding *bind = bind_get(menu, menu->cursor); - - notify_bindings(type, state, menu); + TaiseiEvent type = TAISEI_EVENT(event->type); switch(type) { - case E_CursorDown: + case TE_MENU_CURSOR_UP: + case TE_MENU_CURSOR_DOWN: play_ui_sound("generic_shot"); menu->drawdata[3] = 10; do { - menu->cursor++; - if(menu->cursor >= menu->ecount) + menu->cursor += (type == TE_MENU_CURSOR_UP ? -1 : 1); + + if(menu->cursor >= menu->ecount) { menu->cursor = 0; - } while SHOULD_SKIP; - break; + } - case E_CursorUp: - play_ui_sound("generic_shot"); - menu->drawdata[3] = 10; - do { - menu->cursor--; - if(menu->cursor < 0) + if(menu->cursor < 0) { menu->cursor = menu->ecount - 1; - } while SHOULD_SKIP; + } + + bind = bind_get(menu, menu->cursor); + } while(!menu->entries[menu->cursor].action || (bind && !bind_isactive(bind))); break; - case E_CursorLeft: + case TE_MENU_CURSOR_LEFT: + case TE_MENU_CURSOR_RIGHT: play_ui_sound("generic_shot"); + bool next = (type == TE_MENU_CURSOR_RIGHT); + if(bind) { switch(bind->type) { case BT_GamepadDevice: case BT_IntValue: case BT_Resolution: - bind_setprev(bind); + (next ? bind_setnext : bind_setprev)(bind); break; case BT_Scale: - config_set_float(bind->configentry, clamp(config_get_float(bind->configentry) - bind->scale_step, bind->scale_min, bind->scale_max)); + config_set_float(bind->configentry, + clamp( + config_get_float(bind->configentry) + bind->scale_step * (next ? 1 : -1), + bind->scale_min, bind->scale_max + ) + ); break; default: @@ -1063,27 +1051,7 @@ static void options_input_event(EventType type, int state, void *arg) { } break; - case E_CursorRight: - play_ui_sound("generic_shot"); - if(bind) { - switch(bind->type) { - case BT_GamepadDevice: - case BT_IntValue: - case BT_Resolution: - bind_setnext(bind); - break; - - case BT_Scale: - config_set_float(bind->configentry, clamp(config_get_float(bind->configentry) + bind->scale_step, bind->scale_min, bind->scale_max)); - break; - - default: - break; - } - } - break; - - case E_MenuAccept: + case TE_MENU_ACCEPT: play_ui_sound("shot_special1"); menu->selected = menu->cursor; @@ -1104,7 +1072,7 @@ static void options_input_event(EventType type, int state, void *arg) { } else close_menu(menu); break; - case E_MenuAbort: + case TE_MENU_ABORT: play_ui_sound("hit"); menu->selected = -1; close_menu(menu); @@ -1114,26 +1082,38 @@ static void options_input_event(EventType type, int state, void *arg) { } menu->cursor = (menu->cursor % menu->ecount) + menu->ecount*(menu->cursor < 0); + return false; } #undef SHOULD_SKIP void options_menu_input(MenuData *menu) { OptionBinding *b; - EventFlags flags = EF_Video; + EventFlags flags = EFLAG_MENU; if((b = bind_getinputblocking(menu)) != NULL) { - int flags = 0; - - switch(b->type) { - case BT_StrValue: flags |= EF_Text; break; - case BT_KeyBinding: flags |= EF_Keyboard; break; - case BT_GamepadKeyBinding: flags |= EF_Gamepad | EF_Keyboard; break; - case BT_GamepadAxisBinding: flags |= EF_Gamepad | EF_Keyboard; break; - default: break; + if(b->type == BT_StrValue) { + flags |= EFLAG_TEXT; } - - handle_events(bind_input_event, flags, b); - } else { - handle_events(options_input_event, flags | EF_Menu, menu); } + + events_poll((EventHandler[]){ + { + .proc = options_vidmode_change_handler, + .arg = menu, + .priority = EPRIO_SYSTEM, + .event_type = MAKE_TAISEI_EVENT(TE_VIDEO_MODE_CHANGED), + }, + { + .proc = options_input_handler_for_binding, + .arg = b, + .priority = EPRIO_CAPTURE, + }, + { + .proc = options_input_handler, + .arg = menu, + .priority = EPRIO_NORMAL, + }, + + {NULL} + }, flags); } diff --git a/src/menu/savereplay.c b/src/menu/savereplay.c index 63c950fb..5b91a296 100644 --- a/src/menu/savereplay.c +++ b/src/menu/savereplay.c @@ -73,17 +73,22 @@ void draw_saverpy_menu(MenuData *m) { glPopMatrix(); } -void saverpy_input_event(EventType type, int state, void *arg) { - if(type == E_CursorLeft) - menu_event(E_CursorUp, state, arg); - else if(type == E_CursorRight) - menu_event(E_CursorDown, state, arg); - else - menu_event(type, state, arg); +bool savepry_input_handler(SDL_Event *event, void *arg) { + if(event->type == MAKE_TAISEI_EVENT(TE_MENU_CURSOR_UP)) { + event->type = MAKE_TAISEI_EVENT(TE_MENU_CURSOR_LEFT); + } else if(event->type == MAKE_TAISEI_EVENT(TE_MENU_CURSOR_UP)) { + event->type = MAKE_TAISEI_EVENT(TE_MENU_CURSOR_RIGHT); + } + + return false; } void saverpy_menu_input(MenuData *menu) { - handle_events(saverpy_input_event, EF_Menu, menu); + events_poll((EventHandler[]){ + { .proc = savepry_input_handler, .arg = menu }, + { .proc = menu_input_handler, .arg = menu }, + {NULL} + }, EFLAG_MENU); } void create_saverpy_menu(MenuData *m) { diff --git a/src/resource/resource.c b/src/resource/resource.c index b8c7ff7f..f621ea15 100644 --- a/src/resource/resource.c +++ b/src/resource/resource.c @@ -107,28 +107,19 @@ typedef struct ResourceAsyncLoadData { static int load_resource_async_thread(void *vdata) { ResourceAsyncLoadData *data = vdata; - SDL_Event evt; SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW); data->opaque = data->handler->begin_load(data->path, data->flags); - - SDL_zero(evt); - evt.type = sdl_custom_events.resource_load_finished; - evt.user.data1 = data; - SDL_PushEvent(&evt); + events_emit(TE_RESOURCE_ASYNC_LOADED, 0, data, NULL); return 0; } static Resource* load_resource_finish(void *opaque, ResourceHandler *handler, const char *path, const char *name, char *allocated_path, char *allocated_name, ResourceFlags flags); -bool resource_sdl_event(SDL_Event *evt) { +static bool resource_asyncload_handler(SDL_Event *evt, void *arg) { assert(SDL_ThreadID() == main_thread_id); - if(evt->type != sdl_custom_events.resource_load_finished) { - return false; - } - ResourceAsyncLoadData *data = evt->user.data1; if(!data) { @@ -170,14 +161,18 @@ static void load_resource_async(ResourceHandler *handler, char *path, char *name static void update_async_load_state(void) { SDL_Event evt; - while(SDL_PeepEvents(&evt, 1, SDL_GETEVENT, sdl_custom_events.resource_load_finished, sdl_custom_events.resource_load_finished)) { - resource_sdl_event(&evt); + uint32_t etype = MAKE_TAISEI_EVENT(TE_RESOURCE_ASYNC_LOADED); + + while(SDL_PeepEvents(&evt, 1, SDL_GETEVENT, etype, etype)) { + resource_asyncload_handler(&evt, NULL); } } static bool resource_check_async_load(ResourceHandler *handler, const char *name) { - if(SDL_ThreadID() == main_thread_id) + if(SDL_ThreadID() == main_thread_id) { update_async_load_state(); + } + ResourceAsyncLoadData *data = hashtable_get_string(handler->async_load_data, name); return data; } @@ -357,6 +352,16 @@ void init_resources(void) { ); main_thread_id = SDL_ThreadID(); + + if(!getenvint("TAISEI_NOASYNC", 0)) { + EventHandler h = { + .proc = resource_asyncload_handler, + .priority = EPRIO_SYSTEM, + .event_type = MAKE_TAISEI_EVENT(TE_RESOURCE_ASYNC_LOADED), + }; + + events_register_handler(&h); + } } void resource_util_strip_ext(char *path) { @@ -435,4 +440,8 @@ void free_resources(bool all) { delete_fbo(&resources.fbo.bg[1]); delete_fbo(&resources.fbo.fg[0]); delete_fbo(&resources.fbo.fg[1]); + + if(!getenvint("TAISEI_NOASYNC", 0)) { + events_unregister_handler(resource_asyncload_handler); + } } diff --git a/src/resource/resource.h b/src/resource/resource.h index 78522f94..c1a53979 100644 --- a/src/resource/resource.h +++ b/src/resource/resource.h @@ -118,7 +118,3 @@ void preload_resources(ResourceType type, ResourceFlags flags, const char *first void resource_util_strip_ext(char *path); char* resource_util_basename(const char *prefix, const char *path); const char* resource_util_filename(const char *path); - -bool resource_sdl_event(SDL_Event *evt); - - diff --git a/src/stage.c b/src/stage.c index 2fcd26bf..a4d50964 100644 --- a/src/stage.c +++ b/src/stage.c @@ -241,14 +241,17 @@ void stage_gameover(void) { } } -void stage_input_event(EventType type, int key, void *arg) { +bool stage_input_handler_gameplay(SDL_Event *event, void *arg) { + TaiseiEvent type = TAISEI_EVENT(event->type); + int32_t code = event->user.code; + switch(type) { - case E_PlrKeyDown: - if(key == KEY_NOBACKGROUND) { + case TE_GAME_KEY_DOWN: + if(code == KEY_NOBACKGROUND) { break; } - if(key == KEY_HAHAIWIN) { + if(code == KEY_HAHAIWIN) { #ifdef DEBUG stage_finish(GAMEOVER_WIN); #endif @@ -256,45 +259,53 @@ void stage_input_event(EventType type, int key, void *arg) { } #ifndef DEBUG // no cheating for peasants - if( key == KEY_IDDQD || - key == KEY_POWERUP || - key == KEY_POWERDOWN) + if( code == KEY_IDDQD || + code == KEY_POWERUP || + code == KEY_POWERDOWN) break; #endif - player_event_with_replay(&global.plr, EV_PRESS, key); + player_event_with_replay(&global.plr, EV_PRESS, code); break; - case E_PlrKeyUp: - player_event_with_replay(&global.plr, EV_RELEASE, key); + case TE_GAME_KEY_UP: + player_event_with_replay(&global.plr, EV_RELEASE, code); break; - case E_Pause: + case TE_GAME_PAUSE: stage_pause(); break; - case E_PlrAxisLR: - player_event_with_replay(&global.plr, EV_AXIS_LR, (uint16_t)key); + case TE_GAME_AXIS_LR: + player_event_with_replay(&global.plr, EV_AXIS_LR, (uint16_t)code); break; - case E_PlrAxisUD: - player_event_with_replay(&global.plr, EV_AXIS_UD, (uint16_t)key); + case TE_GAME_AXIS_UD: + player_event_with_replay(&global.plr, EV_AXIS_UD, (uint16_t)code); break; default: break; } + + return false; } -void stage_replay_event(EventType type, int state, void *arg) { - if(type == E_Pause) +bool stage_input_handler_replay(SDL_Event *event, void *arg) { + if(event->type == MAKE_TAISEI_EVENT(TE_GAME_PAUSE)) { stage_pause(); + } + + return false; } void replay_input(void) { ReplayStage *s = global.replay_stage; int i; - handle_events(stage_replay_event, EF_Game, NULL); + events_poll((EventHandler[]){ + { .proc = stage_input_handler_replay }, + {NULL} + }, EFLAG_GAME); for(i = s->playpos; i < s->numevents; ++i) { ReplayEvent *e = s->events + i; @@ -326,7 +337,10 @@ void replay_input(void) { } void stage_input(void) { - handle_events(stage_input_event, EF_Game, NULL); + events_poll((EventHandler[]){ + { .proc = stage_input_handler_gameplay }, + {NULL} + }, EFLAG_GAME); player_fix_input(&global.plr); player_applymovement(&global.plr); } diff --git a/src/video.c b/src/video.c index 378c73f8..954f396b 100644 --- a/src/video.c +++ b/src/video.c @@ -181,13 +181,6 @@ static void video_update_quality(void) { reload_fonts(text); } -static void video_dispatch_mode_changed_event(void) { - SDL_Event evt; - SDL_zero(evt); - evt.type = sdl_custom_events.video_mode_changed; - SDL_PushEvent(&evt); -} - static uint32_t get_fullscreen_flag(void) { if(config_get_int(CONFIG_FULLSCREEN_DESKTOP)) { return SDL_WINDOW_FULLSCREEN_DESKTOP; @@ -218,7 +211,7 @@ static void video_update_mode_settings(void) { video.real.height = video.current.height; video_set_viewport(); video_update_quality(); - video_dispatch_mode_changed_event(); + events_emit(TE_VIDEO_MODE_CHANGED, 0, NULL, NULL); if(video_is_fullscreen() && !config_get_int(CONFIG_FULLSCREEN_DESKTOP)) { video_check_fullscreen_sanity(); @@ -445,14 +438,6 @@ bool video_can_change_resolution(void) { return !video_is_fullscreen() || !config_get_int(CONFIG_FULLSCREEN_DESKTOP); } -void video_resize(int w, int h) { - video.current.width = w; - video.current.height = h; - video_set_viewport(); - video_update_quality(); - video_dispatch_mode_changed_event(); -} - static void video_cfg_fullscreen_callback(ConfigIndex idx, ConfigValue v) { video_set_mode( config_get_int(CONFIG_VID_WIDTH), @@ -523,6 +508,28 @@ static void video_init_sdl(void) { } } +static void video_handle_resize(int w, int h) { + video.current.width = w; + video.current.height = h; + video_set_viewport(); + video_update_quality(); + events_emit(TE_VIDEO_MODE_CHANGED, 0, NULL, NULL); +} + +static bool video_handle_window_event(SDL_Event *event, void *arg) { + switch(event->window.event) { + case SDL_WINDOWEVENT_RESIZED: + video_handle_resize(event->window.data1, event->window.data2); + break; + + case SDL_WINDOWEVENT_FOCUS_LOST: + events_emit(TE_GAME_PAUSE, 0, NULL, NULL); + break; + } + + return true; +} + void video_init(void) { bool fullscreen_available = false; @@ -575,6 +582,13 @@ void video_init(void) { config_set_callback(CONFIG_BG_QUALITY, video_quality_callback); config_set_callback(CONFIG_TEXT_QUALITY, video_quality_callback); + EventHandler h = { + .proc = video_handle_window_event, + .priority = EPRIO_SYSTEM, + .event_type = SDL_WINDOWEVENT, + }; + + events_register_handler(&h); log_info("Video subsystem initialized"); } @@ -584,4 +598,5 @@ void video_shutdown(void) { unload_gl_library(); free(video.modes); SDL_VideoQuit(); + events_unregister_handler(video_handle_window_event); } diff --git a/src/video.h b/src/video.h index 20acc411..0c26b691 100644 --- a/src/video.h +++ b/src/video.h @@ -43,7 +43,6 @@ void video_set_viewport(void); bool video_is_fullscreen(void); bool video_is_resizable(void); bool video_can_change_resolution(void); -void video_resize(int w, int h); void video_take_screenshot(void);