diff --git a/src/config.h b/src/config.h index c0ef98f4..3e3021cc 100644 --- a/src/config.h +++ b/src/config.h @@ -33,7 +33,7 @@ #define SPACE_CELL_BUCKETS_SQRT (256) #define SPACE_CELL_SIZE 1.0f -#define SIM_TICKS_PER_SECOND 50.0 +#define SIM_TICKS_PER_SECOND 50 #define SIM_TIMESCALE 1 #define SIM_PHYSICS_SUBSTEPS 4 @@ -52,7 +52,7 @@ /* How many ticks back in time should the user blend between? * = * - * E.g: At 1.5, the user thread will render 75ms back in time (if sim thread runs at 50FPS) + * E.g: At 1.5, the user thread will render 75ms back in time (if sim thread runs at 50 TPS) */ #define USER_INTERP_RATIO 1.5 #define USER_INTERP_ENABLED 1 diff --git a/src/math.h b/src/math.h index 5cba4738..da1c2525 100644 --- a/src/math.h +++ b/src/math.h @@ -130,6 +130,20 @@ INLINE i64 math_fsign64(f64 f) return 1 + -((i64)(sign_bit << 1)); } +/* ========================== * + * Integer abs + * ========================== */ + +INLINE u32 math_abs_i32(i32 v) +{ + return v * ((v >= 0) - (v < 0)); +} + +INLINE u64 math_abs_i64(i64 v) +{ + return v * ((v >= 0) - (v < 0)); +} + /* ========================== * * Exponential * ========================== */ diff --git a/src/sim.c b/src/sim.c index e507267f..5a8bf9cc 100644 --- a/src/sim.c +++ b/src/sim.c @@ -134,7 +134,7 @@ INTERNAL struct sim_ent *spawn_test_player(struct sim_snapshot *world) e->local_collider.points[0] = V2(0, 0); e->local_collider.count = 1; - e->local_collider.radius = 0.2f; + e->local_collider.radius = 0.15f; struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size); //xf.bx.y = -1.f; @@ -419,6 +419,13 @@ struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct + //sys_sleep_precise(rng_rand_f32(0, 0.050)); + //sys_sleep_precise(0.050); + + + + + world->phys_iteration = prev_snapshot->phys_iteration; @@ -581,7 +588,16 @@ struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct /* Cap movement vector magnitude at 1 */ control->move = v2_norm(control->move); } - client->cursor_pos = cmd->cursor_pos; + + /* Determine cursor pos from focus */ + { + struct sim_ent_handle control_ent_handle = client->control_ent; + struct sim_ent *control_ent = sim_ent_from_handle(world, control_ent_handle); + if (control_ent->valid || sim_ent_handle_eq(control_ent_handle, SIM_ENT_NIL_HANDLE)) { + /* Only update cursor pos if focus ent is valid (or nil) */ + client->cursor_pos = v2_add(sim_ent_get_xform(control_ent).og, client->control.focus); + } + } u32 flags = control->flags; if (flags & SIM_CONTROL_FLAG_DRAGGING) { @@ -594,14 +610,18 @@ struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct } } if (flags & SIM_CONTROL_FLAG_CLEAR_ALL) { - test_clear_level(world); + if (!(old_control.flags & SIM_CONTROL_FLAG_CLEAR_ALL)) { + test_clear_level(world); + } } if (flags & SIM_CONTROL_FLAG_SPAWN_TEST) { - logf_info("Spawning (test)"); - u32 count = 1; - f32 spread = 1; - for (u32 j = 0; j < count; ++j) { - spawn_test_entities(world, V2(0, (((f32)j / (f32)count) - 0.5) * spread)); + if (!(old_control.flags & SIM_CONTROL_FLAG_SPAWN_TEST)) { + logf_info("Spawning (test)"); + u32 count = 1; + f32 spread = 1; + for (u32 j = 0; j < count; ++j) { + spawn_test_entities(world, V2(0, (((f32)j / (f32)count) - 0.5) * spread)); + } } } } break; @@ -990,6 +1010,7 @@ struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct /* Set correction rate dynamically since motor velocity is only set for one frame */ joint_ent->motor_joint_data.correction_rate = 10 * world_dt; + /* Solve for final angle using law of sines */ f32 new_angle; { @@ -1453,8 +1474,6 @@ void sim_cmd_frames_encode(struct bitbuff_writer *bw, struct sim_cmd_frame_list bw_write_f32(bw, cmd->control.focus.x); bw_write_f32(bw, cmd->control.focus.y); bw_write_ubits(bw, cmd->control.flags, 32); - bw_write_f32(bw, cmd->cursor_pos.x); - bw_write_f32(bw, cmd->cursor_pos.y); #if COLLIDER_DEBUG cmd->collider_gjk_steps = br_read_ubits(&br, 32); #endif @@ -1543,8 +1562,6 @@ void sim_cmd_frames_decode(struct arena *arena, struct host_event_array host_eve cmd->control.focus.x = br_read_f32(&br); cmd->control.focus.y = br_read_f32(&br); cmd->control.flags = br_read_ubits(&br, 32); - cmd->cursor_pos.x = br_read_f32(&br); - cmd->cursor_pos.y = br_read_f32(&br); #if COLLIDER_DEBUG cmd->collider_gjk_steps = br_read_ubits(&br, 32); #endif diff --git a/src/sim.h b/src/sim.h index a9df34cf..770037f3 100644 --- a/src/sim.h +++ b/src/sim.h @@ -65,7 +65,6 @@ struct sim_cmd { /* ====================================================================== */ /* SIM_CMD_KIND_CLIENT_CONTROL */ - struct v2 cursor_pos; struct sim_control control; #if RTC diff --git a/src/sim_snapshot.h b/src/sim_snapshot.h index b6903f99..742cd50d 100644 --- a/src/sim_snapshot.h +++ b/src/sim_snapshot.h @@ -42,6 +42,8 @@ struct sim_snapshot { struct arena arena; + i64 published_at_ns; + /* Real time (increases with clock assuming no lag) */ i64 real_dt_ns; i64 real_time_ns; diff --git a/src/user.c b/src/user.c index cef5d6c0..d4f63f29 100644 --- a/src/user.c +++ b/src/user.c @@ -89,22 +89,30 @@ GLOBAL struct { struct sys_mutex user_sim_cmd_mutex; struct sim_control user_sim_cmd_control; struct v2 user_sim_cmd_control_cursor_pos; + u64 last_user_sim_cmd_gen; + u64 user_sim_cmd_gen; u64 user_sim_cmd_ack; /* Local sim -> user */ struct sys_mutex local_sim_ss_mutex; struct sim_snapshot_store *local_sim_ss_store; - i64 real_dt_ns; - i64 real_time_ns; - - u64 local_sim_last_known_tick; - i64 local_sim_last_known_time_ns; - i64 last_snapshot_received_at_ns; + /* Rolling window of local sim publish times */ + i64 snapshot_publish_times_ns[50]; + i64 snapshot_publish_times_index; + i64 last_snapshot_published_at_ns; + i64 average_snapshot_publish_dt_ns; /* Calculated from */ i64 local_sim_predicted_time_ns; - i64 local_sim_predicted_time_smoothed_ns; + i64 render_time_target_ns; + i64 render_time_ns; + + u64 local_sim_last_known_tick; + i64 local_sim_last_known_time_ns; + + i64 real_dt_ns; + i64 real_time_ns; /* Per-frame */ struct v2 screen_size; @@ -407,34 +415,91 @@ INTERNAL void user_update(void) if (last_tick > old_last_tick) { struct sim_snapshot *src = sim_snapshot_from_tick(G.local_sim_ss_store, last_tick); sim_snapshot_alloc(G.unblended_snapshot_store, src, src->tick); + +#if 0 G.last_snapshot_received_at_ns = G.real_time_ns; +#else + G.last_snapshot_published_at_ns = src->published_at_ns; + G.snapshot_publish_times_ns[G.snapshot_publish_times_index++] = src->published_at_ns; + if (G.snapshot_publish_times_index >= (i64)ARRAY_COUNT(G.snapshot_publish_times_ns)) { + G.snapshot_publish_times_index = 0; + } +#endif } sys_mutex_unlock(&lock); } + /* ========================== * + * Determine average local sim publish dt + * ========================== */ + + { + i64 average_publish_dt_ns = 0; + { + i64 num_dts = 0; + for (i64 offset = 0; offset < (i64)ARRAY_COUNT(G.snapshot_publish_times_ns); ++offset) { + i64 t0_index = G.snapshot_publish_times_index + offset - 1; + if (t0_index < 0) { + t0_index += ARRAY_COUNT(G.snapshot_publish_times_ns); + } else if (t0_index >= (i64)ARRAY_COUNT(G.snapshot_publish_times_ns)) { + t0_index -= ARRAY_COUNT(G.snapshot_publish_times_ns); + } + + i64 t1_index = G.snapshot_publish_times_index + offset; + if (t1_index < 0) { + t1_index += ARRAY_COUNT(G.snapshot_publish_times_ns); + } else if (t1_index >= (i64)ARRAY_COUNT(G.snapshot_publish_times_ns)) { + t1_index -= ARRAY_COUNT(G.snapshot_publish_times_ns); + } + + i64 t0 = G.snapshot_publish_times_ns[t0_index]; + i64 t1 = G.snapshot_publish_times_ns[t1_index]; + + if (t0 != 0 && t1 != 0 && t1 > t0) { + i64 dt = t1 - t0; + average_publish_dt_ns += dt; + ++num_dts; + } + } + if (num_dts > 0) { + average_publish_dt_ns /= num_dts; + } + } + G.average_snapshot_publish_dt_ns = average_publish_dt_ns; + } + /* ========================== * * Create user world from blended snapshots * ========================== */ { + /* How along are we between sim ticks (float in range [0, 1]) */ + f64 tick_progress = 0; + i64 next_tick_expected_ns = G.last_snapshot_published_at_ns + G.average_snapshot_publish_dt_ns; + if (next_tick_expected_ns > G.last_snapshot_published_at_ns) { + tick_progress = (f64)(G.real_time_ns - G.last_snapshot_published_at_ns) / (f64)(next_tick_expected_ns - G.last_snapshot_published_at_ns); + } + + /* Predict local sim time based on average snapshot publish dt. */ struct sim_snapshot *newest_snapshot = sim_snapshot_from_tick(G.unblended_snapshot_store, G.unblended_snapshot_store->last_tick); G.local_sim_last_known_time_ns = newest_snapshot->real_time_ns; G.local_sim_last_known_tick = newest_snapshot->tick; u64 keep_unblended_tick = newest_snapshot->tick; - - /* Predict local sim time based on last received snapshot time, - * then smooth it out to prevent sudden jumps in rendering due - * to variance in snapshot receive time. */ - /* TODO: Use a value that indicates desired dt to next frame, rather than real dt from last frame? */ - f64 sim_time_smoothed_correction_rate = SECONDS_FROM_NS(G.real_dt_ns) / 0.05; - i64 time_since_newest_tick_ns = G.real_time_ns - G.last_snapshot_received_at_ns; - G.local_sim_predicted_time_ns = newest_snapshot->real_time_ns + time_since_newest_tick_ns; - G.local_sim_predicted_time_smoothed_ns += G.real_dt_ns; - G.local_sim_predicted_time_smoothed_ns += (G.local_sim_predicted_time_ns - G.local_sim_predicted_time_smoothed_ns) * sim_time_smoothed_correction_rate; - /* FIXME: Signed overflow check */ + G.local_sim_predicted_time_ns = newest_snapshot->real_time_ns + (newest_snapshot->real_dt_ns * tick_progress); #if USER_INTERP_ENABLED - i64 render_time_ns = G.local_sim_predicted_time_smoothed_ns - (USER_INTERP_RATIO * newest_snapshot->real_dt_ns); + /* Determine render time */ + G.render_time_target_ns = G.local_sim_predicted_time_ns - (USER_INTERP_RATIO * G.average_snapshot_publish_dt_ns); + if (G.average_snapshot_publish_dt_ns > 0) { + /* Increase render time based on average publish dt */ + f64 sim_publish_timescale = (f64)newest_snapshot->real_dt_ns / (f64)G.average_snapshot_publish_dt_ns; + G.render_time_ns += G.real_dt_ns * sim_publish_timescale; + } + i64 render_time_target_diff_ns = G.render_time_target_ns - G.render_time_ns; + if (render_time_target_diff_ns > NS_FROM_SECONDS(0.010) || render_time_target_diff_ns < NS_FROM_SECONDS(-0.005)) { + /* Snap render time if it gets too out of sync with target render time */ + G.render_time_ns = G.render_time_target_ns; + } /* Get two snapshots nearest to render time */ struct sim_snapshot *left_snapshot = sim_snapshot_nil(); @@ -444,10 +509,10 @@ INTERNAL void user_update(void) while (ss->valid) { u64 next_tick = ss->next_tick; i64 ss_time_ns = ss->real_time_ns; - if (ss_time_ns < render_time_ns && ss_time_ns > left_snapshot->real_time_ns) { + if (ss_time_ns < G.render_time_ns && ss_time_ns > left_snapshot->real_time_ns) { left_snapshot = ss; } - if (ss_time_ns > render_time_ns && ss_time_ns < right_snapshot->real_time_ns) { + if (ss_time_ns > G.render_time_ns && ss_time_ns < right_snapshot->real_time_ns) { right_snapshot = ss; } ss = sim_snapshot_from_tick(G.unblended_snapshot_store, next_tick); @@ -460,7 +525,7 @@ INTERNAL void user_update(void) /* Create world from blended snapshots */ if (left_snapshot->valid && right_snapshot->valid) { - f64 blend = (f64)(render_time_ns - left_snapshot->real_time_ns) / (f64)(right_snapshot->real_time_ns - left_snapshot->real_time_ns); + f64 blend = (f64)(G.render_time_ns - left_snapshot->real_time_ns) / (f64)(right_snapshot->real_time_ns - left_snapshot->real_time_ns); G.ss_blended = sim_snapshot_alloc_from_lerp(G.blended_snapshot_store, left_snapshot, right_snapshot, blend); } else if (left_snapshot->valid) { G.ss_blended = sim_snapshot_alloc(G.blended_snapshot_store, left_snapshot, left_snapshot->tick); @@ -468,6 +533,9 @@ INTERNAL void user_update(void) G.ss_blended = sim_snapshot_alloc(G.blended_snapshot_store, right_snapshot, right_snapshot->tick); } #else + /* Interp disabled, just copy latest snapshot */ + G.render_time_target_ns = G.local_sim_predicted_time_ns; + G.render_time_ns = newest_snapshot->real_time_ns; if (G.ss_blended->tick != newest_snapshot->tick) { if (G.ss_blended->valid) { sim_snapshot_release(G.ss_blended); @@ -1292,7 +1360,7 @@ INTERNAL void user_update(void) } /* ========================== * - * Queue player control cmd + * Create user sim cmd * ========================== */ { @@ -1343,23 +1411,19 @@ INTERNAL void user_update(void) /* Queue player control cmd */ { struct sim_control control = ZI; + control.move = input_move_dir; + control.focus = input_aim_dir; - if (!G.debug_camera) { - control.move = input_move_dir; - control.focus = input_aim_dir; - - struct bind_state fire_state = G.bind_states[USER_BIND_KIND_FIRE]; - if (fire_state.num_presses || fire_state.is_held) { - control.flags |= SIM_CONTROL_FLAG_FIRING; - } - } - + struct bind_state fire_state = G.bind_states[USER_BIND_KIND_FIRE]; struct bind_state drag_state = G.bind_states[USER_BIND_KIND_DEBUG_DRAG]; struct bind_state clear_state = G.bind_states[USER_BIND_KIND_DEBUG_CLEAR]; struct bind_state pause_state = G.bind_states[USER_BIND_KIND_DEBUG_PAUSE]; struct bind_state step_state = G.bind_states[USER_BIND_KIND_DEBUG_STEP]; struct bind_state spawn_state = G.bind_states[USER_BIND_KIND_DEBUG_SPAWN]; + if (fire_state.num_presses || fire_state.is_held) { + control.flags |= SIM_CONTROL_FLAG_FIRING; + } if (drag_state.num_presses || drag_state.is_held) { control.flags |= SIM_CONTROL_FLAG_DRAGGING; } @@ -1379,6 +1443,13 @@ INTERNAL void user_update(void) /* Set user sim control */ { struct sys_lock lock = sys_mutex_lock_e(&G.user_sim_cmd_mutex); + + /* Reset flags */ + if (G.user_sim_cmd_gen != G.last_user_sim_cmd_gen) { + G.user_sim_cmd_control.flags = 0; + G.last_user_sim_cmd_gen = G.user_sim_cmd_gen; + } + u32 old_flags = G.user_sim_cmd_control.flags; G.user_sim_cmd_control = control; G.user_sim_cmd_control.flags |= old_flags; @@ -1424,33 +1495,39 @@ INTERNAL void user_update(void) pos.y += spacing; pos.y += spacing; - draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("user entities: %F/%F"), FMT_UINT(G.ss_blended->num_ents_allocated), FMT_UINT(G.ss_blended->num_ents_reserved))); + draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("blended world entities: %F/%F"), FMT_UINT(G.ss_blended->num_ents_allocated), FMT_UINT(G.ss_blended->num_ents_reserved))); pos.y += spacing; - draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("user tick: %F"), FMT_UINT(G.ss_blended->tick))); + draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("blended world tick: %F"), FMT_UINT(G.ss_blended->tick))); pos.y += spacing; - draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("user time: %F"), FMT_FLOAT_P(SECONDS_FROM_NS(G.ss_blended->real_time_ns), 3))); + draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("blended world time: %F"), FMT_FLOAT_P(SECONDS_FROM_NS(G.ss_blended->world_time_ns), 3))); pos.y += spacing; pos.y += spacing; - draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("local sim last known tick: %F"), FMT_UINT(G.local_sim_last_known_tick))); + draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("average local sim publish dt: %F"), FMT_FLOAT_P(SECONDS_FROM_NS(G.average_snapshot_publish_dt_ns), 3))); pos.y += spacing; - draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("local sim last known time: %F"), FMT_FLOAT_P(SECONDS_FROM_NS(G.local_sim_last_known_time_ns), 3))); + draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("local sim last known tick: %F"), FMT_UINT(G.local_sim_last_known_tick))); pos.y += spacing; - draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("local sim predicted time: %F"), FMT_FLOAT_P(SECONDS_FROM_NS(G.local_sim_predicted_time_ns), 3))); + draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("local sim last known time: %F"), FMT_FLOAT_P(SECONDS_FROM_NS(G.local_sim_last_known_time_ns), 3))); pos.y += spacing; - draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("local sim predicted time (smoothed): %F"), FMT_FLOAT_P(SECONDS_FROM_NS(G.local_sim_predicted_time_smoothed_ns), 3))); + draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("local sim predicted time: %F"), FMT_FLOAT_P(SECONDS_FROM_NS(G.local_sim_predicted_time_ns), 3))); + pos.y += spacing; + + draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("render time target: %F"), FMT_FLOAT_P(SECONDS_FROM_NS(G.render_time_target_ns), 3))); + pos.y += spacing; + + draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("render time: %F"), FMT_FLOAT_P(SECONDS_FROM_NS(G.render_time_ns), 3))); pos.y += spacing; pos.y += spacing; - draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("Memory usage: %F MiB"), FMT_FLOAT_P((f64)atomic_u64_eval(&app_statistics()->memory_committed) / 1024 / 1024, 3))); + draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("Memory usage: %F MiB"), FMT_FLOAT_P((f64)atomic_u64_eval(&app_statistics()->memory_committed) / 1024 / 1024, 3))); pos.y += spacing; - draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("Virtual memory usage: %F TiB"), FMT_FLOAT_P((f64)atomic_u64_eval(&app_statistics()->memory_reserved) / 1024 / 1024 / 1024 / 1024, 3))); + draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("Virtual memory usage: %F TiB"), FMT_FLOAT_P((f64)atomic_u64_eval(&app_statistics()->memory_reserved) / 1024 / 1024 / 1024 / 1024, 3))); pos.y += spacing; pos.y += spacing; @@ -1714,12 +1791,9 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) struct sim_cmd *user_cmd = arena_push_zero(scratch.arena, struct sim_cmd); user_cmd->kind = SIM_CMD_KIND_CLIENT_CONTROL; user_cmd->control = G.user_sim_cmd_control; - user_cmd->cursor_pos = G.user_sim_cmd_control_cursor_pos; user_cmd_frame->first = user_cmd; user_cmd_frame->last = user_cmd; - - G.user_sim_cmd_control.flags = 0; - + ++G.user_sim_cmd_gen; sys_mutex_unlock(&lock); } if (input_cmds.last) { @@ -1737,8 +1811,9 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) /* TODO: Double buffer */ { struct sys_lock lock = sys_mutex_lock_e(&G.local_sim_ss_mutex); - sim_snapshot_alloc(G.local_sim_ss_store, ss, ss->tick); - sim_snapshot_store_release_ticks_in_range(G.local_sim_ss_store, 0, ss->tick - 1); + struct sim_snapshot *pub_ss = sim_snapshot_alloc(G.local_sim_ss_store, ss, ss->tick); + pub_ss->published_at_ns = sys_time_ns(); + sim_snapshot_store_release_ticks_in_range(G.local_sim_ss_store, 0, pub_ss->tick - 1); sys_mutex_unlock(&lock); }