threading fixes for skateshop
[carveJwlIkooP6JGAAIwe30JlM.git] / ent_skateshop.c
1 #ifndef ENT_SKATESHOP_C
2 #define ENT_SKATESHOP_C
3
4 #include "ent_skateshop.h"
5 #include "world.h"
6 #include "player.h"
7 #include "gui.h"
8
9 /*
10 * Checks string equality but does a hash check first
11 */
12 static inline int const_str_eq( u32 hash, const char *str, const char *cmp )
13 {
14 if( hash == vg_strdjb2(cmp) )
15 if( !strcmp( str, cmp ) )
16 return 1;
17 return 0;
18 }
19
20 /*
21 * Get an existing cache instance, allocate a new one to be loaded, or NULL if
22 * there is no space
23 */
24 VG_STATIC struct cache_board *skateshop_cache_fetch( u32 registry_index )
25 {
26 struct registry_board *reg = NULL;
27
28 if( registry_index < global_skateshop.registry_count ){
29 reg = &global_skateshop.registry[ registry_index ];
30
31 if( reg->cache_ptr ){
32 return reg->cache_ptr;
33 }
34 }
35
36 /* lru eviction. should be a linked list maybe... */
37 double min_time = 1e300;
38 struct cache_board *min_board = NULL;
39
40 SDL_AtomicLock( &global_skateshop.sl_cache_access );
41 for( u32 i=0; i<SKATESHOP_BOARD_CACHE_MAX; i++ ){
42 struct cache_board *cache_ptr = &global_skateshop.cache[i];
43
44 if( cache_ptr->state == k_cache_board_state_load_request ) continue;
45 if( cache_ptr->ref_count ) continue;
46
47 if( cache_ptr->last_use_time < min_time ){
48 min_time = cache_ptr->last_use_time;
49 min_board = cache_ptr;
50 }
51 }
52
53 if( min_board ){
54 if( min_board->state == k_cache_board_state_loaded ){
55 struct registry_board *other =
56 &global_skateshop.registry[ min_board->registry_id ];
57
58 vg_info( "Deallocating board: '%s'\n", min_board, other->filename );
59
60 player_board_unload( &min_board->board );
61 other->cache_ptr = NULL;
62 }
63
64 if( reg ){
65 vg_info( "Allocating board (reg:%u) '%s'\n",
66 registry_index, reg->filename );
67 }
68 else{
69 vg_info( "Pre-allocating board (reg:%u) 'null'\n", registry_index );
70 }
71
72 min_board->registry_id = registry_index;
73 min_board->last_use_time = vg.time;
74 min_board->ref_count = 0;
75 min_board->state = k_cache_board_state_load_request;
76 }
77 else{
78 vg_error( "No free boards to load registry!\n" );
79 }
80
81 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
82 return min_board;
83 }
84
85 VG_STATIC void skateshop_update_viewpage(void)
86 {
87 u32 page = global_skateshop.selected_registry_id/SKATESHOP_VIEW_SLOT_MAX;
88
89 for( u32 i=0; i<SKATESHOP_VIEW_SLOT_MAX; i++ ){
90 struct shop_view_slot *slot = &global_skateshop.shop_view_slots[i];
91 u32 request_id = page*SKATESHOP_VIEW_SLOT_MAX + i;
92
93 if( slot->cache_ptr ) unwatch_cache_board( slot->cache_ptr );
94
95 slot->cache_ptr = skateshop_cache_fetch( request_id );
96 if( slot->cache_ptr ) watch_cache_board( slot->cache_ptr );
97 }
98 }
99
100 /* generic reciever */
101 VG_STATIC void workshop_async_any_complete( void *data, u32 size )
102 {
103 workshop_end_op();
104 }
105
106 /*
107 * op/subroutine: k_workshop_op_item_load
108 * -----------------------------------------------------------------------------
109 */
110
111 /*
112 * Reciever for board completion; only promotes the status in the main thread
113 */
114 VG_STATIC void skateshop_async_board_loaded( void *payload, u32 size )
115 {
116 SDL_AtomicLock( &global_skateshop.sl_cache_access );
117 struct cache_board *cache_ptr = payload;
118 cache_ptr->last_use_time = vg.time;
119 cache_ptr->state = k_cache_board_state_loaded;
120
121 struct registry_board *reg =
122 &global_skateshop.registry[ cache_ptr->registry_id ];
123 reg->cache_ptr = cache_ptr;
124 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
125
126 vg_success( "Async board loaded (%s)\n", reg->filename );
127 }
128
129 /*
130 * Thread(or subroutine of thread), for checking view slots that weve installed.
131 * Load the model if a view slot wants it
132 */
133 VG_STATIC void workshop_visibile_load_loop_thread( void *_args )
134 {
135 char path[1024];
136 for( u32 i=0; i<SKATESHOP_BOARD_CACHE_MAX; i++ ){
137 struct cache_board *cache_ptr = &global_skateshop.cache[i];
138
139 SDL_AtomicLock( &global_skateshop.sl_cache_access );
140 if( cache_ptr->state == k_cache_board_state_load_request ){
141 if( cache_ptr->registry_id >= global_skateshop.registry_count ){
142 /* should maybe have a different value for this case */
143 cache_ptr->state = k_cache_board_state_none;
144 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
145 continue;
146 }
147
148 /* continue with the request */
149 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
150
151 struct registry_board *reg =
152 &global_skateshop.registry[ cache_ptr->registry_id ];
153
154 if( reg->workshop_id ){
155 vg_async_item *call =
156 vg_async_alloc( sizeof(struct async_workshop_filepath_info) );
157
158 struct async_workshop_filepath_info *info = call->payload;
159 info->buf = path;
160 info->id = reg->workshop_id;
161 info->len = vg_list_size(path) - strlen("/board.mdl")-1;
162 vg_async_dispatch( call, async_workshop_get_filepath );
163 vg_async_stall(); /* too bad! */
164
165 if( path[0] == '\0' ){
166 SDL_AtomicLock( &global_skateshop.sl_cache_access );
167 cache_ptr->state = k_cache_board_state_none;
168 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
169
170 vg_error( "Failed SteamAPI_GetItemInstallInfo(" PRINTF_U64 ")\n",
171 reg->workshop_id );
172 continue;
173 }
174 else{
175 strcat( path, "/board.mdl" );
176 }
177 }
178 else{
179 snprintf( path, 256, "models/boards/%s", reg->filename );
180 }
181
182 player_board_load( &cache_ptr->board, path );
183 vg_async_call( skateshop_async_board_loaded, cache_ptr, 0 );
184 }
185 else
186 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
187 }
188 vg_async_call( workshop_async_any_complete, NULL, 0 );
189 }
190
191 /*
192 * op: k_workshop_op_item_scan
193 * -----------------------------------------------------------------------------
194 */
195
196 /*
197 * Reciever for scan completion. copies the registry_count back into t0
198 */
199 VG_STATIC void workshop_async_reg_update( void *data, u32 size )
200 {
201 vg_info( "Registry update notify\n" );
202 global_skateshop.registry_count = global_skateshop.t1_registry_count;
203 }
204
205 /*
206 * Async thread which scans local files for boards, as well as scheduling
207 * synchronous calls to the workshop
208 */
209 VG_STATIC void workshop_scan_thread( void *_args )
210 {
211 vg_linear_clear( vg_mem.scratch );
212
213 for( u32 i=0; i<global_skateshop.t1_registry_count; i++ ){
214 struct registry_board *reg = &global_skateshop.registry[i];
215 reg->state = k_registry_board_state_indexed_absent;
216 }
217
218 /*
219 * Local disk scan
220 */
221 vg_info( "Scanning models/boards/*.mdl\n" );
222 tinydir_dir dir;
223 tinydir_open( &dir, "models/boards" );
224
225 while( dir.has_next ){
226 tinydir_file file;
227 tinydir_readfile( &dir, &file );
228
229 if( file.is_reg ){
230 u32 hash = vg_strdjb2( file.name );
231
232 for( u32 i=0; i<global_skateshop.t1_registry_count; i++ ){
233 struct registry_board *reg = &global_skateshop.registry[i];
234
235 if( const_str_eq( hash, file.name, reg->filename ) ){
236 reg->state = k_registry_board_state_indexed;
237 goto next_file;
238 }
239 }
240
241 if( global_skateshop.t1_registry_count == SKATESHOP_REGISTRY_MAX ){
242 vg_error( "You have too many boards installed!\n" );
243 break;
244 }
245
246 vg_info( "new listing!: %s\n", file.name );
247
248 struct registry_board *reg =
249 &global_skateshop.registry[global_skateshop.t1_registry_count ++];
250
251 reg->cache_ptr = NULL;
252 vg_strncpy( file.name, reg->filename, 64, k_strncpy_always_add_null );
253 reg->filename_hash = hash;
254 reg->workshop_id = 0;
255 reg->state = k_registry_board_state_indexed;
256 }
257
258 next_file: tinydir_next( &dir );
259 }
260
261 tinydir_close(&dir);
262
263 /*
264 * Steam workshop scan
265 */
266 vg_info( "Scanning steam workshop for boards\n" );
267 PublishedFileId_t workshop_ids[ SKATESHOP_REGISTRY_MAX ];
268 u32 workshop_count = SKATESHOP_REGISTRY_MAX;
269
270 vg_async_item *call = vg_async_alloc(
271 sizeof(struct async_workshop_installed_files_info));
272 struct async_workshop_installed_files_info *info = call->payload;
273 info->buffer = workshop_ids;
274 info->len = &workshop_count;
275 vg_async_dispatch( call, async_workshop_get_installed_files );
276 vg_async_stall();
277
278 for( u32 j=0; j<workshop_count; j++ ){
279 PublishedFileId_t id = workshop_ids[j];
280
281 for( u32 i=0; i<global_skateshop.t1_registry_count; i++ ){
282 struct registry_board *reg = &global_skateshop.registry[i];
283
284 if( reg->workshop_id == id ){
285 reg->state = k_registry_board_state_indexed;
286 goto next_file_workshop;
287 }
288 }
289
290 if( global_skateshop.t1_registry_count == SKATESHOP_REGISTRY_MAX ){
291 vg_error( "You have too many boards installed!\n" );
292 break;
293 }
294
295 vg_info( "new listing from the steam workshop!: "PRINTF_U64"\n", id );
296
297 struct registry_board *reg = &global_skateshop.registry[
298 global_skateshop.t1_registry_count ++ ];
299
300 reg->cache_ptr = NULL;
301 snprintf( reg->filename, 64, PRINTF_U64, id );
302 reg->filename_hash = vg_strdjb2( reg->filename );
303 reg->workshop_id = id;
304 reg->state = k_registry_board_state_indexed;
305
306 next_file_workshop:;
307 }
308
309 vg_async_call( workshop_async_reg_update, NULL, 0 );
310 vg_async_stall();
311 workshop_visibile_load_loop_thread(NULL);
312 }
313
314 /*
315 * Asynchronous scan of local disk for items and add them to the registry
316 */
317 VG_STATIC void workshop_op_item_scan(void)
318 {
319 workshop_begin_op( k_workshop_op_item_scan );
320 vg_loader_start( workshop_scan_thread, NULL );
321 }
322
323 /*
324 * Regular stuff
325 * -----------------------------------------------------------------------------
326 */
327
328 /* we can only keep using a viewslot pointer for multiple frames if we watch it
329 * using this function */
330 VG_STATIC void watch_cache_board( struct cache_board *ptr )
331 {
332 if( ptr->ref_count >= 32 ){
333 vg_fatal_error( "dynamic board watch missmatch (limit is 32)\n" );
334 }
335
336 ptr->last_use_time = vg.time;
337 ptr->ref_count ++;
338 }
339
340 /* after this is called, the calling code only has access to the pointer for the
341 * duration of the rest of the frame */
342 VG_STATIC void unwatch_cache_board( struct cache_board *ptr )
343 {
344 if( ptr->ref_count == 0 ){
345 vg_fatal_error( "dynamic board unwatch missmatch (no watchers)\n" );
346 }
347
348 ptr->ref_count --;
349 }
350
351 /*
352 * VG event init
353 */
354 VG_STATIC void skateshop_init(void)
355 {
356 u32 reg_size = sizeof(struct registry_board)*SKATESHOP_REGISTRY_MAX,
357 cache_size = sizeof(struct cache_board)*SKATESHOP_BOARD_CACHE_MAX;
358
359 global_skateshop.registry = vg_linear_alloc( vg_mem.rtmemory, reg_size );
360 global_skateshop.registry_count = 0;
361 global_skateshop.cache = vg_linear_alloc( vg_mem.rtmemory, cache_size );
362
363 memset( global_skateshop.cache, 0, cache_size );
364
365 for( u32 i=0; i<SKATESHOP_BOARD_CACHE_MAX; i++ ){
366 struct cache_board *board = &global_skateshop.cache[i];
367 board->state = k_cache_board_state_none;
368 board->registry_id = 0xffffffff;
369 board->last_use_time = -99999.9;
370 board->ref_count = 0;
371 }
372 }
373
374 VG_STATIC struct cache_board *skateshop_selected_cache_if_loaded(void)
375 {
376 if( global_skateshop.registry_count > 0 ){
377 u32 reg_id = global_skateshop.selected_registry_id;
378 struct registry_board *reg = &global_skateshop.registry[ reg_id ];
379
380 SDL_AtomicLock( &global_skateshop.sl_cache_access );
381 if( reg->cache_ptr &&
382 (reg->cache_ptr->state == k_cache_board_state_loaded ) )
383 {
384 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
385 return reg->cache_ptr;
386 }
387 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
388 }
389
390 return NULL;
391 }
392
393 /*
394 * VG event preupdate
395 */
396 VG_STATIC void global_skateshop_preupdate(void)
397 {
398 float rate = vg_minf( 1.0f, vg.time_frame_delta * 2.0f );
399 global_skateshop.factive = vg_lerpf( global_skateshop.factive,
400 global_skateshop.active, rate );
401
402 if( !global_skateshop.active ) return;
403
404 world_instance *world = get_active_world();
405
406 ent_skateshop *shop = global_skateshop.ptr_ent;
407 ent_camera *ref = mdl_arritm( &world->ent_camera,
408 mdl_entity_id_id(shop->id_camera) );
409 ent_marker *display = mdl_arritm( &world->ent_marker,
410 mdl_entity_id_id(shop->id_display) );
411
412 v3f dir = {0.0f,-1.0f,0.0f};
413 mdl_transform_vector( &ref->transform, dir, dir );
414 player_vector_angles( localplayer.cam_override_angles, dir, 1.0f, 0.0f );
415
416 v3f lookat;
417 v3_sub( display->transform.co, localplayer.rb.co, lookat );
418
419 q_axis_angle( localplayer.rb.q, (v3f){0.0f,1.0f,0.0f},
420 atan2f(lookat[0],lookat[2]) );
421
422 v3_copy( ref->transform.co, localplayer.cam_override_pos );
423 localplayer.cam_override_fov = ref->fov;
424 localplayer.cam_override_strength = global_skateshop.factive;
425
426 gui_helper_action( axis_display_string( k_sraxis_mbrowse_h ), "browse" );
427 gui_helper_action( button_display_string( k_srbind_mback ), "exit" );
428
429 int moved = 0;
430 struct cache_board *selected_cache = skateshop_selected_cache_if_loaded();
431
432 if( selected_cache ){
433 gui_helper_action( button_display_string( k_srbind_maccept ), "pick" );
434 }
435
436 /*
437 * Controls
438 * ----------------------
439 */
440
441 if( global_skateshop.interaction_cooldown > 0.0f ){
442 global_skateshop.interaction_cooldown -= vg.time_delta;
443 return;
444 }
445
446 if( button_down( k_srbind_mleft ) ){
447 if( global_skateshop.selected_registry_id > 0 ){
448 global_skateshop.selected_registry_id --;
449 moved = 1;
450 }
451 }
452
453 if( button_down( k_srbind_mright ) ){
454 if( global_skateshop.selected_registry_id+1 <
455 global_skateshop.registry_count )
456 {
457 global_skateshop.selected_registry_id ++;
458 moved = 1;
459 }
460 }
461
462 if( moved ){
463 vg_info( "Select registry: %u\n",
464 global_skateshop.selected_registry_id );
465 global_skateshop.interaction_cooldown = 0.125f;
466 return;
467 }
468
469 if( selected_cache && button_down( k_srbind_maccept ) ){
470 vg_info( "chose board from skateshop (%u)\n",
471 global_skateshop.selected_registry_id );
472
473 if( localplayer.board_view_slot ){
474 unwatch_cache_board( localplayer.board_view_slot );
475 }
476
477 localplayer.board_view_slot = selected_cache;
478 watch_cache_board( localplayer.board_view_slot );
479
480 global_skateshop_exit();
481 return;
482 }
483
484 if( button_down( k_srbind_mback ) ){
485 global_skateshop_exit();
486 return;
487 }
488 }
489
490 /*
491 * World: render event
492 */
493 VG_STATIC void skateshop_render(void)
494 {
495 if( !global_skateshop.active ) return;
496
497 ent_skateshop *shop = global_skateshop.ptr_ent;
498 world_instance *world = get_active_world();
499
500 u32 slot_count = vg_list_size(global_skateshop.shop_view_slots);
501
502 ent_marker *mark_rack = mdl_arritm( &world->ent_marker,
503 mdl_entity_id_id(shop->id_rack)),
504 *mark_display = mdl_arritm( &world->ent_marker,
505 mdl_entity_id_id(shop->id_display));
506
507 int visibility[ SKATESHOP_VIEW_SLOT_MAX ];
508 SDL_AtomicLock( &global_skateshop.sl_cache_access );
509 for( u32 i=0; i<SKATESHOP_VIEW_SLOT_MAX; i++ ){
510 struct shop_view_slot *slot = &global_skateshop.shop_view_slots[i];
511
512 visibility[i] = 1;
513
514 if( slot->cache_ptr == NULL ) visibility[i] = 0;
515 else if( slot->cache_ptr->state != k_cache_board_state_loaded )
516 visibility[i] = 0;
517 }
518 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
519
520 /* Render loaded boards in the view slots */
521 for( u32 i=0; i<slot_count; i++ ){
522 struct shop_view_slot *slot = &global_skateshop.shop_view_slots[i];
523 float selected = 0.0f;
524
525 if( !visibility[i] ) goto fade_out;
526
527 mdl_transform xform;
528 transform_identity( &xform );
529
530 xform.co[0] = -((float)i - ((float)slot_count)*0.5f)*0.45f;
531 mdl_transform_mul( &mark_rack->transform, &xform, &xform );
532
533 if( slot->cache_ptr->registry_id ==
534 global_skateshop.selected_registry_id ){
535 selected = 1.0f;
536 }
537
538 float t = slot->view_blend;
539 v3_lerp( xform.co, mark_display->transform.co, t, xform.co );
540 q_nlerp( xform.q, mark_display->transform.q, t, xform.q );
541 v3_lerp( xform.s, mark_display->transform.s, t, xform.s );
542
543 m4x3f mmdl;
544 mdl_transform_m4x3( &xform, mmdl );
545 render_board( &main_camera, world, &slot->cache_ptr->board, mmdl,
546 k_board_shader_entity );
547
548 fade_out:;
549 float rate = 5.0f*vg.time_delta;
550 slot->view_blend = vg_lerpf( slot->view_blend, selected, rate );
551 }
552
553 ent_marker *mark_info = mdl_arritm( &world->ent_marker,
554 mdl_entity_id_id(shop->id_info));
555 m4x3f mtext, mrack;
556 mdl_transform_m4x3( &mark_info->transform, mtext );
557 mdl_transform_m4x3( &mark_rack->transform, mrack );
558
559 const char *text_title = "Fish - Title";
560 const char *text_author = "by Shaniqua";
561
562 m4x3f mlocal, mmdl;
563 m4x3_identity( mlocal );
564
565 float scale = 0.2f,
566 thickness = 0.03f;
567
568 font3d_bind( &world_global.font, &main_camera );
569 shader_model_font_uColour( (v4f){1.0f,1.0f,1.0f,1.0f} );
570
571 /* Selection counter
572 * ------------------------------------------------------------------ */
573 m3x3_zero( mlocal );
574 v3_zero( mlocal[3] );
575 mlocal[0][0] = -scale*2.0f;
576 mlocal[1][2] = -scale*2.0f;
577 mlocal[2][1] = -thickness;
578 mlocal[3][2] = -0.7f;
579 m4x3_mul( mrack, mlocal, mmdl );
580
581 if( global_skateshop.registry_count == 0 ){
582 font3d_simple_draw( &world_global.font, 0,
583 "Nothing installed", &main_camera, mmdl );
584 }
585 else{
586 char buf[16];
587 int i=0;
588 i+=highscore_intl( buf+i, global_skateshop.selected_registry_id+1, 3 );
589 buf[i++] = '/';
590 i+=highscore_intl( buf+i, global_skateshop.registry_count, 3 );
591 buf[i++] = '\0';
592
593 font3d_simple_draw( &world_global.font, 0, buf, &main_camera, mmdl );
594 }
595
596 struct cache_board *cache_ptr = skateshop_selected_cache_if_loaded();
597 if( !cache_ptr ) return;
598
599 /* Skin title
600 * ----------------------------------------------------------------- */
601 m3x3_setdiagonalv3( mlocal, (v3f){ scale, scale, thickness } );
602 mlocal[3][0] = -font3d_string_width(&world_global.font,0,text_title);
603 mlocal[3][0] *= scale*0.5f;
604 mlocal[3][1] = 0.1f;
605 m4x3_mul( mtext, mlocal, mmdl );
606 font3d_simple_draw( &world_global.font, 0, text_title, &main_camera, mmdl );
607
608 /* Author name
609 * ----------------------------------------------------------------- */
610 scale *= 0.4f;
611 m3x3_setdiagonalv3( mlocal, (v3f){ scale, scale, thickness } );
612 mlocal[3][0] = -font3d_string_width(&world_global.font,0,text_author);
613 mlocal[3][0] *= scale*0.5f;
614 mlocal[3][1] = 0.0f;
615 m4x3_mul( mtext, mlocal, mmdl );
616 font3d_simple_draw( &world_global.font, 0, text_author, &main_camera, mmdl );
617 }
618
619 /*
620 * Entity logic: entrance event
621 */
622 VG_STATIC void ent_skateshop_call( world_instance *world, ent_call *call )
623 {
624 u32 index = mdl_entity_id_id( call->id );
625 ent_skateshop *shop = mdl_arritm( &world->ent_skateshop, index );
626 vg_info( "skateshop_call\n" );
627
628 if( call->function == k_ent_function_trigger ){
629 if( localplayer.subsystem != k_player_subsystem_walk ){
630 return;
631 }
632
633 vg_info( "Entering skateshop\n" );
634
635 localplayer.immobile = 1;
636 global_skateshop.active = 1;
637
638 v3_zero( localplayer.rb.v );
639 v3_zero( localplayer.rb.w );
640 localplayer._walk.move_speed = 0.0f;
641 global_skateshop.ptr_ent = shop;
642
643 skateshop_update_viewpage();
644 workshop_op_item_scan();
645 }
646 }
647
648 /*
649 * Entity logic: exit event
650 */
651 VG_STATIC void global_skateshop_exit(void)
652 {
653 vg_info( "exit skateshop\n" );
654 localplayer.immobile = 0;
655 global_skateshop.active = 0;
656 srinput.ignore_input_frames = 2;
657 }
658
659 #endif /* ENT_SKATESHOP_C */