Fix major overstep with last commit
[vg.git] / src / vg / vg.h
index b076091d1c627ba252d32ccf81d38165b94895f9..5b2ca81ac824d8dcc9d9ac5f72b6ebba8703851b 100644 (file)
@@ -3,13 +3,8 @@
 #ifndef VG_HEADER_H
 #define VG_HEADER_H
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <dirent.h>
-#include <string.h>
-#include <stdarg.h>
-#include <ctype.h>
-#include <math.h>
+#include "vg_platform.h"
+
 
 #if defined(VG_SERVER) || defined(VG_TOOLS)
  #define VG_NON_CLIENT
 #include "../../dep/glfw/glfw3.h"
 #endif
 
-#define STB_DS_IMPLEMENTATION
-#include "stb/stb_ds.h"
-
-#define QOI_IMPLEMENTATION
-#include "phoboslab/qoi.h"
-
 #include "vg_stdint.h"
-#include "vg_platform.h"
 
 void vg_register_exit( void( *funcptr )(void), const char *name );
 
@@ -64,7 +52,6 @@ double vg_time,
        vg_time_last,
        vg_time_delta;
 
-
 struct vg
 {
    /* Engine sync */
@@ -72,12 +59,22 @@ struct vg
 
    vg_mutex     mux_context;
    vg_semaphore sem_allow_exec,
-                sem_exec_finished;
+                sem_exec_finished,
+                sem_loader,
+                sem_fatal;
    int exec_context;
 
    vg_mutex     mux_engine_status;
-   int engine_running;
+   enum engine_status
+   {
+      k_engine_status_none,
+      k_engine_status_running,
+      k_engine_status_crashed
+   }
+   engine_status;
+   const char *str_const_engine_err;
 
+   int          is_loaded;
 
    /* Gamepad */
    GLFWgamepadstate  gamepad;
@@ -87,111 +84,158 @@ struct vg
 }
 static vg;
 
+struct vg_thread_info
+{
+   enum vg_thread_purpose
+   {
+      k_thread_purpose_nothing,
+      k_thread_purpose_main,
+      k_thread_purpose_loader
+   }
+   purpose;
+
+   int gl_context_level;
+};
+
+static VG_THREAD_LOCAL struct vg_thread_info vg_thread_info;
+
 
 //#define VG_SYNC_DEBUG
 
 #ifdef VG_SYNC_DEBUG
-  #define VG_SYNC_LOG(...) vg_info(__VA_ARGS__)
+  #define VG_SYNC_LOG(STR,...) vg_info(STR,vg_thread_info.purpose,##__VA_ARGS__)
 #else
   #define VG_SYNC_LOG(...)
 #endif
 
-/*
- * Sync execution so that the OpenGL context is switched onto this thread.
- * Anything after this call will be in a valid context.
- */
-static int vg_acquire_thread_sync( int id )
+static void vg_fatal_exit_loop( const char *error );
+
+static void vg_ensure_engine_running(void)
 {
-   if( id == 0 )
-   {
-      /* no action */
-      return 1;
-   }
-   else
+   /* Check if the engine is no longer running */
+   vg_mutex_lock( &vg.mux_engine_status );
+   if( vg.engine_status != k_engine_status_running )
    {
-      /* Check if the engine is no longer running */
-      vg_mutex_lock( &vg.mux_engine_status );
-      if( !vg.engine_running )
+      VG_SYNC_LOG( "[%d] Engine is no longer running\n");
+      vg_mutex_unlock( &vg.mux_engine_status );
+
+      /* Safe to disregard loader thread from this point on, elswhere */
+      if( vg_thread_info.purpose == k_thread_purpose_loader )
       {
-         VG_SYNC_LOG( "[%d] Engine is no longer running\n", id );
-         vg_mutex_unlock( &vg.mux_engine_status );
-         return 0;
+         vg_semaphore_post( &vg.sem_loader );
       }
-      vg_mutex_unlock( &vg.mux_engine_status );
 
+      VG_SYNC_LOG( "[%d] about to kill\n");
+      vg_thread_exit();
+   }
+   vg_mutex_unlock( &vg.mux_engine_status );
+}
 
