From 7e7e6a8f87abdd832e545f05a02b8017141bd551 Mon Sep 17 00:00:00 2001 From: jacob Date: Thu, 27 Feb 2025 18:07:49 -0600 Subject: [PATCH] debug follow --- res/graphics/tim.ase | 4 +- src/common.h | 1 - src/config.h | 2 +- src/phys.c | 10 ++--- src/sim.c | 2 +- src/sim.h | 4 +- src/sim_ent.c | 59 ++++++++++++------------- src/sim_ent.h | 22 ++++++++-- src/sim_step.c | 16 ++++--- src/user.c | 101 ++++++++++++++++++++++++++++++------------- src/user.h | 1 + 11 files changed, 141 insertions(+), 81 deletions(-) diff --git a/res/graphics/tim.ase b/res/graphics/tim.ase index 64e7efae..f975bd8d 100644 --- a/res/graphics/tim.ase +++ b/res/graphics/tim.ase @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:04f924874a610ead86b474ce4b2299eed812a56b88bbb99f82c06c9098269786 -size 3856 +oid sha256:cd0019692bfc14d9c0ad8b9140145ff81e49b26615a3636007d9b5fc01566aa8 +size 6087 diff --git a/src/common.h b/src/common.h index e166163a..e8d31664 100644 --- a/src/common.h +++ b/src/common.h @@ -450,7 +450,6 @@ struct sim_client_handle { struct sim_ent_id { struct uid uid; }; -INLINE b32 sim_ent_id_eq(struct sim_ent_id a, struct sim_ent_id b) { return uid_eq(a.uid, b.uid); } struct space_entry_handle { u64 idx; diff --git a/src/config.h b/src/config.h index 855f7654..93d477a4 100644 --- a/src/config.h +++ b/src/config.h @@ -59,7 +59,7 @@ #define COLLIDER_DEBUG_DETAILED_DRAW_MENKOWSKI 0 /* If enabled, bitbuffs will insert/verify magic numbers & length for each read & write */ -#define BITBUFF_DEBUG RTC +#define BITBUFF_DEBUG 0 #define BITBUFF_TEST RTC /* If enabled, things like network writes & memory allocations will be tracked in a global statistics struct */ diff --git a/src/phys.c b/src/phys.c index 6bec2c4f..6c23fffc 100644 --- a/src/phys.c +++ b/src/phys.c @@ -274,7 +274,7 @@ void phys_prepare_contacts(struct phys_step_ctx *ctx, u64 phys_iteration) for (u64 sim_ent_index = 0; sim_ent_index < ss->num_ents_reserved; ++sim_ent_index) { struct sim_ent *constraint_ent = &ss->ents[sim_ent_index]; - if (!sim_ent_is_valid_and_active(constraint_ent)) continue; + if (!sim_ent_should_simulate(constraint_ent)) continue; if (!sim_ent_has_prop(constraint_ent, SIM_ENT_PROP_CONTACT_CONSTRAINT)) continue; struct phys_contact_constraint *constraint = &constraint_ent->contact_constraint_data; @@ -371,7 +371,7 @@ void phys_prepare_contacts(struct phys_step_ctx *ctx, u64 phys_iteration) struct sim_lookup *debug_lookup = ctx->debug_lookup; for (u64 sim_ent_index = 0; sim_ent_index < ss->num_ents_reserved; ++sim_ent_index) { struct sim_ent *dbg_ent = &ss->ents[sim_ent_index]; - if (!sim_ent_is_valid_and_active(dbg_ent)) continue; + if (!sim_ent_should_simulate(dbg_ent)) continue; if (!sim_ent_has_prop(dbg_ent, SIM_ENT_PROP_COLLISION_DEBUG)) continue; struct phys_collision_debug *dbg = &dbg_ent->collision_debug_data; @@ -379,7 +379,7 @@ void phys_prepare_contacts(struct phys_step_ctx *ctx, u64 phys_iteration) struct sim_ent *e1 = sim_ent_from_id(ss, dbg->e1); - if (!sim_ent_is_valid_and_active(e0) || !sim_ent_is_valid_and_active(e1) + if (!sim_ent_should_simulate(e0) || !sim_ent_should_simulate(e1) || !(sim_ent_has_prop(e0, SIM_ENT_PROP_PHYSICAL_DYNAMIC) || sim_ent_has_prop(e0, SIM_ENT_PROP_PHYSICAL_KINEMATIC)) || !(sim_ent_has_prop(e1, SIM_ENT_PROP_PHYSICAL_DYNAMIC) || sim_ent_has_prop(e1, SIM_ENT_PROP_PHYSICAL_KINEMATIC))) { /* Mark dbg ent for removal */ @@ -406,7 +406,7 @@ void phys_warm_start_contacts(struct phys_step_ctx *ctx) struct sim_snapshot *ss = ctx->sim_step_ctx->world; for (u64 sim_ent_index = 0; sim_ent_index < ss->num_ents_reserved; ++sim_ent_index) { struct sim_ent *constraint_ent = &ss->ents[sim_ent_index]; - if (!sim_ent_is_valid_and_active(constraint_ent)) continue; + if (!sim_ent_should_simulate(constraint_ent)) continue; if (!sim_ent_has_prop(constraint_ent, SIM_ENT_PROP_CONTACT_CONSTRAINT)) continue; struct phys_contact_constraint *constraint = &constraint_ent->contact_constraint_data; @@ -461,7 +461,7 @@ void phys_solve_contacts(struct phys_step_ctx *ctx, f32 dt, b32 apply_bias) struct sim_snapshot *ss = ctx->sim_step_ctx->world; for (u64 sim_ent_index = 0; sim_ent_index < ss->num_ents_reserved; ++sim_ent_index) { struct sim_ent *constraint_ent = &ss->ents[sim_ent_index]; - if (!sim_ent_is_valid_and_active(constraint_ent)) continue; + if (!sim_ent_should_simulate(constraint_ent)) continue; if (!sim_ent_has_prop(constraint_ent, SIM_ENT_PROP_CONTACT_CONSTRAINT)) continue; struct phys_contact_constraint *constraint = &constraint_ent->contact_constraint_data; diff --git a/src/sim.c b/src/sim.c index 5ac59906..b5c50668 100644 --- a/src/sim.c +++ b/src/sim.c @@ -620,7 +620,7 @@ void sim_snapshot_sync_ents(struct sim_snapshot *local_ss, struct sim_snapshot * for (u64 i = 2; i < local_ss->num_ents_reserved; ++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)) { - b32 should_sync = sim_ent_id_eq(local_ent->owner, remote_client_ent) || sim_ent_id_eq(remote_client_ent, SIM_ENT_NIL_ID); + b32 should_sync = sim_ent_id_eq(local_ent->owner, remote_client_ent) || sim_ent_id_is_nil(remote_client_ent); if (should_sync) { struct sim_ent *remote_ent = sim_ent_from_id(remote_ss, local_ent->id); if (remote_ent->valid) { diff --git a/src/sim.h b/src/sim.h index a371b858..8de970e5 100644 --- a/src/sim.h +++ b/src/sim.h @@ -72,7 +72,9 @@ struct sim_client { struct arena snapshots_arena; - i64 rtt_ns; /* Round trip time of the client (if networked) */ + /* Round trip time of the client (if networked) */ + i64 rtt_ns; + struct host_channel_id channel_id; u64 channel_hash; diff --git a/src/sim_ent.c b/src/sim_ent.c index 19480c30..db442435 100644 --- a/src/sim_ent.c +++ b/src/sim_ent.c @@ -25,9 +25,11 @@ INTERNAL struct sim_ent *ent_from_index(struct sim_snapshot *ss, u32 index) * Ent allocation * ========================== */ -/* Allocates an entity with no parent & no id (these must be set immediately after by the caller) */ -struct sim_ent *sim_ent_alloc_raw(struct sim_snapshot *ss) +struct sim_ent *sim_ent_alloc_raw(struct sim_snapshot *ss, struct sim_ent *parent, struct sim_ent_id id) { + ASSERT(parent->valid); + ASSERT(ss->valid); + ASSERT(ss == parent->ss); struct sim_ent *ent; if (ss->first_free_ent > 0 && ss->first_free_ent < ss->num_ents_reserved) { /* Reuse from free list */; @@ -44,61 +46,56 @@ struct sim_ent *sim_ent_alloc_raw(struct sim_snapshot *ss) ent->owner = ss->client->ent_id; ent->_is_xform_dirty = true; ++ss->num_ents_allocated; + + sim_ent_set_id(ent, id); + sim_ent_link_parent(ent, parent); + return ent; } /* Allocates a new entity that will not sync */ struct sim_ent *sim_ent_alloc_local(struct sim_ent *parent) { - ASSERT(parent->valid); struct sim_snapshot *ss = parent->ss; - struct sim_ent *e = sim_ent_alloc_raw(ss); - sim_ent_set_id(e, sim_ent_random_id()); - - sim_ent_link_parent(e, parent); - + struct sim_ent *e = sim_ent_alloc_raw(ss, parent, sim_ent_random_id()); + e->owner = ss->local_client_ent; return e; } struct sim_ent *sim_ent_alloc_local_with_id(struct sim_ent *parent, struct sim_ent_id id) { - ASSERT(parent->valid); struct sim_snapshot *ss = parent->ss; - struct sim_ent *e = sim_ent_alloc_raw(ss); - sim_ent_set_id(e, id); - - sim_ent_link_parent(e, parent); - + struct sim_ent *e = sim_ent_alloc_raw(ss, parent, id); + e->owner = ss->local_client_ent; return e; } -/* Allocates a new entity with a random uid to be synced to clients */ +/* Allocates a new entity to be synced to clients */ struct sim_ent *sim_ent_alloc_sync_src(struct sim_ent *parent) { struct sim_snapshot *ss = parent->ss; - struct sim_ent *e = sim_ent_alloc_raw(ss); - sim_ent_set_id(e, sim_ent_random_id()); - + struct sim_ent *e = sim_ent_alloc_raw(ss, parent, sim_ent_random_id()); sim_ent_enable_prop(e, SIM_ENT_PROP_SYNC_SRC); e->owner = ss->local_client_ent; + return e; +} - sim_ent_link_parent(e, parent); - +struct sim_ent *sim_ent_alloc_sync_src_with_id(struct sim_ent *parent, struct sim_ent_id id) +{ + struct sim_snapshot *ss = parent->ss; + struct sim_ent *e = sim_ent_alloc_raw(ss, parent, id); + sim_ent_enable_prop(e, SIM_ENT_PROP_SYNC_SRC); + e->owner = ss->local_client_ent; return e; } /* Allocates a new entity that will sync with incoming net src ents containing id, and coming from the specified owner */ -struct sim_ent *sim_ent_alloc_sync_dst(struct sim_ent *parent, struct sim_ent_id ent_id, struct sim_ent_id owner_client_ent_id) +struct sim_ent *sim_ent_alloc_sync_dst(struct sim_ent *parent, struct sim_ent_id ent_id, struct sim_ent_id owner_id) { struct sim_snapshot *ss = parent->ss; - struct sim_ent *e = sim_ent_alloc_raw(ss); - sim_ent_set_id(e, ent_id); - - sim_ent_link_parent(e, parent); - + struct sim_ent *e = sim_ent_alloc_raw(ss, parent, ent_id); sim_ent_enable_prop(e, SIM_ENT_PROP_SYNC_DST); - e->owner = owner_client_ent_id; - + e->owner = owner_id; return e; } @@ -187,7 +184,7 @@ void sim_ent_set_id(struct sim_ent *ent, struct sim_ent_id id) struct sim_ent_id old_id = ent->id; if (!sim_ent_id_eq(old_id, id)) { /* Release old from lookup */ - if (!sim_ent_id_eq(old_id, SIM_ENT_NIL_ID)) { + if (!sim_ent_id_is_nil(old_id)) { struct sim_ent_bin *bin = bin_from_id(ss, old_id); u32 prev_index = 0; u32 next_index = 0; @@ -224,7 +221,7 @@ void sim_ent_set_id(struct sim_ent *ent, struct sim_ent_id id) } /* Insert new id into lookup */ - if (!sim_ent_id_eq(id, SIM_ENT_NIL_ID)) { + if (!sim_ent_id_is_nil(id)) { #if RTC { struct sim_ent *existing = sim_ent_from_id(ss, id); @@ -254,7 +251,7 @@ void sim_ent_set_id(struct sim_ent *ent, struct sim_ent_id id) struct sim_ent *sim_ent_from_id(struct sim_snapshot *ss, struct sim_ent_id id) { struct sim_ent *res = sim_ent_nil(); - if (!sim_ent_id_eq(id, SIM_ENT_NIL_ID) && ss->valid) { + if (!sim_ent_id_is_nil(id) && ss->valid) { struct sim_ent_bin *bin = bin_from_id(ss, id); for (struct sim_ent *e = ent_from_index(ss, bin->first); e->valid; e = ent_from_index(ss, e->next_in_id_bin)) { if (sim_ent_id_eq(e->id, id)) { diff --git a/src/sim_ent.h b/src/sim_ent.h index 3f8e9612..e08292f7 100644 --- a/src/sim_ent.h +++ b/src/sim_ent.h @@ -154,7 +154,8 @@ struct sim_ent { b32 client_dbg_drag_stop; /* Client round-trip-time to server */ - i64 client_rtt_ns; + i64 client_last_rtt_ns; + f64 client_average_rtt_seconds; /* ====================================================================== */ /* Collider */ @@ -351,6 +352,20 @@ INLINE struct sim_ent *sim_ent_nil(void) return *_g_sim_ent_nil; } +/* ========================== * + * Id helpers + * ========================== */ + +INLINE b32 sim_ent_id_eq(struct sim_ent_id a, struct sim_ent_id b) +{ + return uid_eq(a.uid, b.uid); +} + +INLINE b32 sim_ent_id_is_nil(struct sim_ent_id id) +{ + return uid_eq(id.uid, SIM_ENT_NIL_ID.uid); +} + /* ========================== * * Property helpers * ========================== */ @@ -409,11 +424,12 @@ INLINE b32 sim_ent_should_simulate(struct sim_ent *ent) * ========================== */ /* Alloc */ -struct sim_ent *sim_ent_alloc_raw(struct sim_snapshot *ss); +struct sim_ent *sim_ent_alloc_raw(struct sim_snapshot *ss, struct sim_ent *parent, struct sim_ent_id id); struct sim_ent *sim_ent_alloc_local(struct sim_ent *parent); struct sim_ent *sim_ent_alloc_local_with_id(struct sim_ent *parent, struct sim_ent_id id); struct sim_ent *sim_ent_alloc_sync_src(struct sim_ent *parent); -struct sim_ent *sim_ent_alloc_sync_dst(struct sim_ent *parent, struct sim_ent_id ent_id, struct sim_ent_id owner_client_ent_id); +struct sim_ent *sim_ent_alloc_sync_src_with_id(struct sim_ent *parent, struct sim_ent_id id); +struct sim_ent *sim_ent_alloc_sync_dst(struct sim_ent *parent, struct sim_ent_id ent_id, struct sim_ent_id owner_id); void sim_ent_release_raw(struct sim_ent *ent); void sim_ent_release(struct sim_ent *ent); diff --git a/src/sim_step.c b/src/sim_step.c index 9b67e1b3..04d17053 100644 --- a/src/sim_step.c +++ b/src/sim_step.c @@ -75,7 +75,7 @@ INTERNAL void spawn_test_entities(struct sim_step_ctx *ctx, struct v2 offset) e->layer = SIM_LAYER_SHOULDERS; sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC); - e->mass_unscaled = 10; + e->mass_unscaled = 100; e->inertia_unscaled = 10; e->linear_ground_friction = 250; e->angular_ground_friction = 200; @@ -392,7 +392,11 @@ void sim_step(struct sim_step_ctx *ctx) /* Update rtt */ if (is_master && client_ent->valid) { - client_ent->client_rtt_ns = client->rtt_ns; + client_ent->client_last_rtt_ns = client->rtt_ns; + f64 avg = client_ent->client_average_rtt_seconds; + avg -= avg / 200; + avg += SECONDS_FROM_NS(client->rtt_ns) / 200; + client_ent->client_average_rtt_seconds = avg; } /* Sync ents from client */ @@ -427,7 +431,7 @@ void sim_step(struct sim_step_ctx *ctx) for (u64 i = 0; i < world->num_ents_reserved; ++i) { struct sim_ent *ent = &world->ents[i]; if (sim_ent_is_valid_and_active(ent) && sim_ent_has_prop(ent, SIM_ENT_PROP_CMD_CONTROL)) { - if (!sim_ent_id_eq(ent->cmd_client, SIM_ENT_NIL_ID) && sim_ent_id_eq(ent->cmd_client, world->local_client_ent)) { + if (!sim_ent_id_is_nil(ent->cmd_client) && sim_ent_id_eq(ent->cmd_client, world->local_client_ent)) { sim_ent_enable_prop(ent, SIM_ENT_PROP_SYNC_SRC); } } @@ -508,7 +512,7 @@ void sim_step(struct sim_step_ctx *ctx) { struct sim_ent_id client_control_ent_id = client_ent->client_control_ent; struct sim_ent *client_control_ent = sim_ent_from_id(world, client_control_ent_id); - if (client_control_ent->valid || sim_ent_id_eq(client_control_ent_id, SIM_ENT_NIL_ID)) { + if (client_control_ent->valid || sim_ent_id_is_nil(client_control_ent_id)) { /* Only update cursor pos if focus ent is valid (or nil) */ client_ent->client_cursor_pos = v2_add(sim_ent_get_xform(client_control_ent).og, client_ent->client_control.focus); } @@ -620,7 +624,7 @@ void sim_step(struct sim_step_ctx *ctx) frame_index = span.start; } - if (span.end > span.start + 1) { + if (span.end > span.start) { struct sprite_sheet_frame frame = sprite_sheet_get_frame(sheet, frame_index); while (time_in_frame > frame.duration) { time_in_frame -= frame.duration; @@ -1040,7 +1044,7 @@ void sim_step(struct sim_step_ctx *ctx) if (sim_ent_should_simulate(target_ent)) { if (!sim_ent_is_valid_and_active(joint_ent)) { /* FIXME: Joint ent may never release */ - joint_ent = sim_ent_alloc_sync_src(root); + joint_ent = sim_ent_alloc_local(root); joint_ent->mass_unscaled = F32_INFINITY; joint_ent->inertia_unscaled = F32_INFINITY; client_ent->client_dbg_drag_joint_ent = joint_ent->id; diff --git a/src/user.c b/src/user.c index 37110e61..72063d7c 100644 --- a/src/user.c +++ b/src/user.c @@ -73,6 +73,7 @@ GLOBAL struct { struct bind_state bind_states[USER_BIND_KIND_COUNT]; + struct sim_ent_id debug_following; b32 debug_camera; b32 debug_camera_panning; struct v2 debug_camera_pan_start; @@ -141,6 +142,7 @@ GLOBAL READONLY enum user_bind_kind g_binds[SYS_BTN_COUNT] = { [SYS_BTN_C] = USER_BIND_KIND_DEBUG_CLEAR, [SYS_BTN_V] = USER_BIND_KIND_DEBUG_SPAWN, [SYS_BTN_N] = USER_BIND_KIND_DEBUG_STEP, + [SYS_BTN_Q] = USER_BIND_KIND_DEBUG_FOLLOW, [SYS_BTN_F1] = USER_BIND_KIND_DEBUG_PAUSE, [SYS_BTN_F2] = USER_BIND_KIND_DEBUG_CAMERA, [SYS_BTN_F3] = USER_BIND_KIND_DEBUG_DRAW, @@ -388,7 +390,7 @@ INTERNAL struct string get_ent_debug_text(struct arena *arena, struct sim_ent *e res.len += string_format(arena, LIT("parent: [%F]\n"), FMT_UID(ent->parent.uid)).len; } - if (!sim_ent_id_eq(ent->next, SIM_ENT_NIL_ID) || !sim_ent_id_eq(ent->prev, SIM_ENT_NIL_ID)) { + if (!sim_ent_id_is_nil(ent->next) || !sim_ent_id_is_nil(ent->prev)) { res.len += string_format(arena, LIT("prev: [%F]\n"), FMT_UID(ent->prev.uid)).len; res.len += string_format(arena, LIT("next: [%F]\n"), FMT_UID(ent->next.uid)).len; } @@ -404,7 +406,7 @@ INTERNAL struct string get_ent_debug_text(struct arena *arena, struct sim_ent *e res.len += string_format(arena, LIT("angular velocity: %F\n"), FMT_FLOAT_P(angular_velocity, 3)).len; /* Children */ - if (!sim_ent_id_eq(ent->first, SIM_ENT_NIL_ID) || !sim_ent_id_eq(ent->last, SIM_ENT_NIL_ID)) { + if (!sim_ent_id_is_nil(ent->first) || !sim_ent_id_is_nil(ent->last)) { struct sim_ent *child = sim_ent_from_id(ss, ent->first); if (!sim_ent_id_eq(ent->first, ent->last) || !child->valid) { res.len += string_format(arena, LIT("first child: [%F]\n"), FMT_UID(ent->first.uid)).len; @@ -679,28 +681,6 @@ INTERNAL void user_update(void) } } - /* ========================== * - * Update user state from binds - * ========================== */ - - /* Test fullscreen */ - { - struct bind_state state = G.bind_states[USER_BIND_KIND_FULLSCREEN]; - if (state.num_presses) { - struct sys_window_settings settings = sys_window_get_settings(G.window); - settings.flags ^= SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN; - sys_window_update_settings(G.window, &settings); - } - } - - if (G.bind_states[USER_BIND_KIND_DEBUG_DRAW].num_presses > 0) { - G.debug_draw = !G.debug_draw; - } - - if (G.bind_states[USER_BIND_KIND_DEBUG_CAMERA].num_presses > 0) { - G.debug_camera = !G.debug_camera; - } - /* ========================== * * Find local entities * ========================== */ @@ -738,6 +718,55 @@ INTERNAL void user_update(void) } } + /* ========================== * + * Update user state from binds + * ========================== */ + + /* Test fullscreen */ + { + struct bind_state state = G.bind_states[USER_BIND_KIND_FULLSCREEN]; + if (state.num_presses) { + struct sys_window_settings settings = sys_window_get_settings(G.window); + settings.flags ^= SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN; + sys_window_update_settings(G.window, &settings); + } + } + + if (G.bind_states[USER_BIND_KIND_DEBUG_DRAW].num_presses > 0) { + G.debug_draw = !G.debug_draw; + } + + if (G.bind_states[USER_BIND_KIND_DEBUG_CAMERA].num_presses > 0) { + G.debug_camera = !G.debug_camera; + } + + { + if (G.bind_states[USER_BIND_KIND_DEBUG_FOLLOW].num_presses > 0) { + if (sim_ent_id_is_nil(G.debug_following)) { + G.debug_following = hovered_ent->id; + } else { + G.debug_following = SIM_ENT_NIL_ID; + } + } + if (!sim_ent_id_is_nil(G.debug_following)) { + struct sim_ent *follow_ent = sim_ent_from_id(G.ss_blended, G.debug_following); + struct sim_ent *follow_camera = sim_ent_nil(); + for (u64 i = 0; i < G.ss_blended->num_ents_reserved; ++i) { + struct sim_ent *ent = &G.ss_blended->ents[i]; + struct sim_ent *ent_camera_follow = sim_ent_from_id(G.ss_blended, ent->camera_follow); + if (ent_camera_follow->valid && ent_camera_follow == follow_ent) { + follow_camera = ent; + break; + } + } + if (follow_camera->valid) { + local_camera = follow_camera; + } else { + G.debug_following = SIM_ENT_NIL_ID; + } + } + } + /* ========================== * * Apply shake * ========================== */ @@ -1645,7 +1674,10 @@ INTERNAL void user_update(void) draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("Network write: %F mbit/s"), FMT_FLOAT_P((f64)G.net_bytes_sent.last_second * 8 / 1000 / 1000, 3))); pos.y += spacing; - draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("Ping: %F ms"), FMT_FLOAT_P(SECONDS_FROM_NS(local_client_ent->client_rtt_ns) * 1000, 3))); + draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("Ping (real): %F ms"), FMT_FLOAT_P(SECONDS_FROM_NS(local_client_ent->client_last_rtt_ns) * 1000, 3))); + pos.y += spacing; + + draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("Ping (average): %F ms"), FMT_FLOAT_P(local_client_ent->client_average_rtt_seconds * 1000, 3))); pos.y += spacing; pos.y += spacing; @@ -2266,7 +2298,6 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) u64 master_tick = master_client->last_tick; struct sim_snapshot *master_ss = sim_snapshot_from_tick(master_client, master_tick); struct sim_ent *master_client_ent = sim_ent_find_first_match_one(master_ss, SIM_ENT_PROP_CLIENT_IS_MASTER); - user_input_client->ent_id = master_ss->local_client_ent; local_client->ent_id = master_ss->local_client_ent; @@ -2288,23 +2319,33 @@ 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. + * 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. + * + * 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. + * + * 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. + */ { i64 cmds_ahead_on_master = (i64)master_client->ack - (i64)master_client->last_tick; if (math_abs_i64(cmds_ahead_on_master) > 50) { /* Cmds are too far from master time, snap step end tick */ 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; + i64 num_predict_ticks = math_round_to_int64(rtt_tick_ratio) + 5; step_end_tick = step_base_tick + num_predict_ticks; compute_timescale = 1.1; } else if (cmds_ahead_on_master > 2) { /* Slow down simulation rate to bring sim time closer to master time */ - compute_timescale = 0.9; + compute_timescale = 0.95; } else if (cmds_ahead_on_master < 1) { /* Speed up simulation rate to give master more inputs to work with */ - compute_timescale = 1.1; + compute_timescale = 1.05; } else { + /* Server's cmd buffer is in a healthy range */ compute_timescale = 1; } } diff --git a/src/user.h b/src/user.h index 78e248fe..1bfab82b 100644 --- a/src/user.h +++ b/src/user.h @@ -28,6 +28,7 @@ enum user_bind_kind { USER_BIND_KIND_DEBUG_CLEAR, USER_BIND_KIND_DEBUG_SPAWN, + USER_BIND_KIND_DEBUG_FOLLOW, USER_BIND_KIND_DEBUG_DRAW, USER_BIND_KIND_DEBUG_CAMERA, USER_BIND_KIND_DEBUG_PAUSE,