sim ctx decoupling progress

This commit is contained in:
jacob 2025-02-19 08:22:04 -06:00
parent 3204e97dcf
commit 8e5b2bd773
12 changed files with 382 additions and 329 deletions

View File

@ -581,12 +581,14 @@ void OnBuild(StringList cli_args)
OS_Exit(1);
}
StringListAppend(&perm, &compile_args, Lit("-DPROFILING=1"));
/* Tracy flags */
StringListAppend(&perm, &compile_args, Lit("-DTRACY_ENABLE=1"));
if (!arg_profiler_sampling) {
StringListAppend(&perm, &compile_args, Lit("-DTRACY_NO_SAMPLING -DTRACY_NO_SYSTEM_TRACING -DTRACY_NO_CALLSTACK"));
}
/* Disable compile_warnings when compiling tracy client */
/* Disable compiler warnings when compiling tracy client */
compile_warnings = (StringList) { 0 };
link_warnings = (StringList) { 0 };
StringListAppend(&perm, &compile_warnings, Lit("-Wno-everything"));

View File

@ -33,7 +33,7 @@
#define SPACE_CELL_BUCKETS_SQRT (256)
#define SPACE_CELL_SIZE 1.0f
#define SIM_TICKS_PER_SECOND 100.0
#define SIM_TICKS_PER_SECOND 50.0
#define SIM_TIMESCALE 1
#define SIM_PHYSICS_SUBSTEPS 4

View File