-      vg_mutex_lock( &vg.mux_context );
-      VG_SYNC_LOG( "[%d] Signal to sync.\n", id );
-      vg.exec_context = id;
-      vg_mutex_unlock( &vg.mux_context );
-      
-      /* wait until told we can go */
-      VG_SYNC_LOG( "[%d] Waiting to acuire sync.\n", id );
-      vg_semaphore_wait( &vg.sem_allow_exec );
-      glfwMakeContextCurrent( vg.window );
+/*
+ * Sync execution so that the OpenGL context is switched onto this thread.
+ * Anything after this call will be in a valid context.
+ */
+static void vg_acquire_thread_sync(void)
+{
+   /* We dont want to do anything if this is the main thread */
+   if( vg_thread_info.purpose == k_thread_purpose_main )
+      return;
 
-      /* context now valid to work in while we hold up main thread */
-      VG_SYNC_LOG( "[%d] Context acquired.\n", id );
+   assert( vg_thread_info.purpose == k_thread_purpose_loader );
 
-      return 1;
+   vg_ensure_engine_running();
+
+   /* Check if thread already has the context */
+   if( vg_thread_info.gl_context_level )
+   {
+      vg_thread_info.gl_context_level ++;
+      VG_SYNC_LOG( "[%d] We already have sync here\n" );
+      return;
    }
+
+   vg_mutex_lock( &vg.mux_context );
+   VG_SYNC_LOG( "[%d] Signal to sync.\n" );
+   vg.exec_context = 1;
+   vg_mutex_unlock( &vg.mux_context );
+   
+   /* wait until told we can go */
+   VG_SYNC_LOG( "[%d] Waiting to acuire sync.\n" );
+   vg_semaphore_wait( &vg.sem_allow_exec );
+   glfwMakeContextCurrent( vg.window );
+
+   /* context now valid to work in while we hold up main thread */
+   VG_SYNC_LOG( "[%d] Context acquired.\n" );
+   vg_thread_info.gl_context_level ++;
 }
 
 /*
  * Signify that we are done with the OpenGL context in this thread.
  * Anything after this call will be in an undefined context.
  */
-static void vg_release_thread_sync( int id )
+static void vg_release_thread_sync(void)
 {
-   if( id == 0 )
-   {
-      vg_mutex_lock( &vg.mux_context );
+   if( vg_thread_info.purpose == k_thread_purpose_main )
+      return;
 
-      if( vg.exec_context != 0 )
-      {
-         VG_SYNC_LOG( "[%d] Allowing content (%d).\n", id, vg.exec_context );
+   assert( vg_thread_info.purpose == k_thread_purpose_loader );
 
-         /* allow operations to go */
-         glfwMakeContextCurrent( NULL );
-         vg_semaphore_post( &vg.sem_allow_exec );
+   /* signal that we are done */
+   vg_thread_info.gl_context_level --;
 
-         /* wait for operations to complete */
-         VG_SYNC_LOG( "[%d] Waiting for content (%d).\n", id, vg.exec_context );
-         vg_semaphore_wait( &vg.sem_exec_finished );
-         
-         /* re-engage main thread */
-         VG_SYNC_LOG( "[%d] Re-engaging.\n", id );
-         vg.exec_context = 0;
-         glfwMakeContextCurrent( vg.window );
-      }
-
-      vg_mutex_unlock( &vg.mux_context );
-   }
-   else
+   if( !vg_thread_info.gl_context_level )
    {
-      /* signal that we are done */
-      VG_SYNC_LOG( "[%d] Releasing context.\n", id );
+      VG_SYNC_LOG( "[%d] Releasing context.\n" );
       glfwMakeContextCurrent( NULL );
       vg_semaphore_post( &vg.sem_exec_finished );
    }
 }
 
+static void vg_run_synced_content(void)
+{
+   assert( vg_thread_info.purpose == k_thread_purpose_main );
+
+   vg_mutex_lock( &vg.mux_context );
+
+   if( vg.exec_context != 0 )
+   {
+      VG_SYNC_LOG( "[%d] Allowing content (%d).\n", vg.exec_context );
+
+      /* allow operations to go */
+      vg_thread_info.gl_context_level = 0;
+      glfwMakeContextCurrent( NULL );
+      vg_semaphore_post( &vg.sem_allow_exec );
+
+      /* wait for operations to complete */
+      VG_SYNC_LOG( "[%d] Waiting for content (%d).\n", vg.exec_context );
+      vg_semaphore_wait( &vg.sem_exec_finished );
+      
+      /* check if we killed the engine */
+      vg_ensure_engine_running();
+      
+      /* re-engage main thread */
+      VG_SYNC_LOG( "[%d] Re-engaging.\n" );
+      vg.exec_context = 0;
+      glfwMakeContextCurrent( vg.window );
+      vg_thread_info.gl_context_level = 1;
+   }
+
+   vg_mutex_unlock( &vg.mux_context );
+}
+
 static void vg_opengl_sync_init(void)
 {
    vg_semaphore_init( &vg.sem_allow_exec, 0 );
    vg_semaphore_init( &vg.sem_exec_finished, 0 );
+   vg_semaphore_init( &vg.sem_loader, 1 );
+   vg_semaphore_init( &vg.sem_fatal, 1 );
    vg_mutex_init( &vg.mux_context );
-}
 
