Fix major overstep with last commit
[vg.git] / src / vg / vg.h
index 22351ea0253249b92f54218ba841802c34669ab6..5b2ca81ac824d8dcc9d9ac5f72b6ebba8703851b 100644 (file)
@@ -1,46 +1,41 @@
-/* Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved */
+/* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
 
 #ifndef VG_HEADER_H
 #define VG_HEADER_H
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <dirent.h>
-#include <stdint.h>
-#include <string.h>
-#include <stdarg.h>
-#include <ctype.h>
-#include <math.h>
+#include "vg_platform.h"
 
-#include "glad/glad.h"
-#include "glfw/glfw3.h"
 
-#define STB_DS_IMPLEMENTATION
-#include "stb/stb_ds.h"
+#if defined(VG_SERVER) || defined(VG_TOOLS)
+ #define VG_NON_CLIENT
+#endif
 
-#define QOI_IMPLEMENTATION
-#include "phoboslab/qoi.h"
+#ifndef VG_SERVER
+#include "../../dep/glad/glad.h"
+#include "../../dep/glfw/glfw3.h"
+#endif
 
-#include "vg/vg_platform.h"
+#include "vg_stdint.h"
 
 void vg_register_exit( void( *funcptr )(void), const char *name );
-void vg_exiterr( const char *strErr );
 
-#include "vg/vg_m.h"
-#include "vg/vg_io.h"
-#include "vg/vg_gldiag.h"
+#include "vg_m.h"
+#include "vg_io.h"
+#include "vg_log.h"
 
-#ifndef VG_TOOLS
-
-/* Engine globals */
-GLFWwindow* vg_window;
+#ifdef VG_STEAM
+//#include "vg_steamworks.h"
+#include "vg_steam.h"
+#endif
 
-#ifdef VG_3D
- m4x4f vg_pv;
-#else
- m3x3f vg_pv;
+#ifndef VG_NON_CLIENT
+#include "vg_gldiag.h"
 #endif
 
+#ifndef VG_NON_CLIENT
+
+m4x4f vg_pv;
+
 #ifdef VG_CAPTURE_MODE
 int vg_window_x = 1920;
 int vg_window_y = 1080;
@@ -57,115 +52,296 @@ double vg_time,
        vg_time_last,
        vg_time_delta;
 
-#include "vg/vg_audio.h"
-#include "vg/vg_shader.h"
-#include "vg/vg_lines.h"
-#include "vg/vg_tex.h"
-#include "vg/vg_input.h"
-#include "vg/vg_ui.h"
-#include "vg/vg_console.h"
-#include "vg/vg_debug.h"
+struct vg
+{
+   /* Engine sync */
+   GLFWwindow* window;
+
+   vg_mutex     mux_context;
+   vg_semaphore sem_allow_exec,
+                sem_exec_finished,
+                sem_loader,
+                sem_fatal;
+   int exec_context;
+
+   vg_mutex     mux_engine_status;
+   enum engine_status
+   {
+      k_engine_status_none,
+      k_engine_status_running,
+      k_engine_status_crashed
+   }
+   engine_status;
+   const char *str_const_engine_err;
 
-#ifdef VG_STEAM
-#include "vg/vg_steamworks.h"
-#endif
+   int          is_loaded;
 
-#ifndef VG_RELEASE
-void vg_checkgl( const char *src_info )
+   /* Gamepad */
+   GLFWgamepadstate  gamepad;
+   int                               gamepad_ready;
+   const char       *gamepad_name;
+   int                               gamepad_id;
+}
+static vg;
+
+struct vg_thread_info
 {
-   GLenum err;
-   while( (err = glGetError()) != GL_NO_ERROR )
+   enum vg_thread_purpose
    {
-      vg_error( "(%s) OpenGL Error: #%d\n", src_info, err );
+      k_thread_purpose_nothing,
+      k_thread_purpose_main,
+      k_thread_purpose_loader
    }
-}
+   purpose;
 
