master snapshot blending

This commit is contained in:
jacob 2025-02-28 15:27:02 -06:00
parent 7e7e6a8f87
commit d0bfbfeb2d
7 changed files with 297 additions and 276 deletions

View File

@ -28,12 +28,22 @@
#define IMAGE_PIXELS_PER_UNIT 256.0 #define IMAGE_PIXELS_PER_UNIT 256.0
/* 64^2 = 4096 bins */ /* How many ticks back in time should the user blend between?
* <Delay> = <USER_INTERP_RATIO> * <Tick interval>
* E.g: At 1.5, the user thread will render 75ms back in time if the sim runs at 50tps
*/
#define USER_INTERP_RATIO 1.5
#define USER_INTERP_ENABLED 1
/* 64^2 = 4096 bins */
#define SPACE_CELL_BINS_SQRT (64) #define SPACE_CELL_BINS_SQRT (64)
#define SPACE_CELL_SIZE 1.0f #define SPACE_CELL_SIZE 1.0f
#define SIM_TICKS_PER_SECOND 50 #define SIM_TICKS_PER_SECOND 50
#define SIM_TIMESCALE 1 #define SIM_TIMESCALE 1
/* Like USER_INTERP_RATIO, but applies to snapshots received by the local sim from the
* master sim (how far back in time should the client render the server's state) */
#define SIM_CLIENT_INTERP_RATIO 2.0
#define SIM_PHYSICS_SUBSTEPS 4 #define SIM_PHYSICS_SUBSTEPS 4
#define SIM_PHYSICS_ENABLE_WARM_STARTING 1 #define SIM_PHYSICS_ENABLE_WARM_STARTING 1
@ -47,13 +57,6 @@
#define SIM_MAX_LINEAR_VELOCITY 500 #define SIM_MAX_LINEAR_VELOCITY 500
#define SIM_MAX_ANGULAR_VELOCITY (TAU * 20) #define SIM_MAX_ANGULAR_VELOCITY (TAU * 20)
/* How many ticks back in time should the user blend between?
* <Delay ms> = <USER_INTERP_RATIO> * <Tick interval>
* E.g: At 1.5, the user thread will render 75ms back in time if the sim runs at 50tps
*/
#define USER_INTERP_RATIO 1.5
#define USER_INTERP_ENABLED 1
#define COLLIDER_DEBUG 0 #define COLLIDER_DEBUG 0
#define COLLIDER_DEBUG_DETAILED 0 #define COLLIDER_DEBUG_DETAILED 0
#define COLLIDER_DEBUG_DETAILED_DRAW_MENKOWSKI 0 #define COLLIDER_DEBUG_DETAILED_DRAW_MENKOWSKI 0

View File