-static void vg_opengl_sync_free(void)
-{
-   vg_semaphore_free( &vg.sem_allow_exec );
-   vg_semaphore_free( &vg.sem_exec_finished );
-   vg_mutex_free( &vg.mux_context );
+   vg_set_thread_name( "[vg] Main" );
+   vg_thread_info.purpose = k_thread_purpose_main;
+   vg_thread_info.gl_context_level = 1;
 }
 
-
-int vg_checkgl( const char *src_info );
+static void vg_checkgl( const char *src_info );
 #define VG_STRINGIT( X ) #X
 #define VG_CHECK_GL_ERR() vg_checkgl( __FILE__ ":L" VG_STRINGIT(__LINE__) )
 
@@ -213,7 +257,7 @@ static void vg_framebuffer_resize(int w, int h) VG_GAMELOOP;
 static void vg_render(void) VG_GAMELOOP;
 static void vg_ui(void) VG_GAMELOOP;
 
-int vg_checkgl( const char *src_info )
+static void vg_checkgl( const char *src_info )
 {
    int fail = 0;
 
@@ -224,11 +268,10 @@ int vg_checkgl( const char *src_info )
       fail = 1;
    }
 
-   return fail;
+   if( fail )
+      vg_fatal_exit_loop( "OpenGL Error" );
 }
 
-
-
 void vg_mouse_callback( GLFWwindow* ptrW, double xpos, double ypos )
 {
    vg_mouse[0] = xpos;
@@ -251,46 +294,39 @@ void vg_framebuffer_resize_callback( GLFWwindow *ptrW, int w, int h )
 
 static int vg_bake_shaders(void)
 {
-   if( vg_acquire_thread_sync(1) )
-   {
-      if( !vg_shaders_recompile() )
-      {
-         vg_shaders_free(NULL);
-         vg_release_thread_sync(1);
-         return 0;
-      }
-      else
-      {
-         vg_release_thread_sync(1);
+   vg_acquire_thread_sync();
 
-         if( !vg_loader_highwater( vg_shaders_free, NULL ) )         return 0;
-         else return 1;
-      }
+   if( !vg_shaders_recompile() )
+   {
+      vg_shaders_free(NULL);
+      vg_release_thread_sync();
+      return 0;
+   }
+   else
+   {
+      vg_release_thread_sync();
+      vg_loader_highwater( NULL, vg_shaders_free, NULL );
+      return 1;
    }
-
-   return 0;
 }
 
-int vg_preload(void);
-int vg_load(void);
-static int vg_load_full(void)
+void vg_preload(void);
+void vg_load(void);
+static void vg_load_full(void)
 {
-   if( !vg_preload() )                                               return 0;
+   vg_preload();
 
    /* internal */
-   if( !vg_gamepad_init() )                                          return 0;
-   if( !vg_loader_highwater( NULL, NULL ) )                          return 0;
-
-   if( !vg_lines_init() )                                            return 0;
-   if( !vg_loader_highwater( vg_lines_free, NULL ) )                 return 0;
-
-   if( !vg_audio_init() )                                            return 0;
-   if( !vg_loader_highwater( vg_audio_free, NULL ) )                 return 0;
+   vg_loader_highwater( vg_gamepad_init, NULL, NULL );
+   vg_loader_highwater( vg_lines_init, vg_lines_free, NULL );
+   vg_loader_highwater( vg_audio_init, vg_audio_free, NULL );
 
    /* client */
-   if( !vg_load() )                                                  return 0;
+   vg_load();
 
-   return 1;
+   vg_acquire_thread_sync();
+   vg.is_loaded = 1;
+   vg_release_thread_sync();
 }
 
 static void vg_enter( int argc, char *argv[], const char *window_name )
@@ -364,21 +400,17 @@ static void vg_enter( int argc, char *argv[], const char *window_name )
       goto il_exit_loader;
 
    vg_mutex_init( &vg.mux_engine_status );
-   vg.engine_running = 1;
+   vg.engine_status = k_engine_status_running;
 
    vg_opengl_sync_init();
    vg_loader_start();
 
    int loaded = 0;
-
+   int counter =0;
    while(1)
    {
-      vg_acquire_thread_sync( 0 );
-
       if( glfwWindowShouldClose( vg.window ) )
-      {
          break;
-      }
 
       v2_copy( (v2f){ 0.0f, 0.0f }, vg_mouse_wheel );
       glfwPollEvents();
@@ -387,9 +419,7 @@ static void vg_enter( int argc, char *argv[], const char *window_name )
       vg_time = glfwGetTime();
       vg_time_delta = vg_minf( vg_time - vg_time_last, 0.1f );
       
-      enum loader_status load_status = vg_loader_status();
-
-      if( load_status == k_loader_status_complete )
+      if( vg.is_loaded )
       {
          glClearColor( 0.0f,sinf(vg_time*20.0)*0.5f+0.5f,0.0f,1.0f );
          glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
@@ -402,19 +432,19 @@ static void vg_enter( int argc, char *argv[], const char *window_name )
       }
       else
       {
-         if( load_status == k_loader_status_fail )
-         {
-            break;
-         }
-         else
-         {
-            vg_loader_render();
-         }
+         vg_loader_render();
       }
 
       vg_update_inputs();
       vg_update( loaded );
 
+      counter ++;
+
+#if 0
+      if( counter == 30 )
+         vg_fatal_exit_loop( "Test crash from main, while loading (-O0)" );
+#endif
+
       if( loaded )
       {
          vg_render();
@@ -434,28 +464,17 @@ static void vg_enter( int argc, char *argv[], const char *window_name )
          }
       }
 
-
-
-
       glfwSwapBuffers( vg.window );
-
-      if( VG_CHECK_GL_ERR() )
-         break;
-
-      vg_release_thread_sync( 0 );
+      vg_run_synced_content();
    }
 
    vg_console_write_persistent();
 
    vg_mutex_lock( &vg.mux_engine_status );
-   vg.engine_running = 0;
+   vg.engine_status = k_engine_status_none;
    vg_mutex_unlock( &vg.mux_engine_status );
 
-
    vg_loader_free();
-   vg_opengl_sync_free();
-
-   vg_mutex_free( &vg.mux_engine_status );
 
 il_exit_loader:
    ui_default_free();
@@ -464,6 +483,90 @@ il_exit_ui:
    glfwTerminate();
 }
 
