prediction progress

This commit is contained in:
jacob 2025-02-21 19:46:04 -06:00
parent 4d8819d3ee
commit d05ecbabb9
6 changed files with 104 additions and 84 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 100 #define SIM_TICKS_PER_SECOND 50
#define SIM_TIMESCALE 1 #define SIM_TIMESCALE 1
#define SIM_PHYSICS_SUBSTEPS 4 #define SIM_PHYSICS_SUBSTEPS 4
@ -62,7 +62,7 @@
#define COLLIDER_DEBUG_DETAILED_DRAW_MENKOWSKI 0 #define COLLIDER_DEBUG_DETAILED_DRAW_MENKOWSKI 0
/* If enabled, bitbuffs will insert/verify magic numbers & length for each read & write */ /* 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 #define BITBUFF_TEST RTC
/* ========================== * /* ========================== *

View File

@ -363,7 +363,7 @@ void phys_prepare_contacts(struct phys_step_ctx *ctx, u64 phys_iteration)
/* Mark constraint for removal */ /* Mark constraint for removal */
constraint_ent->contact_constraint_data.num_points = 0; constraint_ent->contact_constraint_data.num_points = 0;
sim_ent_disable_prop(constraint_ent, SIM_ENT_PROP_ACTIVE); sim_ent_disable_prop(constraint_ent, SIM_ENT_PROP_ACTIVE);
sim_ent_enable_prop(constraint_ent, SIM_ENT_PROP_RELEASE_THIS_TICK); sim_ent_enable_prop(constraint_ent, SIM_ENT_PROP_RELEASE);
/* Remove from lookup */ /* Remove from lookup */
struct sim_lookup_key key = sim_lookup_key_from_two_handles(constraint->e0, constraint->e1); struct sim_lookup_key key = sim_lookup_key_from_two_handles(constraint->e0, constraint->e1);
struct sim_lookup_entry *entry = sim_lookup_get(contact_lookup, key); struct sim_lookup_entry *entry = sim_lookup_get(contact_lookup, key);
@ -392,7 +392,7 @@ void phys_prepare_contacts(struct phys_step_ctx *ctx, u64 phys_iteration)
|| !(sim_ent_has_prop(e1, SIM_ENT_PROP_PHYSICAL_DYNAMIC) || sim_ent_has_prop(e1, 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 */ /* Mark dbg ent for removal */
sim_ent_disable_prop(dbg_ent, SIM_ENT_PROP_ACTIVE); sim_ent_disable_prop(dbg_ent, SIM_ENT_PROP_ACTIVE);
sim_ent_enable_prop(dbg_ent, SIM_ENT_PROP_RELEASE_THIS_TICK); sim_ent_enable_prop(dbg_ent, SIM_ENT_PROP_RELEASE);
/* Remove from lookup */ /* Remove from lookup */
struct sim_lookup_key key = sim_lookup_key_from_two_handles(dbg->e0, dbg->e1); struct sim_lookup_key key = sim_lookup_key_from_two_handles(dbg->e0, dbg->e1);
@ -653,7 +653,7 @@ void phys_prepare_motor_joints(struct phys_step_ctx *ctx)
} else { } else {
/* Mark joint for removal */ /* Mark joint for removal */
sim_ent_disable_prop(joint_ent, SIM_ENT_PROP_ACTIVE); sim_ent_disable_prop(joint_ent, SIM_ENT_PROP_ACTIVE);
sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_RELEASE_THIS_TICK); sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_RELEASE);
} }
} }
} }
@ -825,7 +825,7 @@ void phys_prepare_mouse_joints(struct phys_step_ctx *ctx)
} else { } else {
/* Mark joint for removal */ /* Mark joint for removal */
sim_ent_disable_prop(joint_ent, SIM_ENT_PROP_ACTIVE); sim_ent_disable_prop(joint_ent, SIM_ENT_PROP_ACTIVE);
sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_RELEASE_THIS_TICK); sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_RELEASE);
} }
} }
} }

View File

