better render time determination

This commit is contained in:
jacob 2025-02-19 13:33:54 -06:00
parent 80ed6a7852
commit a5bbdffbe3
6 changed files with 170 additions and 63 deletions

View File

@ -33,7 +33,7 @@
#define SPACE_CELL_BUCKETS_SQRT (256) #define SPACE_CELL_BUCKETS_SQRT (256)
#define SPACE_CELL_SIZE 1.0f #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_TIMESCALE 1
#define SIM_PHYSICS_SUBSTEPS 4 #define SIM_PHYSICS_SUBSTEPS 4
@ -52,7 +52,7 @@
/* How many ticks back in time should the user blend between? /* How many ticks back in time should the user blend between?
* <Delay ms> = <USER_INTERP_RATIO> * <Tick interval> * <Delay ms> = <USER_INTERP_RATIO> * <Tick interval>
* 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_RATIO 1.5
#define USER_INTERP_ENABLED 1 #define USER_INTERP_ENABLED 1

View File

@ -130,6 +130,20 @@ INLINE i64 math_fsign64(f64 f)
return 1 + -((i64)(sign_bit << 1)); 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 * Exponential
* ========================== */ * ========================== */

View File

@ -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.points[0] = V2(0, 0);
e->local_collider.count = 1; 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); struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size);
//xf.bx.y = -1.f; //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; 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 */ /* Cap movement vector magnitude at 1 */
control->move = v2_norm(control->move); 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; u32 flags = control->flags;
if (flags & SIM_CONTROL_FLAG_DRAGGING) { if (flags & SIM_CONTROL_FLAG_DRAGGING) {
@ -594,9 +610,12 @@ struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct
} }
} }
if (flags & SIM_CONTROL_FLAG_CLEAR_ALL) { if (flags & SIM_CONTROL_FLAG_CLEAR_ALL) {
if (!(old_control.flags & SIM_CONTROL_FLAG_CLEAR_ALL)) {
test_clear_level(world); test_clear_level(world);
} }
}
if (flags & SIM_CONTROL_FLAG_SPAWN_TEST) { if (flags & SIM_CONTROL_FLAG_SPAWN_TEST) {
if (!(old_control.flags & SIM_CONTROL_FLAG_SPAWN_TEST)) {
logf_info("Spawning (test)"); logf_info("Spawning (test)");
u32 count = 1; u32 count = 1;
f32 spread = 1; f32 spread = 1;
@ -604,6 +623,7 @@ struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct
spawn_test_entities(world, V2(0, (((f32)j / (f32)count) - 0.5) * spread)); spawn_test_entities(world, V2(0, (((f32)j / (f32)count) - 0.5) * spread));
} }
} }
}
} break; } break;
/* Disconnect client */ /* Disconnect client */
@ -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 */ /* Set correction rate dynamically since motor velocity is only set for one frame */
joint_ent->motor_joint_data.correction_rate = 10 * world_dt; joint_ent->motor_joint_data.correction_rate = 10 * world_dt;
/* Solve for final angle using law of sines */ /* Solve for final angle using law of sines */
f32 new_angle; 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.x);
bw_write_f32(bw, cmd->control.focus.y); bw_write_f32(bw, cmd->control.focus.y);
bw_write_ubits(bw, cmd->control.flags, 32); 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 #if COLLIDER_DEBUG
cmd->collider_gjk_steps = br_read_ubits(&br, 32); cmd->collider_gjk_steps = br_read_ubits(&br, 32);
#endif #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.x = br_read_f32(&br);
cmd->control.focus.y = br_read_f32(&br); cmd->control.focus.y = br_read_f32(&br);
cmd->control.flags = br_read_ubits(&br, 32); 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 #if COLLIDER_DEBUG
cmd->collider_gjk_steps = br_read_ubits(&br, 32); cmd->collider_gjk_steps = br_read_ubits(&br, 32);
#endif #endif

View File