+/*
+ * Immediately transfer away from calling thread into a safe loop, signal for 
+ * others to shutdown, then free everything once the user closes the window.
+ */
+static void vg_fatal_exit_loop( const char *error )
+{
+   vg_error( "Fatal error: %s\n", error );
+   assert( vg_semaphore_trywait( &vg.sem_fatal ) );
+
+   vg_mutex_lock( &vg.mux_engine_status );
+
+   if( vg.engine_status == k_engine_status_none )
+   {
+      vg_mutex_unlock( &vg.mux_engine_status );
+
+      /* TODO: Correct shutdown before other systems */
+      exit(0);
+   }
+   else
+   {
+      vg_mutex_unlock( &vg.mux_engine_status );
+
+      /* 
+       * if main
+       *    if loader running
+       *       wait until loader checks in, it will die
+       *    else
+       *       pass immediately
+       * else
+       *    if have context
+       *       pass immediately
+       *    else
+       *       wait for main to get to us, it will never be used again
+       *
+       * undefined behaviour:
+       *    fatal_exit_loop is called in both threads, preventing an appropriate
+       *                    reaction to the crash. This *should* be made
+       *                    obvious by the assertion
+       */
+      vg_acquire_thread_sync();
+
+      vg_mutex_lock( &vg.mux_engine_status );
+      vg.engine_status = k_engine_status_crashed;
+      vg.str_const_engine_err = error;
+      vg_mutex_unlock( &vg.mux_engine_status );
+
+      /* 
+       * Wait for loader to finish what it was doing, if it was running.
+       * Then we can continue in our nice error screen
+       */
+      if( vg_thread_info.purpose == k_thread_purpose_main )
+      {
+         vg_semaphore_wait( &vg.sem_loader );
+      }
+      vg_audio_free(NULL);
+
+      /*
+       * todo: draw error loop
+       */
+      while(1)
+      {
+         if( glfwWindowShouldClose( vg.window ) )
+            break;
+
+         glfwPollEvents();
+
+         vg_time = glfwGetTime();
+         vg_time_delta = vg_minf( vg_time - vg_time_last, 0.1f );
+      
+         glClearColor( sinf(vg_time*20.0)*0.5f+0.5f, 0.0f, 0.0f,1.0f );
+         glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
+
+         glfwSwapBuffers( vg.window );
+      }
+
+      /* Can now shutdown and EXIT */
+      vg_loader_free();
+      ui_default_free();
+
+      glfwTerminate();
+      exit(0);
+   }
+}
+
 #endif
 
 /*