Clean up assertion API for public use.
authorRyan C. Gordon <icculus@icculus.org>
Wed, 13 Jan 2010 19:29:33 +0000
changeset 3670 62b6a5b99918
parent 3669 46d27a9571fa
child 3671 0d6f520c0eb9
Clean up assertion API for public use.
include/SDL_assert.h
src/SDL_assert.c
--- a/include/SDL_assert.h	Wed Jan 13 16:58:24 2010 +0000
+++ b/include/SDL_assert.h	Wed Jan 13 19:29:33 2010 +0000
@@ -109,7 +109,7 @@
     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 @@
 #   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:
+ *
+ *  <code>
+ *  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;
+ *  }
+ *  </code>
+ *
+ *  \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* */
--- a/src/SDL_assert.c	Wed Jan 13 16:58:24 2010 +0000
+++ b/src/SDL_assert.c	Wed Jan 13 19:29:33 2010 +0000
@@ -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 <windows.h>
@@ -34,13 +32,19 @@
 #include <unistd.h>
 #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 @@
 
 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)
+    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)
     {
-        SDL_assert_data *item = triggered_assertions;
-
         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 @@
         }
         debug_print("\n");
 
-        triggered_assertions = &assertion_list_terminator;
+        SDL_ResetAssertionReport();
     }
-#endif
 }
 
 static void SDL_ExitProcess(int exitcode)
@@ -253,12 +259,15 @@
 }
 
 
-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 @@
         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_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 @@
     }
 
     if (!data->always_ignore) {
-        state = SDL_PromptAssertion(data);
+        state = assertion_handler(data, assertion_userdata);
     }
 
     switch (state)
@@ -417,8 +425,6 @@
     return state;
 }
 
-#endif  /* SDL_ASSERT_LEVEL > 0 */
-
 
 int SDL_AssertionsInit(void)
 {
@@ -428,13 +434,41 @@
 
 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: */