- #define VG_STRINGIT( X ) #X
- #define VG_CHECK_GL() vg_checkgl( __FILE__ ":L" VG_STRINGIT(__LINE__) )
+   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(STR,...) vg_info(STR,vg_thread_info.purpose,##__VA_ARGS__)
 #else
#define VG_CHECK_GL()
 #define VG_SYNC_LOG(...)
 #endif
 
+static void vg_fatal_exit_loop( const char *error );
 
-#define VG_GAMELOOP
+static void vg_ensure_engine_running(void)
+{
+   /* Check if the engine is no longer running */
+   vg_mutex_lock( &vg.mux_engine_status );
+   if( vg.engine_status != k_engine_status_running )
+   {
+      VG_SYNC_LOG( "[%d] Engine is no longer running\n");
+      vg_mutex_unlock( &vg.mux_engine_status );
 
-void( *vg_on_exit[16] )(void);
-u32 vg_exit_count = 0;
+      /* Safe to disregard loader thread from this point on, elswhere */
+      if( vg_thread_info.purpose == k_thread_purpose_loader )
+      {
+         vg_semaphore_post( &vg.sem_loader );
+      }
 
-void vg_register_exit( void( *funcptr )(void), const char *name )
-{
-   vg_info( "exit registered: (%u)'%s'\n", vg_exit_count, name );
-   vg_on_exit[ vg_exit_count ++ ] = funcptr;
+      VG_SYNC_LOG( "[%d] about to kill\n");
+      vg_thread_exit();
+   }
+   vg_mutex_unlock( &vg.mux_engine_status );
 }
 
-void vg_exit(void)
+/*
+ * 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)
 {
-   for( int i = vg_exit_count-1; i >= 0; i -- )
+   /* We dont want to do anything if this is the main thread */
+   if( vg_thread_info.purpose == k_thread_purpose_main )
+      return;
+
+   assert( vg_thread_info.purpose == k_thread_purpose_loader );
+
+   vg_ensure_engine_running();
+
+   /* Check if thread already has the context */
+   if( vg_thread_info.gl_context_level )
    {
-      vg_info( "engine_exit[%d]()\n", i );
-      vg_on_exit[i]();
+      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 );
    
-   vg_info( "done\n" );
+   /* 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 ++;
 }
 
-void vg_exiterr( const char *strErr )
+/*
+ * 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(void)
 {
-   vg_error( "Engine Fatal: %s\n", strErr );
-   vg_exit();
-   exit(0);
+   if( vg_thread_info.purpose == k_thread_purpose_main )
+      return;
+
+   assert( vg_thread_info.purpose == k_thread_purpose_loader );
+
+   /* signal that we are done */
+   vg_thread_info.gl_context_level --;
+
+   if( !vg_thread_info.gl_context_level )
+   {
+      VG_SYNC_LOG( "[%d] Releasing context.\n" );
+      glfwMakeContextCurrent( NULL );
+      vg_semaphore_post( &vg.sem_exec_finished );
+   }
 }
 
-void vg_mouse_callback( GLFWwindow* ptrW, double xpos, double ypos )
+static void vg_run_synced_content(void)
 {
-   vg_mouse[0] = xpos;
-   vg_mouse[1] = ypos;
+   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 );
 }
 
-void vg_scroll_callback( GLFWwindow* ptrW, double xoffset, double yoffset )
+static void vg_opengl_sync_init(void)
 {
-   vg_mouse_wheel[0] += xoffset;
-   vg_mouse_wheel[1] += yoffset;
+   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 );
+
+   vg_set_thread_name( "[vg] Main" );
+   vg_thread_info.purpose = k_thread_purpose_main;
+   vg_thread_info.gl_context_level = 1;
 }
 
+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__) )
+
+#include "vg_audio.h"
+#include "vg_shader.h"
+#include "vg_tex.h"
+#include "vg_input.h"
+#include "vg_ui.h"
+#include "vg_console.h"
+#include "vg_lines.h"
+#include "vg_debug.h"
+#include "vg_loader.h"
 
+#define VG_GAMELOOP
 static void vg_register(void) VG_GAMELOOP;
 static void vg_start(void) VG_GAMELOOP;
-static void vg_update(void) VG_GAMELOOP;
+static void vg_update(int loaded) VG_GAMELOOP;
 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;
-static void vg_free(void) VG_GAMELOOP;
+
+static void vg_checkgl( const char *src_info )
+{
+   int fail = 0;
+
+   GLenum err;
+   while( (err = glGetError()) != GL_NO_ERROR )
+   {
+      vg_error( "(%s) OpenGL Error: #%d\n", src_info, err );
+      fail = 1;
+   }
+
+   if( fail )
+      vg_fatal_exit_loop( "OpenGL Error" );
+}
+
+void vg_mouse_callback( GLFWwindow* ptrW, double xpos, double ypos )
+{
+   vg_mouse[0] = xpos;
+   vg_mouse[1] = ypos;
+}
+
+void vg_scroll_callback( GLFWwindow* ptrW, double xoffset, double yoffset )
+{
+   vg_mouse_wheel[0] += xoffset;
+   vg_mouse_wheel[1] += yoffset;
+}
 
 void vg_framebuffer_resize_callback( GLFWwindow *ptrW, int w, int h )
 {
    vg_window_x = w;
    vg_window_y = h;
 
-#ifdef VG_FRAMEBUFFER_RESIZE
    vg_framebuffer_resize(w,h);
-#endif
 }
 