@ -631,7 +631,7 @@ i64 host_get_channel_last_rtt_ns(struct host *host, struct host_channel_id chann
* Update * Update
* ========================== */ * ========================== */
INTERNAL struct host_event *alloc_event(struct arena *arena, struct host_event_list *list) INTERNAL struct host_event *push_event(struct arena *arena, struct host_event_list *list)
{ {
struct host_event *event = arena_push_zero(arena, struct host_event); struct host_event *event = arena_push_zero(arena, struct host_event);
if (list->last) { if (list->last) {
@ -721,7 +721,7 @@ struct host_event_list host_update_begin(struct arena *arena, struct host *host)
/* We successfully connected to a foreign host and they are ready to receive messages */ /* We successfully connected to a foreign host and they are ready to receive messages */
if (channel->valid && !channel->connected) { if (channel->valid && !channel->connected) {
logf_info("Host received connection from %F", FMT_STR(sock_string_from_address(scratch.arena, address))); logf_info("Host received connection from %F", FMT_STR(sock_string_from_address(scratch.arena, address)));
struct host_event *event = alloc_event(arena, &events); struct host_event *event = push_event(arena, &events);
event->kind = HOST_EVENT_KIND_CHANNEL_OPENED; event->kind = HOST_EVENT_KIND_CHANNEL_OPENED;
event->channel_id = channel->id; event->channel_id = channel->id;
channel->connected = true; channel->connected = true;
@ -733,7 +733,7 @@ struct host_event_list host_update_begin(struct arena *arena, struct host *host)
/* A foreign host disconnected from us */ /* A foreign host disconnected from us */
if (channel->valid) { if (channel->valid) {
logf_info("Host received disconnection from %F", FMT_STR(sock_string_from_address(scratch.arena, address))); logf_info("Host received disconnection from %F", FMT_STR(sock_string_from_address(scratch.arena, address)));
struct host_event *event = alloc_event(arena, &events); struct host_event *event = push_event(arena, &events);
event->kind = HOST_EVENT_KIND_CHANNEL_CLOSED; event->kind = HOST_EVENT_KIND_CHANNEL_CLOSED;
event->channel_id = channel->id; event->channel_id = channel->id;
host_channel_release(channel); host_channel_release(channel);
@ -789,7 +789,7 @@ struct host_event_list host_update_begin(struct arena *arena, struct host *host)
if (ma->num_chunks_received == chunk_count) { if (ma->num_chunks_received == chunk_count) {
/* All chunks filled, message has finished assembling */ /* All chunks filled, message has finished assembling */
/* TODO: Message ordering */ /* TODO: Message ordering */
struct host_event *event = alloc_event(arena, &events); struct host_event *event = push_event(arena, &events);
struct string data = ZI; struct string data = ZI;
data.len = ((chunk_count - 1) * PACKET_MSG_CHUNK_MAX_LEN) + ma->last_chunk_len; data.len = ((chunk_count - 1) * PACKET_MSG_CHUNK_MAX_LEN) + ma->last_chunk_len;
data.text = arena_push_array(arena, u8, data.len); data.text = arena_push_array(arena, u8, data.len);
@ -1014,7 +1014,7 @@ void host_update_end(struct host *host)
} }
} }
/* Process packets */ /* Send packets */
/* TODO: Aggregate small packets */ /* TODO: Aggregate small packets */
{ {
__profscope(host_update_send_packets); __profscope(host_update_send_packets);

View File

@ -2,7 +2,7 @@
#define RAND_H #define RAND_H
struct rand_state { struct rand_state {
/* If a state's seed = 0 upon a to a related function, it will be initialized using platform's true rng source */ /* If a state's seed == 0 upon a call to a related function, it will be initialized using platform's true rng source */
u64 seed; u64 seed;
u64 counter; u64 counter;
}; };

View File

@ -599,7 +599,7 @@ struct sim_snapshot *sim_snapshot_alloc_from_lerp(struct sim_client *client, str
* ========================== */ * ========================== */
/* Syncs entity data between snapshots */ /* Syncs entity data between snapshots */
void sim_snapshot_sync_ents(struct sim_snapshot *local_ss, struct sim_snapshot *remote_ss, struct sim_ent_id remote_client_ent) void sim_snapshot_sync_ents(struct sim_snapshot *local_ss, struct sim_snapshot *remote_ss, struct sim_ent_id remote_client_ent, u32 sync_flags)
{ {
__prof; __prof;
@ -621,6 +621,9 @@ void sim_snapshot_sync_ents(struct sim_snapshot *local_ss, struct sim_snapshot *
struct sim_ent *local_ent = &local_ss->ents[i]; struct sim_ent *local_ent = &local_ss->ents[i];
if (local_ent->valid && sim_ent_has_prop(local_ent, SIM_ENT_PROP_SYNC_DST)) { if (local_ent->valid && sim_ent_has_prop(local_ent, SIM_ENT_PROP_SYNC_DST)) {
b32 should_sync = sim_ent_id_eq(local_ent->owner, remote_client_ent) || sim_ent_id_is_nil(remote_client_ent); b32 should_sync = sim_ent_id_eq(local_ent->owner, remote_client_ent) || sim_ent_id_is_nil(remote_client_ent);
if ((sync_flags & SIM_SYNC_FLAG_NOSYNC_PREDICTABLES) && sim_ent_id_eq(local_ent->predictor, local_ss->local_client_ent)) {
should_sync = false;
}
if (should_sync) { if (should_sync) {
struct sim_ent *remote_ent = sim_ent_from_id(remote_ss, local_ent->id); struct sim_ent *remote_ent = sim_ent_from_id(remote_ss, local_ent->id);
if (remote_ent->valid) { if (remote_ent->valid) {

View File

@ -73,7 +73,7 @@ struct sim_client {
struct arena snapshots_arena; struct arena snapshots_arena;
/* Round trip time of the client (if networked) */ /* Round trip time of the client (if networked) */
i64 rtt_ns; i64 last_rtt_ns;
struct host_channel_id channel_id; struct host_channel_id channel_id;
u64 channel_hash; u64 channel_hash;
@ -129,6 +129,10 @@ struct sim_client *sim_client_from_handle(struct sim_client_store *store, struct
* Snapshot * Snapshot
* ========================== */ * ========================== */
enum sim_sync_flag {
SIM_SYNC_FLAG_NOSYNC_PREDICTABLES = 1 << 0
};
enum sim_control_flag { enum sim_control_flag {
SIM_CONTROL_FLAG_FIRE = 1 << 0, SIM_CONTROL_FLAG_FIRE = 1 << 0,
@ -160,10 +164,6 @@ struct sim_snapshot {
struct arena arena; struct arena arena;
/* Program time of local snapshot publish to user thread (if relevant) */
i64 publish_dt_ns;
i64 publish_time_ns;
/* Sim time (guaranteed to increase by sim_dt_ns each step) */ /* Sim time (guaranteed to increase by sim_dt_ns each step) */
i64 sim_dt_ns; i64 sim_dt_ns;
i64 sim_time_ns; i64 sim_time_ns;
@ -212,7 +212,7 @@ struct sim_snapshot *sim_snapshot_from_closest_tick_gte(struct sim_client *clien
struct sim_snapshot *sim_snapshot_alloc_from_lerp(struct sim_client *client, struct sim_snapshot *ss0, struct sim_snapshot *ss1, f64 blend); struct sim_snapshot *sim_snapshot_alloc_from_lerp(struct sim_client *client, struct sim_snapshot *ss0, struct sim_snapshot *ss1, f64 blend);
/* Sync */ /* Sync */
void sim_snapshot_sync_ents(struct sim_snapshot *local_ss, struct sim_snapshot *remote_ss, struct sim_ent_id remote_client_ent); void sim_snapshot_sync_ents(struct sim_snapshot *local_ss, struct sim_snapshot *remote_ss, struct sim_ent_id remote_client_ent, u32 sync_flags);
/* Encode / decode */ /* Encode / decode */
void sim_snapshot_encode(struct bitbuff_writer *bw, struct sim_client *receiver, struct sim_snapshot *ss0, struct sim_snapshot *ss1); void sim_snapshot_encode(struct bitbuff_writer *bw, struct sim_client *receiver, struct sim_snapshot *ss0, struct sim_snapshot *ss1);

View File

@ -392,18 +392,16 @@ void sim_step(struct sim_step_ctx *ctx)
/* Update rtt */ /* Update rtt */
if (is_master && client_ent->valid) { if (is_master && client_ent->valid) {
client_ent->client_last_rtt_ns = client->rtt_ns; client_ent->client_last_rtt_ns = client->last_rtt_ns;
f64 avg = client_ent->client_average_rtt_seconds; client_ent->client_average_rtt_seconds -= client_ent->client_average_rtt_seconds / 200;
avg -= avg / 200; client_ent->client_average_rtt_seconds += SECONDS_FROM_NS(client->last_rtt_ns) / 200;
avg += SECONDS_FROM_NS(client->rtt_ns) / 200;
client_ent->client_average_rtt_seconds = avg;
} }
/* Sync ents from client */ /* Sync ents from client */
if (client_ent->valid) { if (client_ent->valid) {
struct sim_snapshot *src_ss = sim_snapshot_from_tick(client, world->tick); struct sim_snapshot *src_ss = sim_snapshot_from_tick(client, world->tick);
if (src_ss->valid) { if (src_ss->valid) {
sim_snapshot_sync_ents(world, src_ss, client_ent->id); sim_snapshot_sync_ents(world, src_ss, client_ent->id, 0);
} }
} }
} }
@ -993,6 +991,7 @@ void sim_step(struct sim_step_ctx *ctx)
* Create motor joints from ground friction (gravity) * Create motor joints from ground friction (gravity)
* ========================== */ * ========================== */
#if 0
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index]; struct sim_ent *ent = &world->ents[ent_index];
if (!sim_ent_should_simulate(ent)) continue; if (!sim_ent_should_simulate(ent)) continue;
@ -1017,6 +1016,7 @@ void sim_step(struct sim_step_ctx *ctx)
} }
} }
} }
#endif
/* ========================== * /* ========================== *
* Create mouse joints from client debug drag * Create mouse joints from client debug drag
@ -1275,7 +1275,7 @@ void sim_step(struct sim_step_ctx *ctx)
struct sim_snapshot *pub_world = sim_snapshot_alloc(publish_client, prev_pub_world, world->tick); struct sim_snapshot *pub_world = sim_snapshot_alloc(publish_client, prev_pub_world, world->tick);
/* Sync */ /* Sync */
sim_snapshot_sync_ents(pub_world, world, world_client->ent_id); sim_snapshot_sync_ents(pub_world, world, world_client->ent_id, 0);
/* Mark all synced ents as both sync dsts & sync srcs */ /* Mark all synced ents as both sync dsts & sync srcs */
for (u64 ent_index = 2; ent_index < pub_world->num_ents_reserved; ++ent_index) { for (u64 ent_index = 2; ent_index < pub_world->num_ents_reserved; ++ent_index) {

View File

@ -95,11 +95,11 @@ GLOBAL struct {
struct sys_mutex local_to_user_client_mutex; struct sys_mutex local_to_user_client_mutex;
struct sim_client_store *local_to_user_client_store; struct sim_client_store *local_to_user_client_store;
struct sim_client *local_to_user_client; struct sim_client *local_to_user_client;
i64 local_to_user_client_publish_dt_ns;
i64 local_to_user_client_publish_time_ns;
/* Rolling window of local sim -> user publish time deltas */ /* Rolling window of local sim -> user publish time deltas */
i64 last_local_to_user_snapshot_published_at_ns; i64 last_local_to_user_snapshot_published_at_ns;
i64 local_to_user_snapshot_publish_dts_ns[10];
i64 local_to_user_snapshot_publish_dts_index;
i64 average_local_to_user_snapshot_publish_dt_ns; i64 average_local_to_user_snapshot_publish_dt_ns;
i64 local_sim_predicted_time_ns; /* Calculated from <last local sim to user pubilsh time> + <time since last local sim to user publish> */ i64 local_sim_predicted_time_ns; /* Calculated from <last local sim to user pubilsh time> + <time since last local sim to user publish> */
@ -202,10 +202,8 @@ struct user_startup_receipt user_startup(struct work_startup_receipt *work_sr,
/* TODO: Remove this */ /* TODO: Remove this */
G.connect_address_str = string_copy(&G.arena, connect_address_str); G.connect_address_str = string_copy(&G.arena, connect_address_str);
/* Initialize average dts to a reasonable value */ /* Initialize average dt to a reasonable value */
for (u64 i = 0; i < ARRAY_COUNT(G.local_to_user_snapshot_publish_dts_ns); ++i) { G.average_local_to_user_snapshot_publish_dt_ns = NS_FROM_SECONDS(1) / SIM_TICKS_PER_SECOND;
G.local_to_user_snapshot_publish_dts_ns[i] = NS_FROM_SECONDS(1) / SIM_TICKS_PER_SECOND;
}
/* Sys events */ /* Sys events */
G.sys_events_mutex = sys_mutex_alloc(); G.sys_events_mutex = sys_mutex_alloc();
@ -488,39 +486,13 @@ INTERNAL void user_update(void)
if (last_tick > old_last_tick) { if (last_tick > old_last_tick) {
struct sim_snapshot *src = sim_snapshot_from_tick(G.local_to_user_client, last_tick); struct sim_snapshot *src = sim_snapshot_from_tick(G.local_to_user_client, last_tick);
sim_snapshot_alloc(G.user_unblended_client, src, src->tick); sim_snapshot_alloc(G.user_unblended_client, src, src->tick);
G.last_local_to_user_snapshot_published_at_ns = src->publish_time_ns; G.last_local_to_user_snapshot_published_at_ns = G.local_to_user_client_publish_time_ns;
G.local_to_user_snapshot_publish_dts_ns[G.local_to_user_snapshot_publish_dts_index++] = src->publish_dt_ns; G.average_local_to_user_snapshot_publish_dt_ns -= G.average_local_to_user_snapshot_publish_dt_ns / 50;
if (G.local_to_user_snapshot_publish_dts_index >= (i64)ARRAY_COUNT(G.local_to_user_snapshot_publish_dts_ns)) { G.average_local_to_user_snapshot_publish_dt_ns += G.local_to_user_client_publish_dt_ns / 50;
G.local_to_user_snapshot_publish_dts_index = 0;
}
} }
sys_mutex_unlock(&lock); sys_mutex_unlock(&lock);
} }
/* ========================== *
* Determine average local sim publish dt
* ========================== */
{
i64 average_publish_dt_ns = 0;
{
i64 num_dts = 0;
for (u64 i = 0; i < ARRAY_COUNT(G.local_to_user_snapshot_publish_dts_ns); ++i) {
i64 dt_ns = G.local_to_user_snapshot_publish_dts_ns[i];
if (dt_ns != 0) {
average_publish_dt_ns += dt_ns;
++num_dts;
} else {
break;
}
}
if (num_dts > 0) {
average_publish_dt_ns /= num_dts;
}
}
G.average_local_to_user_snapshot_publish_dt_ns = average_publish_dt_ns;
}
/* ========================== * /* ========================== *
* Create user world from blended snapshots * Create user world from blended snapshots
* ========================== */ * ========================== */
@ -537,7 +509,6 @@ INTERNAL void user_update(void)
struct sim_snapshot *newest_snapshot = sim_snapshot_from_tick(G.user_unblended_client, G.user_unblended_client->last_tick); struct sim_snapshot *newest_snapshot = sim_snapshot_from_tick(G.user_unblended_client, G.user_unblended_client->last_tick);
G.local_sim_last_known_time_ns = newest_snapshot->sim_time_ns; G.local_sim_last_known_time_ns = newest_snapshot->sim_time_ns;
G.local_sim_last_known_tick = newest_snapshot->tick; G.local_sim_last_known_tick = newest_snapshot->tick;
u64 keep_unblended_tick = newest_snapshot->tick;
G.local_sim_predicted_time_ns = newest_snapshot->sim_time_ns + (newest_snapshot->sim_dt_ns * tick_progress); G.local_sim_predicted_time_ns = newest_snapshot->sim_time_ns + (newest_snapshot->sim_dt_ns * tick_progress);
#if USER_INTERP_ENABLED #if USER_INTERP_ENABLED
@ -572,10 +543,6 @@ INTERNAL void user_update(void)
} }
} }
if (left_snapshot->tick < keep_unblended_tick) {
keep_unblended_tick = left_snapshot->tick;
}
/* Create world from blended snapshots */ /* Create world from blended snapshots */
if (left_snapshot->valid && right_snapshot->valid) { if (left_snapshot->valid && right_snapshot->valid) {
f64 blend = (f64)(G.render_time_ns - left_snapshot->sim_time_ns) / (f64)(right_snapshot->sim_time_ns - left_snapshot->sim_time_ns); f64 blend = (f64)(G.render_time_ns - left_snapshot->sim_time_ns) / (f64)(right_snapshot->sim_time_ns - left_snapshot->sim_time_ns);
@ -598,20 +565,14 @@ INTERNAL void user_update(void)
#endif #endif
/* Release unneeded unblended sim snapshots */ /* Release unneeded unblended sim snapshots */
if (keep_unblended_tick > 0) { if (left_snapshot->tick > 0) {
sim_snapshot_release_ticks_in_range(G.user_unblended_client, 0, keep_unblended_tick - 1); sim_snapshot_release_ticks_in_range(G.user_unblended_client, 0, left_snapshot->tick - 1);
} }
/* Release unused blended snapshots */ /* Release unneeded blended snapshots */
{ if (G.ss_blended->tick > 0) {
struct sim_snapshot *ss = sim_snapshot_from_tick(G.user_blended_client, G.user_blended_client->first_tick); sim_snapshot_release_ticks_in_range(G.user_blended_client, 0, G.ss_blended->tick - 1);
while (ss->valid) { sim_snapshot_release_ticks_in_range(G.user_blended_client, G.ss_blended->tick + 1, U64_MAX);
u64 next_tick = ss->next_tick;
if (ss != G.ss_blended) {
sim_snapshot_release(ss);
}
ss = sim_snapshot_from_tick(G.user_blended_client, next_tick);
}
} }
} }
@ -1061,7 +1022,7 @@ INTERNAL void user_update(void)
} }
/* Draw focus arrow */ /* Draw focus arrow */
if (ent == local_player) { if (ent == local_player || sim_ent_id_eq(ent->id, G.debug_following)) {
struct sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, ent->sprite); struct sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, ent->sprite);
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, LIT("attach.wep"), ent->animation_frame); struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, LIT("attach.wep"), ent->animation_frame);
struct v2 start = xform_mul_v2(sprite_xform, slice.center); struct v2 start = xform_mul_v2(sprite_xform, slice.center);
@ -1968,7 +1929,8 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
struct sim_client *local_client = sim_client_alloc(store); /* Stores snapshots produced locally */ struct sim_client *local_client = sim_client_alloc(store); /* Stores snapshots produced locally */
struct sim_client *publish_client = sim_client_alloc(store); /* Stores versions of local snapshots that will be published to remote sims */ struct sim_client *publish_client = sim_client_alloc(store); /* Stores versions of local snapshots that will be published to remote sims */
struct sim_client *master_client = sim_client_nil(); /* Stores snapshots received from master (if relevant) */ struct sim_client *master_client = sim_client_nil(); /* Stores snapshots received from master */
struct sim_client *master_blended_client = sim_client_nil(); /* Stores interpolated master snapshots */
b32 initialized_from_master = false; b32 initialized_from_master = false;
@ -1983,11 +1945,14 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
#if 0
i64 prev_last_tick_from_master = 0;
i64 last_tick_from_master = 0;
i64 last_ticks_ahead_from_master = 0;
#endif
i64 master_blend_time_ns = 0;
i64 average_master_receive_dt_ns = 0;
i64 last_tick_from_master_received_at_ns = 0;
@ -2002,8 +1967,12 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
i64 last_publish_ns = 0;
i64 last_tick_ns = 0;
i64 last_publish_to_user_ns = 0;
i64 real_time_ns = 0;
i64 real_dt_ns = 0;
i64 step_dt_ns = NS_FROM_SECONDS(1) / SIM_TICKS_PER_SECOND; i64 step_dt_ns = NS_FROM_SECONDS(1) / SIM_TICKS_PER_SECOND;
f64 compute_timescale = 1.0; f64 compute_timescale = 1.0;
while (!atomic_i32_eval(&G.local_sim_thread_shutdown)) { while (!atomic_i32_eval(&G.local_sim_thread_shutdown)) {
@ -2011,9 +1980,10 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
struct temp_arena scratch = scratch_begin_no_conflict(); struct temp_arena scratch = scratch_begin_no_conflict();
{ {
__profscope(local_sim_sleep); __profscope(local_sim_sleep);
sleep_frame(last_tick_ns, step_dt_ns / compute_timescale); sleep_frame(real_time_ns, step_dt_ns * compute_timescale);
last_tick_ns = sys_time_ns();
} }
real_dt_ns = sys_time_ns() - real_time_ns;
real_time_ns += real_dt_ns;
struct host_event_list host_events = host_update_begin(scratch.arena, host); struct host_event_list host_events = host_update_begin(scratch.arena, host);
@ -2026,13 +1996,22 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
switch (event->kind) { switch (event->kind) {
case HOST_EVENT_KIND_CHANNEL_OPENED: case HOST_EVENT_KIND_CHANNEL_OPENED:
{ {
/* Create remote client */
if (!client->valid) { if (!client->valid) {
/* TODO: Master challenge */ if (is_master) {
/* Create remote client */
client = sim_client_alloc(store);
sim_client_set_channel_id(client, channel_id);
} else {
/* Create master client */
if (!master_client->valid) {
client = sim_client_alloc(store); client = sim_client_alloc(store);
sim_client_set_channel_id(client, channel_id); sim_client_set_channel_id(client, channel_id);
if (!is_master) {
master_client = client; master_client = client;
master_blended_client = sim_client_alloc(store);
} else {
/* We already have a master client */
ASSERT(false);
}
} }
} }
} break; } break;
@ -2052,7 +2031,7 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
client->double_ack = double_ack; client->double_ack = double_ack;
} }
/* Read snapshots */ /* Read & queue incoming snapshots for decoding */
u64 tmp_encoded_len = br_read_uv(&msg_br); u64 tmp_encoded_len = br_read_uv(&msg_br);
while (tmp_encoded_len > 0) { while (tmp_encoded_len > 0) {
u8 *tmp_encoded_bytes = br_read_bytes_raw(&msg_br, tmp_encoded_len); u8 *tmp_encoded_bytes = br_read_bytes_raw(&msg_br, tmp_encoded_len);
@ -2090,9 +2069,9 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
client->highest_received_tick = tick; client->highest_received_tick = tick;
} }
} }
} else if (client == master_client) { } else {
/* Decode incoming master client snapshots for decoding (only the newest one) */ /* Decode incoming master client snapshots for decoding (only the newest one) */
b32 should_decode = tick > client->highest_received_tick; b32 should_decode = client == master_client && tick > client->highest_received_tick;
if (should_decode) { if (should_decode) {
struct sim_ss_decode_node *node = queue.first ? queue.first : arena_push_zero(scratch.arena, struct sim_ss_decode_node); struct sim_ss_decode_node *node = queue.first ? queue.first : arena_push_zero(scratch.arena, struct sim_ss_decode_node);
node->client = client; node->client = client;
@ -2103,6 +2082,13 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
queue.last = node; queue.last = node;
if (tick > client->highest_received_tick) { if (tick > client->highest_received_tick) {
client->highest_received_tick = tick; client->highest_received_tick = tick;
if (average_master_receive_dt_ns == 0) {
average_master_receive_dt_ns = NS_FROM_SECONDS(1) / SIM_TICKS_PER_SECOND;
} else {
average_master_receive_dt_ns -= average_master_receive_dt_ns / 50;
average_master_receive_dt_ns += (real_time_ns - last_tick_from_master_received_at_ns) / 50;
}
last_tick_from_master_received_at_ns = real_time_ns;
} }
} }
} }
@ -2162,14 +2148,16 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
u64 oldest_client_ack = 0; u64 oldest_client_ack = 0;
for (u64 i = 0; i < store->num_clients_reserved; ++i) { for (u64 i = 0; i < store->num_clients_reserved; ++i) {
struct sim_client *client = &store->clients[i]; struct sim_client *client = &store->clients[i];
if (client->valid && client != local_client && client != publish_client && client != user_input_client) { if (client->valid && client != local_client && client != publish_client && client != user_input_client && client != master_client) {
client->rtt_ns = host_get_channel_last_rtt_ns(host, client->channel_id); client->last_rtt_ns = host_get_channel_last_rtt_ns(host, client->channel_id);
/* Release unneeded received snapshots */ /* Release unneeded received snapshots */
/* TDOO: Cap how many client snapshots we're willing to retain */
if (client->double_ack > 0) { if (client->double_ack > 0) {
//u64 keep_tick = max_u64(min_u64(client->double_ack, step_tick), 1); u64 keep_tick = min_u64(client->double_ack, local_client->last_tick);
u64 keep_tick = 1; if (keep_tick > 0) {
sim_snapshot_release_ticks_in_range(client, 0, keep_tick - 1); sim_snapshot_release_ticks_in_range(client, 0, keep_tick - 1);
} }
}
if (client->ack < oldest_client_ack || oldest_client_ack == 0) { if (client->ack < oldest_client_ack || oldest_client_ack == 0) {
oldest_client_ack = client->ack; oldest_client_ack = client->ack;
} }
@ -2190,7 +2178,7 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
/* Release old local snapshots */ /* Release old local snapshots */
{ {
u64 keep_range = 250; u64 keep_range = 50;
if (local_client->last_tick > keep_range) { if (local_client->last_tick > keep_range) {
u64 keep_tick = local_client->last_tick - keep_range; u64 keep_tick = local_client->last_tick - keep_range;
sim_snapshot_release_ticks_in_range(local_client, 0, keep_tick); sim_snapshot_release_ticks_in_range(local_client, 0, keep_tick);
@ -2203,74 +2191,6 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
#if 0
u64 sim_base_tick = local_client->last_tick;
u64 sim_to_tick = sim_base_tick + 1;
if (master_client->valid) {
prev_last_tick_from_master = master_client->last_tick;
last_tick_from_master = master_client->last_tick;
last_ticks_ahead_from_master = master_client->ack - last_tick_from_master;
if (math_abs_i64(last_ticks_ahead_from_master) > 50) {
/* We're too far from master time, snap to last sim time */
i64 rtt_ns = master_client->rtt_ns;
f64 rtt_tick_ratio = (f64)(rtt_ns + (step_dt_ns - 1)) / (f64)step_dt_ns;
i64 num_predict_ticks = math_round_to_int64(rtt_tick_ratio) + 2;
sim_base_tick = last_tick_from_master;
sim_to_tick = last_tick_from_master + num_predict_ticks;
sim_snapshot_release_ticks_in_range(local_client, sim_base_tick + 1, U64_MAX);
sim_snapshot_release_ticks_in_range(user_input_client, sim_base_tick + 1, U64_MAX);
compute_timescale = 1.1;
} else if (last_ticks_ahead_from_master > 4) {
/* Slow down time to bring sim time closer to master time */
compute_timescale = 0.9;
} else if (last_ticks_ahead_from_master < 2) {
/* Speed up time to give master more inputs to work with */
compute_timescale = 1.1;
} else {
compute_timescale = 1;
}
}
/* Step */
{
struct sim_step_ctx ctx = ZI;
ctx.is_master = is_master;
ctx.sim_dt_ns = step_dt_ns;
ctx.accel = &accel;
ctx.user_input_client = user_input_client;
ctx.master_client = master_client;
ctx.publish_client = publish_client;
if (is_master) {
struct sim_snapshot *prev_world = sim_snapshot_from_tick(local_client, sim_base_tick);
ctx.world = sim_snapshot_alloc(local_client, prev_world, sim_base_tick + 1);
sim_step(&ctx);
} else {
struct sim_snapshot *master_ss = sim_snapshot_from_tick(master_client, master_client->last_tick);
if (master_ss->valid) {
local_client->ent_id = master_ss->local_client_ent;
user_input_client->ent_id = master_ss->local_client_ent;
struct sim_snapshot *prev_world = sim_snapshot_alloc(local_client, master_ss, master_ss->tick);
i64 num_steps = (local_client->last_tick + num_sim_ticks) - master_ss->tick;
for (i64 i = 0; i < num_steps; ++i) {
ctx.world = sim_snapshot_alloc(local_client, prev_world, prev_world->tick + 1);
sim_step(&ctx);
prev_world = ctx.world;
}
}
}
}
#endif
@ -2295,15 +2215,119 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
/* TODO: Eventually determine master tick based on a delay to allow for jitter and also interpolation so we can lower snapshot publish frequency */ /* TODO: Eventually determine master tick based on a delay to allow for jitter and also interpolation so we can lower snapshot publish frequency */
u64 master_tick = master_client->last_tick; b32 master_ss_is_blended = false;
struct sim_snapshot *master_ss = sim_snapshot_from_tick(master_client, master_tick); struct sim_snapshot *master_ss = sim_snapshot_nil();
{
/* How along are we between master sim ticks (0 = start of tick, 1 = end of tick) */
f64 tick_progress = 0;
i64 next_tick_expected_ns = last_tick_from_master_received_at_ns + average_master_receive_dt_ns;
if (next_tick_expected_ns > last_tick_from_master_received_at_ns) {
tick_progress = (f64)(real_time_ns - last_tick_from_master_received_at_ns) / (f64)(next_tick_expected_ns - last_tick_from_master_received_at_ns);
}
/* Predict master sim time based on average snapshot publish dt. */
struct sim_snapshot *newest_snapshot = sim_snapshot_from_tick(master_client, master_client->last_tick);
i64 master_sim_predicted_time_ns = newest_snapshot->sim_time_ns + (newest_snapshot->sim_dt_ns * tick_progress);
/* Determine blend time */
i64 master_blend_time_target_ns = master_sim_predicted_time_ns - (SIM_CLIENT_INTERP_RATIO * average_master_receive_dt_ns);
if (average_master_receive_dt_ns > 0) {
master_blend_time_ns += real_dt_ns;
}
i64 blend_time_target_diff_ns = master_blend_time_target_ns - master_blend_time_ns;
if (blend_time_target_diff_ns > NS_FROM_SECONDS(0.100) || blend_time_target_diff_ns < NS_FROM_SECONDS(-0.100)) {
/* Snap blend time if it gets too far from target blend time */
master_blend_time_ns = master_blend_time_target_ns;
}
u64 master_blend_tick = master_blend_time_ns / newest_snapshot->sim_dt_ns;
/* Get snapshot nearest to master blend time */
/* TODO: Blend */
struct sim_snapshot *left_snapshot = sim_snapshot_nil();
struct sim_snapshot *right_snapshot = newest_snapshot;
{
struct sim_snapshot *ss = sim_snapshot_from_tick(master_client, master_client->first_tick);
while (ss->valid) {
u64 next_tick = ss->next_tick;
i64 ss_time_ns = ss->sim_time_ns;
if (ss_time_ns < master_blend_time_ns && ss_time_ns > left_snapshot->sim_time_ns) {
left_snapshot = ss;
}
if (ss_time_ns > master_blend_time_ns && ss_time_ns < right_snapshot->sim_time_ns) {
right_snapshot = ss;
}
ss = sim_snapshot_from_tick(master_client, next_tick);
}
}
/* Create world from blended master snapshots */
f64 blend = 0;
if (left_snapshot->valid && right_snapshot->valid && right_snapshot->tick > left_snapshot->tick) {
blend = (f64)(master_blend_tick - left_snapshot->tick) / (f64)(right_snapshot->tick - left_snapshot->tick);
f64 epsilon = 0.001;
if (blend < epsilon) {
master_ss_is_blended = false;
master_ss = left_snapshot;
} else if (blend > 1 - epsilon) {
master_ss_is_blended = false;
master_ss = right_snapshot;
} else {
master_ss_is_blended = true;
master_ss = sim_snapshot_alloc_from_lerp(master_blended_client, left_snapshot, right_snapshot, blend);
/* Release unneeded blended master snapshots */
if (master_ss->tick > 0) {
sim_snapshot_release_ticks_in_range(master_blended_client, 0, master_ss->tick - 1);
sim_snapshot_release_ticks_in_range(master_blended_client, master_ss->tick + 1, U64_MAX);
}
}
} else {
master_ss_is_blended = false;
master_ss = left_snapshot->valid ? left_snapshot : right_snapshot;
}
/* Release unneeded master snapshots */
u64 keep_master_tick = min_u64(left_snapshot->tick, master_client->double_ack);
if (keep_master_tick > 0) {
sim_snapshot_release_ticks_in_range(master_client, 0, keep_master_tick - 1);
}
#if 0
DEBUGBREAKABLE;
logf_debug("*************************************************");
logf_debug("local_client->last_tick: %F", FMT_UINT(local_client->last_tick));
logf_debug("master_sim_predicted_time_ns: %F", FMT_SINT(master_sim_predicted_time_ns));
logf_debug("tick_progress: %F", FMT_FLOAT(tick_progress));
logf_debug("sim_publish_timescale: %F", FMT_FLOAT(sim_publish_timescale));
logf_debug("last_tick_from_master_received_at_ns: %F", FMT_SINT(last_tick_from_master_received_at_ns));
logf_debug("average_master_receive_dt_ns: %F", FMT_SINT(average_master_receive_dt_ns));
logf_debug("next_tick_expected_ns: %F", FMT_SINT(next_tick_expected_ns));
logf_debug("master_blend_time_target_ns: %F", FMT_SINT(master_blend_time_target_ns));
logf_debug("blend_time_target_diff_ns: %F", FMT_SINT(blend_time_target_diff_ns));
logf_debug("master_blend_time_ns: %F", FMT_SINT(master_blend_time_ns));
logf_debug("left_snapshot->tick: %F", FMT_UINT(left_snapshot->tick));
logf_debug("right_snapshot->tick: %F", FMT_UINT(right_snapshot->tick));
logf_debug("master_ss->tick: %F", FMT_UINT(master_ss->tick));
#endif
}
if (master_ss->valid) {
struct sim_ent *master_client_ent = sim_ent_find_first_match_one(master_ss, SIM_ENT_PROP_CLIENT_IS_MASTER); struct sim_ent *master_client_ent = sim_ent_find_first_match_one(master_ss, SIM_ENT_PROP_CLIENT_IS_MASTER);
/* Update ent id from master */
{
user_input_client->ent_id = master_ss->local_client_ent; user_input_client->ent_id = master_ss->local_client_ent;
local_client->ent_id = master_ss->local_client_ent; local_client->ent_id = master_ss->local_client_ent;
}
/* Check for misprediction */ /* Check for misprediction */
u64 mispredicted_tick = 0;
if (!master_ss_is_blended) {
/* TODO: Actually check for misprediction rather than triggering mispredict any time a new master snapshot is received */ /* TODO: Actually check for misprediction rather than triggering mispredict any time a new master snapshot is received */
u64 mispredicted_tick = master_tick; mispredicted_tick = master_ss->tick;
}
u64 step_base_tick = local_client->last_tick; u64 step_base_tick = local_client->last_tick;
u64 step_end_tick = step_base_tick + 1; u64 step_end_tick = step_base_tick + 1;
@ -2314,58 +2338,46 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
} }
} }
/* We want to simulate the ahead of the server to predict client input. /* We want to simulate the ahead of the server to predict client input.
* How many ticks ahead we want to simulate is a balance between added latency and the server not receiving our inputs on time. * How many ticks ahead we want to simulate is a balance between added latency and the server not receiving our inputs on time.
* We can take the server's last sent ack - server tick to determine how many cmds of ours the server has buffered. * We can take the server's ack - server's tick to determine how many cmds of ours the server has buffered.
* *
* If this buffer gets too low (because we are lagging behind or the connection is unstable), meaning the server is not getting our input on time: * If this buffer gets too low (because we are lagging behind or the connection is unstable), meaning the server is not getting our input on time:
* - Dilate local compute time (not sim time) to increase the rate at which we predict & produce cmds, until the server's ack indicates a buffer size within desired range. * - Shorten local compute rate to increase the rate at which we predict ahead & produce cmds, until the server's ack indicates a buffer size within desired range.
* *
* If this buffer gets too large (because the client predicts too far ahead), meaning unneeded latency is being introduced: * If this buffer gets too large (because the client predicts too far ahead), meaning unneeded latency is being introduced:
* - Dilate local compute time to decrease the rate at which we predict & produce cmds until the server's ack indicates a buffer size within desired range. * - Dilate local compute rate to decrease the rate at which we predict ahead & produce cmds until the server's ack indicates a buffer size within desired range.
*/ */
{ {
i64 cmds_ahead_on_master = (i64)master_client->ack - (i64)master_client->last_tick; i64 cmds_ahead_on_master = (i64)master_client->ack - (i64)master_client->last_tick;
if (math_abs_i64(cmds_ahead_on_master) > 50) { if (cmds_ahead_on_master < -3 || cmds_ahead_on_master > 10) {
/* Cmds are too far from master time, snap step end tick */ /* Cmds are too far from master time, snap step end tick */
i64 rtt_ns = master_client->rtt_ns; i64 rtt_ns = master_client->last_rtt_ns;
f64 rtt_tick_ratio = (f64)(rtt_ns + (step_dt_ns - 1)) / (f64)step_dt_ns; f64 rtt_tick_ratio = (f64)(rtt_ns + (step_dt_ns - 1)) / (f64)step_dt_ns;
i64 num_predict_ticks = math_round_to_int64(rtt_tick_ratio) + 5; i64 num_predict_ticks = math_round_to_int64(rtt_tick_ratio) + 5;
step_end_tick = step_base_tick + num_predict_ticks; step_end_tick = master_client->last_tick + num_predict_ticks;
compute_timescale = 1.1; compute_timescale = 1.1;
} else if (cmds_ahead_on_master > 2) { } else if (cmds_ahead_on_master > 2) {
/* Slow down simulation rate to bring sim time closer to master time */ /* Slow down simulation to dial back how far ahead we are predicting and bring local sim time closer to master sim time */
compute_timescale = 0.95; compute_timescale = 1.1;
} else if (cmds_ahead_on_master < 1) { } else if (cmds_ahead_on_master < 1) {
/* Speed up simulation rate to give master more inputs to work with */ /* Speed up simulation rate predict more ticks and give master more inputs to work with */
compute_timescale = 1.05; compute_timescale = 0.9;
} else { } else {
/* Server's cmd buffer is in a healthy range */ /* Server's cmd buffer is in a healthy range */
compute_timescale = 1; compute_timescale = 1;
} }
} }
/* Sync master with local base tick */ /* Sync master with local base tick */
struct sim_snapshot *base_ss = sim_snapshot_from_tick(local_client, step_base_tick); struct sim_snapshot *base_ss = sim_snapshot_from_tick(local_client, step_base_tick);
if (mispredicted_tick) {
if (base_ss->valid) { if (base_ss->valid) {
/* FIXME: Set master client ent id somewhere */ sim_snapshot_sync_ents(base_ss, master_ss, master_client_ent->id, 0);
sim_snapshot_sync_ents(base_ss, master_ss, master_client_ent->id);
} else { } else {
base_ss = sim_snapshot_alloc(local_client, master_ss, step_base_tick); base_ss = sim_snapshot_alloc(local_client, master_ss, step_base_tick);
} }
}
/* Release any existing ticks that are about to be simulated */ /* Release any existing ticks that are about to be simulated */
sim_snapshot_release_ticks_in_range(local_client, step_base_tick + 1, U64_MAX); sim_snapshot_release_ticks_in_range(local_client, step_base_tick + 1, U64_MAX);
@ -2385,12 +2397,15 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
struct sim_snapshot *prev_ss = base_ss; struct sim_snapshot *prev_ss = base_ss;
while (step_tick <= step_end_tick) { while (step_tick <= step_end_tick) {
ctx.world = sim_snapshot_alloc(local_client, prev_ss, step_tick); ctx.world = sim_snapshot_alloc(local_client, prev_ss, step_tick);
if (!mispredicted_tick && step_tick == step_end_tick) {
sim_snapshot_sync_ents(ctx.world, master_ss, master_client_ent->id, SIM_SYNC_FLAG_NOSYNC_PREDICTABLES);
}
sim_step(&ctx); sim_step(&ctx);
prev_ss = ctx.world; prev_ss = ctx.world;
++step_tick; ++step_tick;
} }
} }
}
} }
@ -2454,11 +2469,11 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
if (local_ss->valid) { if (local_ss->valid) {
/* TODO: Double buffer */ /* TODO: Double buffer */
struct sys_lock lock = sys_mutex_lock_e(&G.local_to_user_client_mutex); struct sys_lock lock = sys_mutex_lock_e(&G.local_to_user_client_mutex);
struct sim_snapshot *copy_ss = sim_snapshot_alloc(G.local_to_user_client, local_ss, local_ss->tick); sim_snapshot_alloc(G.local_to_user_client, local_ss, local_ss->tick);
i64 publish_ns = sys_time_ns(); i64 publish_ns = sys_time_ns();
copy_ss->publish_dt_ns = publish_ns - last_publish_ns; G.local_to_user_client_publish_dt_ns = publish_ns - last_publish_to_user_ns;
copy_ss->publish_time_ns = publish_ns; G.local_to_user_client_publish_time_ns = publish_ns;
last_publish_ns = publish_ns; last_publish_to_user_ns = publish_ns;
sim_snapshot_release_ticks_in_range(G.local_to_user_client, 0, local_ss->tick - 1); sim_snapshot_release_ticks_in_range(G.local_to_user_client, 0, local_ss->tick - 1);
sys_mutex_unlock(&lock); sys_mutex_unlock(&lock);
} }