From: hgn Date: Tue, 8 Apr 2025 14:38:10 +0000 (+0100) Subject: setting up revised replay system X-Git-Url: https://harrygodden.com/git/?a=commitdiff_plain;h=12cdcfb627538edf1f79f1c8d4e0e0ca5538cf52;p=carveJwlIkooP6JGAAIwe30JlM.git setting up revised replay system --- diff --git a/src/ent_route.c b/src/ent_route.c index 8412d61..ad19724 100644 --- a/src/ent_route.c +++ b/src/ent_route.c @@ -1,8 +1,9 @@ #include "ent_route.h" #include "input.h" #include "gui.h" +#include "network_requests.h" -struct global_ent_route _ent_route; +struct _ent_route _ent_route; entity_call_result ent_route_call( world_instance *world, ent_call *call ) { @@ -16,16 +17,17 @@ entity_call_result ent_route_call( world_instance *world, ent_call *call ) gui_helper_reset( k_gui_helper_mode_clear ); vg_str text; +#if 0 if( (_ent_route.helper_weekly = gui_new_helper( input_button_list[k_srbind_mleft], &text ))) vg_strcat( &text, "Weekly" ); - if( (_ent_route.helper_alltime = gui_new_helper( input_button_list[k_srbind_mright], &text ))) vg_strcat( &text, "All time" ); +#endif if( gui_new_helper( input_button_list[k_srbind_mback], &text ) ) vg_strcat( &text, "Exit" ); - _ent_route.looking_at_board = route; + _ent_route.viewing_route_id = call->id; localplayer.immobile = 1; } @@ -41,7 +43,7 @@ void ent_route_preupdate(void) return; world_instance *world = &_world.main; - ent_route *route = _ent_route.looking_at_board; + ent_route *route = af_arritm( &world->ent_route, mdl_entity_id_id( _ent_route.viewing_route_id ) ); u32 cam_id = 0; if( __builtin_expect( world->meta.version >= 103, 1 ) ) @@ -54,7 +56,104 @@ void ent_route_preupdate(void) world_set_entity_driven_camera( &temp ); } - if( button_down( k_srbind_mleft ) ) + bool select_move = 0; + + /* TODO: Visual buttons like the board shops */ + if( button_down( k_srbind_mup ) ) + { + if( _ent_route.run_selection > 0 ) + { + _ent_route.run_selection --; + select_move = 1; + } + } + if( button_down( k_srbind_mdown ) ) + { + if( _ent_route.run_selection < 10 ) + { + _ent_route.run_selection ++; + select_move = 1; + } + } + + if( select_move ) + { + sfd_encode( (v2i){0,_ent_route.run_selection }, " ", k_world_sfd_left ); + sfd_encode( (v2i){0,_ent_route.run_selection + 1}, "AA", k_world_sfd_left ); + sfd_encode( (v2i){0,_ent_route.run_selection + 2}, " ", k_world_sfd_left ); + } + + if( button_down( k_srbind_maccept ) ) + { + if( _remote_replay.state != k_remote_replay_state_none ) + goto E0; + + struct leaderboard_cache *board = &world->leaderboard_cache[ mdl_entity_id_id( _ent_route.viewing_route_id ) ]; + vg_msg body; + vg_msg_init( &body, board->data, board->data_len ); + + vg_info( "Looking for the %u run\n", _ent_route.run_selection ); + + u32 l = 0; + + if( vg_msg_seekframe( &body, "rows" ) ) + { + while( vg_msg_seekframe( &body, NULL ) ) + { + const char *username = vg_msg_getkvstr( &body, "username" ); + vg_info( "%u: %s\n", l, username ); + + if( l == _ent_route.run_selection ) + { + u32 centiseconds; + vg_msg_getkvintg( &body, "time", k_vg_msg_u32, ¢iseconds, NULL ); + + u32 lastmin; + vg_msg_getkvintg( &body, "lastmin", k_vg_msg_u32, &lastmin, NULL ); + + u64 steamid; + vg_msg_getkvintg( &body, "steamid", k_vg_msg_u64, &steamid, NULL ); + + // TODO: put this in a header + u32 minutes_span = (centiseconds+(200*60)) / (100*60); + + vg_queue_clear( &_remote_replay.replay.buffer ); + _remote_replay.min_frame_t = 0.0; + _remote_replay.total_chunks = minutes_span; + _remote_replay.chunks_downloaded = 0; + _remote_replay.steamid = steamid; + _remote_replay.state = k_remote_replay_state_downloading; + + for( u32 i=0; iminute = lastmin - minutes_span + 1 + i; + chunk->state = k_chunk_state_none; + + // TODO: cache wrapper + // + // reset_replay_cache( span, callback_when_complete, callback_when_fail ) + // get_cache_remote_replay( ... ) x3 + // + //network_download_replay( steamid, want_minute ); + } + + // TODO: UI overlay for downlaoding + sfd_encode( (v2i){0,6}, "Downloading ...", k_world_sfd_center ); + + break; + } + else + { + l ++; + vg_msg_skip_frame( &body ); + } + } + } + } + +#if 0 +if( button_down( k_srbind_mleft ) ) { world_sfd.view_weekly = 1; world_sfd_compile_active_scores(); @@ -65,9 +164,11 @@ void ent_route_preupdate(void) world_sfd.view_weekly = 0; world_sfd_compile_active_scores(); } - _ent_route.helper_alltime->greyed = !world_sfd.view_weekly; _ent_route.helper_weekly->greyed = world_sfd.view_weekly; +#endif + +E0: if( button_down( k_srbind_mback ) ) { diff --git a/src/ent_route.h b/src/ent_route.h index b687c3f..7b5af10 100644 --- a/src/ent_route.h +++ b/src/ent_route.h @@ -1,12 +1,14 @@ #pragma once #include "entity.h" -struct global_ent_route +struct _ent_route { - struct gui_helper *helper_weekly, *helper_alltime; - ent_route *looking_at_board; + //struct gui_helper *helper_weekly, *helper_alltime; + + u32 viewing_route_id; + u32 run_selection; } -extern global_ent_route; +extern _ent_route; entity_call_result ent_route_call( world_instance *world, ent_call *call ); void ent_route_preupdate(void); diff --git a/src/gameserver.c b/src/gameserver.c index 008c8fc..307e2b6 100644 --- a/src/gameserver.c +++ b/src/gameserver.c @@ -3,6 +3,7 @@ */ #define _DEFAULT_SOURCE +#define VG_SERVER #include #include #include @@ -183,6 +184,7 @@ static void remove_client( int index ) memset( client, 0, sizeof(struct gameserver_client) ); gameserver_update_all_knowledge( index, 1 ); _gs_requests_client_disconnect( index ); + _gs_replay_client_disconnect( index ); } /* @@ -614,13 +616,17 @@ static void gameserver_rx_200_300( SteamNetworkingMessage_t *msg ) /* WARNING FIXME WARNING FIXME ------------------------------------------- */ if( !strcmp( prop->msg, "save" ) ) { - _gs_write_replay_to_disk( client_id, 1.0*60.0, "/tmp/server-replay.replay" ); + //_gs_write_replay_to_disk( client_id, 1.0*60.0, "/tmp/server-replay.replay" ); } else if( !strcmp( prop->msg, "transfer" ) ) { //_gs_start_transfer( client_id, "/tmp/server-replay.replay" ); //_gs_test_replay( client_id, "/tmp/server-replay.replay" ); } + else if( !strcmp( prop->msg, "$savemin" ) ) + { + _gs_replay_request_save( client_id, client->steamid, _gs_replay.current_minute, 2, 0 ); + } gameserver_send_to_all( client_id, prop, sizeof(netmsg_chat)+l+1, k_nSteamNetworkingSend_Reliable ); } @@ -654,191 +660,6 @@ u32 gameserver_get_current_week(void) return time(NULL) / (7*24*60*60); } - -#if 0 -static void gameserver_request_respond( enum request_status status, netmsg_request *res, vg_msg *body, - SteamNetworkingMessage_t *msg ) -{ - int client_id = gameserver_conid( msg->m_conn ); - u32 len = 0; - if( body ) - { - len = body->cur.co; - vg_low( "[%d#%d] Response: %d\n", client_id, (i32)res->id, status ); - vg_msg_print( body, len ); - } - - res->status = status; - - SteamAPI_ISteamNetworkingSockets_SendMessageToConnection( hSteamNetworkingSockets, msg->m_conn, - res, sizeof(netmsg_request) + len, - k_nSteamNetworkingSend_Reliable, NULL ); - - SteamAPI_SteamNetworkingMessage_t_Release( msg ); -} - -struct user_request_thread_data { - SteamNetworkingMessage_t *msg; -}; - -static enum request_status gameserver_cat_table( - vg_msg *msg, - const char *mod, const char *route, u32 week, const char *alias ) -{ - char table_name[ DB_TABLE_UID_MAX ]; - if( !db_get_highscore_table_name( mod, route, week, table_name ) ) - return k_request_status_out_of_memory; - - char buf[512]; - vg_str q; - vg_strnull( &q, buf, 512 ); - vg_strcat( &q, "SELECT * FROM \"" ); - vg_strcat( &q, table_name ); - vg_strcat( &q, "\" ORDER BY time ASC LIMIT 10;" ); - if( !vg_strgood(&q) ) - return k_request_status_out_of_memory; - - sqlite3_stmt *stmt = db_stmt( q.buffer ); - if( !stmt ) - return k_request_status_database_error; - - vg_msg_frame( msg, alias ); - for( u32 i=0; i<10; i ++ ){ - int fc = sqlite3_step( stmt ); - - if( fc == SQLITE_ROW ){ - i32 time = sqlite3_column_int( stmt, 1 ); - i64 steamid_i64 = sqlite3_column_int64( stmt, 0 ); - u64 steamid = *((u64 *)&steamid_i64); - - if( steamid == k_steamid_max ) - continue; - - vg_msg_frame( msg, "" ); - vg_msg_wkvnum( msg, "time", k_vg_msg_u32, 1, &time ); - vg_msg_wkvnum( msg, "steamid", k_vg_msg_u64, 1, &steamid ); - - char username[32]; - if( db_getuserinfo( steamid, username, sizeof(username), NULL ) ) - vg_msg_wkvstr( msg, "username", username ); - vg_msg_end_frame( msg ); - } - else if( fc == SQLITE_DONE ){ - break; - } - else { - log_sqlite3( fc ); - break; - } - } - - sqlite3_finalize( stmt ); - vg_msg_end_frame( msg ); - return k_request_status_ok; -} - -static void gameserver_process_user_request( db_request *db_req ) -{ - struct user_request_thread_data *inf = (void *)db_req->data; - SteamNetworkingMessage_t *msg = inf->msg; - - int client_id = gameserver_conid( msg->m_conn ); - if( client_id == -1 ) - { - SteamAPI_SteamNetworkingMessage_t_Release( msg ); - return; - } - - struct gameserver_client *client = &_gameserver.clients[ client_id ]; - - netmsg_request *req = (netmsg_request *)msg->m_pData; - vg_msg data; - vg_msg_init( &data, req->q, msg->m_cbSize - sizeof(netmsg_request) ); - - /* create response packet */ - netmsg_request *res = alloca( sizeof(netmsg_request) + NETWORK_REQUEST_MAX ); - res->inetmsg_id = k_inetmsg_response; - res->id = req->id; - vg_msg body; - vg_msg_init( &body, res->q, NETWORK_REQUEST_MAX ); - - const char *endpoint = vg_msg_getkvstr( &data, "endpoint" ); - - if( !endpoint ) - { - gameserver_request_respond( k_request_status_invalid_endpoint, res, NULL, msg ); - return; - } - - if( !strcmp( endpoint, "scoreboard" ) ) - { - const char *mod = vg_msg_getkvstr( &data, "mod" ); - const char *route = vg_msg_getkvstr( &data, "route" ); - u32 week; - vg_msg_getkvintg( &data, "week", k_vg_msg_u32, &week, NULL ); - - if( week == NETWORK_LEADERBOARD_CURRENT_WEEK ) - { - gameserver_cat_table( &body, mod, route, gameserver_get_current_week(), "rows_weekly" ); - } - else if( week == NETWORK_LEADERBOARD_ALLTIME_AND_CURRENT_WEEK ) - { - gameserver_cat_table( &body, mod, route, 0, "rows" ); - gameserver_cat_table( &body, mod, route, gameserver_get_current_week(), "rows_weekly" ); - } - else - gameserver_cat_table( &body, mod, route, week, "rows" ); - - if( body.error != k_vg_msg_error_OK ) - { - gameserver_request_respond( k_request_status_out_of_memory, res, NULL, msg ); - return; - } - - gameserver_request_respond( k_request_status_ok, res, &body, msg ); - } - else if( !strcmp( endpoint, "setlap" ) ) - { - if( client->steamid == k_steamid_max ) - { - gameserver_request_respond( k_request_status_unauthorized, res, NULL, msg ); - return; - } - - const char *mod = vg_msg_getkvstr( &data, "mod" ); - const char *route = vg_msg_getkvstr( &data, "route" ); - - char weekly_table[ DB_TABLE_UID_MAX ], - alltime_table[ DB_TABLE_UID_MAX ]; - - u32 week = gameserver_get_current_week(); - - if( !db_get_highscore_table_name( mod, route, 0, alltime_table ) || - !db_get_highscore_table_name( mod, route, week, weekly_table ) ) - { - gameserver_request_respond( k_request_status_out_of_memory, res, NULL, msg ); - return; - } - - i32 centiseconds; - vg_msg_getkvintg( &data, "time", k_vg_msg_i32, ¢iseconds, NULL ); - if( centiseconds < 5*100 ) - { - gameserver_request_respond( k_request_status_client_error, res, NULL, msg ); - return; - } - - db_writeusertime( alltime_table, client->steamid, centiseconds, 1 ); - db_writeusertime( weekly_table, client->steamid, centiseconds, 1 ); - gameserver_request_respond( k_request_status_ok, res, NULL, msg ); - } - else - { - gameserver_request_respond( k_request_status_invalid_endpoint, res, NULL, msg ); - } -} -#endif - static void gameserver_rx_300_400( SteamNetworkingMessage_t *msg ) { netmsg_blank *tmp = msg->m_pData; @@ -922,7 +743,6 @@ u64 seconds_to_server_ticks( f64 s ) int main( int argc, char *argv[] ) { _gameserver.thread = pthread_self(); - vg_log_init(); signal( SIGINT, inthandler ); diff --git a/src/gameserver_database.c b/src/gameserver_database.c index f7503cc..01acb5f 100644 --- a/src/gameserver_database.c +++ b/src/gameserver_database.c @@ -34,7 +34,7 @@ sqlite3_stmt *db_stmt( const char *code ) { THREAD_1; #ifdef DB_LOG_SQL_STATEMENTS - vg_low( code ); + vg_low( "%s\n", code ); #endif sqlite3_stmt *stmt; @@ -196,7 +196,7 @@ i32 db_readusertime( char table[DB_TABLE_UID_MAX], u64 steamid ) return 0; } -bool db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid, i32 score, bool only_if_faster ) +bool db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid, i32 score, bool only_if_faster, i32 last_minute ) { THREAD_1; @@ -207,17 +207,12 @@ bool db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid, i32 score, boo vg_strnull( &q, buf, 512 ); vg_strcat( &q, "CREATE TABLE IF NOT EXISTS \n \"" ); vg_strcat( &q, table ); - vg_strcat( &q, "\"\n (steamid BIGINT UNIQUE, time INT);" ); + vg_strcat( &q, "\"\n (steamid BIGINT UNIQUE, time INT, lastmin INT);" ); if( !vg_strgood(&q) ) return 0; - - vg_str str; sqlite3_stmt *create_table = db_stmt( q.buffer ); - if( create_table ) { - db_sqlite3_bind_sz( create_table, 1, table ); - int fc = sqlite3_step( create_table ); sqlite3_finalize( create_table ); if( fc != SQLITE_DONE ) @@ -226,6 +221,22 @@ bool db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid, i32 score, boo else return 0; + /* auto smash append lastmin column */ +#if 0 + vg_strnull( &q, buf, 512 ); + vg_strcat( &q, "ALTER TABLE \"" ); + vg_strcat( &q, table ); + vg_strcat( &q, "\" ADD COLUMN lastmin INT default 0;" ); + if( !vg_strgood(&q) ) + return 0; + sqlite3_stmt *s2 = db_stmt( q.buffer ); + if( s2 ) + { + sqlite3_step( s2 ); + sqlite3_finalize( s2 ); + } +#endif + if( only_if_faster ) { i32 current = db_readusertime( table, steamid ); @@ -238,7 +249,7 @@ bool db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid, i32 score, boo vg_strnull( &q, buf, 512 ); vg_strcat( &q, "REPLACE INTO \"" ); vg_strcat( &q, table ); - vg_strcat( &q, "\"(steamid,time)\n VALUES (?,?);" ); + vg_strcat( &q, "\"(steamid,time,lastmin)\n VALUES (?,?,?);" ); if( !vg_strgood(&q) ) return 0; @@ -248,6 +259,7 @@ bool db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid, i32 score, boo { sqlite3_bind_int64( stmt, 1, *((i64 *)&steamid) ); sqlite3_bind_int( stmt, 2, score ); + sqlite3_bind_int( stmt, 3, last_minute ); int fc = sqlite3_step( stmt ); sqlite3_finalize( stmt ); @@ -363,13 +375,7 @@ void db_action_set_username( u64 steamid, const char *username ) vg_async_task_dispatch( &_gs_db.tasks, task ); } - - - - -enum request_status gameserver_cat_table( - vg_msg *msg, - const char *mod, const char *route, u32 week, const char *alias ) +enum request_status gameserver_cat_table( vg_msg *msg, const char *mod, const char *route, u32 week, const char *alias ) { char table_name[ DB_TABLE_UID_MAX ]; if( !db_get_highscore_table_name( mod, route, week, table_name ) ) @@ -389,11 +395,14 @@ enum request_status gameserver_cat_table( return k_request_status_database_error; vg_msg_frame( msg, alias ); - for( u32 i=0; i<10; i ++ ){ + for( u32 i=0; i<10; i ++ ) + { int fc = sqlite3_step( stmt ); - if( fc == SQLITE_ROW ){ + if( fc == SQLITE_ROW ) + { i32 time = sqlite3_column_int( stmt, 1 ); + i32 last_minute = sqlite3_column_int( stmt, 2 ); i64 steamid_i64 = sqlite3_column_int64( stmt, 0 ); u64 steamid = *((u64 *)&steamid_i64); @@ -403,16 +412,19 @@ enum request_status gameserver_cat_table( vg_msg_frame( msg, "" ); vg_msg_wkvnum( msg, "time", k_vg_msg_u32, 1, &time ); vg_msg_wkvnum( msg, "steamid", k_vg_msg_u64, 1, &steamid ); + vg_msg_wkvnum( msg, "lastmin", k_vg_msg_u32, 1, &last_minute ); char username[32]; if( db_getuserinfo( steamid, username, sizeof(username), NULL ) ) vg_msg_wkvstr( msg, "username", username ); vg_msg_end_frame( msg ); } - else if( fc == SQLITE_DONE ){ + else if( fc == SQLITE_DONE ) + { break; } - else { + else + { log_sqlite3( fc ); break; } @@ -422,5 +434,3 @@ enum request_status gameserver_cat_table( vg_msg_end_frame( msg ); return k_request_status_ok; } - - diff --git a/src/gameserver_database.h b/src/gameserver_database.h index 0b1a703..091c6e5 100644 --- a/src/gameserver_database.h +++ b/src/gameserver_database.h @@ -30,6 +30,9 @@ struct _gs_db extern _gs_db; void db_action_set_username( u64 steamid, const char *username ); +bool db_get_highscore_table_name( const char *mod_uid, const char *run_uid, u32 week, char table_name[DB_TABLE_UID_MAX] ); +i32 db_readusertime( char table[DB_TABLE_UID_MAX], u64 steamid ); +bool db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid, i32 score, bool only_if_faster, i32 last_minute ); #if 0 /* diff --git a/src/gameserver_replay.c b/src/gameserver_replay.c index 2b1b2b8..222e16a 100644 --- a/src/gameserver_replay.c +++ b/src/gameserver_replay.c @@ -3,20 +3,16 @@ struct _gs_replay _gs_replay; -void _gs_replay_server_tick(void) +u32 _gs_replay_get_minute(void) { + return time(NULL) / 60; } -struct serialized_replay_header -{ - u32 total_frames, - head_offset; -}; - struct serialized_replay { - char path[ 1024 ]; - struct serialized_replay_header header; + u64 steamid; + u32 minute; + u32 buffer_size; u8 buffer[]; }; @@ -26,62 +22,197 @@ static void task_write_replay( vg_async_task *task ) THREAD_1; struct serialized_replay *info = (void *)task->data; - FILE *fp = fopen( info->path, "w" ); + vg_make_directory( "replaydata" ); + + char path[1024]; + snprintf( path, sizeof(path), "replaydata/%lx@%x", info->steamid, info->minute ); + + FILE *fp = fopen( path, "w" ); if( !fp ) { vg_error( "Failed to open '%s' for writing\n", fp ); return; } - fwrite( &info->header, sizeof(info->header), 1, fp ); fwrite( &info->buffer, info->buffer_size, 1, fp ); fclose( fp ); - vg_success( "Saved replay: %s\n", info->path ); + vg_success( "Saved replay: %s\n", path ); } -void _gs_write_replay_to_disk( u8 client_id, f64 server_duration, const char *path ) +void _gs_replay_run_save_requests( u32 client_id ) { - u64 min_ticks = _gameserver.ticks - seconds_to_server_ticks( server_duration ); - gs_replay *replay = &_gs_replay.replays[ client_id ]; - if( replay->ring_buffer.allocation_count == 0 ) + for( u32 j=0; jminute_flags[ minute_index ]; + if( flags & k_replay_minute_save_request ) + { + if( !(flags & k_replay_minute_saved) ) + { + /* do the saving */ + vg_info( "Saving replay minute %u#%u\n", client_id, minute ); + + if( !(flags & k_replay_minute_has_data) ) + vg_warn( "This minute doesn't have any data!\n" ); + + if( flags & k_replay_minute_data_deleting ) + vg_warn( "This minute is being recouped / deleted!\n" ); + + /* Rewind to first frame of minute */ + u32 frame_id = replay->ring_buffer.head_offset; + u32 buffer_size = 0, total_frames = 0, start_id = 0xffffffff, end_id = 0xffffffff; + while(1) + { + gs_replay_frame *frame = vg_queue_data( &replay->ring_buffer, frame_id ); + + if( frame->minute == minute ) + { + buffer_size += vg_queue_item_size( &replay->ring_buffer, frame_id ); + total_frames ++; + start_id = frame_id; + + if( end_id == 0xffffffff ) + end_id = frame_id; + } + else if( frame->minute < minute ) + break; + + if( !vg_queue_previous( &replay->ring_buffer, frame_id, &frame_id ) ) + break; + } + + if( (start_id == 0xffffffff) || (end_id == 0xffffffff) ) + { + vg_error( "Rewinding to minute start failed\n" ); + return; + } + + u32 task_size = sizeof( struct serialized_replay ) + buffer_size; + vg_async_task *task = vg_allocate_async_task( &_gs_db.tasks, task_size, 1 ); + task->handler = task_write_replay; + + struct serialized_replay *replay_inf = (void *)task->data; + replay_inf->steamid = _gameserver.clients[ client_id ].steamid; + replay_inf->minute = minute; + + u32 output_size = 0; + frame_id = start_id; + for( u32 i=0; iring_buffer, frame_id ); + + serialized_netmsg *snm = ((void *)replay_inf->buffer) + output_size; + snm->msg_size = frame->frame_size; + snm->unused0 = 0; + snm->unused1 = 0; + memcpy( snm->msg_data, &frame->network_frame, frame->frame_size ); + output_size += sizeof(serialized_netmsg) + frame->frame_size; + + if( !vg_queue_next( &replay->ring_buffer, frame_id, &frame_id ) ) + break; + } + + replay_inf->buffer_size = output_size; + vg_async_task_dispatch( &_gs_db.tasks, task ); + + flags |= (u8)k_replay_minute_saved; + } + + flags &= ~(u8)k_replay_minute_save_request; + } + replay->minute_flags[ minute_index ] = flags; } - - /* FIXME: Should we do this on another thread, or write to a buffer then send that for a save thread */ +} + +void _gs_replay_client_disconnect( u32 client_id ) +{ + _gs_replay_run_save_requests( client_id ); + + gs_replay *dest_replay = &_gs_replay.replays[ client_id ]; + for( u32 i=0; iminute_flags[i] = 0x00; - u32 frame_id = replay->ring_buffer.head_offset; - u32 buffer_size = 0, total_frames = 0; - while(1) + vg_queue_clear( &dest_replay->ring_buffer ); +} + +struct request_save_info +{ + u32 client_id; + u64 steam_id; + u32 start_minute, length; +}; + +void _gs_replay_request_save( u32 client_id, u64 steamid, u32 start_minute, u32 length, bool async ); +static void _gs_replay_async_request_save( vg_async_task *task ) +{ + struct request_save_info *info = (void *)task->data; + _gs_replay_request_save( info->client_id, info->steam_id, info->start_minute, info->length, 0 ); +} + +void _gs_replay_request_save( u32 client_id, u64 steamid, u32 start_minute, u32 length, bool async ) +{ + if( async ) { - gs_replay_frame *frame = vg_queue_data( &replay->ring_buffer, frame_id ); - buffer_size += vg_queue_item_size( &replay->ring_buffer, frame_id ); - total_frames ++; + THREAD_1; + /* C0: In the future, this is like just storing all the call parameters to a struct, and then recalling the + * function later. Really, we could auto-generate such patterns. + * + * void( u32 id ) + * { + * #c0_recall( thread0 ); + * + * .. from here on out we are thread0 + * } + */ + vg_async_task *task = vg_allocate_async_task( &_gameserver.tasks, sizeof(struct request_save_info), 1 ); + struct request_save_info *info = (void *)task->data; + info->client_id = client_id; + info->steam_id = steamid; + info->start_minute = start_minute; + info->length = length; + task->handler = _gs_replay_async_request_save; + vg_async_task_dispatch( &_gameserver.tasks, task ); + return; + } + + THREAD_0; - if( frame->tick_recorded < min_ticks ) - break; + if( _gameserver.clients[ client_id ].steamid != steamid ) + { + /* Probably very rare */ + vg_error( "Tried to request save for a client, but the steamid doesnt match from when it was called.\n" ); + return; + } - if( !vg_queue_previous( &replay->ring_buffer, frame_id, &frame_id ) ) - break; + gs_replay *replay = &_gs_replay.replays[ client_id ]; + for( u32 i=0; iminute_flags[ minute_index ] |= k_replay_minute_save_request; } +} - u32 task_size = sizeof( struct serialized_replay ) + buffer_size; - vg_async_task *task = vg_allocate_async_task( &_gs_db.tasks, task_size, 1 ); - struct serialized_replay *replay_inf = (void *)task->data; - vg_queue_memcpy( &replay->ring_buffer, replay_inf->buffer, frame_id, buffer_size ); - task->handler = task_write_replay; - replay_inf->buffer_size = buffer_size; - replay_inf->header.total_frames = total_frames; - replay_inf->header.head_offset = buffer_size - vg_queue_item_size( &replay->ring_buffer, replay->ring_buffer.head_offset ); - vg_strncpy( path, replay_inf->path, sizeof(replay_inf->path), k_strncpy_always_add_null ); +void _gs_replay_server_tick(void) +{ + u32 new_minute = _gs_replay_get_minute(); - vg_queue_item *first = (void*)replay_inf->buffer; - first->prev_size = 0; + if( new_minute > _gs_replay.current_minute ) + { + for( u32 i=0; iminute_flags[ new_minute % REPLAY_MINUTE_COUNT ] = 0x00; + } - vg_async_task_dispatch( &_gs_db.tasks, task ); + _gs_replay.current_minute = new_minute; + + vg_info( "The new minute is: %u\n", new_minute ); + } } void _gs_replay_save_frame( u8 client_id, netmsg_playerframe *frame, u32 frame_size ) @@ -110,6 +241,17 @@ void _gs_replay_save_frame( u8 client_id, netmsg_playerframe *frame, u32 frame_s if( free_frame->tick_recorded >= min_ticks ) break; + /* check if the minute this frame exists in is under a save request. */ + if( free_frame->minute > (_gs_replay.current_minute-REPLAY_MINUTE_COUNT) ) + { + u32 minute_index = free_frame->minute % REPLAY_MINUTE_COUNT; + u32 minute_flags = dest_replay->minute_flags[ minute_index ]; + if( minute_flags & k_replay_minute_save_request ) + break; + else + dest_replay->minute_flags[ minute_index ] |= k_replay_minute_data_deleting; + } + vg_queue_pop( ring ); } @@ -146,4 +288,15 @@ void _gs_replay_save_frame( u8 client_id, netmsg_playerframe *frame, u32 frame_s dest_frame->tick_recorded = _gameserver.ticks; dest_frame->frame_size = frame_size; dest_frame->_ = 0; + dest_frame->minute = _gs_replay.current_minute; + dest_replay->minute_flags[ _gs_replay.current_minute % REPLAY_MINUTE_COUNT ] |= k_replay_minute_has_data; +} + +#if 0 + +u64 _gs_write_replay_to_disk( u8 client_id, f64 server_duration ) +{ + THREAD_0; + } +#endif diff --git a/src/gameserver_replay.h b/src/gameserver_replay.h index 97b14e8..327c25c 100644 --- a/src/gameserver_replay.h +++ b/src/gameserver_replay.h @@ -2,6 +2,7 @@ #include "network_msg.h" #define REPLAY_SIZE_LIMIT 1024*1024*2 +#define REPLAY_MINUTE_COUNT 8 typedef struct gs_replay_frame gs_replay_frame; typedef struct gs_replay gs_replay; @@ -9,19 +10,31 @@ typedef struct gs_replay gs_replay; struct gs_replay_frame { u64 tick_recorded; + u32 minute; u16 frame_size; u16 _; struct netmsg_playerframe network_frame; }; +enum replay_minute_flag +{ + k_replay_minute_has_data = 0x1, + k_replay_minute_data_deleting= 0x2, + k_replay_minute_save_request = 0x4, + k_replay_minute_saved = 0x8 +}; + struct _gs_replay { struct gs_replay { vg_queue ring_buffer; + u8 minute_flags[ 8 ]; } replays[ NETWORK_MAX_PLAYERS ]; + u32 current_minute; + bool print_info; u64 print_ticker; } @@ -32,3 +45,7 @@ void _gs_replay_save_frame( u8 client_id, netmsg_playerframe *frame, u32 frame_s void _gs_write_replay_to_disk( u8 client_id, f64 server_duration, const char *path ); void _gs_test_replay( u8 client_id, const char *path ); void _gs_transfer_tick(void); + +u32 _gs_replay_get_minute(void); +void _gs_replay_client_disconnect( u32 client_id ); +void _gs_replay_request_save( u32 client_id, u64 steamid, u32 start_minute, u32 length, bool async ); diff --git a/src/gameserver_requests.c b/src/gameserver_requests.c index 19abfd5..93b1414 100644 --- a/src/gameserver_requests.c +++ b/src/gameserver_requests.c @@ -225,10 +225,10 @@ static void task_request_processing_complete( vg_async_task *task ) /* OK or client error */ if( req->client_request_id ) { - if( req->status < k_request_status_ok ) - req->state = k_request_state_server_error; - else + if( req->status == k_request_status_ok ) req->state = k_request_state_transfer_start; + else + req->state = k_request_state_server_error; } else { @@ -268,7 +268,9 @@ static void task_request_run( vg_async_task *task ) const char *route = vg_msg_getkvstr( &client_msg, "route" ); u32 week; vg_msg_getkvintg( &client_msg, "week", k_vg_msg_u32, &week, NULL ); + gameserver_cat_table( &body_msg, mod, route, 0, "rows" ); +#if 0 if( week == NETWORK_LEADERBOARD_CURRENT_WEEK ) { gameserver_cat_table( &body_msg, mod, route, gameserver_get_current_week(), "rows_weekly" ); @@ -280,44 +282,84 @@ static void task_request_run( vg_async_task *task ) } else gameserver_cat_table( &body_msg, mod, route, week, "rows" ); +#endif + } + else if( !strcmp( endpoint, "replay" ) ) + { + u64 steamid; + vg_msg_getkvintg( &client_msg, "steamid", k_vg_msg_u64, &steamid, NULL ); + if( steamid == 0 ) + steamid = req->user_steamid; + + u32 minute; + vg_msg_getkvintg( &client_msg, "minute", k_vg_msg_u32, &minute, NULL ); + if( minute == 0 ) + minute = _gs_replay_get_minute()-1; + + char path[1024]; + snprintf( path, sizeof(path), "replaydata/%lx@%x", steamid, minute ); + + FILE *fp = fopen( path, "rb" ); + if( !fp ) + req->status = k_request_status_not_found; + else + { + u64 l = fread( req->data_buffer, 1, GS_TRANSFER_MAX_SIZE, fp ); + if( feof( fp ) ) + req->data_buffer_send_size = l; + else + { + if( ferror( fp ) ) + req->status = k_request_status_database_error; + else + req->status = k_request_status_out_of_memory; + } + fclose( fp ); + } + goto E0; } -#if 0 else if( !strcmp( endpoint, "setlap" ) ) { - if( client->steamid == k_steamid_max ) + if( req->user_steamid == k_steamid_max ) { - gameserver_request_respond( k_request_status_unauthorized, res, NULL, msg ); - return; + req->status = k_request_status_unauthorized; + goto E0; } - const char *mod = vg_msg_getkvstr( &data, "mod" ); - const char *route = vg_msg_getkvstr( &data, "route" ); + const char *mod = vg_msg_getkvstr( &client_msg, "mod" ); + const char *route = vg_msg_getkvstr( &client_msg, "route" ); - char weekly_table[ DB_TABLE_UID_MAX ], - alltime_table[ DB_TABLE_UID_MAX ]; - - u32 week = gameserver_get_current_week(); + //char weekly_table[ DB_TABLE_UID_MAX ], + char table[ DB_TABLE_UID_MAX ]; - if( !db_get_highscore_table_name( mod, route, 0, alltime_table ) || - !db_get_highscore_table_name( mod, route, week, weekly_table ) ) + if( !db_get_highscore_table_name( mod, route, 0, table ) ) { - gameserver_request_respond( k_request_status_out_of_memory, res, NULL, msg ); - return; + req->status = k_request_status_out_of_memory; + goto E0; } i32 centiseconds; - vg_msg_getkvintg( &data, "time", k_vg_msg_i32, ¢iseconds, NULL ); + vg_msg_getkvintg( &client_msg, "time", k_vg_msg_i32, ¢iseconds, NULL ); if( centiseconds < 5*100 ) { - gameserver_request_respond( k_request_status_client_error, res, NULL, msg ); - return; + req->status = k_request_status_client_error; + goto E0; } - db_writeusertime( alltime_table, client->steamid, centiseconds, 1 ); - db_writeusertime( weekly_table, client->steamid, centiseconds, 1 ); - gameserver_request_respond( k_request_status_ok, res, NULL, msg ); + u32 last_minute = 0; + i32 current_time = db_readusertime( table, req->user_steamid ); + if( (current_time == 0) || (centiseconds < current_time) ) + { + u32 current_minute = _gs_replay_get_minute(), + minutes_span = (centiseconds+(200*60)) / (100*60); + _gs_replay_request_save( req->client_id, req->user_steamid, current_minute, minutes_span, 1 ); + + last_minute = current_minute; + } + + db_writeusertime( table, req->user_steamid, centiseconds, 1, last_minute ); + //db_writeusertime( weekly_table, req->user_steamid, centiseconds, 1, last_minute ); } -#endif else { req->status = k_request_status_invalid_endpoint; @@ -332,6 +374,7 @@ static void task_request_run( vg_async_task *task ) req->data_buffer_send_size = body_msg.cur.co; } +E0:; vg_async_task *return_task = vg_allocate_async_task( &_gameserver.tasks, sizeof(struct task_request_run_info), 1 ); memcpy( return_task->data, info, sizeof(struct task_request_run_info) ); return_task->handler = task_request_processing_complete; @@ -368,6 +411,7 @@ void _gs_handle_request_message( u32 client_id, SteamNetworkingMessage_t *msg ) req->state = k_request_state_none; req->message = msg; req->user_uid = client->session_uid; + req->user_steamid = client->steamid; req->client_id = client_id; req->client_request_id = client_packet->id; req->status = k_request_status_ok; diff --git a/src/gameserver_requests.h b/src/gameserver_requests.h index bf23f9a..24d4764 100644 --- a/src/gameserver_requests.h +++ b/src/gameserver_requests.h @@ -29,6 +29,7 @@ struct gs_request SteamNetworkingMessage_t *message; + u64 user_steamid; u64 user_uid; u32 client_id; u8 client_request_id; diff --git a/src/network.c b/src/network.c index bce62e8..4c3978d 100644 --- a/src/network.c +++ b/src/network.c @@ -298,11 +298,12 @@ static void on_server_connect_status( CallbackMsg_t *msg ) string_ESteamNetworkingConnectionState(info->m_eOldState), string_ESteamNetworkingConnectionState(info->m_info.m_eState) ); - if( info->m_hConn == network_client.remote ){ + if( info->m_hConn == network_client.remote ) + { network_client.state = info->m_info.m_eState; - if( info->m_info.m_eState == - k_ESteamNetworkingConnectionState_Connected ){ + if( info->m_info.m_eState == k_ESteamNetworkingConnectionState_Connected ) + { vg_success(" Connected to remote server.. authenticating\n"); /* send version info to server */ @@ -326,21 +327,21 @@ static void on_server_connect_status( CallbackMsg_t *msg ) hSteamNetworkingSockets, network_client.remote, auth, size, k_nSteamNetworkingSend_Reliable, NULL ); } - else if( info->m_info.m_eState == - k_ESteamNetworkingConnectionState_ClosedByPeer ){ - - if( info->m_info.m_eEndReason == - k_ESteamNetConnectionEnd_Misc_InternalError ){ + else if( info->m_info.m_eState == k_ESteamNetworkingConnectionState_ClosedByPeer ) + { + if( info->m_info.m_eEndReason == k_ESteamNetConnectionEnd_Misc_InternalError ) + { network_client.retries = 40; } network_disconnect(); } - else if( info->m_info.m_eState == - k_ESteamNetworkingConnectionState_ProblemDetectedLocally ){ + else if( info->m_info.m_eState == k_ESteamNetworkingConnectionState_ProblemDetectedLocally ) + { network_disconnect(); } } - else{ + else + { //vg_warn( " Recieved signal from unknown connection\n" ); } diff --git a/src/network_msg.h b/src/network_msg.h index c156a2d..29669c4 100644 --- a/src/network_msg.h +++ b/src/network_msg.h @@ -165,5 +165,15 @@ enum request_status { k_request_status_transfer_continue = 203 }; +typedef struct serialized_netmsg serialized_netmsg; +struct serialized_netmsg +{ + u16 msg_size; + u16 unused0; + u32 unused1; + + u8 msg_data[]; +}; + #pragma pack(pop) #endif /* NETWORK_MSG_H */ diff --git a/src/network_requests.c b/src/network_requests.c index 3338991..924a93b 100644 --- a/src/network_requests.c +++ b/src/network_requests.c @@ -163,6 +163,32 @@ void network_publish_laptime( const char *mod_uid, const char *route_uid, f64 la network_send_request( packet, &data, NULL, 0 ); } +#if 0 +static void replay_download_callback( void *data, u32 data_size, u64 userdata, enum request_status status ) +{ + if( status == k_request_status_ok ) + vg_info( "%u bytes downloaded OK\n", data_size ); + else + vg_warn( "Womp womp\n" ); +} + +void network_download_replay( u64 steamid, u32 minute ) +{ + if( !network_connected() ) + return; + + netmsg_request *packet = alloca( sizeof(netmsg_request) + 512 ); + packet->inetmsg_id = k_inetmsg_request; + + vg_msg data; + vg_msg_init( &data, packet->buffer, 512 ); + vg_msg_wkvstr( &data, "endpoint", "replay" ); + vg_msg_wkvnum( &data, "minute", k_vg_msg_u32, 1, &minute ); + vg_msg_wkvnum( &data, "steamid", k_vg_msg_u64, 1, &steamid ); + network_send_request( packet, &data, replay_download_callback, 0 ); +} +#endif + static void _delete_request( net_request *request ) { vg_pool_unwatch( &_net_requests.request_pool, vg_pool_id( &_net_requests.request_pool, request ) ); diff --git a/src/player.c b/src/player.c index e1789a3..2b18458 100644 --- a/src/player.c +++ b/src/player.c @@ -401,9 +401,11 @@ void net_sfx_exchange( bitpack_ctx *ctx, struct net_sfx *sfx ) void net_sfx_play( struct net_sfx *sfx ) { - if( sfx->system < k_player_subsystem_max ){ + if( sfx->system < k_player_subsystem_max ) + { struct player_subsystem_interface *sys = player_subsystems[sfx->system]; - if( sys->sfx_oneshot ){ + if( sys->sfx_oneshot ) + { sys->sfx_oneshot( sfx->id, sfx->location, sfx->volume ); } } diff --git a/src/player_remote.c b/src/player_remote.c index 12db20a..4e07839 100644 --- a/src/player_remote.c +++ b/src/player_remote.c @@ -172,6 +172,10 @@ void player_remote_rx_200_300( SteamNetworkingMessage_t *msg ) .buffer_len = datasize, .bytes = 0, }; + + /* camera */ + v2f _null_v2f = {0,0}; + bitpack_qv2f( &ctx, 8, 0.0f, VG_TAUf, _null_v2f ); /* animation * -------------------------------------------------------------*/ @@ -180,8 +184,7 @@ void player_remote_rx_200_300( SteamNetworkingMessage_t *msg ) dest->boundary_hash = frame->boundary_hash; struct network_player *player = &netplayers.list[ frame->client ]; - struct player_subsystem_interface *sys = - player_subsystems[ frame->subsystem ]; + struct player_subsystem_interface *sys = player_subsystems[ frame->subsystem ]; memset( &dest->data, 0, sys->animator_size ); if( sys->network_animator_exchange ) @@ -192,23 +195,24 @@ void player_remote_rx_200_300( SteamNetworkingMessage_t *msg ) /* sfx * -------------------------------------------------------------*/ - for( u32 i=0; isound_effects; i ++ ){ + for( u32 i=0; isound_effects; i ++ ) + { struct net_sfx sfx; net_sfx_exchange( &ctx, &sfx ); - f64 t = (frame->timestamp - NETWORK_FRAMERATE) + - (sfx.subframe*NETWORK_FRAMERATE); - + f64 t = (frame->timestamp - NETWORK_FRAMERATE) + (sfx.subframe*NETWORK_FRAMERATE); f32 remaining = t - ib->t; if( remaining <= 0.0f ) net_sfx_play( &sfx ); - else{ + else + { struct net_sfx *dst = NULL; - - for( u32 j=0; jsystem == k_player_subsystem_invalid ){ + if( sj->system == k_player_subsystem_invalid ) + { dst = sj; break; } @@ -226,8 +230,8 @@ void player_remote_rx_200_300( SteamNetworkingMessage_t *msg ) * -------------------------------------------------------------*/ memset( &dest->data_glider, 0, sizeof(struct remote_glider_animator) ); - if( dest->flags & (NETMSG_PLAYERFRAME_HAVE_GLIDER| - NETMSG_PLAYERFRAME_GLIDER_ORPHAN) ){ + if( dest->flags & (NETMSG_PLAYERFRAME_HAVE_GLIDER|NETMSG_PLAYERFRAME_GLIDER_ORPHAN) ) + { player_glide_remote_animator_exchange( &ctx, &dest->data_glider ); } @@ -1112,10 +1116,8 @@ void remote_players_init(void) { vg_console_reg_cmd( "network_info", cmd_network_info, NULL ); vg_console_reg_cmd( "add_test_players", remote_players_randomize, NULL ); - vg_console_reg_var( "k_show_own_name", &k_show_own_name, - k_var_dtype_i32, 0 ); - for( u32 i=0; ihead = NULL; } -static replay_frame *replay_newframe( replay_buffer *replay, - u16 animator_size, - u16 gamestate_size, - u16 sfx_count, - bool save_glider ){ +static replay_frame *replay_newframe( replay_buffer *replay, u16 animator_size, u16 gamestate_size, u16 sfx_count, + bool save_glider ) +{ u16 data_table[ k_replay_framedata_rows ][2]; data_table[ k_replay_framedata_animator ][1] = animator_size; data_table[ k_replay_framedata_gamestate ][1] = gamestate_size; @@ -143,7 +141,8 @@ check_again:; return frame; } -static void replay_emit_frame_sounds( replay_frame *frame ){ +static void replay_emit_frame_sounds( replay_frame *frame ) +{ void *baseptr = frame; u16 *inf = frame->data_table[k_replay_framedata_sfx]; struct net_sfx *buffer = baseptr + inf[0]; @@ -407,15 +406,14 @@ void skaterift_record_frame( replay_buffer *replay, int force_gamestate ) if( delta > k_replay_rate ) save_frame = 1; if( save_state ) save_frame = 1; - if( localplayer.have_glider || localplayer.glider_orphan || - localplayer.subsystem == k_player_subsystem_glide ){ + if( localplayer.have_glider || localplayer.glider_orphan || localplayer.subsystem == k_player_subsystem_glide ) save_glider = 1; - } if( !save_frame ) return; u16 gamestate_size = 0; - if( save_state ){ + if( save_state ) + { /* TODO: have as part of system struct */ gamestate_size = (u32 []){ [k_player_subsystem_walk ] = sizeof(struct player_walk_state), @@ -429,15 +427,14 @@ void skaterift_record_frame( replay_buffer *replay, int force_gamestate ) u16 animator_size = player_subsystems[localplayer.subsystem]->animator_size; - replay_frame *frame = replay_newframe( replay, - animator_size, gamestate_size, + replay_frame *frame = replay_newframe( replay, animator_size, gamestate_size, localplayer.local_sfx_buffer_count, save_glider ); frame->system = localplayer.subsystem; - if( save_state ){ - replay_gamestate *gs = - replay_frame_data( frame, k_replay_framedata_internal_gamestate ); + if( save_state ) + { + replay_gamestate *gs = replay_frame_data( frame, k_replay_framedata_internal_gamestate ); gs->current_run_version = _world.current_run_version; gs->drowned = localplayer.drowned; @@ -476,10 +473,9 @@ void skaterift_record_frame( replay_buffer *replay, int force_gamestate ) } } - if( save_glider ){ - struct replay_glider_data *inf = - replay_frame_data( frame, k_replay_framedata_glider ); - + if( save_glider ) + { + struct replay_glider_data *inf = replay_frame_data( frame, k_replay_framedata_glider ); inf->have_glider = localplayer.have_glider; inf->glider_orphan = localplayer.glider_orphan; inf->t = player_glide.t; @@ -615,6 +611,7 @@ static void skaterift_replay_resume(void) } } +static void _remote_replay_pre_update(void); void skaterift_replay_pre_update(void) { if( skaterift.activity != k_skaterift_replay ) @@ -718,6 +715,9 @@ void skaterift_replay_pre_update(void) void skaterift_open_replay(void) { + vg_error( "stub!!!\n" ); + return; + skaterift.activity = k_skaterift_replay; skaterift_record_frame( &player_replay.local, 1 ); if( player_replay.local.head ) @@ -732,9 +732,9 @@ void skaterift_open_replay(void) void skaterift_replay_init(void) { - u32 bytes = 1024*1024*10; - player_replay.local.data = vg_linear_alloc( vg_mem.rtmemory, bytes ); - player_replay.local.size = bytes; + u32 MB = 1024*1024; + player_replay.local.data = vg_linear_alloc( vg_mem.rtmemory, 6*MB ); + player_replay.local.size = 6*MB; replay_clear( &player_replay.local ); } diff --git a/src/player_replay.h b/src/player_replay.h index c1ebf99..d1f190d 100644 --- a/src/player_replay.h +++ b/src/player_replay.h @@ -78,6 +78,7 @@ struct replay_sfx { struct replay_globals { replay_buffer local; + replay_frame *resume_target; f64 resume_begin; f32 resume_transition; @@ -92,13 +93,11 @@ struct replay_globals struct gui_helper *helper_resume, *helper_freecam; vg_camera replay_freecam; - bool use_freecam; bool hide_ui; v3f freecam_v, freecam_w; i32 editor_mode; - replay_keyframe keyframes[32]; u32 keyframe_count; i32 active_keyframe; diff --git a/src/replay2.c b/src/replay2.c new file mode 100644 index 0000000..be53cc8 --- /dev/null +++ b/src/replay2.c @@ -0,0 +1,433 @@ +#include "replay2.h" + +struct _remote_replay _remote_replay; +struct _replay_player _replay_player; + +void _replay2_init(void) +{ + u32 MB = 1024*1024, + size = 4*MB; + + _remote_replay.replay.buffer.buffer = vg_linear_alloc( vg_mem.rtmemory, size ); + _remote_replay.replay.buffer.size = size; +} + +void _replay2_open_player( replay2 *replay, bool end ) +{ + if( replay == NULL ) + { + vg_error( "Cannot open a NULL replay\n" ); + return; + } + if( replay->buffer.allocation_count < 2 ) + { + vg_error( "Not enough frames in that replay\n" ); + return; + } + + skaterift.activity = k_skaterift_replay; + _replay_player.replay = replay; + + if( end ) + _replay_player.cursor_frame_offset = replay->buffer.head_offset; + else + _replay_player.cursor_frame_offset = replay->buffer.tail_offset; + + replay2_frame *frame = vg_queue_data( &replay->buffer, _replay_player.cursor_frame_offset ); + _replay_player.cursor = frame->time; +} + +/* remote replay downloader + * ------------------------------------------------------------------ */ + +static void replay_download_callback( void *data, u32 data_size, u64 userdata, enum request_status status ) +{ + bool is_from_network = userdata; + replay2 *replay = &_remote_replay.replay; + + struct remote_replay_chunk *chunk = &_remote_replay.chunks[ _remote_replay.chunks_downloaded ]; + if( status == k_request_status_ok ) + { + if( is_from_network ) + { + char path[1024]; + snprintf( path, sizeof(path), "replaydata/%lx@%x", _remote_replay.steamid, chunk->minute ); + + FILE *fp = fopen( path, "wb" ); + if( fp ) + { + fwrite( data, data_size, 1, fp ); + fclose( fp ); + vg_low( "Cached %s to disk\n", path ); + } + else + { + vg_error( "Can't open '%s' for writing\n", path ); + } + } + + vg_info( "Processing %u bytes of network frames\n", data_size ); + + u32 offset = 0; + while(1) + { + serialized_netmsg *snm = data + offset; + if( snm->msg_size < sizeof(netmsg_blank) ) + { + vg_error( "Message size too small\n" ); + goto E0; + } + + if( offset + sizeof(serialized_netmsg) + snm->msg_size > data_size ) + { + vg_error( "Message size too big (doesn't fit buffer)\n" ); + goto E0; + } + + netmsg_blank *tmp = (void *)snm->msg_data; + if( tmp->inetmsg_id == k_inetmsg_playerframe ) + { + netmsg_playerframe *playerframe = (void *)snm->msg_data; + + if( playerframe->timestamp <= _remote_replay.min_frame_t ) + { + vg_warn( "Dropping packet with lesser timestamp (%f). (FIXME)\n", playerframe->timestamp ); + goto E1; + } + _remote_replay.min_frame_t = playerframe->timestamp; + + replay2_frame *dst_frame = vg_queue_alloc( &replay->buffer, sizeof(replay2_frame) + snm->msg_size, NULL ); + if( !dst_frame ) + { + vg_error( "Out of mem adding frame!\n" ); + goto E0; + } + dst_frame->net_frame_size = snm->msg_size; + dst_frame->time = playerframe->timestamp; + memcpy( &dst_frame->net_frame, playerframe, snm->msg_size ); + } + +E1: offset += sizeof(serialized_netmsg) + snm->msg_size; + if( offset == data_size ) + break; + } + + chunk->state = k_chunk_state_processed; + _remote_replay.chunks_downloaded ++; + + if( _remote_replay.chunks_downloaded == _remote_replay.total_chunks ) + { + _remote_replay.state = k_remote_replay_state_ready; + vg_success( "Winner! (%u total frames)\n", replay->buffer.allocation_count ); + + _replay2_open_player( replay, 0 ); + } + + return; + } + +E0:chunk->state = k_chunk_state_broken; + _remote_replay.state = k_remote_replay_state_failed; +} + +struct async_cache_check_result +{ + bool found_in_cache; + bool broken; + u32 data_length; + u8 data[]; +}; + +static void async_cache_check_result( void *payload, u32 size ) +{ + struct async_cache_check_result *result = payload; + struct remote_replay_chunk *chunk = &_remote_replay.chunks[ _remote_replay.chunks_downloaded ]; + + if( result->found_in_cache ) + { + if( result->broken ) + replay_download_callback( NULL, 0, 0, k_request_status_not_found ); + else + replay_download_callback( result->data, result->data_length, 0, k_request_status_ok ); + } + else + { + if( !network_connected() ) + return; + + chunk->state = k_chunk_state_downloading; + vg_make_directory( "replaydata" ); + + netmsg_request *packet = alloca( sizeof(netmsg_request) + 512 ); + packet->inetmsg_id = k_inetmsg_request; + + vg_msg data; + vg_msg_init( &data, packet->buffer, 512 ); + vg_msg_wkvstr( &data, "endpoint", "replay" ); + vg_msg_wkvnum( &data, "minute", k_vg_msg_u32, 1, &chunk->minute ); + vg_msg_wkvnum( &data, "steamid", k_vg_msg_u64, 1, &_remote_replay.steamid ); + network_send_request( packet, &data, replay_download_callback, 1 ); + } +} + +static void _remote_replay_cache_check( void *_ ) +{ + struct remote_replay_chunk *chunk = &_remote_replay.chunks[ _remote_replay.chunks_downloaded ]; + + char path[1024]; + snprintf( path, sizeof(path), "replaydata/%lx@%x", _remote_replay.steamid, chunk->minute ); + + vg_async_item *item = vg_async_alloc( sizeof(struct async_cache_check_result) + 20*1024*1024 ); + struct async_cache_check_result *result = item->payload; + result->broken = 0; + + FILE *fp = fopen( path, "rb" ); + if( fp ) + { + result->found_in_cache = 1; + result->data_length = fread( result->data, 1, 20*1024*1024, fp ); + + if( !feof( fp ) ) + result->broken = 1; + + fclose( fp ); + } + else + { + result->found_in_cache = 0; + result->data_length = 0; + } + + vg_async_dispatch( item, async_cache_check_result ); +} + +static void _remote_replay_pre_update(void) +{ + if( _remote_replay.state == k_remote_replay_state_none ) + return; + + if( _remote_replay.state == k_remote_replay_state_ready ) + return; + + if( _remote_replay.state == k_remote_replay_state_failed ) + return; + + struct remote_replay_chunk *chunk = &_remote_replay.chunks[ _remote_replay.chunks_downloaded ]; + if( chunk->state == k_chunk_state_none ) + { + if( vg_loader_availible() ) + { + chunk->state = k_chunk_state_cache_check; + vg_loader_start( _remote_replay_cache_check, NULL ); + } + } +} + +void _replay2_pre_update(void) +{ + _remote_replay_pre_update(); +} + +void _replay2_seek( f64 t, bool play_sounds ) +{ + replay2 *replay = _replay_player.replay; + VG_ASSERT( replay ); + + replay2_frame *frame_start = vg_queue_data( &replay->buffer, replay->buffer.tail_offset ), + *frame_end = vg_queue_data( &replay->buffer, replay->buffer.head_offset ); + + if( t < frame_start->time ) t = frame_start->time; + if( t > frame_end->time ) t = frame_end->time; + + f64 dir = t - _replay_player.cursor; + if( dir == 0.0 ) + return; + dir = vg_signf( dir ); + + u32 current_offset = _replay_player.cursor_frame_offset; + replay2_frame *current_frame = vg_queue_data( &replay->buffer, current_offset ); + for( u32 i=0; i<4096; i ++ ) + { + if( dir < 0.0 ) + { + if( t > current_frame->time ) + { + break; + } + } + + bool next = 0; + if( dir > 0.0 ) + next = vg_queue_next( &replay->buffer, current_offset, ¤t_offset ); + else + next = vg_queue_previous( &replay->buffer, current_offset, ¤t_offset ); + + if( !next ) + break; + + current_frame = vg_queue_data( &replay->buffer, current_offset ); + if( dir > 0.0 ) + { + if( t < current_frame->time ) + { + break; + } + } + + if( play_sounds ) + { + //replay_emit_frame_sounds( next ); + } + } + + _replay_player.cursor_frame_offset = current_offset; + _replay_player.cursor = current_frame->time; +} + +void _replay2_imgui( ui_context *ctx ) +{ + if( skaterift.activity != k_skaterift_replay ) + return; + + if( _replay_player.hide_ui ) + return; + + if( vg_input.display_input_method != k_input_method_controller ) + { + ui_capture_mouse( ctx, 1 ); + } + + replay2 *replay = _replay_player.replay; + VG_ASSERT( replay ); + + f64 start = _replay_player.cursor, + end = start; + replay2_frame *frame_start = vg_queue_data( &replay->buffer, replay->buffer.tail_offset ), + *frame_end = vg_queue_data( &replay->buffer, replay->buffer.head_offset ); + start = frame_start->time; + end = frame_end->time; + f64 len = end - start, + cur = (_replay_player.cursor - start) / len; + + /* mainbar */ + ui_rect timeline = { 8, vg.window_y-(32+8), vg.window_x-16, 32 }; + ui_rect start_box; + ui_split( timeline, k_ui_axis_v, 32, 8, start_box, timeline ); + + const char *start_text = (_replay_player.replay_control == k_replay_control_play)? "||": ">"; + if( menu_button_rect( ctx, start_box, 0, 1, start_text ) ) + { + _replay_player.replay_control ^= k_replay_control_play; + } + + ui_fill( ctx, timeline, ui_colour( ctx, k_ui_bg ) ); + + /* cursor frame block */ + replay2_frame *frame_cursor = vg_queue_data( &replay->buffer, _replay_player.cursor_frame_offset ); + u32 next_frame_offset; + if( vg_queue_next( &replay->buffer, _replay_player.cursor_frame_offset, &next_frame_offset ) ) + { + replay2_frame *next_frame = vg_queue_data( &replay->buffer, next_frame_offset ); + f64 l = (next_frame->time - frame_cursor->time )/len, + s = (frame_cursor->time - start) / len; + ui_rect box = { timeline[0] + s*(f64)timeline[2], timeline[1], + VG_MAX(4,(ui_px)(l*timeline[2])), timeline[3]+2 }; + ui_fill( ctx, box, ui_colour( ctx, k_ui_bg+4 ) ); + } + + /* cursor */ + ui_rect cusor = { timeline[0] + cur*(f64)timeline[2] - 1, timeline[1], 2, timeline[3] }; + ui_fill( ctx, cusor, ui_colour( ctx, k_ui_bg+7 ) ); + + /* latest state marker */ +#if 0 + if( skaterift.allow_replay_resume ) + { + if( replay->statehead ) + { + f64 t = (replay->statehead->time - start) / len; + ui_rect tag = { timeline[0] + t*(f64)timeline[2], timeline[1], + 2, timeline[3]+8 }; + ui_fill( ctx, tag, ui_colour( ctx, k_ui_green+k_ui_brighter ) ); + } + + /* previous state marker */ + replay_frame *prev = replay_find_recent_stateframe( replay ); + if( prev ) + { + f64 t = (prev->time - start) / len; + ui_rect tag = { timeline[0] + t*(f64)timeline[2], timeline[1], + 2, timeline[3]+8 }; + ui_fill( ctx, tag, ui_colour( ctx, k_ui_yellow+k_ui_brighter ) ); + } + } +#endif + + char buffer[ 128 ]; + snprintf( buffer, 128, "-%.2fs", (end-_replay_player.cursor) ); + ui_text( ctx, timeline, buffer, 1, k_ui_align_middle_left, 0 ); + ui_text( ctx, timeline, "0s", 1, k_ui_align_middle_right, 0 ); + + /* helpers */ + ctx->font = &vgf_default_large; + + ui_rect helper_list_l = { 10, timeline[1] - (ctx->font->sy+8), vg.window_x/2, ctx->font->sy }; + char buf[256]; + vg_str str; + vg_strnull( &str, buf, sizeof(buf) ); + vg_input_string( &str, input_axis_list[k_sraxis_replay_h], 1 ); + vg_strcat( &str, "\x07 Scrub" ); + ui_text( ctx, helper_list_l, buf, 1, k_ui_align_left, 0 ); + helper_list_l[1] -= helper_list_l[3]+2; + + vg_strnull( &str, buf, sizeof(buf) ); + vg_input_string( &str, input_button_list[k_srbind_replay_play], 1 ); + vg_strcat( &str, (_replay_player.replay_control == k_replay_control_play)? "\x07 Pause": "\x07 Play" ); + ui_text( ctx, helper_list_l, buf, 1, k_ui_align_left, 0 ); + helper_list_l[1] -= helper_list_l[3]+2; + + vg_strnull( &str, buf, sizeof(buf) ); + vg_input_string( &str, input_button_list[k_srbind_replay_freecam], 1 ); + vg_strcat( &str, "\x07 Freecam" ); + ui_text( ctx, helper_list_l, buf, 1, k_ui_align_left, 0 ); + helper_list_l[1] -= helper_list_l[3]+2; + + vg_strnull( &str, buf, sizeof(buf) ); + vg_input_string( &str, input_button_list[k_srbind_replay_hide_ui], 1 ); + vg_strcat( &str, "\x07 Hide UI" ); + ui_text( ctx, helper_list_l, buf, 1, k_ui_align_left, 0 ); + helper_list_l[1] -= helper_list_l[3]+2; + + ui_rect helper_list_r = { vg.window_x/2, timeline[1] - (ctx->font->sy+8), vg.window_x/2-10, ctx->font->sy }; + vg_strnull( &str, buf, sizeof(buf) ); + vg_input_string( &str, input_button_list[k_srbind_mback], 1 ); + vg_strcat( &str, "\x07 Exit Replay" ); + ui_text( ctx, helper_list_r, buf, 1, k_ui_align_right, 0 ); + helper_list_l[1] -= helper_list_r[3]+2; + + if( _replay_player.use_freecam ) + { + ui_rect box = { vg.window_x/2 - 200, 40, 400, ctx->font->sy }; + ui_text( ctx, box, KYEL "\x06\x02--- Freecam Enabled ---", 1, k_ui_align_center, 0 ); + } + + ctx->font = &vgf_default_small; + + /* timeline scrub */ + bool start_in_timeline = + ui_clicking(ctx, UI_MOUSE_LEFT) && + ui_inside_rect(timeline, ctx->mouse_click); + + if( (ui_inside_rect( timeline, ctx->mouse )) || start_in_timeline ) + { + ui_rect cursor = { ctx->mouse[0], timeline[1], 4, timeline[3] }; + ui_fill( ctx, cursor, ui_colour( ctx, k_ui_fg ) ); + ctx->cursor = k_ui_cursor_ibeam; + + if( ui_clicking( ctx, UI_MOUSE_LEFT ) && start_in_timeline ) + { + f64 mouse_t = start + ((f64)(ctx->mouse[0]-timeline[0]) / (f64)timeline[2])*len; + _replay2_seek( mouse_t, 1 ); + } + } +} diff --git a/src/replay2.h b/src/replay2.h new file mode 100644 index 0000000..fe4c637 --- /dev/null +++ b/src/replay2.h @@ -0,0 +1,81 @@ +#pragma once +#include "vg/vg_mem_queue.h" + +typedef struct replay2 replay2; +struct replay2 +{ + enum replay_type + { + k_replay_type_local, + k_replay_type_network + } + type; + + vg_queue buffer; +}; + +typedef struct replay2_frame replay2_frame; +struct replay2_frame +{ + f64 time; // duplicated from net_frame + u32 net_frame_size; + netmsg_playerframe net_frame; +}; + +struct _remote_replay +{ + u32 total_chunks, + chunks_downloaded; + + u64 steamid; + struct remote_replay_chunk + { + u32 minute; + enum chunk_state + { + k_chunk_state_none, + k_chunk_state_cache_check, + k_chunk_state_downloading, + k_chunk_state_processed, + k_chunk_state_broken + } + state; + } + chunks[8]; + f64 min_frame_t; + + enum remote_replay_state + { + k_remote_replay_state_none, + k_remote_replay_state_downloading, + k_remote_replay_state_ready, + k_remote_replay_state_failed + } + state; + + replay2 replay; +} +extern _remote_replay; + +struct _replay_player +{ + replay2 *replay; + + /* TODO: Modifiers / keyframes lane */ + + u32 cursor_frame_offset; + f64 cursor; + enum replay_control replay_control; + f32 track_velocity; + + vg_camera replay_freecam; + bool use_freecam; + bool hide_ui; + v3f freecam_v, freecam_w; +} +extern _replay_player; + +void _replay2_init(void); +void _replay2_pre_update(void); +void _replay2_imgui( ui_context *ctx ); +void _replay2_open_player( replay2 *replay, bool end ); diff --git a/src/serialized_replay.h b/src/serialized_replay.h new file mode 100644 index 0000000..c5e76c6 --- /dev/null +++ b/src/serialized_replay.h @@ -0,0 +1,2 @@ +#pragma once +#include "network_msg.h" diff --git a/src/skaterift.c b/src/skaterift.c index 0c6b89d..e7762ee 100644 --- a/src/skaterift.c +++ b/src/skaterift.c @@ -54,6 +54,7 @@ #include "ent_script.h" #include "board_maker.h" #include "compass.h" +#include "replay2.h" struct skaterift_globals skaterift = { @@ -106,6 +107,7 @@ void game_load(void) vg_loader_step( world_map_init, NULL ); vg_loader_step( ent_tornado_init, NULL ); vg_loader_step( skaterift_replay_init, NULL ); + vg_loader_step( _replay2_init, NULL ); vg_loader_step( skaterift_load_player_content, NULL ); vg_loader_set_user_information( "Compiling shaders" ); @@ -199,6 +201,7 @@ void vg_pre_update(void) } skaterift_replay_pre_update(); + _replay2_pre_update(); remote_sfx_pre_update(); v3f listen_co; @@ -380,10 +383,10 @@ static void render_main_game(void) { player__animate_from_replay( &player_replay.local ); } - else{ + else + { player__animate(); - skaterift_record_frame( &player_replay.local, - localplayer.deferred_frame_record ); + skaterift_record_frame( &player_replay.local, localplayer.deferred_frame_record ); localplayer.deferred_frame_record = 0; } @@ -533,7 +536,8 @@ void vg_gui( ui_context *ctx ) world_instance *world = &_world.main; world_routes_imgui( ctx, world ); - skaterift_replay_imgui( ctx ); + //skaterift_replay_imgui( ctx ); + _replay2_imgui( ctx ); workshop_form_gui( ctx ); world_gui( ctx, world ); @@ -615,6 +619,7 @@ void vg_framebuffer_resize( int w, int h ) #include "skaterift_script.c" #include "board_maker.c" #include "compass.c" +#include "replay2.c" //TODO //#include "vg/submodules/hashmap.c/hashmap.c" diff --git a/src/world_render.c b/src/world_render.c index 0eb8101..46accf1 100644 --- a/src/world_render.c +++ b/src/world_render.c @@ -1423,7 +1423,7 @@ void render_world_cubemaps( world_instance *world ) void render_world_portal_cubemaps(void) { - if( vg_loader_availible() ) + if( vg_loader_availible() ) // HACK? { qoi_desc desc = { diff --git a/src/world_routes.h b/src/world_routes.h index ae1ade0..09e183b 100644 --- a/src/world_routes.h +++ b/src/world_routes.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved + * Copyright (C) 2021-2025 Mt.ZERO Software, Harry Godden - All Rights Reserved */ #pragma once @@ -9,10 +9,8 @@ #include "network_msg.h" void world_routes_init(void); -void world_routes_fracture( world_instance *world, ent_gate *gate, - v3f imp_co, v3f imp_v ); -void world_routes_activate_entry_gate( world_instance *world, - ent_gate *rg ); +void world_routes_fracture( world_instance *world, ent_gate *gate, v3f imp_co, v3f imp_v ); +void world_routes_activate_entry_gate( world_instance *world, ent_gate *rg ); void render_world_routes( world_instance *world, world_instance *host_world, m4x3f mmdl, vg_camera *cam, diff --git a/src/world_sfd.c b/src/world_sfd.c index d41037f..ecc3418 100644 --- a/src/world_sfd.c +++ b/src/world_sfd.c @@ -57,6 +57,9 @@ void sfd_encode( v2i co, const char *str, enum world_sfd_align align ) i32 row_h = world_sfd.h -1 -co[1]; i32 offset_x = 0; + if( row_h < 0 ) + return; + i32 w = VG_MIN( strlen(str), world_sfd.w ); if( align == k_world_sfd_center ) offset_x = (world_sfd.w - w) / 2; @@ -65,7 +68,8 @@ void sfd_encode( v2i co, const char *str, enum world_sfd_align align ) else offset_x = co[0]; - for( i32 i=0; ient_route ) ) + { + board = &world->leaderboard_cache[ world_sfd.active_route_board ]; + ent_route *route = af_arritm( &world->ent_route, world_sfd.active_route_board ); + title = af_str( &world->meta.af, route->pstr_name ); + } + for( u32 i=0; i<13; i++ ) sfd_clear(i); sfd_encode( (v2i){0,0}, title, k_world_sfd_left ); - if( !board ){ + if( !board ) + { sfd_encode( (v2i){-1,4}, "Error out of range", k_world_sfd_center ); return; } - if( !network_connected() ){ + if( !network_connected() ) + { sfd_encode( (v2i){-1,0}, "Offline", k_world_sfd_right ); return; } - if( board->status == k_request_status_not_found ){ + if( board->status == k_request_status_not_found ) + { sfd_encode( (v2i){-1,4}, "No records", k_world_sfd_center ); return; } - if( board->status != k_request_status_ok ){ + if( board->status != k_request_status_ok ) + { char buf[32]; vg_str s; vg_strnull( &s, buf, 32 ); @@ -116,17 +136,21 @@ void world_sfd_compile_scores( struct leaderboard_cache *board, const char *titl const char *alias = "rows"; +#if 0 if( world_sfd.view_weekly ){ alias = "rows_weekly"; sfd_encode( (v2i){-1,0}, "Weekly", k_world_sfd_right ); } else { - sfd_encode( (v2i){-1,0}, "All-Time", k_world_sfd_right ); } +#endif + sfd_encode( (v2i){-1,0}, "All-Time", k_world_sfd_right ); u32 l = 1; - if( vg_msg_seekframe( &body, alias ) ){ - while( vg_msg_seekframe( &body, NULL ) ){ + if( vg_msg_seekframe( &body, alias ) ) + { + while( vg_msg_seekframe( &body, NULL ) ) + { /* name */ const char *username = vg_msg_getkvstr( &body, "username" ); @@ -147,7 +171,7 @@ void world_sfd_compile_scores( struct leaderboard_cache *board, const char *titl vg_strnull( &str, buf, 100 ); u32 centiseconds; - vg_msg_getkvintg( &body, "time", k_vg_msg_i32, ¢iseconds, NULL ); + vg_msg_getkvintg( &body, "time", k_vg_msg_u32, ¢iseconds, NULL ); i32 seconds = centiseconds / 100, minutes = seconds / 60; @@ -167,39 +191,26 @@ void world_sfd_compile_scores( struct leaderboard_cache *board, const char *titl vg_msg_skip_frame( &body ); } } - else { + else + { sfd_encode( (v2i){-1,4}, "No records", k_world_sfd_center ); } } -void world_sfd_compile_active_scores(void) -{ - world_instance *world = &_world.main; - - struct leaderboard_cache *board = NULL; - const char *name = "Out of range"; - - if( world_sfd.active_route_board < af_arrcount( &world->ent_route ) ){ - board = &world->leaderboard_cache[ world_sfd.active_route_board ]; - ent_route *route = af_arritm( &world->ent_route, - world_sfd.active_route_board ); - name = af_str( &world->meta.af, route->pstr_name ); - } - - world_sfd_compile_scores( board, name ); -} - void world_sfd_update( world_instance *world, v3f pos ) { - if( af_arrcount( &world->ent_route ) ){ + if( af_arrcount( &world->ent_route ) ) + { u32 closest = 0; float min_dist = INFINITY; - for( u32 i=0; ient_route ); i++ ){ + for( u32 i=0; ient_route ); i++ ) + { ent_route *route = af_arritm( &world->ent_route, i ); float dist = v3_dist2( route->board_transform[3], pos ); - if( dist < min_dist ){ + if( dist < min_dist ) + { min_dist = dist; closest = i; } @@ -228,14 +239,14 @@ void world_sfd_update( world_instance *world, v3f pos ) } /* compile board text if we changed. */ - if( world_sfd.active_route_board != closest ){ + if( world_sfd.active_route_board != closest ) world_sfd_compile_active_scores(); - } world_sfd.active_route_board = closest; } - for( int i=0; i