-static void vg_init( int argc, char *argv[], const char *window_name )
+static int vg_bake_shaders(void)
 {
-#ifdef VG_STEAM
-   if( !sw_init() )
-      return;
-#endif
-   
+   vg_acquire_thread_sync();
+
+   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;
+   }
+}
+
+void vg_preload(void);
+void vg_load(void);
+static void vg_load_full(void)
+{
+   vg_preload();
+
+   /* internal */
+   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 */
+   vg_load();
+
+   vg_acquire_thread_sync();
+   vg.is_loaded = 1;
+   vg_release_thread_sync();
+}
+
+static void vg_enter( int argc, char *argv[], const char *window_name )
+{
+   vg_log_init();
+   vg_console_init();
+
    glfwInit();
    glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR, 3 );
    glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR, 3 );
    glfwWindowHint( GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE );
    glfwWindowHint( GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE );
    
-#ifdef VG_CAPTURE_MODE
-   glfwWindowHint( GLFW_RESIZABLE, GLFW_FALSE );
-#else
    glfwWindowHint( GLFW_RESIZABLE, GLFW_TRUE );
-#endif
-   glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
+   glfwWindowHint( GLFW_DOUBLEBUFFER, GLFW_TRUE );
    
 #if 0
    glfwWindowHint(GLFW_SAMPLES,4);
@@ -178,126 +354,218 @@ static void vg_init( int argc, char *argv[], const char *window_name )
    glfwWindowHint( GLFW_GREEN_BITS, mode->greenBits );
    glfwWindowHint( GLFW_BLUE_BITS, mode->blueBits );
    
+   /* This is set like this because of an OS issue */
    int refresh_rate = mode->refreshRate;
-
    if( refresh_rate < 28 || refresh_rate >= 144 )
       refresh_rate = 60;
-
    glfwWindowHint( GLFW_REFRESH_RATE, refresh_rate );
 