@ -65,7 +65,6 @@ struct sim_cmd {
/* ====================================================================== */ /* ====================================================================== */
/* SIM_CMD_KIND_CLIENT_CONTROL */ /* SIM_CMD_KIND_CLIENT_CONTROL */
struct v2 cursor_pos;
struct sim_control control; struct sim_control control;
#if RTC #if RTC

View File

@ -42,6 +42,8 @@ struct sim_snapshot {
struct arena arena; struct arena arena;
i64 published_at_ns;
/* Real time (increases with clock assuming no lag) */ /* Real time (increases with clock assuming no lag) */
i64 real_dt_ns; i64 real_dt_ns;
i64 real_time_ns; i64 real_time_ns;

View File

@ -89,22 +89,30 @@ GLOBAL struct {
struct sys_mutex user_sim_cmd_mutex; struct sys_mutex user_sim_cmd_mutex;
struct sim_control user_sim_cmd_control; struct sim_control user_sim_cmd_control;
struct v2 user_sim_cmd_control_cursor_pos; struct v2 user_sim_cmd_control_cursor_pos;
u64 last_user_sim_cmd_gen;
u64 user_sim_cmd_gen;
u64 user_sim_cmd_ack; u64 user_sim_cmd_ack;
/* Local sim -> user */ /* Local sim -> user */
struct sys_mutex local_sim_ss_mutex; struct sys_mutex local_sim_ss_mutex;
struct sim_snapshot_store *local_sim_ss_store; struct sim_snapshot_store *local_sim_ss_store;
i64 real_dt_ns; /* Rolling window of local sim publish times */
i64 real_time_ns; i64 snapshot_publish_times_ns[50];
i64 snapshot_publish_times_index;
u64 local_sim_last_known_tick; i64 last_snapshot_published_at_ns;
i64 local_sim_last_known_time_ns; i64 average_snapshot_publish_dt_ns;
i64 last_snapshot_received_at_ns;
/* Calculated from <last snapshot receive time + time since packet receive> */ /* Calculated from <last snapshot receive time + time since packet receive> */
i64 local_sim_predicted_time_ns; 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 */ /* Per-frame */
struct v2 screen_size; struct v2 screen_size;
@ -407,34 +415,91 @@ 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_sim_ss_store, 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); sim_snapshot_alloc(G.unblended_snapshot_store, src, src->tick);
#if 0
G.last_snapshot_received_at_ns = G.real_time_ns; 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); 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 * 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); 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_time_ns = newest_snapshot->real_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; u64 keep_unblended_tick = newest_snapshot->tick;
G.local_sim_predicted_time_ns = newest_snapshot->real_time_ns + (newest_snapshot->real_dt_ns * tick_progress);
/* 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 */
#if USER_INTERP_ENABLED #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 */ /* Get two snapshots nearest to render time */
struct sim_snapshot *left_snapshot = sim_snapshot_nil(); struct sim_snapshot *left_snapshot = sim_snapshot_nil();
@ -444,10 +509,10 @@ INTERNAL void user_update(void)
while (ss->valid) { while (ss->valid) {
u64 next_tick = ss->next_tick; u64 next_tick = ss->next_tick;
i64 ss_time_ns = ss->real_time_ns; 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; 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; right_snapshot = ss;
} }
ss = sim_snapshot_from_tick(G.unblended_snapshot_store, next_tick); 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 */ /* Create world from blended snapshots */
if (left_snapshot->valid && right_snapshot->valid) { 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); G.ss_blended = sim_snapshot_alloc_from_lerp(G.blended_snapshot_store, left_snapshot, right_snapshot, blend);
} else if (left_snapshot->valid) { } else if (left_snapshot->valid) {
G.ss_blended = sim_snapshot_alloc(G.blended_snapshot_store, left_snapshot, left_snapshot->tick); 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); G.ss_blended = sim_snapshot_alloc(G.blended_snapshot_store, right_snapshot, right_snapshot->tick);
} }
#else #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->tick != newest_snapshot->tick) {
if (G.ss_blended->valid) { if (G.ss_blended->valid) {
sim_snapshot_release(G.ss_blended); 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 */ /* Queue player control cmd */
{ {
struct sim_control control = ZI; struct sim_control control = ZI;
if (!G.debug_camera) {
control.move = input_move_dir; control.move = input_move_dir;
control.focus = input_aim_dir; control.focus = input_aim_dir;
struct bind_state fire_state = G.bind_states[USER_BIND_KIND_FIRE]; 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 drag_state = G.bind_states[USER_BIND_KIND_DEBUG_DRAG]; 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 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 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 step_state = G.bind_states[USER_BIND_KIND_DEBUG_STEP];
struct bind_state spawn_state = G.bind_states[USER_BIND_KIND_DEBUG_SPAWN]; 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) { if (drag_state.num_presses || drag_state.is_held) {
control.flags |= SIM_CONTROL_FLAG_DRAGGING; control.flags |= SIM_CONTROL_FLAG_DRAGGING;
} }
@ -1379,6 +1443,13 @@ INTERNAL void user_update(void)
/* Set user sim control */ /* Set user sim control */
{ {
struct sys_lock lock = sys_mutex_lock_e(&G.user_sim_cmd_mutex); 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; u32 old_flags = G.user_sim_cmd_control.flags;
G.user_sim_cmd_control = control; G.user_sim_cmd_control = control;
G.user_sim_cmd_control.flags |= old_flags; G.user_sim_cmd_control.flags |= old_flags;
@ -1424,16 +1495,19 @@ INTERNAL void user_update(void)
pos.y += spacing; pos.y += spacing;
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; 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; 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;
pos.y += spacing; pos.y += spacing;
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 tick: %F"), FMT_UINT(G.local_sim_last_known_tick))); 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; pos.y += spacing;
@ -1443,7 +1517,10 @@ INTERNAL void user_update(void)
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 predicted time: %F"), FMT_FLOAT_P(SECONDS_FROM_NS(G.local_sim_predicted_time_ns), 3)));
pos.y += spacing; 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("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;
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); struct sim_cmd *user_cmd = arena_push_zero(scratch.arena, struct sim_cmd);
user_cmd->kind = SIM_CMD_KIND_CLIENT_CONTROL; user_cmd->kind = SIM_CMD_KIND_CLIENT_CONTROL;
user_cmd->control = G.user_sim_cmd_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->first = user_cmd;
user_cmd_frame->last = user_cmd; user_cmd_frame->last = user_cmd;
++G.user_sim_cmd_gen;
G.user_sim_cmd_control.flags = 0;
sys_mutex_unlock(&lock); sys_mutex_unlock(&lock);
} }
if (input_cmds.last) { 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 */ /* TODO: Double buffer */
{ {
struct sys_lock lock = sys_mutex_lock_e(&G.local_sim_ss_mutex); struct sys_lock lock = sys_mutex_lock_e(&G.local_sim_ss_mutex);
sim_snapshot_alloc(G.local_sim_ss_store, ss, ss->tick); struct sim_snapshot *pub_ss = 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); 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); sys_mutex_unlock(&lock);
} }