@ -45,10 +45,10 @@ struct phys_collision_data_array phys_create_and_update_contacts(struct arena *a
struct phys_collision_data_array res = ZI;
res.a = arena_dry_push(arena, struct phys_collision_data);
struct sim_snapshot *ss = ctx->ss;
struct sim_ent_lookup *contact_lookup = &ss->contact_lookup;
struct space *space = ss->space;
struct space *space = ctx->space;
struct sim_ent_lookup *contact_lookup = ctx->contact_lookup;
#if COLLIDER_DEBUG
struct sim_ent_lookup *debug_lookup = &ss->collision_debug_lookup;
struct sim_ent_lookup *debug_lookup = ctx->collision_debug_lookup;
#endif
struct sim_ent *root = sim_ent_from_handle(ss, SIM_ENT_ROOT_HANDLE);
@ -284,7 +284,7 @@ void phys_prepare_contacts(struct phys_ctx *ctx, u64 phys_iteration)
{
__prof;
struct sim_snapshot *ss = ctx->ss;
struct sim_ent_lookup *contact_lookup = &ss->contact_lookup;
struct sim_ent_lookup *contact_lookup = ctx->contact_lookup;
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];
@ -1003,7 +1003,7 @@ f32 phys_determine_earliest_toi_for_bullets(struct phys_ctx *ctx, f32 step_dt, f
{
__prof;
struct sim_snapshot *ss = ctx->ss;
struct space *space = ss->space;
struct space *space = ctx->space;
f32 smallest_t = 1;
for (u64 e0_index = 0; e0_index < ss->num_ents_reserved; ++e0_index) {
@ -1053,7 +1053,7 @@ f32 phys_determine_earliest_toi_for_bullets(struct phys_ctx *ctx, f32 step_dt, f
void phys_update_aabbs(struct phys_ctx *ctx)
{
struct sim_snapshot *ss = ctx->ss;
struct space *space = ss->space;
struct space *space = ctx->space;
for (u64 sim_ent_index = 0; sim_ent_index < ss->num_ents_reserved; ++sim_ent_index) {
struct sim_ent *ent = &ss->ents[sim_ent_index];
if (!sim_ent_is_valid_and_active(ent)) continue;

View File

@ -33,6 +33,12 @@ struct phys_ctx {
struct sim_snapshot *ss;
phys_collision_callback_func *pre_solve_callback;
phys_collision_callback_func *post_solve_callback;
struct space *space;
struct sim_ent_lookup *contact_lookup;
#if COLLIDER_DEBUG
struct sim_ent_lookup *collision_debug_lookup;
#endif
};
/* ========================== *

150
src/sim.c
View File

@ -52,7 +52,7 @@ struct sim_ctx *sim_ctx_alloc(struct sprite_startup_receipt *sprite_sr,
/* Create snapshot store */
snapshot_store = sim_snapshot_store_alloc();
world = sim_snapshot_nil();
ss_blended = sim_snapshot_nil();
return ctx;
}
@ -277,15 +277,16 @@ INTERNAL void test_clear_level(struct sim_snapshot *world)
* Release entities
* ========================== */
INTERNAL void release_entities_with_prop(struct sim_snapshot *world, enum sim_ent_prop prop)
#if 0
INTERNAL void release_entities_with_prop(struct sim_snapshot *ss_blended, enum sim_ent_prop prop)
{
struct temp_arena scratch = scratch_begin_no_conflict();
struct space *space = world->space;
struct space *space = ss_blended->space;
struct sim_ent **ents_to_release = arena_dry_push(scratch.arena, struct sim_ent *);
u64 ents_to_release_count = 0;
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
for (u64 ent_index = 0; ent_index < ss_blended->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &ss_blended->ents[ent_index];
if (ent->valid && sim_ent_has_prop(ent, prop)) {
*arena_push(scratch.arena, struct sim_ent *) = ent;
++ents_to_release_count;
@ -316,6 +317,34 @@ INTERNAL void release_entities_with_prop(struct sim_snapshot *world, enum sim_en
scratch_end(scratch);
}
#else
INTERNAL void release_entities_with_prop(struct sim_snapshot *world, enum sim_ent_prop prop)
{
struct temp_arena scratch = scratch_begin_no_conflict();
struct sim_ent **ents_to_release = arena_dry_push(scratch.arena, struct sim_ent *);
u64 ents_to_release_count = 0;
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
if (ent->valid && sim_ent_has_prop(ent, prop)) {
*arena_push(scratch.arena, struct sim_ent *) = ent;
++ents_to_release_count;
}
}
/* Release from snapshot */
/* TODO: Breadth first iteration to only release parent entities (since
* child entities will be released along with parent anyway) */
for (u64 i = 0; i < ents_to_release_count; ++i) {
struct sim_ent *ent = ents_to_release[i];
if (ent->is_top && !ent->is_root) {
sim_ent_release(ent);
}
}
scratch_end(scratch);
}
#endif
/* ========================== *
* Respond to physics collisions
@ -423,7 +452,7 @@ struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct
/* Release old snapshots */
{
/* TODO: Something better */
i64 release_tick = (i64)world->tick - 25; /* Arbitrary tick offset */
i64 release_tick = (i64)ss_blended->tick - 25; /* Arbitrary tick offset */
if (release_tick > 0) {
struct sim_snapshot *old = sim_snapshot_from_tick(snapshot_store, release_tick);
if (old->valid) {
@ -438,14 +467,36 @@ struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct
/* TODO: Remove this */
world->space = prev_snapshot->space;
world->contact_lookup = prev_snapshot->contact_lookup;
if (!world->space) {
world->space = space_alloc(1, 256);
/* Acceleration structures */
struct space *space = space_alloc(1, 256);
for (u64 sim_ent_index = 0; sim_ent_index < world->num_ents_reserved; ++sim_ent_index) {
struct sim_ent *ent = &world->ents[sim_ent_index];
if (!sim_ent_is_valid_and_active(ent)) continue;
MEMZERO_STRUCT(&ent->space_handle);
}
if (!world->contact_lookup.arena.base) {
world->contact_lookup = sim_ent_lookup_alloc(4096);
#if COLLIDER_DEBUG
struct sim_ent_lookup collision_debug_lookup = sim_ent_lookup_alloc(4096);
#endif
struct sim_ent_lookup contact_lookup = sim_ent_lookup_alloc(4096);
for (u64 sim_ent_index = 0; sim_ent_index < world->num_ents_reserved; ++sim_ent_index) {
struct sim_ent *constraint_ent = &world->ents[sim_ent_index];
if (!sim_ent_is_valid_and_active(constraint_ent)) continue;
if (!sim_ent_has_prop(constraint_ent, SIM_ENT_PROP_CONTACT_CONSTRAINT)) continue;
struct sim_ent_lookup_key key = sim_ent_lookup_key_from_two_handles(constraint_ent->contact_constraint_data.e0, constraint_ent->contact_constraint_data.e1);
sim_ent_lookup_set(&contact_lookup, key, constraint_ent->handle);
}
world->phys_iteration = prev_snapshot->phys_iteration;
@ -539,22 +590,35 @@ struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct
{
/* Create connecting clients */
for (struct sim_cmd_frame *frame = input_frames.first; frame; frame = frame->next) {
struct host_channel_id channel_id = frame->channel;
struct sim_client *client = sim_client_from_channel_id(world, channel_id);
struct sim_client *client;
if (frame->sender_is_local) {
client = sim_client_from_handle(world, world->local_client);
if (!client->valid) {
client = sim_client_alloc(world, HOST_CHANNEL_ID_NIL, SIM_CLIENT_KIND_LOCAL);
world->local_client = client->handle;
}
} else {
client = sim_client_from_channel_id(world, frame->sender_channel);
if (!client->valid) {
for (struct sim_cmd *cmd = frame->first; cmd; cmd = cmd->next) {
enum sim_cmd_kind kind = cmd->kind;
if (kind == SIM_CMD_KIND_SIM_CLIENT_CONNECT && !host_channel_id_is_nil(channel_id)) {
client = sim_client_alloc(world, channel_id);
if (kind == SIM_CMD_KIND_SIM_CLIENT_CONNECT && !host_channel_id_is_nil(frame->sender_channel)) {
client = sim_client_alloc(world, frame->sender_channel, SIM_CLIENT_KIND_NETSIM);
break;
}
}
}
}
}
/* Sort cmd frames by client */
client_frames = arena_push_array_zero(scratch.arena, struct sim_cmd_frame *, world->num_clients_reserved);
for (struct sim_cmd_frame *frame = input_frames.first; frame; frame = frame->next) {
struct sim_client *client = sim_client_from_channel_id(world, frame->channel);
struct sim_client *client;
if (frame->sender_is_local) {
client = sim_client_from_handle(world, world->local_client);
} else {
client = sim_client_from_channel_id(world, frame->sender_channel);
}
if (client->valid && frame->tick == world->tick) {
client_frames[client->handle.idx] = frame;
}
@ -694,13 +758,17 @@ struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct
/* Update animation */
{
struct sprite_sheet_span span = sprite_sheet_get_span(sheet, ent->sprite_span_name);
if (ent->animation_last_frame_change_time_ns == 0) {
ent->animation_last_frame_change_time_ns = world_time;
}
f64 time_in_frame = ent->animation_time_in_frame + world_dt;
f64 time_in_frame = SECONDS_FROM_NS(world->world_time_ns - ent->animation_last_frame_change_time_ns);
u64 frame_index = ent->animation_frame;
if (frame_index < span.start || frame_index > span.end) {
frame_index = span.start;
}
if (span.end > span.start + 1) {
struct sprite_sheet_frame frame = sprite_sheet_get_frame(sheet, frame_index);
while (time_in_frame > frame.duration) {
time_in_frame -= frame.duration;
@ -710,9 +778,10 @@ struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct
frame_index = span.start;
}
frame = sprite_sheet_get_frame(sheet, frame_index);
ent->animation_last_frame_change_time_ns = world->world_time_ns;
}
}
ent->animation_time_in_frame = time_in_frame;
ent->animation_frame = frame_index;
}
@ -805,8 +874,8 @@ struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct
* ========================== */
#if 0
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
for (u64 ent_index = 0; ent_index < ss_blended->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &ss_blended->ents[ent_index];
if (!sim_ent_is_valid_and_active(ent)) continue;
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_TEST)) continue;
@ -1162,6 +1231,11 @@ struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct
struct phys_ctx phys = ZI;
phys.ss = world;
phys.pre_solve_callback = on_collision;
phys.space = space;
phys.contact_lookup = &contact_lookup;
#if COLLIDER_DEBUG
phys.collision_debug_lookup = collision_debug_lookup;
#endif
/* Step */
phys_step(&phys, world_dt);
@ -1212,7 +1286,7 @@ struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct
/* Add shooter velocity to bullet */
{
/* TODO: Add angular velocity as well? */
struct sim_ent *top = sim_ent_from_handle(world, src->top);
struct sim_ent *top = sim_ent_from_handle(ss_blended, src->top);
impulse = v2_add(impulse, v2_mul(top->linear_velocity, dt));
}
#endif
@ -1358,19 +1432,19 @@ struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct
#if 0
struct sim_cmd_queue_list output_cmds = ZI;
for (u64 i = 0; i < world->num_clients_reserved; ++i) {
struct sim_client *client = &world->clients[i];
for (u64 i = 0; i < ss_blended->num_clients_reserved; ++i) {
struct sim_client *client = &ss_blended->clients[i];
if (client->valid) {
struct temp_arena temp = arena_temp_begin(scratch.arena);
struct sim_snapshot *ss0 = sim_snapshot_from_tick(snapshot_store, client->ack);
struct sim_snapshot *ss1 = world;
struct sim_snapshot *ss1 = ss_blended;
/* Create & encode snapshot cmd */
{
struct sim_cmd *cmd = arena_push(arena, struct sim_cmd);
cmd->kind = SIM_CMD_KIND_SNAPSHOT;
cmd->tick = world->tick;
cmd->tick = ss_blended->tick;
cmd->snapshot_tick_start = ss0->tick;
cmd->snapshot_tick_end = ss1->tick;
@ -1404,11 +1478,27 @@ struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct
#endif
/* ========================== *
* End frame cache scopes
* End frame
* ========================== */
/* TODO: Remove this */
#if COLLIDER_DEBUG
sim_ent_lookup_release(&collision_debug_lookup);
#endif
sim_ent_lookup_release(&contact_lookup);
space_release(space);
sprite_scope_end(sprite_frame_scope);
scratch_end(scratch);
return world;
}
@ -1472,7 +1562,7 @@ void sim_cmd_frames_decode(struct arena *arena, struct host_event_array host_eve
{
/* Create a stand-alone cmd frame for connecting */
struct sim_cmd_frame *frame = arena_push_zero(arena, struct sim_cmd_frame);
frame->channel = host_event.channel_id;
frame->sender_channel = host_event.channel_id;
struct sim_cmd *cmd = arena_push_zero(arena, struct sim_cmd);
cmd->kind = SIM_CMD_KIND_SIM_CLIENT_CONNECT;
@ -1492,7 +1582,7 @@ void sim_cmd_frames_decode(struct arena *arena, struct host_event_array host_eve
{
/* Create a stand-alone cmd frame for disconnecting */
struct sim_cmd_frame *frame = arena_push_zero(arena, struct sim_cmd_frame);
frame->channel = host_event.channel_id;
frame->sender_channel = host_event.channel_id;
struct sim_cmd *cmd = arena_push_zero(arena, struct sim_cmd);
cmd->kind = SIM_CMD_KIND_SIM_CLIENT_DISCONNECT;
@ -1515,7 +1605,7 @@ void sim_cmd_frames_decode(struct arena *arena, struct host_event_array host_eve
struct bitbuff_reader br = br_from_bitbuff(&bb);
struct sim_cmd_frame *frame = arena_push_zero(arena, struct sim_cmd_frame);
frame->channel = host_event.channel_id;
frame->sender_channel = host_event.channel_id;
frame->tick = br_read_uv(&br);
frame->ack = br_read_uv(&br);

View File

@ -89,9 +89,11 @@ struct sim_cmd {
/* Represents all cmds generated by a user/sim for a particular channel in a single tick. */
struct sim_cmd_frame {
struct host_channel_id channel; /* Sender's channel id (if this cmd frame was received by a host) */
u64 tick; /* Sender's tick (this will always be 0 for user -> local sim cmds) */
u64 ack; /* Sender's last received cmd frame tick from dst channel */
struct host_channel_id sender_channel; /* Sender's channel ID will be nil if sender_is_local = true */
b32 sender_is_local;
u64 tick; /* The tick that this cmd frame is meant to execute on */
u64 ack; /* Sender's last received cmd frame tick */
struct sim_cmd *first;
struct sim_cmd *last;
@ -152,7 +154,7 @@ struct sim_ctx {
/* Snapshot */
struct sim_snapshot_store *snapshot_store;
struct sim_snapshot *world;
struct sim_snapshot *ss_blended;
};
/* TODO: Get rid of startup receipts */

View File

@ -41,7 +41,7 @@ struct sim_client *sim_client_from_channel_id(struct sim_snapshot *ss, struct ho
return res;
}
struct sim_client *sim_client_alloc(struct sim_snapshot *ss, struct host_channel_id channel_id)
struct sim_client *sim_client_alloc(struct sim_snapshot *ss, struct host_channel_id channel_id, enum sim_client_kind kind)
{
struct sim_client_handle handle = ZI;
struct sim_client *client = sim_client_from_handle(ss, ss->first_free_client);
@ -58,8 +58,11 @@ struct sim_client *sim_client_alloc(struct sim_snapshot *ss, struct host_channel
++ss->num_clients_allocated;
*client = *sim_client_nil();
client->valid = true;
client->kind = kind;
client->handle = handle;
if (kind == SIM_CLIENT_KIND_NETSIM) {
ASSERT(!host_channel_id_is_nil(channel_id));
u64 channel_hash = hash_from_channel_id(channel_id);
client->channel_id = channel_id;
client->channel_hash = channel_hash;
@ -77,6 +80,7 @@ struct sim_client *sim_client_alloc(struct sim_snapshot *ss, struct host_channel
}
bucket->last = client->handle;
}
}
return client;
}
@ -91,6 +95,7 @@ void sim_client_release(struct sim_client *client)
--ss->num_clients_allocated;
/* Remove from channel lookup */
if (client->kind == SIM_CLIENT_KIND_NETSIM) {
u64 bucket_index = client->channel_hash % ss->num_client_lookup_buckets;
struct sim_client_lookup_bucket *bucket = &ss->client_lookup_buckets[bucket_index];
struct sim_client *prev = sim_client_from_handle(ss, client->prev_in_bucket);
@ -106,6 +111,7 @@ void sim_client_release(struct sim_client *client)
bucket->last = prev->handle;
}
}
}
/* ========================== *
* Lerp

View File

@ -8,6 +8,12 @@
struct sim_client_channel_lookup_bucket;
struct sim_snapshot;
enum sim_client_kind {
SIM_CLIENT_KIND_INVALID,
SIM_CLIENT_KIND_LOCAL,
SIM_CLIENT_KIND_NETSIM
};
struct sim_client_lookup_bucket {
struct sim_client_handle first;
struct sim_client_handle last;
@ -15,6 +21,7 @@ struct sim_client_lookup_bucket {
struct sim_client {
b32 valid;
enum sim_client_kind kind;
struct sim_client_handle handle;
struct sim_snapshot *ss;
@ -51,7 +58,7 @@ INLINE struct sim_client *sim_client_nil(void)
struct sim_client *sim_client_from_handle(struct sim_snapshot *ss, struct sim_client_handle handle);
struct sim_client *sim_client_from_channel_id(struct sim_snapshot *ss, struct host_channel_id channel_id);
struct sim_client *sim_client_alloc(struct sim_snapshot *ss, struct host_channel_id channel_id);
struct sim_client *sim_client_alloc(struct sim_snapshot *ss, struct host_channel_id channel_id, enum sim_client_kind kind);
void sim_client_release(struct sim_client *client);
/* ========================== *

View File

@ -464,7 +464,7 @@ void sim_ent_lerp(struct sim_ent *e, struct sim_ent *e0, struct sim_ent *e1, f64
e->control.focus = v2_lerp(e0->control.focus, e1->control.focus, blend);
e->sprite_local_xform = xform_lerp(e0->sprite_local_xform, e1->sprite_local_xform, blend);
e->animation_time_in_frame = math_lerp_f64(e0->animation_time_in_frame, e1->animation_time_in_frame, (f64)blend);
e->animation_last_frame_change_time_ns = math_lerp_i64(e0->animation_last_frame_change_time_ns, e1->animation_last_frame_change_time_ns, (f64)blend);
e->animation_frame = (u32)math_round_to_int(math_lerp_f32(e0->animation_frame, e1->animation_frame, blend));
e->camera_quad_xform = xform_lerp(e0->camera_quad_xform, e1->camera_quad_xform, blend);

View File

@ -212,7 +212,7 @@ struct sim_ent {
/* Animation */
/* SIM_ENT_PROP_ANIMATING */
f64 animation_time_in_frame;
i64 animation_last_frame_change_time_ns;
u32 animation_frame;
/* ====================================================================== */

View File

@ -47,15 +47,7 @@ struct sim_snapshot {
/* FIXME: Remove this */
struct space *space;
/* Bookkeeping structures */
/* TODO: Store in snapshot for determinism */
struct sim_ent_lookup contact_lookup;
#if COLLIDER_DEBUG
struct sim_ent_lookup collision_debug_lookup;
#endif

View File

@ -49,9 +49,9 @@ GLOBAL struct {
struct host *host;
struct string connect_address_str;
struct sim_snapshot_store *sim_snapshot_store; /* Contains buffered snapshots from sim */
struct sim_snapshot_store *world_snapshot_store; /* Contains single world snapshot from result of blending sim snapshots */
struct sim_snapshot *world;
struct sim_snapshot_store *unblended_snapshot_store; /* Contains buffered snapshots from sim */
struct sim_snapshot_store *blended_snapshot_store; /* Contains single world snapshot from result of blending sim snapshots */
struct sim_snapshot *ss_blended;
/* Dynamic bitbuff used by encoders */
struct bitbuff encoder_bitbuff;
@ -82,15 +82,26 @@ GLOBAL struct {
b32 debug_draw;
/* User thread input */
/* Window -> user */
struct sys_mutex sys_events_mutex;
struct arena sys_events_arena;
/* User -> local sim */
struct sys_mutex user_sim_cmd_mutex;
struct sim_control user_sim_cmd_control;
struct v2 user_sim_cmd_control_cursor_pos;
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;
/* Calculated from <last snapshot receive time + time since packet receive> */
i64 local_sim_predicted_time_ns;
@ -180,13 +191,22 @@ struct user_startup_receipt user_startup(struct work_startup_receipt *work_sr,
(UNUSED)sim_snapshot_sr;
G.arena = arena_alloc(GIGABYTE(64));
/* Snapshot store */
G.unblended_snapshot_store = sim_snapshot_store_alloc();
G.blended_snapshot_store = sim_snapshot_store_alloc();
G.ss_blended = sim_snapshot_nil();
/* Sys events */
G.sys_events_mutex = sys_mutex_alloc();
G.sys_events_arena = arena_alloc(GIGABYTE(64));
/* Snapshot store */
G.sim_snapshot_store = sim_snapshot_store_alloc();
G.world_snapshot_store = sim_snapshot_store_alloc();
G.world = sim_snapshot_nil();
/* User sim control */
G.user_sim_cmd_mutex = sys_mutex_alloc();
/* Local sim snapshot store */
G.local_sim_ss_mutex = sys_mutex_alloc();
G.local_sim_ss_store = sim_snapshot_store_alloc();
//struct sock_address bind_addr = sock_address_from_any_local_interface_with_dynamic_port();
G.host = host_alloc(0);
@ -245,10 +265,8 @@ INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(user_shutdown)
sim_ctx_release(G.local_sim_ctx);
}
#else
if (G.local_sim_ctx) {
atomic_i32_eval_exchange(&G.local_sim_thread_shutdown, true);
sys_thread_wait_release(&G.local_sim_thread);
}
#endif
}
@ -364,47 +382,6 @@ INTERNAL SORT_COMPARE_FUNC_DEF(ent_draw_order_cmp, arg_a, arg_b, udata)
/* ========================== *
* Update
* ========================== */
INTERNAL void queue_sim_cmd(struct arena *arena, struct sim_cmd_frame *frame, struct sim_cmd src)
{
struct sim_cmd *cmd = arena_push(arena, struct sim_cmd);
*cmd = src;
if (frame->last) {
frame->last->next = cmd;
} else {
frame->first = cmd;
}
frame->last = cmd;
}
INTERNAL void user_update(void)
{
@ -419,8 +396,8 @@ INTERNAL void user_update(void)
G.screen_size = sys_window_get_size(G.window);
struct sprite_scope *sprite_frame_scope = sprite_scope_begin();
struct sim_cmd_frame cmd_frame = ZI;
#if 0
/* ========================== *
* Process host events into sim cmds
* ========================== */
@ -444,6 +421,7 @@ INTERNAL void user_update(void)
* Process sim cmd frame
* ========================== */
#if 0
{
static f64 last_try_connect = 0;
f64 now = SECONDS_FROM_NS(sys_time_ns());
@ -470,7 +448,7 @@ INTERNAL void user_update(void)
case SIM_CMD_KIND_SNAPSHOT:
{
/* TODO: Only read newest tick cmd */
if (cmd->snapshot_tick_end > G.world->tick) {
if (cmd->snapshot_tick_end > G.ss_blended->tick) {
u64 ss0_tick = cmd->snapshot_tick_start;
u64 ss1_tick = cmd->snapshot_tick_end;
struct sim_snapshot *ss0 = sim_snapshot_from_tick(G.sim_snapshot_store, ss0_tick);
@ -495,13 +473,64 @@ INTERNAL void user_update(void)
}
}
}
#else
{
for (struct sim_cmd *cmd = incoming_cmd_frame.first; cmd; cmd = cmd->next) {
enum sim_cmd_kind kind = cmd->kind;
switch (kind) {
case SIM_CMD_KIND_SNAPSHOT:
{
/* TODO: Only read newest tick cmd */
if (cmd->snapshot_tick_end > G.ss_blended->tick) {
u64 ss0_tick = cmd->snapshot_tick_start;
u64 ss1_tick = cmd->snapshot_tick_end;
struct sim_snapshot *ss0 = sim_snapshot_from_tick(G.sim_snapshot_store, ss0_tick);
struct sim_snapshot *ss1 = sim_snapshot_from_tick(G.sim_snapshot_store, ss1_tick);
if (ss0->tick == ss0_tick) {
if (!ss1->valid) {
ss1 = sim_snapshot_alloc(G.sim_snapshot_store, ss0, ss1_tick);
ss1->received_at_ns = G.real_time_ns;
struct bitbuff bb = bitbuff_from_string(cmd->snapshot_encoded);
struct bitbuff_reader br = br_from_bitbuff(&bb);
sim_snapshot_decode(&br, ss1);
}
} else {
/* User should always have src tick present */
ASSERT(false);
}
}
} break;
default: break;
}
}
}
#endif
#endif
/* ========================== *
* Pull latest snapshot
* ========================== */
{
struct sys_lock lock = sys_mutex_lock_e(&G.local_sim_ss_mutex);
u64 old_last_tick = G.unblended_snapshot_store->last_tick;
u64 last_tick = G.local_sim_ss_store->last_tick;
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);
G.last_snapshot_received_at_ns = G.real_time_ns;
}
sys_mutex_unlock(&lock);
}
/* ========================== *
* Create user world from blended snapshots
* ========================== */
{
struct sim_snapshot *newest_snapshot = sim_snapshot_from_tick(G.sim_snapshot_store, G.sim_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_tick = newest_snapshot->tick;
@ -515,11 +544,11 @@ INTERNAL void user_update(void)
* 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 - newest_snapshot->received_at_ns;
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;
/* FIXME: Signed overflow check */
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
i64 render_time_ns = G.local_sim_predicted_time_smoothed_ns - (USER_INTERP_RATIO * newest_snapshot->real_dt_ns);
@ -528,7 +557,7 @@ INTERNAL void user_update(void)
struct sim_snapshot *left_snapshot = sim_snapshot_nil();
struct sim_snapshot *right_snapshot = newest_snapshot;
{
struct sim_snapshot *ss = sim_snapshot_from_tick(G.sim_snapshot_store, G.sim_snapshot_store->first_tick);
struct sim_snapshot *ss = sim_snapshot_from_tick(G.unblended_snapshot_store, G.unblended_snapshot_store->first_tick);
while (ss->valid) {
u64 next_tick = ss->next_tick;
i64 ss_time_ns = ss->real_time_ns;
@ -542,29 +571,29 @@ INTERNAL void user_update(void)
if (ss_time_ns > render_time_ns && ss_time_ns < right_snapshot->real_time_ns) {
right_snapshot = ss;
}
ss = sim_snapshot_from_tick(G.sim_snapshot_store, next_tick);
ss = sim_snapshot_from_tick(G.unblended_snapshot_store, next_tick);
}
}
/* 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);
G.world = sim_snapshot_alloc_from_lerp(G.world_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) {
G.world = sim_snapshot_alloc(G.world_snapshot_store, left_snapshot, left_snapshot->tick);
G.ss_blended = sim_snapshot_alloc(G.blended_snapshot_store, left_snapshot, left_snapshot->tick);
} else if (right_snapshot->valid) {
G.world = sim_snapshot_alloc(G.world_snapshot_store, right_snapshot, right_snapshot->tick);
G.ss_blended = sim_snapshot_alloc(G.blended_snapshot_store, right_snapshot, right_snapshot->tick);
}
/* Release all other render snapshots */
{
struct sim_snapshot *ss = sim_snapshot_from_tick(G.world_snapshot_store, G.world_snapshot_store->first_tick);
struct sim_snapshot *ss = sim_snapshot_from_tick(G.blended_snapshot_store, G.blended_snapshot_store->first_tick);
while (ss->valid) {
u64 next_tick = ss->next_tick;
if (ss != G.world) {
if (ss != G.ss_blended) {
sim_snapshot_release(ss);
}
ss = sim_snapshot_from_tick(G.world_snapshot_store, next_tick);
ss = sim_snapshot_from_tick(G.blended_snapshot_store, next_tick);
}
}
@ -582,11 +611,11 @@ INTERNAL void user_update(void)
}
}
if (G.world->tick != newest_snapshot->tick) {
if (G.world->valid) {
sim_snapshot_release(G.world);
if (G.ss_blended->tick != newest_snapshot->tick) {
if (G.ss_blended->valid) {
sim_snapshot_release(G.ss_blended);
}
G.world = sim_snapshot_alloc(G.world_snapshot_store, newest_snapshot, newest_snapshot->tick);
G.ss_blended = sim_snapshot_alloc(G.blended_snapshot_store, newest_snapshot, newest_snapshot->tick);
}
#endif
}
@ -684,16 +713,16 @@ INTERNAL void user_update(void)
* Find local entities
* ========================== */
struct sim_client *local_client = sim_client_from_handle(G.world, G.world->local_client);
struct sim_ent *local_player = sim_ent_from_handle(G.world, local_client->control_ent);
struct sim_ent *local_camera = sim_ent_from_handle(G.world, local_client->camera_ent);
struct sim_client *local_client = sim_client_from_handle(G.ss_blended, G.ss_blended->local_client);
struct sim_ent *local_player = sim_ent_from_handle(G.ss_blended, local_client->control_ent);
struct sim_ent *local_camera = sim_ent_from_handle(G.ss_blended, local_client->camera_ent);
/* ========================== *
* Apply shake
* ========================== */
for (u64 ent_index = 0; ent_index < G.world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &G.world->ents[ent_index];
for (u64 ent_index = 0; ent_index < G.ss_blended->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &G.ss_blended->ents[ent_index];
if (!sim_ent_is_valid_and_active(ent)) continue;
/* How much time between camera shakes */
@ -701,7 +730,7 @@ INTERNAL void user_update(void)
f32 shake = ent->shake;
if (shake > 0) {
u64 basis = hash_fnv64(HASH_FNV64_BASIS, STRING_FROM_STRUCT(&ent->handle));
u64 angle_seed0 = basis + (u64)(G.world->world_time_ns / frequency_ns);
u64 angle_seed0 = basis + (u64)(G.ss_blended->world_time_ns / frequency_ns);
u64 angle_seed1 = angle_seed0 + 1;
f32 angle0 = rng_noise_f32(angle_seed0, 0, TAU);
f32 angle1 = rng_noise_f32(angle_seed1, 0, TAU);
@ -711,7 +740,7 @@ INTERNAL void user_update(void)
struct v2 vec1 = v2_with_len(v2_from_angle(angle1), shake);
/* TODO: Cubic interp? */
f32 blend = (f32)(G.world->world_time_ns % frequency_ns) / (f32)frequency_ns;
f32 blend = (f32)(G.ss_blended->world_time_ns % frequency_ns) / (f32)frequency_ns;
struct v2 vec = v2_lerp(vec0, vec1, blend);
struct xform xf = sim_ent_get_xform(ent);
@ -860,8 +889,8 @@ INTERNAL void user_update(void)
/* Copy valid entities */
{
__profscope(copy_sprites_for_sorting);
for (u64 ent_index = 0; ent_index < G.world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &G.world->ents[ent_index];
for (u64 ent_index = 0; ent_index < G.ss_blended->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &G.ss_blended->ents[ent_index];
if (sim_ent_is_valid_and_active(ent)) {
*arena_push(scratch.arena, struct sim_ent *) = ent;
++sorted_count;
@ -888,7 +917,7 @@ INTERNAL void user_update(void)
struct sprite_tag sprite = ent->sprite;
struct sim_ent *parent = sim_ent_from_handle(G.world, ent->parent);
struct sim_ent *parent = sim_ent_from_handle(G.ss_blended, ent->parent);
struct xform xf = sim_ent_get_xform(ent);
struct xform parent_xf = sim_ent_get_xform(parent);
@ -1073,8 +1102,8 @@ INTERNAL void user_update(void)
/* Draw contact constraint */
if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTACT_CONSTRAINT)) {
struct phys_contact_constraint *data = &ent->contact_constraint_data;
struct sim_ent *e0 = sim_ent_from_handle(G.world, data->e0);
struct sim_ent *e1 = sim_ent_from_handle(G.world, data->e1);
struct sim_ent *e0 = sim_ent_from_handle(G.ss_blended, data->e0);
struct sim_ent *e1 = sim_ent_from_handle(G.ss_blended, data->e1);
(UNUSED)e0;
(UNUSED)e1;
@ -1147,8 +1176,8 @@ INTERNAL void user_update(void)
if (sim_ent_has_prop(ent, SIM_ENT_PROP_COLLISION_DEBUG)) {
struct phys_collision_debug *data = &ent->collision_debug_data;
struct collider_collision_points_result collider_res = data->res;
struct sim_ent *e0 = sim_ent_from_handle(G.world, data->e0);
struct sim_ent *e1 = sim_ent_from_handle(G.world, data->e1);
struct sim_ent *e0 = sim_ent_from_handle(G.ss_blended, data->e0);
struct sim_ent *e1 = sim_ent_from_handle(G.ss_blended, data->e1);
struct collider_shape e0_collider = e0->local_collider;
struct collider_shape e1_collider = e1->local_collider;
(UNUSED)e0_collider;
@ -1472,11 +1501,16 @@ INTERNAL void user_update(void)
control.flags |= SIM_CONTROL_FLAG_SPAWN_TEST;
}
queue_sim_cmd(scratch.arena, &cmd_frame, (struct sim_cmd) {
.kind = SIM_CMD_KIND_CLIENT_CONTROL,
.control = control,
.cursor_pos = G.world_cursor,
});
/* Set user sim control */
{
struct sys_lock lock = sys_mutex_lock_e(&G.user_sim_cmd_mutex);
u32 old_flags = G.user_sim_cmd_control.flags;
G.user_sim_cmd_control = control;
G.user_sim_cmd_control.flags |= old_flags;
G.user_sim_cmd_control_cursor_pos = G.world_cursor;
G.user_sim_cmd_ack = G.local_sim_last_known_tick;
sys_mutex_unlock(&lock);
}
}
#if COLLIDER_DEBUG
@ -1515,13 +1549,13 @@ 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.world->num_ents_allocated), FMT_UINT(G.world->num_ents_reserved)));
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)));
pos.y += spacing;
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("user tick: %F"), FMT_UINT(G.world->tick)));
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("user 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.world->real_time_ns), 3)));
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)));
pos.y += spacing;
pos.y += spacing;
@ -1602,6 +1636,7 @@ INTERNAL void user_update(void)
}
#if 0
/* Publish sim cmds */
{
struct sim_cmd_frame_list l = ZI;
@ -1615,6 +1650,7 @@ INTERNAL void user_update(void)
}
host_update(G.host);
#endif
/* Update network usage stats */
G.client_bytes_read.last_second_end = G.host->bytes_received;
@ -1768,18 +1804,11 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
#else
struct host *host = host_alloc(12345);
#endif
(UNUSED)arg;
struct bitbuff encoder_bitbuff = bitbuff_alloc(GIGABYTE(64));
struct sim_snapshot_store *snapshot_store = sim_snapshot_store_alloc();
(UNUSED)arg;
#if 1
struct sim_snapshot *prev_ss = sim_snapshot_nil();
i64 last_tick_ns = 0;
@ -1810,109 +1839,72 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
}
}
struct sim_cmd_frame_list raw_input_cmd_frames = ZI;
/* Retrieve cmds */
struct sim_cmd_frame_list input_cmds = ZI;
{
/* Grab cmds from host */
{
host_update(host);
struct host_event_array host_events = host_pop_events(scratch.arena, host);
sim_cmd_frames_decode(scratch.arena, host_events, &raw_input_cmd_frames);
sim_cmd_frames_decode(scratch.arena, host_events, &input_cmds);
}
b32 should_break = false;
/* Merge cmd frames from user thread */
struct sim_cmd *user_control_cmd = NULL;
struct sim_cmd_frame user_cmd_frame = ZI;
struct sim_cmd_frame_list input_cmd_frames = ZI;
/* Generate user sim cmd from user thread */
struct sim_cmd_frame *user_cmd_frame;
{
struct sim_cmd_frame *frame = raw_input_cmd_frames.first;
while (frame) {
struct sim_cmd_frame *next_frame = frame->next;
/* FIXME: Only do this for user cmds */
if (frame->tick == 0) {
/* All cmd frames from the user thread need to be merged into a single cmd frame representing inputs for one sim tick */
if (user_cmd_frame.tick == 0) {
user_cmd_frame = *frame;
user_cmd_frame.tick = prev_ss->tick + 1;
user_cmd_frame.next = NULL;
}
if (frame->ack > user_cmd_frame.ack) {
user_cmd_frame.ack = frame->ack;
}
struct sim_cmd *cmd = frame->first;
while (cmd) {
struct sim_cmd *next_cmd = cmd->next;
b32 should_insert = true;
if (cmd->kind == SIM_CMD_KIND_CLIENT_CONTROL) {
if (user_control_cmd) {
/* Merge with existing control cmd */
struct sys_lock lock = sys_mutex_lock_e(&G.user_sim_cmd_mutex);
user_cmd_frame = arena_push_zero(scratch.arena, struct sim_cmd_frame);
user_cmd_frame->tick = prev_ss->tick + 1;
user_cmd_frame->ack = G.user_sim_cmd_ack;
user_cmd_frame->sender_is_local = true;
if (cmd->control.flags & SIM_CONTROL_FLAG_CLEAR_ALL) {
should_break = true;
DEBUGBREAKABLE;
}
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;
should_insert = false;
u32 flags = user_control_cmd->control.flags;
*user_control_cmd = *cmd;
user_control_cmd->control.flags |= flags;
} else {
user_control_cmd = cmd;
}
}
if (should_insert) {
if (user_cmd_frame.last) {
user_cmd_frame.last->next = cmd;
} else {
user_cmd_frame.first = cmd;
}
user_cmd_frame.last = cmd;
}
cmd->next = NULL;
cmd = next_cmd;
}
} else {
if (input_cmd_frames.last) {
input_cmd_frames.last = frame;
} else {
input_cmd_frames.first = frame;
}
input_cmd_frames.last = frame;
}
frame->next = NULL;
frame = next_frame;
}
}
if (user_cmd_frame.tick != 0) {
if (input_cmd_frames.last) {
input_cmd_frames.last->next = &user_cmd_frame;
} else {
input_cmd_frames.first = &user_cmd_frame;
}
input_cmd_frames.last = &user_cmd_frame;
}
G.user_sim_cmd_control.flags = 0;
if (input_cmd_frames.first && input_cmd_frames.first->first && input_cmd_frames.first->first->control.flags & SIM_CONTROL_FLAG_CLEAR_ALL) {
DEBUGBREAKABLE;
} else if (should_break) {
DEBUGBREAKABLE;
sys_mutex_unlock(&lock);
}
if (input_cmds.last) {
input_cmds.last->next = user_cmd_frame;
} else {
input_cmds.first = user_cmd_frame;
}
input_cmds.last = user_cmd_frame;
}
/* Step */
struct sim_snapshot *ss = sim_step(snapshot_store, prev_ss, input_cmd_frames, target_dt_ns);
struct sim_snapshot *ss = sim_step(snapshot_store, prev_ss, input_cmds, target_dt_ns);
/* Publish snapshot cmds */
/* Publish snapshot to user */
/* 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);
struct sim_snapshot *remss = sim_snapshot_from_tick(G.local_sim_ss_store, G.local_sim_ss_store->first_tick);
while (remss) {
u64 next_tick = remss->next_tick;
if (remss->tick < ss->tick) {
sim_snapshot_release(remss);
} else {
break;
}
remss = sim_snapshot_from_tick(G.local_sim_ss_store, next_tick);
}
sys_mutex_unlock(&lock);
}
/* Publish snapshot cmds to networked clients */
u64 oldest_ack_tick = 0;
for (u64 i = 0; i < ss->num_clients_reserved; ++i) {
struct sim_client *client = &ss->clients[i];
if (client->valid) {
if (client->valid && client->kind == SIM_CLIENT_KIND_NETSIM) {
struct temp_arena temp = arena_temp_begin(scratch.arena);
if (oldest_ack_tick == 0 || client->ack < oldest_ack_tick) {
@ -1967,50 +1959,6 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
prev_ss = ss;
}
#else
i64 last_tick_ns = 0;
i64 target_dt_ns = NS_FROM_SECONDS(1) / SIM_TICKS_PER_SECOND;;
while (!atomic_i32_eval(&G.local_sim_thread_shutdown)) {
struct temp_arena scratch = scratch_begin_no_conflict();
{
__profscope(local_sim_sleep);
sleep_frame(last_tick_ns, target_dt_ns);
last_tick_ns = sys_time_ns();
}
struct sim_cmd_frame user_frame = ZI;
user_frame.tick = prev_ss->tick + 1;
user_frame.ack = prev_ss->tick;
/* Read cmds from host */
host_update(host);
struct host_event_array host_events = host_pop_events(scratch.arena, host);
sim_cmds_decode(scratch.arena, host_events, &sim_cmds);
/* Step */
struct sim_snapshot *ss = sim_step(ss_store, prev_ss, user_cmds, target_dt_ns);
/* Encode & enqueue sim_cmd frames */
for (struct sim_cmd_frame *f = output_cmds.first; f; f = f->next) {
struct host_channel_id channel_id = f->dst_channel;
struct string cmd_frame_msg = ZI;
struct bitbuff_writer bw = bw_from_bitbuff(&encoder_bitbuff);
sim_cmd_frame_encode(&bw, q);
cmd_frame_msg = bw_get_written(temp.arena, &bw);
host_queue_write(host, channel_id, cmd_frame_msg, 0);
}
/* Send host messages */
//host_update(host);
__profframe("Local sim");
scratch_end(scratch);
}
#endif
sim_snapshot_store_release(snapshot_store);
bitbuff_release(&encoder_bitbuff);
host_release(host);