-   if( !(vg_window = glfwCreateWindow( vg_window_x, vg_window_y, 
+   if( !(vg.window = glfwCreateWindow( vg_window_x, vg_window_y, 
                                        window_name, NULL, NULL)) )
-      vg_exiterr( "GLFW Failed to initialize" );
-   else
-      vg_register_exit( &glfwTerminate, "glfwTerminate" );
+   {
+      vg_error( "GLFW Failed to initialize\n" );
+      return;
+   }
    
-   glfwMakeContextCurrent( vg_window );
+   glfwMakeContextCurrent( vg.window );
    glfwSwapInterval( 1 );
 
-   glfwSetWindowSizeLimits( vg_window, 800, 600, GLFW_DONT_CARE,GLFW_DONT_CARE);
-   glfwSetFramebufferSizeCallback( vg_window, vg_framebuffer_resize_callback );
+   glfwSetWindowSizeLimits( vg.window, 800, 600, GLFW_DONT_CARE,GLFW_DONT_CARE);
+   glfwSetFramebufferSizeCallback( vg.window, vg_framebuffer_resize_callback );
 
-   glfwSetCursorPosCallback( vg_window, vg_mouse_callback );
-   glfwSetScrollCallback( vg_window, vg_scroll_callback );
+   glfwSetCursorPosCallback( vg.window, vg_mouse_callback );
+   glfwSetScrollCallback( vg.window, vg_scroll_callback );
    
-   glfwSetCharCallback( vg_window, console_proc_wchar );
-   glfwSetKeyCallback( vg_window, console_proc_key );
+   glfwSetCharCallback( vg.window, console_proc_wchar );
+   glfwSetKeyCallback( vg.window, console_proc_key );
 #if 0
    glfwSetInputMode(vg_window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
 #endif
 
    if( !gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) ) 
-      vg_exiterr( "Glad failed to initialize" );
+   {
+      vg_error( "Glad Failed to initialize\n" );
+      glfwTerminate();
+      return;
+   }
 
    const unsigned char* glver = glGetString( GL_VERSION );
    vg_success( "Load setup complete, OpenGL version: %s\n", glver );
-   
    vg_run_gfx_diagnostics();
-   
-   vg_gamepad_init();
-   vg_lines_init();
-   vg_register_exit( &vg_lines_free, "vg_lines_free" );
-   ui_default_init();
-   vg_register_exit( &ui_default_free, "UI" );
-      
-   vg_register();
-   vg_register_exit( &vg_free, "vg_free" );
-   
-   if( vg_shaders_compile() )
+
+   if( !ui_default_init() )
+      goto il_exit_ui;
+
+   if( !vg_loader_init() )
+      goto il_exit_loader;
+
+   vg_mutex_init( &vg.mux_engine_status );
+   vg.engine_status = k_engine_status_running;
+
+   vg_opengl_sync_init();
+   vg_loader_start();
+
+   int loaded = 0;
+   int counter =0;
+   while(1)
    {
-      vg_start();
-   
-      vg_console_init();
-      vg_register_exit( &vg_console_free, "Console" );
-   
-      vg_audio_init();
-      vg_register_exit( &vg_audio_free, "vg_audio_free" );
-      
-      vg_debugtools_setup();
+      if( glfwWindowShouldClose( vg.window ) )
+         break;
+
+      v2_copy( (v2f){ 0.0f, 0.0f }, vg_mouse_wheel );
+      glfwPollEvents();
+
+      vg_time_last = vg_time;
+      vg_time = glfwGetTime();
+      vg_time_delta = vg_minf( vg_time - vg_time_last, 0.1f );
       
-      /* 
-       * Main gameloop
-       */
-      while( !glfwWindowShouldClose( vg_window ) )
+      if( vg.is_loaded )
       {
-         v2_copy( (v2f){ 0.0f, 0.0f }, vg_mouse_wheel );
-
-         glfwPollEvents();
+         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 );
          
-         #ifdef VG_STEAM
-         sw_event_loop();
-         #endif
-         
-         vg_time_last = vg_time;
-         vg_time = glfwGetTime();
-         vg_time_delta = vg_minf( vg_time - vg_time_last, 0.1f );
-         
-         vg_update_inputs();
-         vg_update();
+         if( !loaded )
+         {
+            vg_start();
+            loaded = 1;
+         }
+      }
+      else
+      {
+         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();
-         
-         vg_lines_drawall((float*)vg_pv);
-         
+
+         /* ui */
          {
             ui_begin( &ui_global_ctx, vg_window_x, vg_window_y );
             ui_set_mouse( &ui_global_ctx, vg_mouse[0], vg_mouse[1], 
                   vg_get_button_state( "primary" ) );
             
+            audio_debug_ui( vg_pv );
             vg_ui();
             vg_console_draw();
-            vg_debugtools_draw();
             
             ui_resolve( &ui_global_ctx );
             ui_draw( &ui_global_ctx, NULL );
          }
-         
-         glfwSwapBuffers( vg_window );
-         VG_CHECK_GL();
       }
+
+      glfwSwapBuffers( vg.window );
+      vg_run_synced_content();
    }
-   
-   vg_exit();
+
+   vg_console_write_persistent();
+
+   vg_mutex_lock( &vg.mux_engine_status );
+   vg.engine_status = k_engine_status_none;
+   vg_mutex_unlock( &vg.mux_engine_status );
+
+   vg_loader_free();
+
+il_exit_loader:
+   ui_default_free();
+
+il_exit_ui:
+   glfwTerminate();
 }
 
-#ifndef VG_3D
-void vg_projection_update(void)
+/*
+ * 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 )
 {
-   /*
-    * Reproject screenspace mouse into world
-    */
+   vg_error( "Fatal error: %s\n", error );
+   assert( vg_semaphore_trywait( &vg.sem_fatal ) );
 
-   vg_mouse_ws[0] = vg_mouse[0];
-   vg_mouse_ws[1] = vg_mouse[1];
-   vg_mouse_ws[2] = 1.0f;
-   
-   vg_mouse_ws[0] =   (2.0f * vg_mouse_ws[0]) / ((float)vg_window_x) - 1.0f;
-   vg_mouse_ws[1] = -((2.0f * vg_mouse_ws[1]) / ((float)vg_window_y) - 1.0f);
-   
-   m3x3f inverse;
-   m3x3_inv( vg_pv, inverse ); 
-   m3x3_mulv( inverse, vg_mouse_ws, vg_mouse_ws );
+   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
 
 #endif