From 68784449c9c98fad3b236a5106127689b931766d Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 13 Jan 2010 19:29:33 +0000 Subject: [PATCH] Clean up assertion API for public use. --- include/SDL_assert.h | 64 +++++++++++++++++++++++++++++++++++- src/SDL_assert.c | 78 +++++++++++++++++++++++++++++++------------- 2 files changed, 119 insertions(+), 23 deletions(-) diff --git a/include/SDL_assert.h b/include/SDL_assert.h index f29c25a19..b2f821201 100644 --- a/include/SDL_assert.h +++ b/include/SDL_assert.h @@ -109,7 +109,7 @@ typedef struct SDL_assert_data const char *filename; int linenum; const char *function; - struct SDL_assert_data *next; + const struct SDL_assert_data *next; } SDL_assert_data; /* Never call this directly. Use the SDL_assert* macros. */ @@ -166,6 +166,68 @@ extern DECLSPEC SDL_assert_state SDLCALL SDL_ReportAssertion(SDL_assert_data *, # error Unknown assertion level. #endif + +typedef SDL_assert_state (SDLCALL *SDL_AssertionHandler)( + const SDL_assert_data *, void *userdata); + +/** + * \brief Set an application-defined assertion handler. + * + * This allows an app to show its own assertion UI and/or force the + * response to an assertion failure. If the app doesn't provide this, SDL + * will try to do the right thing, popping up a system-specific GUI dialog, + * and probably minimizing any fullscreen windows. + * + * This callback may fire from any thread, but it runs wrapped in a mutex, so + * it will only fire from one thread at a time. + * + * Setting the callback to NULL restores SDL's original internal handler. + * + * This callback is NOT reset to SDL's internal handler upon SDL_Quit()! + * + * \return SDL_assert_state value of how to handle the assertion failure. + * + * \param handler Callback function, called when an assertion fails. + * \param userdata A pointer passed to the callback as-is. + */ +extern DECLSPEC void SDLCALL SDL_SetAssertionHandler( + SDL_AssertionHandler handler, + void *userdata); + +/** + * \brief Get a list of all assertion failures. + * + * Get all assertions triggered since last call to SDL_ResetAssertionReport(), + * or the start of the program. + * + * The proper way to examine this data looks something like this: + * + * + * const SDL_assert_data *item = SDL_GetAssertionReport(); + * while (item->condition) { + * printf("'%s', %s (%s:%d), triggered %u times, always ignore: %s.\n", + * item->condition, item->function, item->filename, + * item->linenum, item->trigger_count, + * item->always_ignore ? "yes" : "no"); + * item = item->next; + * } + * + * + * \return List of all assertions. This never returns NULL, + * even if there are no items. + * \sa SDL_ResetAssertionReport + */ +extern DECLSPEC const SDL_assert_data * SDLCALL SDL_GetAssertionReport(void); + +/** + * \brief Reset the list of all assertion failures. + * + * Reset list of all assertions triggered. + * + * \sa SDL_GetAssertionReport + */ +extern DECLSPEC void SDLCALL SDL_ResetAssertionReport(void); + /* Ends C function definitions when using C++ */ #ifdef __cplusplus /* *INDENT-OFF* */ diff --git a/src/SDL_assert.c b/src/SDL_assert.c index 1f91bf3e2..e86811afb 100644 --- a/src/SDL_assert.c +++ b/src/SDL_assert.c @@ -23,8 +23,6 @@ #include "SDL.h" #include "SDL_assert.h" -#if (SDL_ASSERT_LEVEL > 0) - #ifdef _WINDOWS #define WIN32_LEAN_AND_MEAN 1 #include @@ -34,13 +32,19 @@ #include #endif -/* We can keep all triggered assertions in a singly-linked list so we can +static SDL_assert_state +SDL_PromptAssertion(const SDL_assert_data *data, void *userdata); + +/* + * We keep all triggered assertions in a singly-linked list so we can * generate a report later. */ -#if !SDL_ASSERTION_REPORT_DISABLED static SDL_assert_data assertion_list_terminator = { 0, 0, 0, 0, 0, 0, 0 }; static SDL_assert_data *triggered_assertions = &assertion_list_terminator; -#endif + +static SDL_mutex *assertion_mutex = NULL; +static SDL_AssertionHandler assertion_handler = SDL_PromptAssertion; +static void *assertion_userdata = NULL; #ifdef __GNUC__ static void @@ -198,27 +202,30 @@ SDL_PromptAssertion_windows(const SDL_assert_data *data) static void SDL_AddAssertionToReport(SDL_assert_data *data) { -#if !SDL_ASSERTION_REPORT_DISABLED /* (data) is always a static struct defined with the assert macros, so we don't have to worry about copying or allocating them. */ if (data->next == NULL) { /* not yet added? */ data->next = triggered_assertions; triggered_assertions = data; } -#endif } + static void SDL_GenerateAssertionReport(void) { -#if !SDL_ASSERTION_REPORT_DISABLED - if (triggered_assertions != &assertion_list_terminator) - { - SDL_assert_data *item = triggered_assertions; + const SDL_assert_data *item; + /* only do this if the app hasn't assigned an assertion handler. */ + if (assertion_handler != SDL_PromptAssertion) + return; + + item = SDL_GetAssertionReport(); + if (item->condition) + { debug_print("\n\nSDL assertion report.\n"); debug_print("All SDL assertions between last init/quit:\n\n"); - while (item != &assertion_list_terminator) { + while (item->condition) { debug_print( "'%s'\n" " * %s (%s:%d)\n" @@ -232,9 +239,8 @@ static void SDL_GenerateAssertionReport(void) } debug_print("\n"); - triggered_assertions = &assertion_list_terminator; + SDL_ResetAssertionReport(); } -#endif } static void SDL_ExitProcess(int exitcode) @@ -253,12 +259,15 @@ static void SDL_AbortAssertion(void) } -static SDL_assert_state SDL_PromptAssertion(const SDL_assert_data *data) +static SDL_assert_state +SDL_PromptAssertion(const SDL_assert_data *data, void *userdata) { const char *envr; SDL_assert_state state = SDL_ASSERTION_ABORT; SDL_WindowID window; + (void) userdata; /* unused in default handler. */ + debug_print("\n\n" "Assertion failure at %s (%s:%d), triggered %u time%s:\n" " '%s'\n" @@ -291,6 +300,7 @@ static SDL_assert_state SDL_PromptAssertion(const SDL_assert_data *data) if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) { SDL_MinimizeWindow(window); } else { + /* !!! FIXME: ungrab the input if we're not fullscreen? */ /* No need to mess with the window */ window = 0; } @@ -344,8 +354,6 @@ static SDL_assert_state SDL_PromptAssertion(const SDL_assert_data *data) } -static SDL_mutex *assertion_mutex = NULL; - SDL_assert_state SDL_ReportAssertion(SDL_assert_data *data, const char *func, const char *file, int line) @@ -391,7 +399,7 @@ SDL_ReportAssertion(SDL_assert_data *data, const char *func, const char *file, } if (!data->always_ignore) { - state = SDL_PromptAssertion(data); + state = assertion_handler(data, assertion_userdata); } switch (state) @@ -417,8 +425,6 @@ SDL_ReportAssertion(SDL_assert_data *data, const char *func, const char *file, return state; } -#endif /* SDL_ASSERT_LEVEL > 0 */ - int SDL_AssertionsInit(void) { @@ -428,13 +434,41 @@ int SDL_AssertionsInit(void) void SDL_AssertionsQuit(void) { -#if (SDL_ASSERT_LEVEL > 0) SDL_GenerateAssertionReport(); if (assertion_mutex != NULL) { SDL_DestroyMutex(assertion_mutex); assertion_mutex = NULL; } -#endif +} + +void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata) +{ + if (handler != NULL) { + assertion_handler = handler; + assertion_userdata = userdata; + } else { + assertion_handler = SDL_PromptAssertion; + assertion_userdata = NULL; + } +} + +const SDL_assert_data *SDL_GetAssertionReport(void) +{ + return triggered_assertions; +} + +void SDL_ResetAssertionReport(void) +{ + SDL_assert_data *item = triggered_assertions; + SDL_assert_data *next = NULL; + for (item = triggered_assertions; item->condition; item = next) { + next = (SDL_assert_data *) item->next; + item->always_ignore = SDL_FALSE; + item->trigger_count = 0; + item->next = NULL; + } + + triggered_assertions = &assertion_list_terminator; } /* vi: set ts=4 sw=4 expandtab: */