@ -17,8 +17,7 @@ enum sim_ent_prop {
SIM_ENT_PROP_ACTIVE, SIM_ENT_PROP_ACTIVE,
SIM_ENT_PROP_RELEASE_THIS_TICK, SIM_ENT_PROP_RELEASE,
SIM_ENT_PROP_RELEASE_NEXT_TICK,
SIM_ENT_PROP_REMOTE, SIM_ENT_PROP_REMOTE,
SIM_ENT_PROP_CLIENT, SIM_ENT_PROP_CLIENT,

View File

@ -403,7 +403,7 @@ INTERNAL void test_clear_level(struct sim_snapshot *world)
for (u64 j = 0; j < world->num_ents_reserved; ++j) { for (u64 j = 0; j < world->num_ents_reserved; ++j) {
struct sim_ent *ent = &world->ents[j]; struct sim_ent *ent = &world->ents[j];
if (ent->valid) { if (ent->valid) {
sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE_NEXT_TICK); sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE);
} }
} }
} }
@ -523,7 +523,7 @@ INTERNAL PHYS_COLLISION_CALLBACK_FUNC_DEF(on_collision, collision_data_array, st
/* Update bullet */ /* Update bullet */
bullet->bullet_has_hit = true; bullet->bullet_has_hit = true;
sim_ent_enable_prop(bullet, SIM_ENT_PROP_RELEASE_THIS_TICK); sim_ent_enable_prop(bullet, SIM_ENT_PROP_RELEASE);
/* Update tracer */ /* Update tracer */
struct sim_ent *tracer = sim_ent_from_handle(world, bullet->bullet_tracer); struct sim_ent *tracer = sim_ent_from_handle(world, bullet->bullet_tracer);
@ -572,13 +572,13 @@ INTERNAL PHYS_COLLISION_CALLBACK_FUNC_DEF(on_collision, collision_data_array, st
* Update * Update
* ========================== */ * ========================== */
void sim_step(struct sim_step_ctx *step_ctx) void sim_step(struct sim_step_ctx *ctx)
{ {
__prof; __prof;
struct temp_arena scratch = scratch_begin_no_conflict(); struct temp_arena scratch = scratch_begin_no_conflict();
struct sim_snapshot *world = step_ctx->world; struct sim_snapshot *world = ctx->world;
i64 sim_dt_ns = step_ctx->sim_dt_ns; i64 sim_dt_ns = ctx->sim_dt_ns;
/* ========================== * /* ========================== *
* Begin frame * Begin frame
@ -595,27 +595,13 @@ void sim_step(struct sim_step_ctx *step_ctx)
struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE); struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE);
if (step_ctx->is_master) { /* ========================== *
/* ========================== * * Release entities
* Spawn test entities * ========================== */
* ========================== */
/* TODO: remove this (testing) */ release_entities_with_prop(world, SIM_ENT_PROP_RELEASE);
/* Initialize entities */
{
static b32 run = 0;
if (!run) {
run = 1;
spawn_test_entities(world, V2(0, 0));
}
}
/* ========================== *
* Release entities
* ========================== */
release_entities_with_prop(world, SIM_ENT_PROP_RELEASE_NEXT_TICK);
if (ctx->is_master) {
/* ========================== * /* ========================== *
* Activate entities * Activate entities
* ========================== */ * ========================== */
@ -632,6 +618,20 @@ void sim_step(struct sim_step_ctx *step_ctx)
} }
} }
/* ========================== *
* Spawn test entities
* ========================== */
/* TODO: remove this (testing) */
/* Initialize entities */
{
static b32 run = 0;
if (!run) {
run = 1;
spawn_test_entities(world, V2(0, 0));
}
}
/* ========================== * /* ========================== *
* Reset triggered entities * Reset triggered entities
* ========================== */ * ========================== */
@ -1324,7 +1324,7 @@ void sim_step(struct sim_step_ctx *step_ctx)
{ {
struct phys_step_ctx phys = ZI; struct phys_step_ctx phys = ZI;
phys.sim_step_ctx = step_ctx; phys.sim_step_ctx = ctx;
phys.pre_solve_callback = on_collision; phys.pre_solve_callback = on_collision;
phys_step(&phys, sim_dt); phys_step(&phys, sim_dt);
} }
@ -1346,7 +1346,7 @@ void sim_step(struct sim_step_ctx *step_ctx)
if (v2_dot(tick_velocity, v2_sub(gradient_start, end)) > 0) { if (v2_dot(tick_velocity, v2_sub(gradient_start, end)) > 0) {
/* Tracer has disappeared */ /* Tracer has disappeared */
sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE_NEXT_TICK); sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE);
} }
ent->tracer_gradient_start = gradient_start; ent->tracer_gradient_start = gradient_start;
@ -1475,7 +1475,7 @@ void sim_step(struct sim_step_ctx *step_ctx)
ent->quake_intensity = max_f32(0, ent->quake_intensity - (ent->quake_fade * sim_dt)); ent->quake_intensity = max_f32(0, ent->quake_intensity - (ent->quake_fade * sim_dt));
if (ent->quake_intensity <= 0) { if (ent->quake_intensity <= 0) {
sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE_NEXT_TICK); sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE);
} }
} }
@ -1507,14 +1507,14 @@ void sim_step(struct sim_step_ctx *step_ctx)
arena_temp_end(temp); arena_temp_end(temp);
} }
/* ========================== *
* Release entities
* ========================== */
release_entities_with_prop(world, SIM_ENT_PROP_RELEASE_THIS_TICK);
} }
/* ========================== *
* Release entities
* ========================== */
release_entities_with_prop(world, SIM_ENT_PROP_RELEASE);
/* ========================== * /* ========================== *
* End frame * End frame
* ========================== */ * ========================== */

