+/*
+ * write the full path of the addon's folder into the vg_str
+ */
+static int addon_get_content_folder( addon_reg *reg, vg_str *folder ){
+ if( reg->alias.workshop_id ){
+ vg_async_item *call =
+ vg_async_alloc( sizeof(struct async_workshop_filepath_info) );
+ struct async_workshop_filepath_info *info = call->payload;
+ info->buf = folder->buffer;
+ info->id = reg->alias.workshop_id;
+ info->len = folder->len;
+ vg_async_dispatch( call, async_workshop_get_filepath );
+ vg_async_stall(); /* too bad! */
+ 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;
+ }
+}
+
+/*
+ * Return existing cache id if reg_index points to a registry with its cache
+ * already set.
+ */
+static u16 addon_cache_fetch( enum addon_type type, u32 reg_index ){
+ addon_reg *reg = NULL;
+
+ if( reg_index < addon_count( type ) ){
+ reg = get_addon_from_index( type, reg_index );
+ if( reg->cache_id )
+ return reg->cache_id;
+ }
+
+ return 0;
+}
+
+/*
+ * Allocate a new cache item from the pool
+ */
+static 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 ) )
+ reg = get_addon_from_index( type, reg_index );
+
+ if( new_entry ){
+ if( new_entry->reg_ptr )
+ new_entry->reg_ptr->cache_id = 0;
+
+ 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
+ */
+static 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.
+ */
+static 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
+ */
+static 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 root = {0};
+ root.buf = reg->metadata;
+ root.len = reg->metadata_len;
+ root.max = sizeof(reg->metadata);
+
+ const char *kv_content = vg_msg_seekkvstr( &root, "content", 0 );
+ 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 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) ){
+ /* 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 );
+ entry->reg_ptr = reg;
+
+ vg_str folder;
+ vg_strnull( &folder, path_buf, 4096 );
+ if( addon_get_content_folder( reg, &folder ) ){
+ 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 );
+ }
+ }
+}
+
+/*
+ * Perform the cache interactions required to create a viewslot which will
+ * eventually be loaded by other parts of the system.
+ */
+static 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( cache_id )
+ vg_pool_watch( pool, cache_id );
+
+ return cache_id;
+}
+
+static 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 );
+ }
+}
+
+static 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 );
+}
+
+static 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 );
+}
+