5 #include "addon_types.h"
10 static u32
addon_count( enum addon_type type
){
11 return addon_system
.registry_type_counts
[ type
];
14 /* these kind of suck, oh well. */
15 static addon_reg
*get_addon_from_index(enum addon_type type
, u32 index
){
17 for( u32 i
=0; count
<addon_count(type
); i
++ ){
18 addon_reg
*reg
= &addon_system
.registry
[i
];
19 if( reg
->alias
.type
== type
){
30 static u32
get_index_from_addon( enum addon_type type
, addon_reg
*a
){
32 for( u32 i
=0; count
<addon_system
.registry_type_counts
[type
]; i
++ ){
33 addon_reg
*reg
= &addon_system
.registry
[i
];
34 if( reg
->alias
.type
== type
){
45 static u32
addon_match( addon_alias
*alias
){
46 u32 foldername_djb2
= vg_strdjb2( alias
->foldername
);
49 for( u32 i
=0; count
<addon_system
.registry_type_counts
[alias
->type
]; i
++ ){
50 addon_reg
*reg
= &addon_system
.registry
[i
];
51 if( reg
->alias
.type
== alias
->type
){
53 if( alias
->workshop_id
){
54 if( alias
->workshop_id
== reg
->alias
.workshop_id
)
58 if( reg
->foldername_hash
== foldername_djb2
){
59 if( !strcmp( reg
->alias
.foldername
, alias
->foldername
) ){
72 static void addon_alias_uid( addon_alias
*alias
, char buf
[76] ){
73 if( alias
->workshop_id
){
74 snprintf( buf
, 128, "sr%03d-steam-"PRINTF_U64
,
75 alias
->type
, alias
->workshop_id
);
78 snprintf( buf
, 128, "sr%03d-local-%s",
79 alias
->type
, alias
->foldername
);
83 static void addon_system_init( void ){
84 u32 reg_size
= sizeof(addon_reg
)*ADDON_MOUNTED_MAX
;
85 addon_system
.registry
= vg_linear_alloc( vg_mem
.rtmemory
, reg_size
);
87 for( u32 type
=0; type
<k_addon_type_max
; type
++ ){
88 struct addon_type_info
*inf
= &addon_type_infos
[type
];
89 struct addon_cache
*cache
= &addon_system
.cache
[type
];
91 if( inf
->cache_count
){
92 /* create the allocations pool */
93 u32 alloc_size
= sizeof(struct addon_cache_entry
)*inf
->cache_count
;
94 cache
->allocs
= vg_linear_alloc( vg_mem
.rtmemory
, alloc_size
);
95 memset( cache
->allocs
, 0, alloc_size
);
97 cache
->pool
.buffer
= cache
->allocs
;
98 cache
->pool
.count
= inf
->cache_count
;
99 cache
->pool
.stride
= sizeof( struct addon_cache_entry
);
100 cache
->pool
.offset
= offsetof( struct addon_cache_entry
, poolnode
);
101 vg_pool_init( &cache
->pool
);
103 /* create the real memory */
104 u32 cache_size
= inf
->cache_stride
*inf
->cache_count
;
105 cache
->items
= vg_linear_alloc( vg_mem
.rtmemory
, cache_size
);
106 cache
->stride
= inf
->cache_stride
;
107 memset( cache
->items
, 0, cache_size
);
109 for( i32 j
=0; j
<inf
->cache_count
; j
++ ){
110 struct addon_cache_entry
*alloc
= &cache
->allocs
[j
];
111 alloc
->reg_ptr
= NULL
;
112 alloc
->reg_index
= 0xffffffff;
120 * -----------------------------------------------------------------------------
124 * Reciever for scan completion. copies the registry counts back into main fred
126 VG_STATIC
void async_addon_reg_update( void *data
, u32 size
)
128 vg_info( "Registry update notify\n" );
130 for( u32 i
=0; i
<k_addon_type_max
; i
++ ){
131 addon_system
.registry_type_counts
[i
] = 0;
134 for( u32 i
=0; i
<addon_system
.registry_count
; i
++ ){
135 enum addon_type type
= addon_system
.registry
[i
].alias
.type
;
136 addon_system
.registry_type_counts
[ type
] ++;
140 VG_STATIC
void addon_set_foldername( addon_reg
*reg
, const char name
[64] ){
141 vg_strncpy( name
, reg
->alias
.foldername
, 64, k_strncpy_always_add_null
);
142 reg
->foldername_hash
= vg_strdjb2( reg
->alias
.foldername
);
146 * Create a new registry
148 VG_STATIC addon_reg
*addon_alloc_reg( PublishedFileId_t workshop_id
,
149 enum addon_type type
){
150 if( addon_system
.registry_count
== ADDON_MOUNTED_MAX
){
151 vg_error( "You have too many addons installed!\n" );
155 addon_reg
*reg
= &addon_system
.registry
[ addon_system
.registry_count
];
156 reg
->metadata_len
= 0;
158 reg
->state
= k_addon_state_indexed
;
159 reg
->alias
.workshop_id
= workshop_id
;
160 reg
->alias
.foldername
[0] = '\0';
161 reg
->alias
.type
= type
;
165 snprintf( foldername
, 64, PRINTF_U64
, workshop_id
);
166 addon_set_foldername( reg
, foldername
);
172 * If the addon.inf exists int the folder, load into the reg
174 VG_STATIC
int addon_try_load_metadata( addon_reg
*reg
, vg_str folder_path
){
175 vg_str meta_path
= folder_path
;
176 vg_strcat( &meta_path
, "/addon.inf" );
177 if( !vg_strgood( &meta_path
) ){
178 vg_error( "The metadata path is too long\n" );
182 FILE *fp
= fopen( meta_path
.buffer
, "rb" );
184 vg_error( "Could not open the '%s'\n", meta_path
.buffer
);
188 reg
->metadata_len
= fread( reg
->metadata
, 1, 512, fp
);
189 if( reg
->metadata_len
!= 512 ){
192 vg_error( "unknown error codition" );
193 reg
->metadata_len
= 0;
201 VG_STATIC
void addon_print_info( addon_reg
*reg
){
202 vg_info( "addon_reg #%u{\n", addon_system
.registry_count
);
203 vg_info( " type: %d\n", reg
->alias
.type
);
204 vg_info( " workshop_id: " PRINTF_U64
"\n", reg
->alias
.workshop_id
);
205 vg_info( " folder: [%u]%s\n", reg
->foldername_hash
, reg
->alias
.foldername
);
206 vg_info( " metadata_len: %u\n", reg
->metadata_len
);
207 vg_info( " cache_id: %hu\n", reg
->cache_id
);
211 VG_STATIC
void addon_mount_finish( addon_reg
*reg
){
212 addon_print_info( reg
);
213 addon_system
.registry_count
++;
217 * Mount a fully packaged addon, one that certainly has a addon.inf
219 VG_STATIC addon_reg
*addon_mount_workshop_folder( PublishedFileId_t workshop_id
,
222 addon_reg
*reg
= addon_alloc_reg( workshop_id
, k_addon_type_none
);
223 if( !reg
) return NULL
;
225 if( !addon_try_load_metadata( reg
, folder_path
) ){
229 enum addon_type type
= k_addon_type_none
;
231 root
.buf
= reg
->metadata
;
232 root
.len
= reg
->metadata_len
;
233 root
.max
= sizeof(reg
->metadata
);
235 vg_msg workshop
= root
;
236 if( vg_msg_seekframe( &workshop
, "workshop", k_vg_msg_first
)){
237 type
= vg_msg_seekkvu32( &workshop
, "type", k_vg_msg_first
);
240 if( type
== k_addon_type_none
){
241 vg_error( "Cannot determine addon type\n" );
245 reg
->alias
.type
= type
;
246 addon_mount_finish( reg
);
251 * Mount a local folder. may or may not have addon.inf
253 VG_STATIC addon_reg
*addon_mount_local_addon( const char *folder
,
254 enum addon_type type
,
255 const char *content_ext
)
257 char folder_path_buf
[4096];
259 vg_strnull( &folder_path
, folder_path_buf
, 4096 );
260 vg_strcat( &folder_path
, folder
);
262 const char *folder_name
= vg_strch( &folder_path
, '/' )+1;
263 u32 folder_hash
= vg_strdjb2(folder_name
);
264 for( u32 i
=0; i
<addon_system
.registry_count
; i
++ ){
265 addon_reg
*reg
= &addon_system
.registry
[i
];
267 if( (reg
->alias
.type
== type
) && (reg
->foldername_hash
== folder_hash
) ){
268 if( !strcmp( reg
->alias
.foldername
, folder_name
) ){
269 reg
->state
= k_addon_state_indexed
;
275 addon_reg
*reg
= addon_alloc_reg( 0, type
);
276 if( !reg
) return NULL
;
277 addon_set_foldername( reg
, folder_name
);
278 addon_try_load_metadata( reg
, folder_path
);
280 if( reg
->metadata_len
== 0 ){
281 /* create our own content commands */
283 msg
.buf
= reg
->metadata
;
285 msg
.max
= sizeof(reg
->metadata
);
287 u32 content_count
= 0;
289 vg_strcat( &folder_path
, "" );
290 vg_warn( "Creating own metadata for: %s\n", folder_path
.buffer
);
293 if( !vg_dir_open(&subdir
, folder_path
.buffer
) ){
294 vg_error( "Failed to open '%s'\n", folder_path
.buffer
);
298 while( vg_dir_next_entry(&subdir
) ){
299 if( vg_dir_entry_type(&subdir
) == k_vg_entry_type_file
){
300 const char *fname
= vg_dir_entry_name(&subdir
);
301 vg_str file
= folder_path
;
302 vg_strcat( &file
, "/" );
303 vg_strcat( &file
, fname
);
304 if( !vg_strgood( &file
) ) continue;
306 char *ext
= vg_strch( &file
, '.' );
308 if( strcmp(ext
,content_ext
) ) continue;
310 vg_msg_wkvstr( &msg
, "content", fname
);
314 vg_dir_close(&subdir
);
316 if( !content_count
) return NULL
;
317 if( msg
.error
== k_vg_msg_error_OK
)
318 reg
->metadata_len
= msg
.cur
;
320 vg_error( "Error creating metadata: %d\n", msg
.error
);
325 addon_mount_finish( reg
);
330 * Check all subscribed items
332 VG_STATIC
void addon_mount_workshop_items(void){
333 if( !steam_ready
) return;
335 * Steam workshop scan
337 vg_info( "Mounting steam workshop subscriptions\n" );
338 PublishedFileId_t workshop_ids
[ ADDON_MOUNTED_MAX
];
339 u32 workshop_count
= ADDON_MOUNTED_MAX
;
341 vg_async_item
*call
= vg_async_alloc(
342 sizeof(struct async_workshop_installed_files_info
));
343 struct async_workshop_installed_files_info
*info
= call
->payload
;
344 info
->buffer
= workshop_ids
;
345 info
->len
= &workshop_count
;
346 vg_async_dispatch( call
, async_workshop_get_installed_files
);
349 for( u32 j
=0; j
<workshop_count
; j
++ ){
350 /* check for existance in both our caches
351 * ----------------------------------------------------------*/
352 PublishedFileId_t id
= workshop_ids
[j
];
353 for( u32 i
=0; i
<addon_system
.registry_count
; i
++ ){
354 addon_reg
*reg
= &addon_system
.registry
[i
];
356 if( reg
->alias
.workshop_id
== id
){
357 reg
->state
= k_addon_state_indexed
;
358 goto next_file_workshop
;
362 vg_async_item
*call1
=
363 vg_async_alloc( sizeof(struct async_workshop_filepath_info
) );
367 struct async_workshop_filepath_info
*info
= call1
->payload
;
370 info
->len
= vg_list_size(path
);
371 vg_async_dispatch( call1
, async_workshop_get_filepath
);
372 vg_async_stall(); /* too bad! */
374 vg_str folder
= {.buffer
= path
, .i
=strlen(path
), .len
=4096};
375 addon_mount_workshop_folder( id
, folder
);
381 * Scan a local content folder for addons. It must find at least one file with
382 * the specified content_ext to be considered.
384 VG_STATIC
void addon_mount_content_folder( enum addon_type type
,
385 const char *base_folder
,
386 const char *content_ext
)
388 vg_info( "Mounting addons(type:%d) matching skaterift/%s/*/*%s\n",
389 type
, base_folder
, content_ext
);
393 vg_strnull( &path
, path_buf
, 4096 );
394 vg_strcat( &path
, base_folder
);
397 if( !vg_dir_open(&dir
,path
.buffer
) ){
398 vg_error( "vg_dir_open('%s') failed\n", path
.buffer
);
402 vg_strcat(&path
,"/");
404 while( vg_dir_next_entry(&dir
) ){
405 if( vg_dir_entry_type(&dir
) == k_vg_entry_type_dir
){
406 const char *d_name
= vg_dir_entry_name(&dir
);
408 vg_str folder
= path
;
409 if( strlen( d_name
) > ADDON_FOLDERNAME_MAX
){
410 vg_warn( "folder too long: %s\n", d_name
);
414 vg_strcat( &folder
, d_name
);
415 if( !vg_strgood( &folder
) ) continue;
417 addon_mount_local_addon( folder
.buffer
, type
, content_ext
);
424 * write the full path of the addon's folder into the vg_str
426 static int addon_get_content_folder( addon_reg
*reg
, vg_str
*folder
){
427 if( reg
->alias
.workshop_id
){
428 vg_async_item
*call
=
429 vg_async_alloc( sizeof(struct async_workshop_filepath_info
) );
430 struct async_workshop_filepath_info
*info
= call
->payload
;
431 info
->buf
= folder
->buffer
;
432 info
->id
= reg
->alias
.workshop_id
;
433 info
->len
= folder
->len
;
434 vg_async_dispatch( call
, async_workshop_get_filepath
);
435 vg_async_stall(); /* too bad! */
436 if( info
->buf
[0] == '\0' ){
437 vg_error( "Failed SteamAPI_GetItemInstallInfo(" PRINTF_U64
")\n",
438 reg
->alias
.workshop_id
);
441 folder
->i
= strlen( folder
->buffer
);
447 const char *local_folder
=
448 addon_type_infos
[reg
->alias
.type
].local_content_folder
;
450 if( !local_folder
) return 0;
451 vg_strcat( folder
, local_folder
);
452 vg_strcat( folder
, reg
->alias
.foldername
);
458 * Return existing cache id if reg_index points to a registry with its cache
461 static u16
addon_cache_fetch( enum addon_type type
, u32 reg_index
){
462 addon_reg
*reg
= NULL
;
464 if( reg_index
< addon_count( type
) ){
465 reg
= get_addon_from_index( type
, reg_index
);
467 return reg
->cache_id
;
474 * Allocate a new cache item from the pool
476 static u16
addon_cache_alloc( enum addon_type type
, u32 reg_index
){
477 struct addon_cache
*cache
= &addon_system
.cache
[ type
];
479 u16 new_id
= vg_pool_lru( &cache
->pool
);
480 struct addon_cache_entry
*new_entry
= vg_pool_item( &cache
->pool
, new_id
);
482 addon_reg
*reg
= NULL
;
483 if( reg_index
< addon_count( type
) )
484 reg
= get_addon_from_index( type
, reg_index
);
487 if( new_entry
->reg_ptr
)
488 new_entry
->reg_ptr
->cache_id
= 0;
491 reg
->cache_id
= new_id
;
493 new_entry
->reg_ptr
= reg
;
494 new_entry
->reg_index
= reg_index
;
498 vg_error( "cache full (type: %u)!\n", type
);
504 * Get the real item data for cache id
506 static void *addon_cache_item( enum addon_type type
, u16 id
){
507 if( !id
) return NULL
;
509 struct addon_cache
*cache
= &addon_system
.cache
[type
];
510 return cache
->items
+ ((size_t)(id
-1) * cache
->stride
);
514 * Get the real item data for cache id ONLY if the item is completely loaded.
516 static void *addon_cache_item_if_loaded( enum addon_type type
, u16 id
){
517 if( !id
) return NULL
;
519 struct addon_cache
*cache
= &addon_system
.cache
[type
];
520 struct addon_cache_entry
*entry
= vg_pool_item( &cache
->pool
, id
);
522 if( entry
->state
== k_addon_cache_state_loaded
)
523 return addon_cache_item( type
, id
);
528 * Updates the item state from the main thread
530 static void async_addon_setstate( void *_entry
, u32 _state
){
531 addon_cache_entry
*entry
= _entry
;
532 SDL_AtomicLock( &addon_system
.sl_cache_using_resources
);
533 entry
->state
= _state
;
534 SDL_AtomicUnlock( &addon_system
.sl_cache_using_resources
);
535 vg_success( " loaded (%s)\n", entry
->reg_ptr
->alias
.foldername
);
539 * Handles the loading of an individual item
541 static int addon_cache_load_request( enum addon_type type
, u16 id
,
542 addon_reg
*reg
, vg_str folder
){
544 /* load content files
545 * --------------------------------- */
546 vg_str content_path
= folder
;
549 root
.buf
= reg
->metadata
;
550 root
.len
= reg
->metadata_len
;
551 root
.max
= sizeof(reg
->metadata
);
553 const char *kv_content
= vg_msg_seekkvstr( &root
, "content", 0 );
555 vg_strcat( &content_path
, "/" );
556 vg_strcat( &content_path
, kv_content
);
559 vg_error( " No content paths in metadata\n" );
563 if( !vg_strgood( &content_path
) ) {
564 vg_error( " Metadata path too long\n" );
568 if( type
== k_addon_type_board
){
569 struct player_board
*board
= addon_cache_item( type
, id
);
570 player_board_load( board
, content_path
.buffer
);
573 else if( type
== k_addon_type_player
){
574 struct player_model
*model
= addon_cache_item( type
, id
);
575 player_model_load( model
, content_path
.buffer
);
585 static void addon_cache_free_item( enum addon_type type
, u16 id
){
586 if( type
== k_addon_type_board
){
587 struct player_board
*board
= addon_cache_item( type
, id
);
588 player_board_unload( board
);
590 else if( type
== k_addon_type_player
){
591 struct player_model
*model
= addon_cache_item( type
, id
);
592 player_model_unload( model
);
597 * Goes over cache item load requests and calls the above ^
599 static void addon_cache_load_loop(void){
600 vg_info( "Running load loop\n" );
603 for( u32 type
=0; type
<k_addon_type_max
; type
++ ){
604 struct addon_cache
*cache
= &addon_system
.cache
[type
];
606 for( u32 id
=1; id
<=cache
->pool
.count
; id
++ ){
607 addon_cache_entry
*entry
= vg_pool_item( &cache
->pool
, id
);
609 SDL_AtomicLock( &addon_system
.sl_cache_using_resources
);
610 if( entry
->state
== k_addon_cache_state_load_request
){
611 vg_info( "process cache load request (%u#%u, reg:%u)\n",
612 type
, id
, entry
->reg_index
);
614 if( entry
->reg_index
>= addon_count(type
) ){
615 /* should maybe have a different value for this case */
616 entry
->state
= k_addon_cache_state_none
;
617 SDL_AtomicUnlock( &addon_system
.sl_cache_using_resources
);
621 SDL_AtomicUnlock( &addon_system
.sl_cache_using_resources
);
623 /* continue with the request */
624 addon_reg
*reg
= get_addon_from_index( type
, entry
->reg_index
);
625 entry
->reg_ptr
= reg
;
628 vg_strnull( &folder
, path_buf
, 4096 );
629 if( addon_get_content_folder( reg
, &folder
) ){
630 if( addon_cache_load_request( type
, id
, reg
, folder
) ){
631 vg_async_call( async_addon_setstate
,
632 entry
, k_addon_cache_state_loaded
);
637 vg_warn( "cache item did not load (%u#%u)\n", type
, id
);
638 SDL_AtomicLock( &addon_system
.sl_cache_using_resources
);
639 entry
->state
= k_addon_cache_state_none
;
640 SDL_AtomicUnlock( &addon_system
.sl_cache_using_resources
);
643 SDL_AtomicUnlock( &addon_system
.sl_cache_using_resources
);
649 * Perform the cache interactions required to create a viewslot which will
650 * eventually be loaded by other parts of the system.
652 static u16
addon_cache_create_viewer( enum addon_type type
, u16 reg_id
){
653 struct addon_cache
*cache
= &addon_system
.cache
[type
];
654 vg_pool
*pool
= &cache
->pool
;
656 u16 cache_id
= addon_cache_fetch( type
, reg_id
);
658 cache_id
= addon_cache_alloc( type
, reg_id
);
661 SDL_AtomicLock( &addon_system
.sl_cache_using_resources
);
662 addon_cache_entry
*entry
= vg_pool_item( pool
, cache_id
);
664 if( entry
->state
== k_addon_cache_state_loaded
){
665 addon_cache_free_item( type
, cache_id
);
668 entry
->state
= k_addon_cache_state_load_request
;
669 SDL_AtomicUnlock( &addon_system
.sl_cache_using_resources
);
674 vg_pool_watch( pool
, cache_id
);
679 static void addon_cache_watch( enum addon_type type
, u16 cache_id
){
680 if( !cache_id
) return;
682 struct addon_cache
*cache
= &addon_system
.cache
[type
];
683 vg_pool
*pool
= &cache
->pool
;
684 vg_pool_watch( pool
, cache_id
);
687 static void addon_cache_unwatch( enum addon_type type
, u16 cache_id
){
688 if( !cache_id
) return;
690 struct addon_cache
*cache
= &addon_system
.cache
[type
];
691 vg_pool
*pool
= &cache
->pool
;
692 vg_pool_unwatch( pool
, cache_id
);