View File

@ -81,6 +81,6 @@ struct sim_step_ctx {
i64 sim_dt_ns; i64 sim_dt_ns;
}; };
void sim_step(struct sim_step_ctx *step_ctx); void sim_step(struct sim_step_ctx *ctx);
#endif #endif

View File

@ -859,12 +859,13 @@ INTERNAL void user_update(void)
struct v2 vdc = v2_neg(vcd); struct v2 vdc = v2_neg(vcd);
f32 opacity_a = 1; f32 opacity_a = 1;
if (v2_dot(velocity, vca) < 0) { if (v2_dot(velocity, vca) <= 0) {
a = c; a = c;
opacity_a = 0; opacity_a = 0;
} else { } else {
opacity_a = v2_dot(vcd, vca) / v2_len_sq(vcd); opacity_a = v2_dot(vcd, vca) / v2_len_sq(vcd);
} }
opacity_a = clamp_f32(opacity_a, 0, 1);
f32 opacity_b = clamp_f32(1.f - (v2_dot(vdc, vdb) / v2_len_sq(vdc)), 0, 1); f32 opacity_b = clamp_f32(1.f - (v2_dot(vdc, vdb) / v2_len_sq(vdc)), 0, 1);
@ -1740,11 +1741,14 @@ INTERNAL void sim_ent_sync_with_remote(struct sim_accel *accel, struct sim_ent *
local->remote_client = remote_client_handle; local->remote_client = remote_client_handle;
local->remote_ent = remote->handle; local->remote_ent = remote->handle;
sim_ent_enable_prop(local, SIM_ENT_PROP_REMOTE); sim_ent_enable_prop(local, SIM_ENT_PROP_REMOTE);
sim_ent_set_xform(local, sim_ent_get_xform(remote)); //sim_ent_set_xform(local, sim_ent_get_xform(remote));
/* Translate remote handles */ /* Translate remote handles */
translate(top);
translate(parent); translate(parent);
if (!sim_ent_from_handle(local->ss, local->parent)->valid) {
local->parent = SIM_ENT_ROOT_HANDLE;
}
translate(top);
translate(next); translate(next);
translate(prev); translate(prev);
translate(first); translate(first);
@ -1768,6 +1772,34 @@ INTERNAL void sim_ent_sync_with_remote(struct sim_accel *accel, struct sim_ent *
} }
INTERNAL void sim_ent_alloc_from_remote(struct sim_accel *accel, struct sim_ent *local_parent, struct sim_ent *remote)
{
struct sim_snapshot *local_ss = local_parent->ss;
struct sim_snapshot *remote_ss = remote->ss;
struct sim_client_handle remote_client_handle = remote_ss->client->handle;
struct sim_lookup *remote_lookup = &accel->remote_lookup;
if (remote->valid) {
struct sim_lookup_key key = sim_lookup_key_from_client_and_ent_handles(remote_client_handle, remote->handle);
struct sim_ent *local_ent = sim_ent_nil();
{
struct sim_lookup_entry *entry = sim_lookup_get(remote_lookup, key);
if (entry) {
local_ent = sim_ent_from_handle(local_ss, entry->ent);
}
}
if (!local_ent->valid) {
local_ent = sim_ent_alloc(local_parent);
sim_ent_enable_prop(local_ent, SIM_ENT_PROP_REMOTE);
local_ent->remote_client = remote_client_handle;
local_ent->remote_ent = remote->handle;
sim_lookup_set(remote_lookup, key, local_ent->handle);
}
for (struct sim_ent *remote_child = sim_ent_from_handle(remote_ss, remote->first); remote_child->valid; remote_child = sim_ent_from_handle(remote_ss, remote_child->next)) {
sim_ent_alloc_from_remote(accel, local_ent, remote_child);
}
}
}
INTERNAL void sim_snapshot_sync_with_remote(struct sim_accel *accel, struct sim_snapshot *local_ss, struct sim_snapshot *remote_ss) INTERNAL void sim_snapshot_sync_with_remote(struct sim_accel *accel, struct sim_snapshot *local_ss, struct sim_snapshot *remote_ss)
@ -1776,6 +1808,7 @@ INTERNAL void sim_snapshot_sync_with_remote(struct sim_accel *accel, struct sim_
/* FIXME: Only sync cmds from non-master remote */ /* FIXME: Only sync cmds from non-master remote */
struct sim_ent *local_root = sim_ent_from_handle(local_ss, SIM_ENT_ROOT_HANDLE); struct sim_ent *local_root = sim_ent_from_handle(local_ss, SIM_ENT_ROOT_HANDLE);
struct sim_ent *remote_root = sim_ent_from_handle(remote_ss, SIM_ENT_ROOT_HANDLE);
struct sim_client_handle remote_client_handle = remote_ss->client->handle; struct sim_client_handle remote_client_handle = remote_ss->client->handle;
struct sim_lookup *remote_lookup = &accel->remote_lookup; struct sim_lookup *remote_lookup = &accel->remote_lookup;
@ -1788,7 +1821,6 @@ INTERNAL void sim_snapshot_sync_with_remote(struct sim_accel *accel, struct sim_
client_ent = client_entry ? sim_ent_from_handle(local_ss, client_entry->ent) : sim_ent_nil(); client_ent = client_entry ? sim_ent_from_handle(local_ss, client_entry->ent) : sim_ent_nil();
} }
/* Build remote lookup table */ /* Build remote lookup table */
for (u64 i = 0; i < local_ss->num_ents_reserved; ++i) { for (u64 i = 0; i < local_ss->num_ents_reserved; ++i) {
struct sim_ent *ent = &local_ss->ents[i]; struct sim_ent *ent = &local_ss->ents[i];
@ -1799,27 +1831,8 @@ INTERNAL void sim_snapshot_sync_with_remote(struct sim_accel *accel, struct sim_
} }
/* Create new ents from remote */ /* Create new ents from remote */
/* Skipping root entity */ for (struct sim_ent *remote_top = sim_ent_from_handle(remote_ss, remote_root->first); remote_top->valid; remote_top = sim_ent_from_handle(remote_ss, remote_top->next)) {
for (u64 i = SIM_ENT_ROOT_HANDLE.idx + 1; i < remote_ss->num_ents_reserved; ++i) { sim_ent_alloc_from_remote(accel, local_root, remote_top);
struct sim_ent *remote_ent = &remote_ss->ents[i];
if (remote_ent->valid) {
struct sim_lookup_key key = sim_lookup_key_from_client_and_ent_handles(remote_client_handle, remote_ent->handle);
struct sim_ent *local_ent = sim_ent_nil();
{
struct sim_lookup_entry *entry = sim_lookup_get(remote_lookup, key);
if (entry) {
local_ent = sim_ent_from_handle(local_ss, entry->ent);
}
}
if (!local_ent->valid) {
local_ent = sim_ent_alloc(local_root);
sim_ent_enable_prop(local_ent, SIM_ENT_PROP_REMOTE);
local_ent->remote_client = remote_client_handle;
local_ent->remote_ent = remote_ent->handle;
sim_lookup_set(remote_lookup, key, local_ent->handle);
}
}
} }
/* Sync ents with remote */ /* Sync ents with remote */
@ -1832,7 +1845,7 @@ INTERNAL void sim_snapshot_sync_with_remote(struct sim_accel *accel, struct sim_
sim_ent_sync_with_remote(accel, local_ent, remote_ent, client_ent); sim_ent_sync_with_remote(accel, local_ent, remote_ent, client_ent);
} else { } else {
/* Remote ent is no longer valid / networked, release it */ /* Remote ent is no longer valid / networked, release it */
sim_ent_enable_prop(local_ent, SIM_ENT_PROP_RELEASE_THIS_TICK); sim_ent_enable_prop(local_ent, SIM_ENT_PROP_RELEASE);
sim_ent_disable_prop(local_ent, SIM_ENT_PROP_REMOTE); sim_ent_disable_prop(local_ent, SIM_ENT_PROP_REMOTE);
} }
} }
@ -1855,7 +1868,6 @@ INTERNAL void sim_snapshot_sync_with_remote(struct sim_accel *accel, struct sim_
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
{ {
#if 0 #if 0
@ -1942,9 +1954,10 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
/* Read snapshots */ /* Read snapshots */
u64 tmp_encoded_len = br_read_uv(&msg_br); u64 tmp_encoded_len = br_read_uv(&msg_br);
u8 *tmp_encoded_bytes = br_read_bytes_raw(&msg_br, tmp_encoded_len);
if (!tmp_encoded_bytes) tmp_encoded_len = 0;
while (tmp_encoded_len > 0) { while (tmp_encoded_len > 0) {
u8 *tmp_encoded_bytes = br_read_bytes_raw(&msg_br, tmp_encoded_len);
if (!tmp_encoded_bytes) break;
struct bitbuff decoder_bb = bitbuff_from_string(STRING(tmp_encoded_len, tmp_encoded_bytes)); struct bitbuff decoder_bb = bitbuff_from_string(STRING(tmp_encoded_len, tmp_encoded_bytes));
struct bitbuff_reader decoder_br = br_from_bitbuff(&decoder_bb); struct bitbuff_reader decoder_br = br_from_bitbuff(&decoder_bb);
u64 base_tick = br_read_uv(&decoder_br); u64 base_tick = br_read_uv(&decoder_br);
@ -1963,8 +1976,6 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
} }
} }
tmp_encoded_len = br_read_uv(&msg_br); tmp_encoded_len = br_read_uv(&msg_br);
tmp_encoded_bytes = br_read_bytes_raw(&msg_br, tmp_encoded_len);
if (!tmp_encoded_bytes) tmp_encoded_len = 0;
} }
} }
} break; } break;
@ -1974,6 +1985,12 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
} }
} }
if (master_client->valid) {
if (master_client->last_tick > step_tick && (master_client->last_tick - step_tick) > 50) {
step_tick = master_client->last_tick;
}
}
/* Create user input */ /* Create user input */
{ {
struct sim_snapshot *prev_user_input_ss = sim_snapshot_from_tick(user_input_client, user_input_client->last_tick); struct sim_snapshot *prev_user_input_ss = sim_snapshot_from_tick(user_input_client, user_input_client->last_tick);
@ -2002,7 +2019,7 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
/* Rebuild acceleration tables */ /* Rebuild acceleration tables */
sim_accel_rebuild(local_ss, &accel); sim_accel_rebuild(local_ss, &accel);
/* Sync clients */ /* Sync ents with local client */
{ {
struct sim_ent *local_root = sim_ent_from_handle(local_ss, SIM_ENT_ROOT_HANDLE); struct sim_ent *local_root = sim_ent_from_handle(local_ss, SIM_ENT_ROOT_HANDLE);
for (u64 client_index = 0; client_index < store->num_clients_reserved; ++client_index) { for (u64 client_index = 0; client_index < store->num_clients_reserved; ++client_index) {
@ -2032,6 +2049,9 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
struct sim_snapshot *client_ss = sim_snapshot_from_tick(client, step_tick); struct sim_snapshot *client_ss = sim_snapshot_from_tick(client, step_tick);
if (client_ss->valid) { if (client_ss->valid) {
sim_snapshot_sync_with_remote(&accel, local_ss, client_ss); sim_snapshot_sync_with_remote(&accel, local_ss, client_ss);
if (!is_master && client == master_client) {
local_ss->local_client = client_ss->local_client;
}
} }
} }
} }
@ -2050,11 +2070,6 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
} }
} }
/* Release unneeded snapshots */ /* Release unneeded snapshots */
#if 0 #if 0
#if 0 #if 0
@ -2121,7 +2136,13 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
/* Construct publishable snapshot */ /* Construct publishable snapshot */
{
if (is_master) {
//struct sim_snapshot *prev_pub_ss = sim_snapshot_from_tick(publish_client, publish_client->last_tick);
struct sim_snapshot *pub_ss = sim_snapshot_alloc(publish_client, local_ss, local_ss->tick);
(UNUSED)pub_ss;
}
}
@ -2186,11 +2207,11 @@ 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_to_user_client_mutex); struct sys_lock lock = sys_mutex_lock_e(&G.local_to_user_client_mutex);
struct sim_snapshot *pub_ss = sim_snapshot_alloc(G.local_to_user_client, local_ss, local_ss->tick); struct sim_snapshot *copy_ss = sim_snapshot_alloc(G.local_to_user_client, local_ss, local_ss->tick);
pub_ss->local_client = local_ss->local_client; copy_ss->local_client = local_ss->local_client;
i64 publish_ns = sys_time_ns(); i64 publish_ns = sys_time_ns();
pub_ss->publish_dt_ns = publish_ns - last_publish_ns; copy_ss->publish_dt_ns = publish_ns - last_publish_ns;
pub_ss->publish_time_ns = publish_ns; copy_ss->publish_time_ns = publish_ns;
last_publish_ns = publish_ns; last_publish_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);