basic features of replay editor
[carveJwlIkooP6JGAAIwe30JlM.git] / addon.c
diff --git a/addon.c b/addon.c
index c2ad38c0d929f34ab96c48da70a155b221d3715b..72083e3b503ddae15db766bcd6830417e9181125 100644 (file)
--- a/addon.c
+++ b/addon.c
@@ -1,20 +1,51 @@
-#ifndef ADDON_C
-#define ADDON_C
-
+#include "vg/vg_engine.h"
+#include "vg/vg_io.h"
+#include "vg/vg_loader.h"
 #include "addon.h"
+#include "addon_types.h"
 #include "vg/vg_msg.h"
 #include "steam.h"
+#include "workshop.h"
+#include <string.h>
+
+struct addon_system addon_system;
+
+u32 addon_count( enum addon_type type, u32 ignoreflags )
+{
+   if( ignoreflags ){
+      u32 typecount = 0, count = 0;
+      for( u32 i=0; typecount<addon_count( type, 0 ); i++ ){
+         addon_reg *reg = &addon_system.registry[i];
+         if( reg->alias.type == type ){
+            typecount ++;
+
+            if( reg->flags & ignoreflags )
+               continue;
+            
+            count ++;
+         }
+      }
 
-static u32 addon_count( enum workshop_file_type type ){
-   return addon_system.registry_type_counts[ type ];
+      return count;
+   }
+   else
+      return addon_system.registry_type_counts[ type ];
 }
 
+
 /* these kind of suck, oh well. */
-static addon_reg *get_addon_from_index(enum workshop_file_type type, u32 index){
-   u32 count = 0;
-   for( u32 i=0; count<addon_count(type); i++ ){
+addon_reg *get_addon_from_index( enum addon_type type, u32 index, 
+                                 u32 ignoreflags )
+{
+   u32 typecount = 0, count = 0;
+   for( u32 i=0; typecount<addon_count(type,0); i++ ){
       addon_reg *reg = &addon_system.registry[i];
-      if( reg->type == type ){
+      if( reg->alias.type == type ){
+         typecount ++;
+
+         if( reg->flags & ignoreflags )
+            continue;
+
          if( index == count )
             return reg;
 
@@ -25,11 +56,12 @@ static addon_reg *get_addon_from_index(enum workshop_file_type type, u32 index){
    return NULL;
 }
 
-static u32 get_index_from_addon( enum workshop_file_type type, addon_reg *a ){
+u32 get_index_from_addon( enum addon_type type, addon_reg *a )
+{
    u32 count = 0;
    for( u32 i=0; count<addon_system.registry_type_counts[type]; i++ ){
       addon_reg *reg = &addon_system.registry[i];
-      if( reg->type == type ){
+      if( reg->alias.type == type ){
          if( reg == a )
             return count;
 
@@ -40,9 +72,160 @@ static u32 get_index_from_addon( enum workshop_file_type type, addon_reg *a ){
    return 0xffffffff;
 }
 
-static void addon_system_init( void ){
+u32 addon_match( addon_alias *alias )
+{
+   if( alias->type == k_addon_type_none ) return 0xffffffff;
+
+   u32 foldername_djb2 = 0;
+   if( !alias->workshop_id )
+      foldername_djb2 = vg_strdjb2( alias->foldername );
+
+   u32 count = 0;
+   for( u32 i=0; count<addon_system.registry_type_counts[alias->type]; i++ ){
+      addon_reg *reg = &addon_system.registry[i];
+      if( reg->alias.type == alias->type ){
+         
+         if( alias->workshop_id ){
+            if( alias->workshop_id == reg->alias.workshop_id )
+               return count;
+         }
+         else{
+            if( reg->foldername_hash == foldername_djb2 ){
+               if( !strcmp( reg->alias.foldername, alias->foldername ) ){
+                  return count;
+               }
+            }
+         }
+
+         count ++;
+      }
+   }
+
+   return 0xffffffff;
+}
+
+/*
+ * Create a string version of addon alias in buf
+ */
+void addon_alias_uid( addon_alias *alias, char buf[ADDON_UID_MAX] )
+{
+   if( alias->workshop_id ){
+      snprintf( buf, 128, "sr%03d-steam-"PRINTF_U64, 
+            alias->type, alias->workshop_id );
+   }
+   else {
+      snprintf( buf, 128, "sr%03d-local-%s",
+            alias->type, alias->foldername );
+   }
+}
+
+/*
+ * equality check
+ */
+int addon_alias_eq( addon_alias *a, addon_alias *b )
+{
+   if( a->type == b->type ){
+      if( a->workshop_id == b->workshop_id ){
+         if( a->workshop_id )
+            return 1;
+         else
+            return !strcmp( a->foldername, b->foldername );
+      }
+      else
+         return 0;
+   }
+   else return 0;
+}
+
+/*
+ * make alias represent NULL.
+ */
+void invalidate_addon_alias( addon_alias *alias )
+{
+   alias->type = k_addon_type_none;
+   alias->workshop_id = 0;
+   alias->foldername[0] = '\0';
+}
+
+/*
+ * parse uid to alias. returns 1 if successful
+ */
+int addon_uid_to_alias( const char *uid, addon_alias *alias )
+{
+/*           1
+ * 01234567890123
+ * sr&&&-@@@@@-#*
+ *    |    |   |
+ *  type   |   id
+ *         |
+ *     location
+ */
+   if( strlen(uid) < 13 ){
+      invalidate_addon_alias( alias );
+      return 0;
+   }
+   if( !((uid[0] == 's') && (uid[1] == 'r')) ){
+      invalidate_addon_alias( alias );
+      return 0;
+   }
+
+   char type[4];
+   memcpy( type, uid+2, 3 );
+   type[3] = '\0';
+   alias->type = atoi(type);
+
+   char location[6];
+   memcpy( location, uid+6, 5 );
+   location[5] = '\0';
+
+   if( !strcmp(location,"steam") )
+      alias->workshop_id = atoll( uid+12 );
+   else if( !strcmp(location,"local") ){
+      alias->workshop_id = 0;
+      vg_strncpy( uid+12, alias->foldername, 64, k_strncpy_always_add_null );
+   }
+   else{
+      invalidate_addon_alias( alias );
+      return 0;
+   }
+
+   return 1;
+}
+
+void addon_system_init( void )
+{
    u32 reg_size   = sizeof(addon_reg)*ADDON_MOUNTED_MAX;
    addon_system.registry = vg_linear_alloc( vg_mem.rtmemory, reg_size );
+
+   for( u32 type=0; type<k_addon_type_max; type++ ){
+      struct addon_type_info *inf = &addon_type_infos[type];
+      struct addon_cache *cache = &addon_system.cache[type];
+
+      if( inf->cache_count ){
+         /* create the allocations pool */
+         u32 alloc_size = sizeof(struct addon_cache_entry)*inf->cache_count;
+         cache->allocs = vg_linear_alloc( vg_mem.rtmemory, alloc_size );
+         memset( cache->allocs, 0, alloc_size );
+
+         cache->pool.buffer = cache->allocs;
+         cache->pool.count  = inf->cache_count;
+         cache->pool.stride = sizeof( struct addon_cache_entry );
+         cache->pool.offset = offsetof( struct addon_cache_entry, poolnode );
+         vg_pool_init( &cache->pool );
+
+         /* create the real memory */
+         u32 cache_size = inf->cache_stride*inf->cache_count;
+         cache->items = vg_linear_alloc( vg_mem.rtmemory, cache_size );
+         cache->stride = inf->cache_stride;
+         memset( cache->items, 0, cache_size );
+
+         for( i32 j=0; j<inf->cache_count; j++ ){
+            struct addon_cache_entry *alloc = &cache->allocs[j];
+            alloc->reg_ptr   = NULL;
+            alloc->reg_index = 0xffffffff;
+         }
+      }
+   }
 }
 
 /*
@@ -53,41 +236,43 @@ static void addon_system_init( void ){
 /*
  * Reciever for scan completion. copies the registry counts back into main fred
  */
-VG_STATIC void async_addon_reg_update( void *data, u32 size )
+void async_addon_reg_update( void *data, u32 size )
 {
    vg_info( "Registry update notify\n" );
    
-   for( u32 i=0; i<k_workshop_file_type_max; i++ ){
+   for( u32 i=0; i<k_addon_type_max; i++ ){
       addon_system.registry_type_counts[i] = 0;
    }
 
    for( u32 i=0; i<addon_system.registry_count; i++ ){
-      addon_system.registry_type_counts[ addon_system.registry[i].type ] ++;
+      enum addon_type type = addon_system.registry[i].alias.type;
+      addon_system.registry_type_counts[ type ] ++;
    }
 }
 
-VG_STATIC void addon_set_foldername( addon_reg *reg, const char name[64] ){
-   vg_strncpy( name, reg->foldername, 64, k_strncpy_always_add_null );
-   reg->foldername_hash = vg_strdjb2( reg->foldername );
+static void addon_set_foldername( addon_reg *reg, const char name[64] ){
+   vg_strncpy( name, reg->alias.foldername, 64, k_strncpy_always_add_null );
+   reg->foldername_hash = vg_strdjb2( reg->alias.foldername );
 }
 
 /*
  * Create a new registry 
  */
-VG_STATIC addon_reg *addon_alloc_reg( PublishedFileId_t workshop_id,
-                                      enum workshop_file_type type ){
+static addon_reg *addon_alloc_reg( PublishedFileId_t workshop_id,
+                                      enum addon_type type ){
    if( addon_system.registry_count == ADDON_MOUNTED_MAX ){
       vg_error( "You have too many addons installed!\n" );
       return NULL;
    }
 
    addon_reg *reg = &addon_system.registry[ addon_system.registry_count ];
+   reg->flags = 0;
    reg->metadata_len = 0;
-   reg->userdata = NULL;
+   reg->cache_id = 0;
    reg->state = k_addon_state_indexed;
-   reg->workshop_id = workshop_id;
-   reg->foldername[0] = '\0';
-   reg->type = type;
+   reg->alias.workshop_id = workshop_id;
+   reg->alias.foldername[0] = '\0';
+   reg->alias.type = type;
 
    if( workshop_id ){
       char foldername[64];
@@ -100,7 +285,7 @@ VG_STATIC addon_reg *addon_alloc_reg( PublishedFileId_t workshop_id,
 /*
  * If the addon.inf exists int the folder, load into the reg
  */
-VG_STATIC int addon_try_load_metadata( addon_reg *reg, vg_str folder_path ){
+static int addon_try_load_metadata( addon_reg *reg, vg_str folder_path ){
    vg_str meta_path = folder_path;
    vg_strcat( &meta_path, "/addon.inf" );
    if( !vg_strgood( &meta_path ) ){
@@ -127,64 +312,142 @@ VG_STATIC int addon_try_load_metadata( addon_reg *reg, vg_str folder_path ){
    return 1;
 }
 
-VG_STATIC void addon_mount_finish( addon_reg *reg ){
+static void addon_print_info( addon_reg *reg ){
    vg_info( "addon_reg #%u{\n", addon_system.registry_count );
-   vg_info( "  type: %d\n", reg->type );
-   vg_info( "  workshop_id: " PRINTF_U64 "\n", reg->workshop_id );
-   vg_info( "  folder: [%u]%s\n", reg->foldername_hash, reg->foldername );
+   vg_info( "  type: %d\n", reg->alias.type );
+   vg_info( "  workshop_id: " PRINTF_U64 "\n", reg->alias.workshop_id );
+   vg_info( "  folder: [%u]%s\n", reg->foldername_hash, reg->alias.foldername );
    vg_info( "  metadata_len: %u\n", reg->metadata_len );
-   vg_info( "  userdata: %p\n", reg->userdata );
+   vg_info( "  cache_id: %hu\n", reg->cache_id );
    vg_info( "}\n" );
+}
 
+static void addon_mount_finish( addon_reg *reg ){
+#if 0
+   addon_print_info( reg );
+#endif
    addon_system.registry_count ++;
 }
 
 /*
  * Mount a fully packaged addon, one that certainly has a addon.inf
  */
-VG_STATIC void addon_mount_workshop_folder( PublishedFileId_t workshop_id,
-                                            vg_str folder_path )
+static addon_reg *addon_mount_workshop_folder( PublishedFileId_t workshop_id,
+                                                  vg_str folder_path )
 {
-   addon_reg *reg = addon_alloc_reg( workshop_id, k_workshop_file_type_none );
-   if( !reg ) return;
+   addon_reg *reg = addon_alloc_reg( workshop_id, k_addon_type_none );
+   if( !reg ) return NULL;
 
    if( !addon_try_load_metadata( reg, folder_path ) ){
-      return;
+      return NULL;
    }
 
-   enum workshop_file_type type = k_workshop_file_type_none;
+   enum addon_type type = k_addon_type_none;
    vg_msg msg;
    vg_msg_init( &msg, reg->metadata, reg->metadata_len );
-   vg_msg_cmd cmd;
-   while( vg_msg_next( &msg, &cmd ) ){
-      if( (msg.depth == 1) && (cmd.code == k_vg_msg_code_frame) ){
-         if( VG_STRDJB2_EQ( "workshop", cmd.key, cmd.key_djb2 ) ){
-            u32 depth = msg.depth;
-            while( (msg.depth == depth) && vg_msg_next( &msg, &cmd ) ){
-               if( cmd.code & k_vg_msg_code_unsigned ){
-                  if( VG_STRDJB2_EQ( "type", cmd.key, cmd.key_djb2 ) ){
-                     type = cmd.value._u32;
-                  }
-               }
-            }
+
+   if( vg_msg_seekframe( &msg, "workshop" )){
+      vg_msg_getkvintg( &msg, "type", k_vg_msg_u32, &type );
+   }
+
+   if( type == k_addon_type_none ){
+      vg_error( "Cannot determine addon type\n" );
+      return NULL;
+   }
+
+   reg->alias.type = type;
+   addon_mount_finish( reg );
+   return reg;
+}
+
+/*
+ * Mount a local folder. may or may not have addon.inf
+ */
+addon_reg *addon_mount_local_addon( const char *folder,
+                                    enum addon_type type,
+                                    const char *content_ext )
+{
+   char folder_path_buf[4096];
+   vg_str folder_path;
+   vg_strnull( &folder_path, folder_path_buf, 4096 );
+   vg_strcat( &folder_path, folder );
+
+   const char *folder_name = vg_strch( &folder_path, '/' )+1;
+   u32 folder_hash = vg_strdjb2(folder_name);
+   for( u32 i=0; i<addon_system.registry_count; i++ ){
+      addon_reg *reg = &addon_system.registry[i];
+
+      if( (reg->alias.type == type) && (reg->foldername_hash == folder_hash) ){
+         if( !strcmp( reg->alias.foldername, folder_name ) ){
+            reg->state = k_addon_state_indexed;
+            return reg;
          }
       }
    }
 
-   if( type == k_workshop_file_type_none ){
-      vg_error( "Cannot determine addon type\n" );
-      return;
+   addon_reg *reg = addon_alloc_reg( 0, type );
+   if( !reg ) return NULL;
+   addon_set_foldername( reg, folder_name );
+   addon_try_load_metadata( reg, folder_path );
+
+   if( reg->metadata_len == 0 ){
+      /* create our own content commands */
+      vg_msg msg;
+      vg_msg_init( &msg, reg->metadata, sizeof(reg->metadata) );
+
+      u32 content_count = 0;
+
+      vg_strcat( &folder_path, "" );
+      vg_warn( "Creating own metadata for: %s\n", folder_path.buffer );
+
+      vg_dir subdir;
+      if( !vg_dir_open(&subdir, folder_path.buffer) ){
+         vg_error( "Failed to open '%s'\n", folder_path.buffer );
+         return NULL;
+      }
+
+      while( vg_dir_next_entry(&subdir) ){
+         if( vg_dir_entry_type(&subdir) == k_vg_entry_type_file ){
+            const char *fname = vg_dir_entry_name(&subdir);
+            vg_str file = folder_path;
+            vg_strcat( &file, "/" );
+            vg_strcat( &file, fname );
+            if( !vg_strgood( &file ) ) continue;
+
+            char *ext = vg_strch( &file, '.' );
+            if( !ext ) continue;
+            if( strcmp(ext,content_ext) ) continue;
+            
+            vg_msg_wkvstr( &msg, "content", fname );
+            content_count ++;
+         }
+      }
+      vg_dir_close(&subdir);
+
+      if( !content_count ) return NULL;
+      if( msg.error == k_vg_msg_error_OK )
+         reg->metadata_len = msg.cur.co;
+      else{
+         vg_error( "Error creating metadata: %d\n", msg.error );
+         return NULL;
+      }
    }
 
-   reg->type = type;
    addon_mount_finish( reg );
+   return reg;
 }
 
 /*
  * Check all subscribed items
  */
-VG_STATIC void addon_mount_workshop_items(void){
+void addon_mount_workshop_items(void)
+{
+   if( skaterift.demo_mode ){
+      vg_info( "Won't load workshop items in demo mode\n" );
+      return;
+   }
    if( !steam_ready ) return;
+
    /*
     * Steam workshop scan
     */
@@ -207,14 +470,12 @@ VG_STATIC void addon_mount_workshop_items(void){
       for( u32 i=0; i<addon_system.registry_count; i++ ){
          addon_reg *reg = &addon_system.registry[i];
 
-         if( reg->workshop_id == id ){
+         if( reg->alias.workshop_id == id ){
             reg->state = k_addon_state_indexed;
             goto next_file_workshop;
          }
       }
 
-      /* new one, lets find out what type it is
-       * ---------------------------------------------------------------*/
       vg_async_item *call1 = 
          vg_async_alloc( sizeof(struct async_workshop_filepath_info) );
 
@@ -237,9 +498,9 @@ next_file_workshop:;
  * Scan a local content folder for addons. It must find at least one file with 
  * the specified content_ext to be considered.
  */
-VG_STATIC void addon_mount_local_folder( enum workshop_file_type type,
-                                         const char *base_folder, 
-                                         const char *content_ext )
+void addon_mount_content_folder( enum addon_type type,
+                                 const char *base_folder, 
+                                 const char *content_ext )
 {
    vg_info( "Mounting addons(type:%d) matching skaterift/%s/*/*%s\n", 
                   type, base_folder, content_ext );
@@ -252,7 +513,6 @@ VG_STATIC void addon_mount_local_folder( enum workshop_file_type type,
    vg_dir dir;
    if( !vg_dir_open(&dir,path.buffer) ){
       vg_error( "vg_dir_open('%s') failed\n", path.buffer );
-      vg_async_call( workshop_async_any_complete, NULL, 0 );
       return;
    }
 
@@ -263,8 +523,6 @@ VG_STATIC void addon_mount_local_folder( enum workshop_file_type type,
          const char *d_name = vg_dir_entry_name(&dir);
 
          vg_str folder = path;
-         char *folder_name = folder.buffer+folder.i;
-
          if( strlen( d_name ) > ADDON_FOLDERNAME_MAX ){
             vg_warn( "folder too long: %s\n", d_name );
             continue;
@@ -273,97 +531,349 @@ VG_STATIC void addon_mount_local_folder( enum workshop_file_type type,
          vg_strcat( &folder, d_name );
          if( !vg_strgood( &folder ) ) continue;
 
-         int present = 0;
-         u32 folder_hash = vg_strdjb2(folder_name);
-         for( u32 i=0; i<addon_system.registry_count; i++ ){
-            addon_reg *reg = &addon_system.registry[i];
+         addon_mount_local_addon( folder.buffer, type, content_ext );
+      }
+   }
+   vg_dir_close(&dir);
+}
 
-            if( (reg->type == type) && (reg->foldername_hash == folder_hash) ){
-               if( !strcmp( reg->foldername, folder_name ) ){
-                  reg->state = k_addon_state_indexed;
-                  present = 1;
-                  break;
-               }
-            }
-         }
+/*
+ * write the full path of the addon's folder into the vg_str
+ */
+int addon_get_content_folder( addon_reg *reg, vg_str *folder, int async)
+{
+   if( reg->alias.workshop_id ){
+      struct async_workshop_filepath_info *info = NULL;
+      vg_async_item *call = NULL;
 
-         if( !present ){
-            addon_reg *reg = addon_alloc_reg( 0, type );
-            if( !reg ) continue;
-            addon_set_foldername( reg, folder_name );
-            addon_try_load_metadata( reg, folder );
+      if( async ){
+         call = vg_async_alloc( sizeof(struct async_workshop_filepath_info) );
+         info = call->payload;
+      }
+      else 
+         info = alloca( sizeof(struct async_workshop_filepath_info) );
 
-            if( reg->metadata_len == 0 ){
-               /* create our own content commands */
-               vg_msg msg;
-               vg_msg_init( &msg, reg->metadata, sizeof(reg->metadata) );
+      info->buf = folder->buffer;
+      info->id = reg->alias.workshop_id;
+      info->len = folder->len;
 
-               u32 content_count = 0;
+      if( async ){
+         vg_async_dispatch( call, async_workshop_get_filepath );
+         vg_async_stall(); /* too bad! */
+      }
+      else {
+         async_workshop_get_filepath( info, 0 );
+      }
 
-               vg_strcat( &folder, "" );
-               vg_warn( "Creating own metadata for: %s\n", folder.buffer );
+      if( info->buf[0] == '\0' ){
+         vg_error( "Failed SteamAPI_GetItemInstallInfo(" PRINTF_U64 ")\n",
+                     reg->alias.workshop_id );
+         return 0;
+      }
+      folder->i = strlen( folder->buffer );
+      return 1;
+   }
+   else{
+      folder->i = 0;
+      
+      const char *local_folder = 
+         addon_type_infos[reg->alias.type].local_content_folder;
+
+      if( !local_folder ) return 0;
+      vg_strcat( folder, local_folder );
+      vg_strcat( folder, reg->alias.foldername );
+      return 1;
+   }
+}
 
-               vg_dir subdir;
-               if( !vg_dir_open(&subdir, folder.buffer) ){
-                  vg_error( "Failed to open '%s'\n", folder.buffer );
-                  continue;
-               }
+/*
+ * Return existing cache id if reg_index points to a registry with its cache
+ * already set.
+ */
+u16 addon_cache_fetch( enum addon_type type, u32 reg_index )
+{
+   addon_reg *reg = NULL;
 
-               while( vg_dir_next_entry(&subdir) ){
-                  if( vg_dir_entry_type(&subdir) == k_vg_entry_type_file ){
-                     const char *fname = vg_dir_entry_name(&subdir);
-                     vg_str file = folder;
-                     vg_strcat( &file, "/" );
-                     vg_strcat( &file, fname );
-                     if( !vg_strgood( &file ) ) continue;
-
-                     char *ext = vg_strch( &file, '.' );
-                     if( !ext ) continue;
-                     if( strcmp(ext,content_ext) ) continue;
-                     
-                     vg_msg_wkvstr( &msg, "content", fname );
-                     content_count ++;
-                  }
-               }
-               vg_dir_close(&subdir);
+   if( reg_index < addon_count( type, 0 ) ){
+      reg = get_addon_from_index( type, reg_index, 0 );
+      if( reg->cache_id ) 
+         return reg->cache_id;
+   }
 
-               if( !content_count ){
-                  continue;
-               }
+   return 0;
+}
+
+/*
+ * Allocate a new cache item from the pool
+ */
+u16 addon_cache_alloc( enum addon_type type, u32 reg_index )
+{
+   struct addon_cache *cache = &addon_system.cache[ type ];
+
+   u16 new_id = vg_pool_lru( &cache->pool );
+   struct addon_cache_entry *new_entry = vg_pool_item( &cache->pool, new_id );
+
+   addon_reg *reg = NULL;
+   if( reg_index < addon_count( type, 0 ) )
+      reg = get_addon_from_index( type, reg_index, 0 );
+
+   if( new_entry ){
+      if( new_entry->reg_ptr )
+         new_entry->reg_ptr->cache_id = 0;
 
-               if( msg.error == k_vg_msg_error_OK )
-                  reg->metadata_len = msg.cur;
-               else{
-                  vg_error( "Error creating metadata: %d\n", msg.error );
+      if( reg )
+         reg->cache_id = new_id;
+
+      new_entry->reg_ptr = reg;
+      new_entry->reg_index = reg_index;
+      return new_id;
+   }
+   else{
+      vg_error( "cache full (type: %u)!\n", type );
+      return 0;
+   }
+}
+
+/*
+ * Get the real item data for cache id 
+ */
+void *addon_cache_item( enum addon_type type, u16 id )
+{
+   if( !id ) return NULL;
+
+   struct addon_cache *cache = &addon_system.cache[type];
+   return cache->items + ((size_t)(id-1) * cache->stride);
+}
+
+/*
+ * Get the real item data for cache id ONLY if the item is completely loaded.
+ */
+void *addon_cache_item_if_loaded( enum addon_type type, u16 id )
+{
+   if( !id ) return NULL;
+
+   struct addon_cache *cache = &addon_system.cache[type];
+   struct addon_cache_entry *entry = vg_pool_item( &cache->pool, id );
+
+   if( entry->state == k_addon_cache_state_loaded )
+      return addon_cache_item( type, id );
+   else return NULL;
+}
+
+/* 
+ * Updates the item state from the main thread
+ */
+void async_addon_setstate( void *_entry, u32 _state )
+{
+   addon_cache_entry *entry = _entry;
+   SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+   entry->state = _state;
+   SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+   vg_success( "   loaded (%s)\n", entry->reg_ptr->alias.foldername );
+}
+
+/*
+ * Handles the loading of an individual item
+ */
+static int addon_cache_load_request( enum addon_type type, u16 id,
+                                     addon_reg *reg, vg_str folder ){
+   
+   /* load content files
+    * --------------------------------- */
+   vg_str content_path = folder;
+
+   vg_msg msg;
+   vg_msg_init( &msg, reg->metadata, reg->metadata_len );
+
+   const char *kv_content = vg_msg_getkvstr( &msg, "content" );
+   if( kv_content ){
+      vg_strcat( &content_path, "/" );
+      vg_strcat( &content_path, kv_content );
+   }
+   else{
+      vg_error( "   No content paths in metadata\n" );
+      return 0;
+   }
+
+   if( !vg_strgood( &content_path ) ) {
+      vg_error( "   Metadata path too long\n" );
+      return 0;
+   }
+   
+   if( type == k_addon_type_board ){
+      struct player_board *board = addon_cache_item( type, id );
+      player_board_load( board, content_path.buffer );
+      return 1;
+   }
+   else if( type == k_addon_type_player ){
+      struct player_model *model = addon_cache_item( type, id );
+      player_model_load( model, content_path.buffer );
+      return 1;
+   }
+   else {
+      return 0;
+   }
+
+   return 0;
+}
+
+static void addon_cache_free_item( enum addon_type type, u16 id ){
+   if( type == k_addon_type_board ){
+      struct player_board *board = addon_cache_item( type, id );
+      player_board_unload( board );
+   }
+   else if( type == k_addon_type_player ){
+      struct player_model *model = addon_cache_item( type, id );
+      player_model_unload( model );
+   }
+}
+
+/*
+ * Goes over cache item load requests and calls the above ^
+ */
+static void T1_addon_cache_load_loop(void *_)
+{
+   vg_info( "Running load loop\n" );
+   char path_buf[4096];
+
+   for( u32 type=0; type<k_addon_type_max; type++ )
+   {
+      struct addon_cache *cache = &addon_system.cache[type];
+
+      for( u32 id=1; id<=cache->pool.count; id++ )
+      {
+         addon_cache_entry *entry = vg_pool_item( &cache->pool, id );
+
+         SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+         if( entry->state == k_addon_cache_state_load_request )
+         {
+            vg_info( "process cache load request (%u#%u, reg:%u)\n",
+                        type, id, entry->reg_index );
+
+            if( entry->reg_index >= addon_count(type,0) )
+            {
+               /* should maybe have a different value for this case */
+               entry->state = k_addon_cache_state_none;
+               SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+               continue;
+            }
+
+            SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+
+            /* continue with the request */
+            addon_reg *reg = get_addon_from_index( type, entry->reg_index, 0 );
+            entry->reg_ptr = reg;
+
+            vg_str folder;
+            vg_strnull( &folder, path_buf, 4096 );
+            if( addon_get_content_folder( reg, &folder, 1 ) )
+            {
+               if( addon_cache_load_request( type, id, reg, folder ) )
+               {
+                  vg_async_call( async_addon_setstate, 
+                                 entry, k_addon_cache_state_loaded );
                   continue;
                }
             }
+            
+            vg_warn( "cache item did not load (%u#%u)\n", type, id );
+            SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+            entry->state = k_addon_cache_state_none;
+            SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+         }
+         else
+            SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+      }
+   }
+}
 
-            addon_mount_finish( reg );
+void addon_system_pre_update(void)
+{
+   if( !vg_loader_availible() ) return;
+
+   SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+   for( u32 type=0; type<k_addon_type_max; type++ )
+   {
+      struct addon_cache *cache = &addon_system.cache[type];
+
+      for( u32 id=1; id<=cache->pool.count; id++ )
+      {
+         addon_cache_entry *entry = vg_pool_item( &cache->pool, id );
+         if( entry->state == k_addon_cache_state_load_request )
+         {
+            SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+            vg_loader_start( T1_addon_cache_load_loop, NULL );
+            return;
          }
       }
    }
-   vg_dir_close(&dir);
+   SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
 }
 
-#if 0
 /*
- * Async thread which scans local files for addons, as well as scheduling 
- * synchronous calls to the workshop
- *
- * Call this from in the loader thread.
+ * Perform the cache interactions required to create a viewslot which will
+ * eventually be loaded by other parts of the system.
  */
-VG_STATIC void addon_system_scan_all(void){
-   for( u32 i=0; i<addon_system.registry_count; i++ ){
-      addon_reg *reg = &addon_system.registry[i];
-      reg->state = k_addon_state_indexed_absent;
+u16 addon_cache_create_viewer( enum addon_type type, u16 reg_id )
+{
+   struct addon_cache *cache = &addon_system.cache[type];
+   vg_pool *pool = &cache->pool;
+
+   u16 cache_id = addon_cache_fetch( type, reg_id );
+   if( !cache_id ){
+      cache_id = addon_cache_alloc( type, reg_id );
+
+      if( cache_id ){
+         SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+         addon_cache_entry *entry = vg_pool_item( pool, cache_id );
+
+         if( entry->state == k_addon_cache_state_loaded ){
+            addon_cache_free_item( type, cache_id );
+         }
+
+         entry->state = k_addon_cache_state_load_request;
+         SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+      }
    }
 
-   if( steam_ready ) addon_mount_workshop_items();
-   
-   vg_async_call( addon_async_reg_update, NULL, 0 );
-   vg_async_stall();
+   if( cache_id )
+      vg_pool_watch( pool, cache_id );
+
+   return cache_id;
 }
-#endif
 
-#endif /* ADDON_C */
+u16 addon_cache_create_viewer_from_uid( enum addon_type type,
+                                        char uid[ADDON_UID_MAX] )
+{
+   addon_alias q;
+   if( !addon_uid_to_alias( uid, &q ) ) return 0;
+   if( q.type != type ) return 0;
+
+   u32 reg_id = addon_match( &q );
+
+   if( reg_id == 0xffffffff ){
+      vg_warn( "We dont have the addon '%s' installed.\n", uid );
+      return 0;
+   }
+   else {
+      return addon_cache_create_viewer( type, reg_id );
+   }
+}
+
+void addon_cache_watch( enum addon_type type, u16 cache_id )
+{
+   if( !cache_id ) return;
+
+   struct addon_cache *cache = &addon_system.cache[type];
+   vg_pool *pool = &cache->pool;
+   vg_pool_watch( pool, cache_id );
+}
+
+void addon_cache_unwatch( enum addon_type type, u16 cache_id )
+{
+   if( !cache_id ) return;
+
+   struct addon_cache *cache = &addon_system.cache[type];
+   vg_pool *pool = &cache->pool;
+   vg_pool_unwatch( pool, cache_id );
+}