From e0dee3e9e8a4c49e686d6c22b9afef90fe12ea23 Mon Sep 17 00:00:00 2001 From: jacob Date: Thu, 20 Feb 2025 17:16:36 -0600 Subject: [PATCH] sim snasphot store -> client refactor --- src/app.c | 7 +- src/bitbuff.c | 13 + src/bitbuff.h | 1 + src/phys.c | 107 +-- src/phys.h | 46 +- src/sim.c | 2023 +++++++++++++------------------------------- src/sim.h | 253 +++++- src/sim_client.c | 189 ----- src/sim_client.h | 81 -- src/sim_ent.c | 271 ++---- src/sim_ent.h | 79 +- src/sim_snapshot.c | 575 ------------- src/sim_snapshot.h | 160 ---- src/sim_step.c | 1459 ++++++++++++++++++++++++++++++++ src/sim_step.h | 83 ++ src/space.c | 20 +- src/space.h | 2 + src/user.c | 500 ++--------- src/user.h | 4 +- 19 files changed, 2672 insertions(+), 3201 deletions(-) delete mode 100644 src/sim_client.c delete mode 100644 src/sim_client.h delete mode 100644 src/sim_snapshot.c delete mode 100644 src/sim_snapshot.h create mode 100644 src/sim_step.c create mode 100644 src/sim_step.h diff --git a/src/app.c b/src/app.c index 8a98bea6..6837042b 100644 --- a/src/app.c +++ b/src/app.c @@ -6,7 +6,7 @@ #include "work.h" #include "user.h" #include "sim.h" -#include "sim_snapshot.h" +#include "sim.h" #include "playback.h" #include "log.h" #include "resource.h" @@ -25,6 +25,7 @@ #include "rng.h" #include "sock.h" #include "host.h" +#include "bitbuff.h" struct exit_callback { app_exit_callback_func *func; @@ -235,8 +236,8 @@ void app_entry_point(struct string args_str) struct sound_startup_receipt sound_sr = sound_startup(&work_sr, &asset_cache_sr, &resource_sr); struct draw_startup_receipt draw_sr = draw_startup(&renderer_sr, &font_sr); struct phys_startup_receipt phys_sr = phys_startup(); - struct sim_snapshot_startup_receipt sim_snapshot_sr = sim_snapshot_startup(); - struct user_startup_receipt user_sr = user_startup(&work_sr, &renderer_sr, &font_sr, &sprite_sr, &draw_sr, &asset_cache_sr, &sound_sr, &mixer_sr, &phys_sr, &host_sr, &sim_snapshot_sr, args_str, &window); + struct sim_startup_receipt sim_sr = sim_startup(); + struct user_startup_receipt user_sr = user_startup(&work_sr, &renderer_sr, &font_sr, &sprite_sr, &draw_sr, &asset_cache_sr, &sound_sr, &mixer_sr, &phys_sr, &host_sr, &sim_sr, args_str, &window); struct playback_startup_receipt playback_sr = playback_startup(&mixer_sr); (UNUSED)user_sr; diff --git a/src/bitbuff.c b/src/bitbuff.c index f46c5010..cf7903fe 100644 --- a/src/bitbuff.c +++ b/src/bitbuff.c @@ -140,16 +140,19 @@ struct bitbuff_writer bw_from_bitbuff_no_debug(struct bitbuff *bb) return res; } +/* FIXME: Handle overflowed bw */ u64 bw_num_bits_written(struct bitbuff_writer *bw) { return bw->cur_bit; } +/* FIXME: Handle overflowed bw */ u64 bw_num_bytes_written(struct bitbuff_writer *bw) { return (bw->cur_bit + 7) >> 3; } +/* FIXME: Handle overflowed bw */ struct string bw_get_written(struct arena *arena, struct bitbuff_writer *bw) { struct string res = ZI; @@ -159,6 +162,12 @@ struct string bw_get_written(struct arena *arena, struct bitbuff_writer *bw) return res; } +/* FIXME: Handle overflowed bw */ +u8 *bw_get_written_raw(struct bitbuff_writer *bw) +{ + return bw->base; +} + /* Returns true if num_bits would cause the writer to overflow its fixed buffer size (if writer is not backed by a dynamic arena bitbuff) */ b32 bw_check_overflow_bits(struct bitbuff_writer *bw, u64 num_bits) { @@ -374,24 +383,28 @@ struct bitbuff_reader br_from_bitbuff_no_debug(struct bitbuff *bb) } /* Returns the number of bits read from the bitbuff */ +/* FIXME: Handle overflowed br */ u64 br_cur_bit(struct bitbuff_reader *br) { return br->cur_bit; } /* Returns the number of *full* bytes read from the bitbuff */ +/* FIXME: Handle overflowed br */ u64 br_cur_byte(struct bitbuff_reader *br) { return br->cur_bit >> 3; } /* Returns the number of bits left until the bitbuff overflows */ +/* FIXME: Handle overflowed br */ u64 br_num_bits_left(struct bitbuff_reader *br) { return (br->base_len << 3) - br->cur_bit; } /* Returns the number of *full* bytes left until the bitbuff overflows */ +/* FIXME: Handle overflowed br */ u64 br_num_bytes_left(struct bitbuff_reader *br) { return br->base_len - (br->cur_bit >> 3); diff --git a/src/bitbuff.h b/src/bitbuff.h index c9f9b11b..72204a56 100644 --- a/src/bitbuff.h +++ b/src/bitbuff.h @@ -42,6 +42,7 @@ u64 bw_num_bits_written(struct bitbuff_writer *bw); u64 bw_num_bytes_written(struct bitbuff_writer *bw); struct string bw_get_written(struct arena *arena, struct bitbuff_writer *bw); +u8 *bw_get_written_raw(struct bitbuff_writer *bw); b32 bw_check_overflow_bits(struct bitbuff_writer *bw, u64 num_bits); diff --git a/src/phys.c b/src/phys.c index eb04200c..09406df5 100644 --- a/src/phys.c +++ b/src/phys.c @@ -1,6 +1,6 @@ #include "phys.h" -#include "sim_snapshot.h" #include "sim_ent.h" +#include "sim_step.h" #include "math.h" #include "scratch.h" #include "space.h" @@ -39,16 +39,16 @@ struct phys_startup_receipt phys_startup(void) * Contact * ========================== */ -struct phys_collision_data_array phys_create_and_update_contacts(struct arena *arena, struct phys_ctx *ctx, f32 elapsed_dt, u64 phys_iteration) +struct phys_collision_data_array phys_create_and_update_contacts(struct arena *arena, struct phys_step_ctx *ctx, f32 elapsed_dt, u64 phys_iteration) { __prof; struct phys_collision_data_array res = ZI; res.a = arena_dry_push(arena, struct phys_collision_data); - struct sim_snapshot *ss = ctx->ss; - struct space *space = ctx->space; - struct sim_ent_lookup *contact_lookup = ctx->contact_lookup; + struct sim_snapshot *ss = ctx->sim_step_ctx->world; + struct space *space = ctx->sim_step_ctx->accel->space; + struct sim_lookup *contact_lookup = &ctx->sim_step_ctx->accel->contact_lookup; #if COLLIDER_DEBUG - struct sim_ent_lookup *debug_lookup = ctx->collision_debug_lookup; + struct sim_lookup *debug_lookup = ctx->collision_debug_lookup; #endif struct sim_ent *root = sim_ent_from_handle(ss, SIM_ENT_ROOT_HANDLE); @@ -96,8 +96,8 @@ struct phys_collision_data_array phys_create_and_update_contacts(struct arena *a e1_collider = check0_collider; } - struct sim_ent_lookup_key key = sim_ent_lookup_key_from_two_handles(e0->handle, e1->handle); - struct sim_ent_lookup_entry *constraint_entry = sim_ent_lookup_get(contact_lookup, key); + struct sim_lookup_key key = sim_lookup_key_from_two_handles(e0->handle, e1->handle); + struct sim_lookup_entry *constraint_entry = sim_lookup_get(contact_lookup, key); struct sim_ent *constraint_ent = sim_ent_nil(); if (constraint_entry) { @@ -111,7 +111,7 @@ struct phys_collision_data_array phys_create_and_update_contacts(struct arena *a } } else { /* Constraint ent no longer valid, delete constraint_entry*/ - sim_ent_lookup_remove(contact_lookup, constraint_entry); + sim_lookup_remove(contact_lookup, constraint_entry); constraint_entry= NULL; } } @@ -139,7 +139,7 @@ struct phys_collision_data_array phys_create_and_update_contacts(struct arena *a sim_ent_enable_prop(constraint_ent, SIM_ENT_PROP_CONTACT_CONSTRAINT); sim_ent_activate(constraint_ent, tick); ASSERT(!constraint_entry); /* Existing entry should never be present here */ - sim_ent_lookup_set(contact_lookup, key, constraint_ent->handle); + sim_lookup_set(contact_lookup, key, constraint_ent->handle); } /* Push collision data */ @@ -238,7 +238,7 @@ struct phys_collision_data_array phys_create_and_update_contacts(struct arena *a #if COLLIDER_DEBUG && COLLIDER_DEBUG_DETAILED { struct sim_ent *dbg_ent = sim_ent_nil(); - struct sim_ent_lookup_entry *dbg_entry = sim_ent_lookup_get(debug_lookup, key); + struct sim_lookup_entry *dbg_entry = sim_lookup_get(debug_lookup, key); if (dbg_entry) { dbg_ent = sim_ent_from_handle(ss, dbg_entry->entity); } @@ -247,7 +247,7 @@ struct phys_collision_data_array phys_create_and_update_contacts(struct arena *a /* FIXME: Entity never released */ dbg_ent = sim_ent_alloc(root); sim_ent_enable_prop(dbg_ent, SIM_ENT_PROP_COLLISION_DEBUG); - sim_ent_lookup_set(debug_lookup, key, dbg_ent->handle); + sim_lookup_set(debug_lookup, key, dbg_ent->handle); } struct phys_collision_debug *dbg = &dbg_ent->collision_debug_data; @@ -280,11 +280,11 @@ struct phys_collision_data_array phys_create_and_update_contacts(struct arena *a return res; } -void phys_prepare_contacts(struct phys_ctx *ctx, u64 phys_iteration) +void phys_prepare_contacts(struct phys_step_ctx *ctx, u64 phys_iteration) { __prof; - struct sim_snapshot *ss = ctx->ss; - struct sim_ent_lookup *contact_lookup = ctx->contact_lookup; + struct sim_snapshot *ss = ctx->sim_step_ctx->world; + struct sim_lookup *contact_lookup = &ctx->sim_step_ctx->accel->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]; @@ -365,10 +365,10 @@ void phys_prepare_contacts(struct phys_ctx *ctx, u64 phys_iteration) sim_ent_disable_prop(constraint_ent, SIM_ENT_PROP_ACTIVE); sim_ent_enable_prop(constraint_ent, SIM_ENT_PROP_RELEASE_THIS_TICK); /* Remove from lookup */ - struct sim_ent_lookup_key key = sim_ent_lookup_key_from_two_handles(constraint->e0, constraint->e1); - struct sim_ent_lookup_entry *entry = sim_ent_lookup_get(contact_lookup, key); + 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); if (entry) { - sim_ent_lookup_remove(contact_lookup, entry); + sim_lookup_remove(contact_lookup, entry); } else { ASSERT(false); /* This should always exist */ } @@ -376,7 +376,7 @@ void phys_prepare_contacts(struct phys_ctx *ctx, u64 phys_iteration) } #if COLLIDER_DEBUG - struct sim_ent_lookup *debug_lookup = ctx->debug_lookup; + 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; @@ -395,11 +395,11 @@ void phys_prepare_contacts(struct phys_ctx *ctx, u64 phys_iteration) sim_ent_enable_prop(dbg_ent, SIM_ENT_PROP_RELEASE_THIS_TICK); /* Remove from lookup */ - struct sim_ent_lookup_key key = sim_ent_lookup_key_from_two_handles(dbg->e0, dbg->e1); - struct sim_ent_lookup_entry *entry = sim_ent_lookup_get(debug_lookup, key); + struct sim_lookup_key key = sim_lookup_key_from_two_handles(dbg->e0, dbg->e1); + struct sim_lookup_entry *entry = sim_lookup_get(debug_lookup, key); if (entry) { - sim_ent_lookup_remove(debug_lookup, entry); + sim_lookup_remove(debug_lookup, entry); } else { ASSERT(false); /* This should always exist */ } @@ -408,10 +408,10 @@ void phys_prepare_contacts(struct phys_ctx *ctx, u64 phys_iteration) #endif } -void phys_warm_start_contacts(struct phys_ctx *ctx) +void phys_warm_start_contacts(struct phys_step_ctx *ctx) { __prof; - struct sim_snapshot *ss = ctx->ss; + 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; @@ -463,10 +463,10 @@ void phys_warm_start_contacts(struct phys_ctx *ctx) } } -void phys_solve_contacts(struct phys_ctx *ctx, f32 dt, b32 apply_bias) +void phys_solve_contacts(struct phys_step_ctx *ctx, f32 dt, b32 apply_bias) { __prof; - struct sim_snapshot *ss = ctx->ss; + 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; @@ -594,10 +594,10 @@ struct phys_motor_joint phys_motor_joint_from_def(struct phys_motor_joint_def de return res; } -void phys_prepare_motor_joints(struct phys_ctx *ctx) +void phys_prepare_motor_joints(struct phys_step_ctx *ctx) { __prof; - struct sim_snapshot *ss = ctx->ss; + 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 *joint_ent = &ss->ents[sim_ent_index]; if (!sim_ent_is_valid_and_active(joint_ent)) continue; @@ -658,10 +658,10 @@ void phys_prepare_motor_joints(struct phys_ctx *ctx) } } -void phys_warm_start_motor_joints(struct phys_ctx *ctx) +void phys_warm_start_motor_joints(struct phys_step_ctx *ctx) { __prof; - struct sim_snapshot *ss = ctx->ss; + 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 *joint_ent = &ss->ents[sim_ent_index]; if (!sim_ent_is_valid_and_active(joint_ent)) continue; @@ -690,10 +690,10 @@ void phys_warm_start_motor_joints(struct phys_ctx *ctx) } } -void phys_solve_motor_joints(struct phys_ctx *ctx, f32 dt) +void phys_solve_motor_joints(struct phys_step_ctx *ctx, f32 dt) { __prof; - struct sim_snapshot *ss = ctx->ss; + 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 *joint_ent = &ss->ents[sim_ent_index]; if (!sim_ent_is_valid_and_active(joint_ent)) continue; @@ -783,10 +783,10 @@ struct phys_mouse_joint phys_mouse_joint_from_def(struct phys_mouse_joint_def de return res; } -void phys_prepare_mouse_joints(struct phys_ctx *ctx) +void phys_prepare_mouse_joints(struct phys_step_ctx *ctx) { __prof; - struct sim_snapshot *ss = ctx->ss; + 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 *joint_ent = &ss->ents[sim_ent_index]; if (!sim_ent_is_valid_and_active(joint_ent)) continue; @@ -830,10 +830,10 @@ void phys_prepare_mouse_joints(struct phys_ctx *ctx) } } -void phys_warm_start_mouse_joints(struct phys_ctx *ctx) +void phys_warm_start_mouse_joints(struct phys_step_ctx *ctx) { __prof; - struct sim_snapshot *ss = ctx->ss; + 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 *joint_ent = &ss->ents[sim_ent_index]; if (!sim_ent_is_valid_and_active(joint_ent)) continue; @@ -852,10 +852,10 @@ void phys_warm_start_mouse_joints(struct phys_ctx *ctx) } } -void phys_solve_mouse_joints(struct phys_ctx *ctx, f32 dt) +void phys_solve_mouse_joints(struct phys_step_ctx *ctx, f32 dt) { __prof; - struct sim_snapshot *ss = ctx->ss; + 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 *joint_ent = &ss->ents[sim_ent_index]; if (!sim_ent_is_valid_and_active(joint_ent)) continue; @@ -939,10 +939,10 @@ INTERNAL struct xform get_derived_xform(struct sim_ent *ent, f32 dt) return xf; } -void phys_integrate_forces(struct phys_ctx *ctx, f32 dt) +void phys_integrate_forces(struct phys_step_ctx *ctx, f32 dt) { __prof; - struct sim_snapshot *ss = ctx->ss; + 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 *ent = &ss->ents[sim_ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; @@ -981,10 +981,10 @@ void phys_integrate_forces(struct phys_ctx *ctx, f32 dt) } } -void phys_integrate_velocities(struct phys_ctx *ctx, f32 dt) +void phys_integrate_velocities(struct phys_step_ctx *ctx, f32 dt) { __prof; - struct sim_snapshot *ss = ctx->ss; + 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 *ent = &ss->ents[sim_ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; @@ -999,11 +999,11 @@ void phys_integrate_velocities(struct phys_ctx *ctx, f32 dt) * Earliest time of impact * ========================== */ -f32 phys_determine_earliest_toi_for_bullets(struct phys_ctx *ctx, f32 step_dt, f32 tolerance, u32 max_iterations) +f32 phys_determine_earliest_toi_for_bullets(struct phys_step_ctx *ctx, f32 step_dt, f32 tolerance, u32 max_iterations) { __prof; - struct sim_snapshot *ss = ctx->ss; - struct space *space = ctx->space; + struct sim_snapshot *ss = ctx->sim_step_ctx->world; + struct space *space = ctx->sim_step_ctx->accel->space; f32 smallest_t = 1; for (u64 e0_index = 0; e0_index < ss->num_ents_reserved; ++e0_index) { @@ -1050,10 +1050,10 @@ f32 phys_determine_earliest_toi_for_bullets(struct phys_ctx *ctx, f32 step_dt, f * Space * ========================== */ -void phys_update_aabbs(struct phys_ctx *ctx) +void phys_update_aabbs(struct phys_step_ctx *ctx) { - struct sim_snapshot *ss = ctx->ss; - struct space *space = ctx->space; + struct sim_snapshot *ss = ctx->sim_step_ctx->world; + struct space *space = ctx->sim_step_ctx->accel->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; @@ -1075,11 +1075,12 @@ void phys_update_aabbs(struct phys_ctx *ctx) * ========================== */ /* Returns phys iteration to be fed into next step. Supplied iteration must be > 0. */ -void phys_step(struct phys_ctx *ctx, f32 timestep) +void phys_step(struct phys_step_ctx *ctx, f32 timestep) { __prof; phys_integrate_forces(ctx, timestep); - u64 phys_iteration = ctx->ss->phys_iteration; + struct sim_snapshot *ss = ctx->sim_step_ctx->world; + u64 phys_iteration = ss->phys_iteration; phys_update_aabbs(ctx); @@ -1112,7 +1113,7 @@ void phys_step(struct phys_ctx *ctx, f32 timestep) if (ctx->pre_solve_callback) { __profscope(pre_solve_callback); - ctx->pre_solve_callback(collision_data, ctx->ss); + ctx->pre_solve_callback(collision_data, ctx->sim_step_ctx); } f32 substep_dt = step_dt / SIM_PHYSICS_SUBSTEPS; @@ -1146,11 +1147,11 @@ void phys_step(struct phys_ctx *ctx, f32 timestep) if (ctx->post_solve_callback) { __profscope(post_solve_callback); - ctx->post_solve_callback(collision_data, ctx->ss); + ctx->post_solve_callback(collision_data, ctx->sim_step_ctx); } scratch_end(scratch); } - ctx->ss->phys_iteration = phys_iteration; + ss->phys_iteration = phys_iteration; } diff --git a/src/phys.h b/src/phys.h index 8d4b28b2..64e4bcd7 100644 --- a/src/phys.h +++ b/src/phys.h @@ -6,7 +6,7 @@ struct space; struct sim_ent_lookup; -struct sim_snapshot; +struct sim_step_ctx; struct phys_contact_constraint; struct phys_collision_data { @@ -25,20 +25,14 @@ struct phys_collision_data_array { }; struct phys_collision_data; -#define PHYS_COLLISION_CALLBACK_FUNC_DEF(name, arg_collision_data, arg_ss) void name(struct phys_collision_data_array arg_collision_data, struct sim_snapshot *arg_ss) -typedef PHYS_COLLISION_CALLBACK_FUNC_DEF(phys_collision_callback_func, collision_data, ss); +#define PHYS_COLLISION_CALLBACK_FUNC_DEF(name, arg_collision_data, arg_sim_step_ctx) void name(struct phys_collision_data_array arg_collision_data, struct sim_step_ctx *arg_sim_step_ctx) +typedef PHYS_COLLISION_CALLBACK_FUNC_DEF(phys_collision_callback_func, collision_data, ctx); /* Structure containing data used for a single physics step */ -struct phys_ctx { - struct sim_snapshot *ss; +struct phys_step_ctx { + struct sim_step_ctx *sim_step_ctx; 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 }; /* ========================== * @@ -107,10 +101,10 @@ struct phys_collision_debug { struct xform xf1; }; -struct phys_collision_data_array phys_create_and_update_contacts(struct arena *arena, struct phys_ctx *ctx, f32 elapsed_dt, u64 phys_iteration); -void phys_prepare_contacts(struct phys_ctx *ctx, u64 phys_iteration); -void phys_warm_start_contacts(struct phys_ctx *ctx); -void phys_solve_contacts(struct phys_ctx *ctx, f32 dt, b32 apply_bias); +struct phys_collision_data_array phys_create_and_update_contacts(struct arena *arena, struct phys_step_ctx *ctx, f32 elapsed_dt, u64 phys_iteration); +void phys_prepare_contacts(struct phys_step_ctx *ctx, u64 phys_iteration); +void phys_warm_start_contacts(struct phys_step_ctx *ctx); +void phys_solve_contacts(struct phys_step_ctx *ctx, f32 dt, b32 apply_bias); /* ========================== * * Motor joint @@ -147,9 +141,9 @@ struct phys_motor_joint { }; struct phys_motor_joint phys_motor_joint_from_def(struct phys_motor_joint_def def); -void phys_prepare_motor_joints(struct phys_ctx *ctx); -void phys_warm_start_motor_joints(struct phys_ctx *ctx); -void phys_solve_motor_joints(struct phys_ctx *ctx, f32 dt); +void phys_prepare_motor_joints(struct phys_step_ctx *ctx); +void phys_warm_start_motor_joints(struct phys_step_ctx *ctx); +void phys_solve_motor_joints(struct phys_step_ctx *ctx, f32 dt); /* ========================== * * Mouse joint @@ -180,33 +174,33 @@ struct phys_mouse_joint { }; struct phys_mouse_joint phys_mouse_joint_from_def(struct phys_mouse_joint_def def); -void phys_prepare_mouse_joints(struct phys_ctx *ctx); -void phys_warm_start_mouse_joints(struct phys_ctx *ctx); -void phys_solve_mouse_joints(struct phys_ctx *ctx, f32 dt); +void phys_prepare_mouse_joints(struct phys_step_ctx *ctx); +void phys_warm_start_mouse_joints(struct phys_step_ctx *ctx); +void phys_solve_mouse_joints(struct phys_step_ctx *ctx, f32 dt); /* ========================== * * Integration * ========================== */ -void phys_integrate_forces(struct phys_ctx *ctx, f32 dt); -void phys_integrate_velocities(struct phys_ctx *ctx, f32 dt); +void phys_integrate_forces(struct phys_step_ctx *ctx, f32 dt); +void phys_integrate_velocities(struct phys_step_ctx *ctx, f32 dt); /* ========================== * * Earliest time of impact * ========================== */ -f32 phys_determine_earliest_toi_for_bullets(struct phys_ctx *ctx, f32 step_dt, f32 tolerance, u32 max_iterations); +f32 phys_determine_earliest_toi_for_bullets(struct phys_step_ctx *ctx, f32 step_dt, f32 tolerance, u32 max_iterations); /* ========================== * * Space * ========================== */ -void phys_update_aabbs(struct phys_ctx *ctx); +void phys_update_aabbs(struct phys_step_ctx *ctx); /* ========================== * * Step * ========================== */ -void phys_step(struct phys_ctx *ctx, f32 timestep); +void phys_step(struct phys_step_ctx *ctx, f32 timestep); #endif diff --git a/src/sim.c b/src/sim.c index 36dba40d..e07d1a6a 100644 --- a/src/sim.c +++ b/src/sim.c @@ -1,1481 +1,646 @@ #include "sim.h" #include "sim_ent.h" -#include "sim_client.h" -#include "sim_snapshot.h" -#include "sys.h" -#include "util.h" -#include "sprite.h" -#include "math.h" -#include "scratch.h" -#include "atomic.h" -#include "app.h" -#include "log.h" -#include "phys.h" -#include "collider.h" -#include "rng.h" -#include "space.h" -#include "bitbuff.h" #include "host.h" +#include "arena.h" +#include "util.h" +#include "arena.h" +#include "bitbuff.h" + +/* Sim hierarchy is as follows: + * + * Client store -> clients -> snapshots -> ents + * + * A client store holds clients, which can be retrieved by client handle or by a host channel id (if one is set). + * + * A client holds snapshots, which can be retrieved by tick (64 bit unsigned int). + * - The snapshots stored in clients & the contents of those snapshots are determined from data transmitted by the client. + * - Different kinds of clients will transmit different subsets of snapshot data (e.g. a master client will transmit most/all ent state, while slave clients may just transmit 1 or more cmds) + * - A client will never hold more than one snapshot for the same tick (e.g. a client will never have 2 snapshots at tick 5) + * + * A snapshot holds the ent tree for a particular tick, in which ents can be retrieved by ent handle. + * - A tick is the quantized time step that all clients implicitly conform to. + * + * An ent is the smallest unit of simulation state. + */ + +#define CLIENT_LOOKUP_BUCKETS 127 +#define TICK_LOOKUP_BUCKETS 127 /* ========================== * - * Test + * Startup * ========================== */ -/* TODO: Remove this */ +GLOBAL struct { + struct arena nil_arena; + struct sim_client_store *nil_client_store; + struct sim_client *nil_client; + struct sim_snapshot *nil_snapshot; + struct sim_ent *nil_ent; +} G = ZI, DEBUG_ALIAS(G, G_sim); -INTERNAL void spawn_test_entities(struct sim_snapshot *world, struct v2 offset) +/* Accessed via `sim_client_store_nil()` */ +READONLY struct sim_client_store **_g_sim_client_store_nil = &G.nil_client_store; + +/* Accessed via `sim_client_nil()` */ +READONLY struct sim_client **_g_sim_client_nil = &G.nil_client; + +/* Accessed via `sim_snapshot_nil()` */ +READONLY struct sim_snapshot **_g_sim_snapshot_nil = &G.nil_snapshot; + +/* Accessed via `sim_ent_nil()` */ +READONLY struct sim_ent **_g_sim_ent_nil = &G.nil_ent; + +struct sim_startup_receipt sim_startup(void) { - struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE); - root->mass_unscaled = F32_INFINITY; - root->inertia_unscaled = F32_INFINITY; + G.nil_arena = arena_alloc(GIGABYTE(1)); - /* Enemy */ - { - struct sim_ent *e = sim_ent_alloc(root); + /* Nil client store */ + G.nil_client_store = arena_push_zero(&G.nil_arena, struct sim_client_store); + G.nil_client_store->valid = false; - struct v2 pos = V2(1, -2); - pos = v2_add(pos, offset); - f32 r = 0; - struct v2 size = V2(1, 1); - struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size); - sim_ent_set_xform(e, xf); + /* Nil client */ + G.nil_client = arena_push_zero(&G.nil_arena, struct sim_client); + G.nil_client->valid = false; + G.nil_client->store = sim_client_store_nil(); - e->sprite = sprite_tag_from_path(LIT("res/graphics/tim.ase")); - e->sprite_collider_slice = LIT("shape"); - e->layer = SIM_LAYER_SHOULDERS; + /* Nil snapshot */ + G.nil_snapshot = arena_push_zero(&G.nil_arena, struct sim_snapshot); + G.nil_snapshot->valid = false; + G.nil_snapshot->client = sim_client_nil(); - sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC); - e->mass_unscaled = 10; - e->inertia_unscaled = 10; - e->linear_ground_friction = 250; - e->angular_ground_friction = 200; - } + /* Nil ent */ + G.nil_ent = arena_push_zero(&G.nil_arena, struct sim_ent); + G.nil_ent->ss = sim_snapshot_nil(); + G.nil_ent->valid = false; + G.nil_ent->handle = SIM_ENT_NIL_HANDLE; + G.nil_ent->local_xform = XFORM_IDENT; + G.nil_ent->cached_global_xform = XFORM_IDENT; + G.nil_ent->cached_global_xform_dirty = false; + G.nil_ent->friction = 0.5f; + G.nil_ent->mass_unscaled = 1; + G.nil_ent->inertia_unscaled = 1; + G.nil_ent->sprite_local_xform = XFORM_IDENT; + G.nil_ent->sprite_tint = COLOR_WHITE; - /* Big box */ -#if 1 - { - struct sim_ent *e = sim_ent_alloc(root); - - struct v2 pos = V2(1, -0.5); - pos = v2_add(pos, offset); - f32 r = 0; - struct v2 size = V2(0.5, 0.25); - struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size); - sim_ent_set_xform(e, xf); - - e->sprite = sprite_tag_from_path(LIT("res/graphics/box.ase")); - e->sprite_collider_slice = LIT("shape"); - e->layer = SIM_LAYER_SHOULDERS; - - sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC); - e->mass_unscaled = 100; - e->inertia_unscaled = 50; - e->linear_ground_friction = 100; - e->angular_ground_friction = 50; - } -#endif - - /* Tiny box */ -#if 0 - { - struct sim_ent *e = sim_ent_alloc(root); - - struct v2 pos = V2(1, -0.5); - pos = v2_add(pos, offset); - f32 r = PI / 4; - struct v2 size = V2(0.5, 0.25); - struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size); - sim_ent_set_xform(e, xf); - - e->sprite = sprite_tag_from_path(LIT("res/graphics/bullet.ase")); - e->sprite_collider_slice = LIT("shape"); - e->layer = SIM_LAYER_SHOULDERS; - - sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC); - e->mass_unscaled = 0.5; - e->inertia_unscaled = 1000; - e->linear_ground_friction = 0.001; - } -#endif -} - -INTERNAL struct sim_ent *spawn_test_player(struct sim_snapshot *world) -{ - struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE); - - /* Player */ - struct sim_ent *player_ent = sim_ent_nil(); - //if (!ctx->extra_spawn) { - { - - struct sim_ent *e = sim_ent_alloc(root); - - struct v2 pos = V2(1, -1); - - //struct v2 size = V2(0.5, 0.5); - //struct v2 size = V2(0.5, 0.25); - struct v2 size = V2(1.0, 1.0); - - //f32 r = PI / 4; - f32 r = 0; - - { - sim_ent_enable_prop(e, SIM_ENT_PROP_TEST); - e->sprite = sprite_tag_from_path(LIT("res/graphics/tim.ase")); - e->mass_unscaled = 10; - e->inertia_unscaled = 5; - } - - //e->sprite = sprite_tag_from_path(LIT("res/graphics/box_rounded.ase")); - //e->sprite_span_name = LIT("idle.unarmed"); - //e->sprite_span_name = LIT("idle.one_handed"); - e->sprite_span_name = LIT("idle.two_handed"); - e->layer = SIM_LAYER_SHOULDERS; - - e->local_collider.points[0] = V2(0, 0); - e->local_collider.count = 1; - e->local_collider.radius = 0.15f; - - struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size); - //xf.bx.y = -1.f; - - sim_ent_set_xform(e, xf); - - e->linear_ground_friction = 250; - e->angular_ground_friction = 200; - - e->friction = 0; - - //e->control_force = 500; - e->control_force = 500; - e->control_force_max_speed = 4; - - //e->control_torque = 5000; - e->control_torque = F32_INFINITY; - - - sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC); - - player_ent = e; - } - - /* Player weapon */ - if (player_ent->valid) { - struct sim_ent *e = sim_ent_alloc(player_ent); - e->sprite = sprite_tag_from_path(LIT("res/graphics/gun.ase")); - - sim_ent_enable_prop(e, SIM_ENT_PROP_ATTACHED); - e->attach_slice = LIT("attach.wep"); - e->layer = SIM_LAYER_RELATIVE_WEAPON; - - sim_ent_enable_prop(e, SIM_ENT_PROP_WEAPON); - e->trigger_delay = 1.0f / 10.0f; - //e->trigger_delay = 1.0f / 100.0f; - - player_ent->equipped = e->handle; - } - - return player_ent; -} - -INTERNAL struct sim_ent *spawn_test_player_camera(struct sim_snapshot *world, struct sim_ent *player_ent) -{ - struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE); - - struct sim_ent *camera_ent = sim_ent_nil(); - if (player_ent->valid) { - camera_ent = sim_ent_alloc(root); - sim_ent_set_xform(camera_ent, XFORM_IDENT); - - sim_ent_enable_prop(camera_ent, SIM_ENT_PROP_CAMERA); - sim_ent_enable_prop(camera_ent, SIM_ENT_PROP_CAMERA_ACTIVE); - camera_ent->camera_follow = player_ent->handle; - - f32 width = (f32)DEFAULT_CAMERA_WIDTH; - f32 height = (f32)DEFAULT_CAMERA_HEIGHT; - camera_ent->camera_quad_xform = XFORM_TRS(.s = V2(width, height)); - } - - return camera_ent; -} - -INTERNAL void test_clear_level(struct sim_snapshot *world) -{ - for (u64 j = 0; j < world->num_ents_reserved; ++j) { - struct sim_ent *ent = &world->ents[j]; - if (ent->valid) { - sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE_NEXT_TICK); - } - } + /* Lock nil arena */ + arena_set_readonly(&G.nil_arena); + return (struct sim_startup_receipt) { 0 }; } /* ========================== * - * Release entities + * Client store alloc * ========================== */ -#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 = 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 < 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; - } - } - - /* Release references */ - for (u64 i = 0; i < ents_to_release_count; ++i) { - struct sim_ent *ent = ents_to_release[i]; - /* Release space entry */ - { - struct space_entry *space_entry = space_entry_from_handle(space, ent->space_handle); - if (space_entry->valid) { - space_entry_release(space_entry); - } - } - } - - /* 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); -} -#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 - * ========================== */ - -INTERNAL PHYS_COLLISION_CALLBACK_FUNC_DEF(on_collision, collision_data_array, world) -{ - struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE); - - for (u64 i = 0; i < collision_data_array.count; ++i) { - struct phys_collision_data *data = &collision_data_array.a[i]; - - struct phys_contact_constraint *constraint = data->constraint; - struct sim_ent *e0 = sim_ent_from_handle(world, data->e0); - struct sim_ent *e1 = sim_ent_from_handle(world, data->e1); - - if (sim_ent_is_valid_and_active(e0) && sim_ent_is_valid_and_active(e1)) { - /* Bullet hit entity */ - if (sim_ent_has_prop(e0, SIM_ENT_PROP_BULLET) || sim_ent_has_prop(e1, SIM_ENT_PROP_BULLET)) { - struct v2 normal = data->normal; /* Impact normal */ - struct v2 vrel = v2_neg(data->vrel); /* Impact velocity */ - - struct sim_ent *target = e0; - struct sim_ent *bullet = e1; - if (sim_ent_has_prop(e0, SIM_ENT_PROP_BULLET)) { - target = e1; - bullet = e0; - normal = v2_neg(normal); - vrel = v2_neg(vrel); - } - struct sim_ent *src = sim_ent_from_handle(world, bullet->bullet_src); - - if (bullet->bullet_has_hit || sim_ent_handle_eq(src->top, target->top)) { - /* Ignore collision if bullet already spent or if weapon and - * target share same top level parent */ - /* NOTE: Since bullet is most likely just a sensor skip_solve is probably already true */ - constraint->skip_solve = true; - } else { - struct v2 point = data->point; - - /* Update bullet */ - bullet->bullet_has_hit = true; - sim_ent_enable_prop(bullet, SIM_ENT_PROP_RELEASE_THIS_TICK); - - /* Update tracer */ - struct sim_ent *tracer = sim_ent_from_handle(world, bullet->bullet_tracer); - if (sim_ent_is_valid_and_active(tracer)) { - struct xform xf = sim_ent_get_xform(tracer); - xf.og = point; - sim_ent_set_xform(tracer, xf); - sim_ent_set_linear_velocity(tracer, V2(0, 0)); - } - - /* Update target */ - struct v2 knockback = v2_mul(v2_norm(vrel), bullet->bullet_knockback); - sim_ent_apply_linear_impulse(target, knockback, point); - - /* Create test blood */ - /* TODO: Remove this */ - { - struct xform xf = XFORM_TRS(.t = point, .r = rng_rand_f32(0, TAU)); - struct sim_ent *decal = sim_ent_alloc(root); - decal->sprite = sprite_tag_from_path(LIT("res/graphics/blood.ase")); - decal->sprite_tint = RGBA_32_F(1, 1, 1, 0.25f); - decal->layer = SIM_LAYER_FLOOR_DECALS; - sim_ent_set_xform(decal, xf); - - f32 perp_range = 0.5; - struct v2 linear_velocity = v2_mul(normal, 0.5); - linear_velocity = v2_add(linear_velocity, v2_mul(v2_perp(normal), rng_rand_f32(-perp_range, perp_range))); - - f32 angular_velocity_range = 5; - f32 angular_velocity = rng_rand_f32(-angular_velocity_range, angular_velocity_range); - - sim_ent_enable_prop(decal, SIM_ENT_PROP_PHYSICAL_KINEMATIC); - sim_ent_set_linear_velocity(decal, linear_velocity); - sim_ent_set_angular_velocity(decal, angular_velocity); - - decal->linear_damping = 5.0f; - decal->angular_damping = 5.0f; - } - } - } - } - } -} - -/* ========================== * - * Update - * ========================== */ - -void sim_step(struct sim_snapshot *world, struct sim_snapshot_list cmd_snapshots, i64 real_dt_ns) +struct sim_client_store *sim_client_store_alloc(void) { __prof; - struct temp_arena scratch = scratch_begin_no_conflict(); - - /* ========================== * - * Begin frame - * ========================== */ - - - - - - - /* TODO: Remove this */ - - /* 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); + struct sim_client_store *store; + { + struct arena arena = arena_alloc(GIGABYTE(64)); + store = arena_push_zero(&arena, struct sim_client_store); + store->arena = arena; } + store->valid = true; + store->num_client_lookup_buckets = CLIENT_LOOKUP_BUCKETS; + store->client_lookup_buckets = arena_push_array_zero(&store->arena, struct sim_client_lookup_bucket, store->num_client_lookup_buckets); + store->clients_arena = arena_alloc(GIGABYTE(64)); + store->clients = arena_dry_push(&store->clients_arena, struct sim_client); + return store; +} -#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); +void sim_client_store_release(struct sim_client_store *store) +{ + __prof; + for (u64 i = 0; i < store->num_clients_reserved; ++i) { + struct sim_client *client = &store->clients[i]; + if (client->valid) { + sim_client_release(client); + } } - - - - - - - - //sys_sleep_precise(rng_rand_f32(0, 0.050)); - //sys_sleep_precise(0.050); - - world->real_dt_ns = max_i64(0, real_dt_ns); - world->real_time_ns += world->real_dt_ns; - - world->world_timescale = SIM_TIMESCALE; - world->world_dt_ns = max_i64(0, real_dt_ns * world->world_timescale); - world->world_time_ns += world->world_dt_ns; - - f64 real_dt = SECONDS_FROM_NS(world->real_dt_ns); - f64 real_time = SECONDS_FROM_NS(world->real_time_ns); - f64 world_dt = SECONDS_FROM_NS(world->world_dt_ns); - f64 world_time = SECONDS_FROM_NS(world->world_time_ns); - (UNUSED)real_dt; - (UNUSED)real_time; - (UNUSED)world_dt; - (UNUSED)world_time; - - struct sprite_scope *sprite_frame_scope = sprite_scope_begin(); - - struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE); - - if (world->is_master) { - /* ========================== * - * Spawn test entities - * ========================== */ - - /* TODO: remove this (testing) */ - /* 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); - - /* ========================== * - * Activate entities - * ========================== */ - - for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { - struct sim_ent *ent = &world->ents[ent_index]; - if (!ent->valid) continue; - - if (!sim_ent_has_prop(ent, SIM_ENT_PROP_ACTIVE)) { - u64 atick = ent->activation_tick; - if (atick != 0 || world->tick >= atick) { - sim_ent_activate(ent, world->tick); - } - } - } - - /* ========================== * - * Reset triggered entities - * ========================== */ - - for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { - struct sim_ent *ent = &world->ents[ent_index]; - if (!sim_ent_is_valid_and_active(ent)) continue; - - if (sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGER_NEXT_TICK)) { - sim_ent_disable_prop(ent, SIM_ENT_PROP_TRIGGER_NEXT_TICK); - sim_ent_enable_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK); - } else if (sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK)) { - sim_ent_disable_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK); - } - } - - /* ========================== * - * Create / update client ents - * ========================== */ - - for (struct sim_snapshot_list_node *n = cmd_snapshots.first; n; n = n->next) { - struct sim_snapshot *cmd_snapshot = n->ss; - struct sim_client_handle client_handle = cmd_snapshot->producer_client; - struct sim_ent *client_ent = sim_ent_from_client_handle(world, client_handle); - - /* Create client ent if it doesn't exist */ - if (!client_ent->valid) { - client_ent = sim_ent_alloc(root); - client_ent->client_handle = client_handle; - sim_ent_enable_prop(client_ent, SIM_ENT_PROP_CLIENT); - if (cmd_snapshot->producer_client_is_local) { - sim_ent_enable_prop(client_ent, SIM_ENT_PROP_LOCAL_CLIENT); - } - sim_ent_enable_prop(client_ent, SIM_ENT_PROP_ACTIVE); - } - - client_ent->client_control = cmd_snapshot->control; - } - - /* ========================== * - * Process client cmds - * ========================== */ - - 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)) continue; - if (sim_ent_has_prop(ent, SIM_ENT_PROP_CLIENT)) { - ent->client_dbg_drag_start = false; - ent->client_dbg_drag_stop = false; - - struct sim_control old_control = ent->client_control; - struct sim_control *control = &ent->client_control; - *control = cmd->control; - if (v2_len_sq(control->move) > 1) { - /* Cap movement vector magnitude at 1 */ - control->move = v2_norm(control->move); - } - - /* Determine cursor pos from focus */ - { - struct sim_ent_handle control_ent_handle = ent->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) */ - ent->client_cursor_pos = v2_add(sim_ent_get_xform(control_ent).og, ent->client_control.focus); - } - } - - u32 flags = control->flags; - if (flags & SIM_CONTROL_FLAG_DRAGGING) { - if (!(old_control.flags & SIM_CONTROL_FLAG_DRAGGING)) { - ent->client_dbg_drag_start = true; - } - } else { - if (old_control.flags & SIM_CONTROL_FLAG_DRAGGING) { - ent->client_dbg_drag_stop = true; - } - } - if (flags & SIM_CONTROL_FLAG_CLEAR_ALL) { - if (!(old_control.flags & SIM_CONTROL_FLAG_CLEAR_ALL)) { - test_clear_level(world); - } - } - if (flags & SIM_CONTROL_FLAG_SPAWN_TEST) { - if (!(old_control.flags & SIM_CONTROL_FLAG_SPAWN_TEST)) { - logf_info("Spawning (test)"); - u32 count = 1; - f32 spread = 1; - for (u32 j = 0; j < count; ++j) { - spawn_test_entities(world, V2(0, (((f32)j / (f32)count) - 0.5) * spread)); - } - } - } - } - } - - /* ========================== * - * Create client player ents - * ========================== */ - - 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)) continue; - if (sim_ent_has_prop(ent, SIM_ENT_PROP_CLIENT)) { - /* FIXME: Ents never released when client disconnects */ - struct sim_ent *player_ent = sim_ent_from_handle(world, ent->client_player_ent); - if (!player_ent->valid) { - player_ent = spawn_test_player(world); - sim_ent_enable_prop(player_ent, SIM_ENT_PROP_CONTROLLED); - ent->client_player_ent = player_ent->handle; - player_ent->controlling_client = ent->handle; - } - struct sim_ent *camera_ent = sim_ent_from_handle(world, ent->client_camera_ent); - if (!camera_ent->valid) { - camera_ent = spawn_test_player_camera(world, player_ent); - ent->client_camera_ent = camera_ent->handle; - } - } - } - - /* ========================== * - * Update entity control from client control - * ========================== */ - - for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { - struct sim_ent *ent = &world->ents[ent_index]; - if (!sim_ent_is_valid_and_active(ent)) continue; - - if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) { - struct sim_ent *client_ent = sim_client_from_handle(world, ent->controlling_client); - if (client_ent->valid) { - ent->control = client_ent->client_control; - /* TODO: Move this */ - if (ent->control.flags & SIM_CONTROL_FLAG_FIRING) { - sim_ent_enable_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED); - } else { - sim_ent_disable_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED); - } - } - } - } - - /* ========================== * - * Update entities from sprite - * ========================== */ - - for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { - struct sim_ent *ent = &world->ents[ent_index]; - if (!sim_ent_is_valid_and_active(ent)) continue; - if (sprite_tag_is_nil(ent->sprite)) continue; - - struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, ent->sprite); - - /* 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 = 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; - ++frame_index; - if (frame_index > span.end) { - /* Loop animation */ - 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_frame = frame_index; - } - - /* Update sprite local xform */ - { - struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, LIT("pivot"), ent->animation_frame); - struct v2 sprite_size = v2_div(sheet->frame_size, (f32)IMAGE_PIXELS_PER_UNIT); - - struct v2 dir = v2_mul_v2(sprite_size, slice.dir); - f32 rot = v2_angle(dir) + PI / 2; - - struct xform xf = XFORM_IDENT; - xf = xform_rotated(xf, -rot); - xf = xform_scaled(xf, sprite_size); - xf = xform_translated(xf, v2_neg(slice.center)); - ent->sprite_local_xform = xf; - } - - /* Update collider from sprite */ - if (ent->sprite_collider_slice.len > 0) { - struct xform cxf = ent->sprite_local_xform; - - struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, ent->sprite_collider_slice, ent->animation_frame); - ent->local_collider = collider_from_quad(xform_mul_quad(cxf, quad_from_rect(slice.rect))); - } - - /* Test collider */ - #if 0 - if (sim_ent_has_prop(ent, SIM_ENT_PROP_TEST)) { - //if ((true)) { - #if 0 - ent->local_collider.points[0] = V2(0, 0); - ent->local_collider.count = 1; - ent->local_collider.radius = 0.5; - #elif 0 - ent->local_collider.points[0] = v2_with_len(V2(0.08f, 0.17f), 0.15f); - ent->local_collider.points[1] = v2_with_len(V2(-0.07f, -0.2f), 0.15f); - ent->local_collider.count = 2; - ent->local_collider.radius = 0.075f; - #elif 1 - #if 0 - /* "Bad" winding order */ - ent->local_collider.points[0] = V2(-0.15, 0.15); - ent->local_collider.points[1] = V2(0.15, 0.15); - ent->local_collider.points[2] = V2(0, -0.15); - #else - ent->local_collider.points[0] = V2(0, -0.15); - ent->local_collider.points[1] = V2(0.15, 0.15); - ent->local_collider.points[2] = V2(-0.15, 0.15); - #endif - ent->local_collider.count = 3; - ent->local_collider.radius = 0.25; - //ent->local_collider.radius = math_fabs(math_sin(ctx->tick.time) / 3); - #else - //ent->local_collider.radius = 0.5; - ent->local_collider.radius = 0.25; - //ent->local_collider.radius = 0.; - #endif - } - #endif - } - - /* ========================== * - * Update attachments - * ========================== */ - - for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { - struct sim_ent *ent = &world->ents[ent_index]; - if (!sim_ent_is_valid_and_active(ent)) continue; - if (!sim_ent_has_prop(ent, SIM_ENT_PROP_ATTACHED)) continue; - - struct sim_ent *parent = sim_ent_from_handle(world, ent->parent); - struct sprite_tag parent_sprite = parent->sprite; - struct sprite_sheet *parent_sheet = sprite_sheet_from_tag_await(sprite_frame_scope, parent_sprite); - - struct xform parent_sprite_xf = parent->sprite_local_xform; - - struct sprite_sheet_slice attach_slice = sprite_sheet_get_slice(parent_sheet, ent->attach_slice, parent->animation_frame); - struct v2 attach_pos = xform_mul_v2(parent_sprite_xf, attach_slice.center); - struct v2 attach_dir = xform_basis_mul_v2(parent_sprite_xf, attach_slice.dir); - - struct xform xf = sim_ent_get_local_xform(ent); - xf.og = attach_pos; - xf = xform_basis_with_rotation_world(xf, v2_angle(attach_dir) + PI / 2); - sim_ent_set_local_xform(ent, xf); - } - - /* ========================== * - * Test - * ========================== */ - - #if 0 - 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; - - #if 0 - if (!ent->test_initialized) { - ent->test_initialized = true; - ent->test_start_local_xform = sim_ent_get_local_xform(ent); - ent->test_start_sprite_xform = ent->sprite_local_xform; - } - - f32 t = (f32)time; - struct v2 og = v2_mul(V2(math_cos(t), math_sin(t)), 3); - f32 r = t * 3; - struct v2 s = V2(1 + (math_fabs(math_sin(t * 5)) * 3), 1); - (UNUSED)og; - (UNUSED)r; - (UNUSED)s; - - og = v2_add(og, ent->test_start_local_xform.og); - r += xform_get_rotation(ent->test_start_local_xform); - s = v2_add(s, xform_get_scale(ent->test_start_local_xform)); - - struct xform xf = sim_ent_get_local_xform(ent); - xf.og = og; - xf = xform_rotated_to(xf, r); - xf = xform_scaled_to(xf, s); - sim_ent_set_local_xform(ent, xf); - #else - f32 t = (f32)time; - struct v2 og = v2_mul(V2(math_cos(t), math_sin(t)), 3); - f32 rot = t * PI / 3; - struct v2 scale = V2(1 + (math_fabs(math_sin(t * 5)) * 3), 1); - (UNUSED)og; - (UNUSED)rot; - (UNUSED)scale; - - struct xform xf = sim_ent_get_local_xform(ent); - xf = xform_rotated_to(xf, rot); - xf = xform_scaled_to(xf, scale); - sim_ent_set_local_xform(ent, xf); - #endif - } - #endif - - /* ========================== * - * Trigger equipped - * ========================== */ - - for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { - struct sim_ent *ent = &world->ents[ent_index]; - if (!sim_ent_is_valid_and_active(ent)) continue; - - if (sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED)) { - struct sim_ent *eq = sim_ent_from_handle(world, ent->equipped); - if (sim_ent_is_valid_and_active(eq)) { - sim_ent_enable_prop(eq, SIM_ENT_PROP_TRIGGERED_THIS_TICK); - } - } - } - - /* ========================== * - * Process triggered entities - * ========================== */ - - for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { - struct sim_ent *ent = &world->ents[ent_index]; - if (!sim_ent_is_valid_and_active(ent)) continue; - if (!sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK)) continue; - if ((world_time - ent->last_triggered < ent->trigger_delay) && ent->last_triggered != 0) continue; - - ent->last_triggered = world_time; - - /* Fire weapon */ - if (sim_ent_has_prop(ent, SIM_ENT_PROP_WEAPON)) { - struct sprite_tag sprite = ent->sprite; - u32 animation_frame = ent->animation_frame; - struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, sprite); - - struct xform sprite_local_xform = ent->sprite_local_xform; - - struct sprite_sheet_slice out_slice = sprite_sheet_get_slice(sheet, LIT("out"), animation_frame); - struct v2 rel_pos = xform_mul_v2(sprite_local_xform, out_slice.center); - struct v2 rel_dir = xform_basis_mul_v2(sprite_local_xform, out_slice.dir); - - /* Spawn bullet */ - struct sim_ent *bullet; - { - bullet = sim_ent_alloc(root); - - bullet->bullet_src = ent->handle; - bullet->bullet_src_pos = rel_pos; - bullet->bullet_src_dir = rel_dir; - //bullet->bullet_impulse = 0.75f; - bullet->bullet_impulse = 2.0f; - bullet->bullet_knockback = 10; - bullet->mass_unscaled = 0.04f; - bullet->inertia_unscaled = 0.00001f; - bullet->layer = SIM_LAYER_BULLETS; - - #if 1 - /* Point collider */ - bullet->local_collider.points[0] = V2(0, 0); - bullet->local_collider.count = 1; - #else - bullet->sprite = sprite_tag_from_path(LIT("res/graphics/bullet.ase")); - bullet->sprite_collider_slice = LIT("shape"); - #endif - - sim_ent_enable_prop(bullet, SIM_ENT_PROP_BULLET); - sim_ent_enable_prop(bullet, SIM_ENT_PROP_SENSOR); - } - - /* Spawn tracer */ - { - struct sim_ent *tracer = sim_ent_alloc(root); - tracer->tracer_fade_duration = 0.025f; - tracer->layer = SIM_LAYER_TRACERS; - sim_ent_enable_prop(tracer, SIM_ENT_PROP_TRACER); - - bullet->bullet_tracer = tracer->handle; - } - } - } - - /* ========================== * - * Create motor joints from control move - * ========================== */ - - for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { - struct sim_ent *ent = &world->ents[ent_index]; - if (!sim_ent_is_valid_and_active(ent)) continue; - - if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) { - struct sim_ent *joint_ent = sim_ent_from_handle(world, ent->move_joint); - if (!sim_ent_is_valid_and_active(joint_ent)) { - joint_ent = sim_ent_alloc(root); - joint_ent->mass_unscaled = F32_INFINITY; - joint_ent->inertia_unscaled = F32_INFINITY; - sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_MOTOR_JOINT); - sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_ACTIVE); - ent->move_joint = joint_ent->handle; - - struct phys_motor_joint_def def = ZI; - def.e0 = joint_ent->handle; /* Re-using joint entity as e0 */ - def.e1 = ent->handle; - def.correction_rate = 0; - def.max_force = ent->control_force; - def.max_torque = 0; - joint_ent->motor_joint_data = phys_motor_joint_from_def(def); - } - - sim_ent_set_xform(joint_ent, XFORM_IDENT); /* Reset joint ent position */ - sim_ent_set_linear_velocity(joint_ent, v2_mul(v2_clamp_len(ent->control.move, 1), ent->control_force_max_speed)); - } - } - - /* ========================== * - * Create motor joints from control focus (aim) - * ========================== */ - - #if SIM_PLAYER_AIM - for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { - struct sim_ent *ent = &world->ents[ent_index]; - if (!sim_ent_is_valid_and_active(ent)) continue; - - if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) { - struct xform xf = sim_ent_get_xform(ent); - struct xform sprite_xf = xform_mul(xf, ent->sprite_local_xform); - - /* Retrieve / create aim joint */ - struct sim_ent *joint_ent = sim_ent_from_handle(world, ent->aim_joint); - if (!sim_ent_is_valid_and_active(joint_ent)) { - joint_ent = sim_ent_alloc(root); - joint_ent->mass_unscaled = F32_INFINITY; - joint_ent->inertia_unscaled = F32_INFINITY; - sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_PHYSICAL_KINEMATIC); /* Since we'll be setting velocity manually */ - sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_MOTOR_JOINT); - sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_ACTIVE); - ent->aim_joint = joint_ent->handle; - - struct phys_motor_joint_def def = ZI; - def.e0 = joint_ent->handle; /* Re-using joint entity as e0 */ - def.e1 = ent->handle; - def.max_force = 0; - def.max_torque = ent->control_torque; - joint_ent->motor_joint_data = phys_motor_joint_from_def(def); - } - - /* Set correction rate dynamically since motor velocity is only set for one frame */ - joint_ent->motor_joint_data.correction_rate = 10 * world_dt; - - - /* Solve for final angle using law of sines */ - f32 new_angle; - { - struct v2 ent_pos = xf.og; - struct v2 focus_pos = v2_add(ent_pos, ent->control.focus); - - struct v2 sprite_hold_pos; - struct v2 sprite_hold_dir; - { - struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, ent->sprite); - struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, LIT("attach.wep"), ent->animation_frame); - sprite_hold_pos = slice.center; - sprite_hold_dir = slice.dir; - } - - struct v2 hold_dir = xform_basis_mul_v2(sprite_xf, sprite_hold_dir); - struct v2 hold_pos = xform_mul_v2(sprite_xf, sprite_hold_pos); - if (v2_eq(hold_pos, ent_pos)) { - /* If hold pos is same as origin (E.G if pivot is being used as hold pos), then move hold pos forward a tad to avoid issue */ - sprite_hold_pos = v2_add(sprite_hold_pos, V2(0, -1)); - hold_pos = xform_mul_v2(sprite_xf, sprite_hold_pos); - } - - f32 forward_hold_angle_offset; - { - struct xform xf_unrotated = xform_basis_with_rotation_world(xf, 0); - struct v2 hold_pos_unrotated = xform_mul_v2(xf_unrotated, xform_mul_v2(ent->sprite_local_xform, sprite_hold_pos)); - forward_hold_angle_offset = v2_angle_from_dirs(V2(0, -1), v2_sub(hold_pos_unrotated, xf_unrotated.og)); - } - - struct v2 hold_ent_dir = v2_sub(ent_pos, hold_pos); - struct v2 focus_ent_dir = v2_sub(ent_pos, focus_pos); - - f32 hold_ent_len = v2_len(hold_ent_dir); - f32 focus_ent_len = v2_len(focus_ent_dir); - - f32 final_hold_angle_btw_ent_and_focus = v2_angle_from_dirs(hold_ent_dir, hold_dir); - f32 final_focus_angle_btw_ent_and_hold = math_asin((math_sin(final_hold_angle_btw_ent_and_focus) * hold_ent_len) / focus_ent_len); - f32 final_ent_angle_btw_focus_and_hold = PI - (final_focus_angle_btw_ent_and_hold + final_hold_angle_btw_ent_and_focus); - - new_angle = math_unwind_angle(v2_angle_from_dirs(V2(0, -1), v2_sub(focus_pos, ent_pos)) + final_ent_angle_btw_focus_and_hold - forward_hold_angle_offset); - } - - f32 new_vel = 0; - if (!F32_IS_NAN(new_angle)) { - const f32 angle_error_allowed = 0.001f; - struct xform joint_xf = sim_ent_get_xform(joint_ent); - f32 diff = math_unwind_angle(new_angle - xform_get_rotation(joint_xf)); - if (math_fabs(diff) > angle_error_allowed) { - /* Instantly snap joint ent to new angle */ - new_vel = diff / real_dt; - } - } - sim_ent_set_angular_velocity(joint_ent, new_vel); - } - } - #endif - - /* ========================== * - * Create motor joints from ground friction (gravity) - * ========================== */ - - for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { - struct sim_ent *ent = &world->ents[ent_index]; - if (!sim_ent_is_valid_and_active(ent)) continue; - if (!sim_ent_has_prop(ent, SIM_ENT_PROP_PHYSICAL_DYNAMIC)) continue; - - struct sim_ent *joint_ent = sim_ent_from_handle(world, ent->ground_friction_joint); - - struct phys_motor_joint_def def = ZI; - def.e0 = root->handle; - def.e1 = ent->handle; - def.correction_rate = 0; - def.max_force = ent->linear_ground_friction; - def.max_torque = ent->angular_ground_friction; - if (joint_ent->motor_joint_data.max_force != def.max_force || joint_ent->motor_joint_data.max_torque != def.max_torque) { - if (!sim_ent_is_valid_and_active(joint_ent)) { - joint_ent = sim_ent_alloc(root); - sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_MOTOR_JOINT); - sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_ACTIVE); - joint_ent->motor_joint_data = phys_motor_joint_from_def(def); - ent->ground_friction_joint = joint_ent->handle; - } - } - } - - /* ========================== * - * Create mouse joints from client debug drag - * ========================== */ - - for (u64 i = 0; i < world->num_clients_reserved; ++i) { - struct sim_client *client = &world->clients[i]; - if (client->valid) { - struct v2 cursor = client->cursor_pos; - b32 start_dragging = client->dbg_drag_start; - b32 stop_dragging = client->dbg_drag_stop; - - struct sim_ent *joint_ent = sim_ent_from_handle(world, client->dbg_drag_joint_ent); - struct sim_ent *target_ent = sim_ent_from_handle(world, joint_ent->mouse_joint_data.target); - - if (start_dragging) { - struct xform mouse_xf = xform_from_pos(cursor); - struct collider_shape mouse_shape = ZI; - mouse_shape.points[0] = V2(0, 0); - mouse_shape.count = 1; - - 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; - if (!sim_ent_has_prop(ent, SIM_ENT_PROP_PHYSICAL_DYNAMIC)) continue; - - struct collider_shape ent_collider = ent->local_collider; - if (ent_collider.count > 0) { - struct xform ent_xf = sim_ent_get_xform(ent); - /* TODO: Can just use boolean GJK */ - struct collider_collision_points_result res = collider_collision_points(&ent_collider, &mouse_shape, ent_xf, mouse_xf); - if (res.num_points > 0) { - target_ent = ent; - break; - } - } - } - } - if (stop_dragging) { - target_ent = sim_ent_nil(); - } - - if (sim_ent_is_valid_and_active(target_ent)) { - if (!sim_ent_is_valid_and_active(joint_ent)) { - /* FIXME: Joint ent may never release */ - joint_ent = sim_ent_alloc(root); - joint_ent->mass_unscaled = F32_INFINITY; - joint_ent->inertia_unscaled = F32_INFINITY; - client->dbg_drag_joint_ent = joint_ent->handle; - sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_MOUSE_JOINT); - sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_ACTIVE); - } - struct xform xf = sim_ent_get_xform(target_ent); - f32 mass = target_ent->mass_unscaled * math_fabs(xform_get_determinant(xf)); - - struct phys_mouse_joint_def def = ZI; - def.target = target_ent->handle; - if (sim_ent_handle_eq(joint_ent->mouse_joint_data.target, target_ent->handle)) { - def.point_local_start = joint_ent->mouse_joint_data.point_local_start; - } else { - def.point_local_start = xform_invert_mul_v2(xf, cursor); - } - def.point_local_end = xform_invert_mul_v2(xf, cursor); - def.max_force = mass * 1000; - joint_ent->mouse_joint_data = phys_mouse_joint_from_def(def); - } else if (sim_ent_is_valid_and_active(joint_ent)) { - joint_ent->mouse_joint_data.target = target_ent->handle; - } - } - } - - /* ========================== * - * Physics - * ========================== */ - - { - 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); - } - - /* ========================== * - * Update tracers - * ========================== */ - - for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { - struct sim_ent *ent = &world->ents[ent_index]; - if (!sim_ent_is_valid_and_active(ent)) continue; - if (!sim_ent_has_prop(ent, SIM_ENT_PROP_TRACER)) continue; - - struct v2 end = sim_ent_get_xform(ent).og; - - struct v2 tick_velocity = v2_mul(ent->tracer_start_velocity, world_dt); - struct v2 gradient_start = v2_add(ent->tracer_gradient_start, tick_velocity); - struct v2 gradient_end = v2_add(ent->tracer_gradient_end, tick_velocity); - - if (v2_dot(tick_velocity, v2_sub(gradient_start, end)) > 0) { - /* Tracer has disappeared */ - sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE_NEXT_TICK); - } - - ent->tracer_gradient_start = gradient_start; - ent->tracer_gradient_end = gradient_end; - } - - /* ========================== * - * Initialize bullet kinematics from sources - * ========================== */ - - for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { - struct sim_ent *ent = &world->ents[ent_index]; - if (!sim_ent_is_valid_and_active(ent)) continue; - if (!sim_ent_has_prop(ent, SIM_ENT_PROP_BULLET)) continue; - - if (ent->activation_tick == world->tick) { - struct sim_ent *src = sim_ent_from_handle(world, ent->bullet_src); - struct xform src_xf = sim_ent_get_xform(src); - - struct v2 pos = xform_mul_v2(src_xf, ent->bullet_src_pos); - struct v2 impulse = xform_basis_mul_v2(src_xf, ent->bullet_src_dir); - impulse = v2_with_len(impulse, ent->bullet_impulse); - - #if 0 - /* Add shooter velocity to bullet */ - { - /* TODO: Add angular velocity as well? */ - struct sim_ent *top = sim_ent_from_handle(ss_blended, src->top); - impulse = v2_add(impulse, v2_mul(top->linear_velocity, dt)); - } - #endif - - struct xform xf = XFORM_TRS(.t = pos, .r = v2_angle(impulse) + PI / 2); - sim_ent_set_xform(ent, xf); - sim_ent_enable_prop(ent, SIM_ENT_PROP_PHYSICAL_KINEMATIC); - - sim_ent_apply_linear_impulse_to_center(ent, impulse); - - /* Initialize tracer */ - struct sim_ent *tracer = sim_ent_from_handle(world, ent->bullet_tracer); - if (sim_ent_is_valid_and_active(tracer)) { - sim_ent_set_xform(tracer, xf); - sim_ent_enable_prop(tracer, SIM_ENT_PROP_PHYSICAL_KINEMATIC); - sim_ent_set_linear_velocity(tracer, ent->linear_velocity); - tracer->tracer_start = pos; - tracer->tracer_start_velocity = ent->linear_velocity; - tracer->tracer_gradient_end = pos; - tracer->tracer_gradient_start = v2_sub(pos, v2_mul(ent->linear_velocity, tracer->tracer_fade_duration)); - } - - /* Spawn quake */ - { - struct sim_ent *quake = sim_ent_alloc(root); - sim_ent_set_xform(quake, XFORM_POS(pos)); - quake->quake_intensity = 0.2f; - quake->quake_fade = quake->quake_intensity / 0.1f; - sim_ent_enable_prop(quake, SIM_ENT_PROP_QUAKE); - } - } - } - - /* ========================== * - * Update cameras - * ========================== */ - - for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { - struct sim_ent *ent = &world->ents[ent_index]; - if (!sim_ent_is_valid_and_active(ent)) continue; - if (!sim_ent_has_prop(ent, SIM_ENT_PROP_CAMERA)) continue; - - struct xform xf = sim_ent_get_xform(ent); - - /* Camera follow */ - { - struct sim_ent *follow = sim_ent_from_handle(world, ent->camera_follow); - - f32 aspect_ratio = 1.0; - { - struct xform quad_xf = xform_mul(sim_ent_get_xform(ent), ent->camera_quad_xform); - struct v2 camera_size = xform_get_scale(quad_xf); - if (!v2_is_zero(camera_size)) { - aspect_ratio = camera_size.x / camera_size.y; - } - } - f32 ratio_y = 0.33f; - f32 ratio_x = ratio_y / aspect_ratio; - struct v2 camera_focus_dir = v2_mul_v2(follow->control.focus, V2(ratio_x, ratio_y)); - struct v2 camera_focus_pos = v2_add(sim_ent_get_xform(follow).og, camera_focus_dir); - ent->camera_xform_target = xf; - ent->camera_xform_target.og = camera_focus_pos; - - /* Lerp camera */ - if (ent->camera_applied_lerp_continuity_gen_plus_one == ent->camera_lerp_continuity_gen + 1) { - f32 t = 1 - math_pow(2.f, -20.f * (f32)world_dt); - xf = xform_lerp(xf, ent->camera_xform_target, t); - } else { - /* Skip lerp */ - xf = ent->camera_xform_target; - } - ent->camera_applied_lerp_continuity_gen_plus_one = ent->camera_lerp_continuity_gen + 1; - } - - /* Camera shake */ - { - /* TODO: Update based on distance to quake */ - ent->shake = 0; - for (u64 quake_ent_index = 0; quake_ent_index < world->num_ents_reserved; ++quake_ent_index) { - struct sim_ent *quake = &world->ents[quake_ent_index]; - if (!sim_ent_is_valid_and_active(quake)) continue; - if (!sim_ent_has_prop(quake, SIM_ENT_PROP_QUAKE)) continue; - ent->shake += quake->quake_intensity; - } - } - - sim_ent_set_xform(ent, xf); - } - - /* ========================== * - * Update quakes - * ========================== */ - - for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { - struct sim_ent *ent = &world->ents[ent_index]; - if (!sim_ent_is_valid_and_active(ent)) continue; - if (!sim_ent_has_prop(ent, SIM_ENT_PROP_QUAKE)) continue; - - ent->quake_intensity = max_f32(0, ent->quake_intensity - (ent->quake_fade * world_dt)); - if (ent->quake_intensity <= 0) { - sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE_NEXT_TICK); - } - } - - /* ========================== * - * Update relative layers - * ========================== */ - - { - struct temp_arena temp = arena_temp_begin(scratch.arena); - - struct sim_ent **stack = arena_push(temp.arena, struct sim_ent *); - u64 stack_count = 1; - *stack = root; - - while (stack_count > 0) { - struct sim_ent *parent; - arena_pop(temp.arena, struct sim_ent *, &parent); - --stack_count; - - i32 parent_layer = parent->final_layer; - for (struct sim_ent *child = sim_ent_from_handle(world, parent->first); child->valid; child = sim_ent_from_handle(world, child->next)) { - if (sim_ent_is_valid_and_active(child)) { - child->final_layer = parent_layer + child->layer; - *arena_push(temp.arena, struct sim_ent *) = child; - ++stack_count; - } - } - } - - arena_temp_end(temp); - } - - /* ========================== * - * Release entities - * ========================== */ - - release_entities_with_prop(world, SIM_ENT_PROP_RELEASE_THIS_TICK); - } - - - /* ========================== * - * 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; + arena_release(&store->clients_arena); + arena_release(&store->arena); } /* ========================== * - * Cmd frame + * Client alloc * ========================== */ -void sim_cmd_frames_encode(struct bitbuff_writer *bw, struct sim_cmd_frame_list frames) +struct sim_client *sim_client_alloc(struct sim_client_store *store, enum sim_client_kind kind) { - __prof; - for (struct sim_cmd_frame *frame = frames.first; frame; frame = frame->next) { - bw_write_uv(bw, frame->tick); - bw_write_uv(bw, frame->ack); + struct sim_client_handle handle = ZI; + struct sim_client *client = sim_client_from_handle(store, store->first_free_client); - for (struct sim_cmd *cmd = frame->first; cmd; cmd = cmd->next) { - bw_write_bit(bw, 1); - bw_write_ibits(bw, cmd->kind, 8); + if (client->valid) { + store->first_free_client = client->next_free; + handle = client->handle; + ++handle.gen; + } else { + client = arena_push(&store->clients_arena, struct sim_client); + handle.gen = 1; + handle.idx = store->num_clients_reserved; + ++store->num_clients_reserved; + } + ++store->num_clients_allocated; + *client = *sim_client_nil(); + client->store = store; + client->valid = true; + client->kind = kind; + client->handle = handle; -#if COLLIDER_DEBUG - bw_write_ubits(bw, cmd->collider_gjk_steps, 32); -#endif + client->snapshots_arena = arena_alloc(GIGABYTE(8)); + client->num_snapshot_lookup_buckets = TICK_LOOKUP_BUCKETS; + client->snapshot_lookup_buckets = arena_push_array_zero(&client->snapshots_arena, struct sim_snapshot_lookup_bucket, client->num_snapshot_lookup_buckets); - switch (cmd->kind) { - case SIM_CMD_KIND_CLIENT_CONTROL: - { - bw_write_f32(bw, cmd->control.move.x); - bw_write_f32(bw, cmd->control.move.y); - bw_write_f32(bw, cmd->control.focus.x); - bw_write_f32(bw, cmd->control.focus.y); - bw_write_ubits(bw, cmd->control.flags, 32); -#if COLLIDER_DEBUG - cmd->collider_gjk_steps = br_read_ubits(&br, 32); -#endif - } break; + return client; +} - case SIM_CMD_KIND_SNAPSHOT: - { - bw_write_uv(bw, cmd->snapshot_tick_start); - bw_write_uv(bw, cmd->snapshot_tick_end); - bw_write_string(bw, cmd->snapshot_encoded); - } break; +void sim_client_release(struct sim_client *client) +{ + /* Release internal snapshot memory */ + for (u64 i = 0; i < client->num_snapshot_lookup_buckets; ++i) { + struct sim_snapshot_lookup_bucket *bucket = &client->snapshot_lookup_buckets[i]; + struct sim_snapshot *ss = bucket->first; + while (ss) { + struct sim_snapshot *next = ss->next_in_bucket; + arena_release(&ss->ents_arena); + arena_release(&ss->arena); + ss = next; + } + } - default: break; + /* Remove from channel lookup */ + sim_client_set_channel_id(client, HOST_CHANNEL_ID_NIL); + + /* Release client */ + struct sim_client_store *store = client->store; + client->valid = false; + client->next_free = store->first_free_client; + store->first_free_client = client->handle; + --store->num_clients_allocated; + ++client->handle.gen; + arena_release(&client->snapshots_arena); +} + +/* ========================== * + * Client lookup + * ========================== */ + +INTERNAL u64 hash_from_channel_id(struct host_channel_id channel_id) +{ + return hash_fnv64(HASH_FNV64_BASIS, STRING_FROM_STRUCT(&channel_id)); +} + +void sim_client_set_channel_id(struct sim_client *client, struct host_channel_id channel_id) +{ + struct sim_client_store *store = client->store; + struct host_channel_id old_channel_id = client->channel_id; + + /* Remove old channel id from channel lookup */ + if (!host_channel_id_is_nil(old_channel_id)) { + u64 bucket_index = client->channel_hash % store->num_client_lookup_buckets; + struct sim_client_lookup_bucket *bucket = &store->client_lookup_buckets[bucket_index]; + struct sim_client *prev = sim_client_from_handle(store, client->prev_in_bucket); + struct sim_client *next = sim_client_from_handle(store, client->next_in_bucket); + if (prev->valid) { + prev->next_in_bucket = next->handle; + } else { + bucket->first = next->handle; + } + if (next->valid) { + next->prev_in_bucket = prev->handle; + } else { + bucket->last = prev->handle; + } + } + + /* Insert into channel lookup */ + /* TODO: Enforce no duplicates */ + u64 channel_hash = hash_from_channel_id(channel_id); + client->channel_id = channel_id; + client->channel_hash = channel_hash; + if (!host_channel_id_is_nil(channel_id)) { + u64 bucket_index = channel_hash % store->num_client_lookup_buckets; + struct sim_client_lookup_bucket *bucket = &store->client_lookup_buckets[bucket_index]; + { + struct sim_client *prev_in_bucket = sim_client_from_handle(store, bucket->last); + if (prev_in_bucket->valid) { + prev_in_bucket->next_in_bucket = client->handle; + client->prev_in_bucket = prev_in_bucket->handle; + } else { + bucket->first = client->handle; + } + bucket->last = client->handle; + } + } +} + +struct sim_client *sim_client_from_channel_id(struct sim_client_store *store, struct host_channel_id channel_id) +{ + struct sim_client *res = sim_client_nil(); + u64 channel_hash = hash_from_channel_id(channel_id); + u64 bucket_index = channel_hash % store->num_client_lookup_buckets; + struct sim_client_lookup_bucket *bucket = &store->client_lookup_buckets[bucket_index]; + for (struct sim_client *client = sim_client_from_handle(store, bucket->first); client->valid; client = sim_client_from_handle(store, client->next_in_bucket)) { + if (client->channel_hash == channel_hash) { + res = client; + break; + } + } + return res; +} + +struct sim_client *sim_client_from_handle(struct sim_client_store *store, struct sim_client_handle handle) +{ + if (handle.gen != 0 && handle.idx < store->num_clients_reserved) { + struct sim_client *client = &store->clients[handle.idx]; + if (client->handle.gen == handle.gen) { + return client; + } + } + return sim_client_nil(); +} + +/* ========================== * + * Snapshot alloc + * ========================== */ + + /* Produces a new snapshot at `tick` with data copied from `src` snapshot. */ +struct sim_snapshot *sim_snapshot_alloc(struct sim_client *client, struct sim_snapshot *src, u64 tick) +{ + if (tick == 0) { + return sim_snapshot_nil(); + } + + struct sim_snapshot *ss; + { + struct arena arena = ZI; + struct arena ents_arena = ZI; + { + ss = client->first_free_snapshot; + if (ss) { + /* Re-use existing snasphot arenas */ + client->first_free_snapshot = ss->next_free; + ents_arena = ss->ents_arena; + arena = ss->arena; + } else { + /* Arenas allocated here will be released with client */ + arena = arena_alloc(GIGABYTE(8)); + ents_arena = arena_alloc(GIGABYTE(8)); } } + arena_reset(&arena); + ss = arena_push_zero(&arena, struct sim_snapshot); + ss->arena = arena; + + ss->ents_arena = ents_arena; + arena_reset(&ss->ents_arena); + } + + ss->tick = tick; + ss->valid = true; + ss->client = client; + ++client->num_ticks; + + /* Copy src info */ + ss->is_master = src->is_master; + ss->real_dt_ns = src->real_dt_ns; + ss->real_time_ns = src->real_time_ns; + ss->world_timescale = src->world_timescale; + ss->world_dt_ns = src->world_dt_ns; + ss->world_time_ns = src->world_time_ns; + ss->continuity_gen = src->continuity_gen; + ss->local_client = src->local_client; + ss->phys_iteration = src->phys_iteration; + + /* Copy entities */ + ss->first_free_ent = src->first_free_ent; + ss->num_ents_allocated = src->num_ents_allocated; + ss->num_ents_reserved = src->num_ents_reserved; + ss->ents = arena_push_array(&ss->ents_arena, struct sim_ent, ss->num_ents_reserved); + if (ss->num_ents_reserved == 0) { + /* Create root ent if copying from nil snapshot */ + struct sim_ent *root = arena_push(&ss->ents_arena, struct sim_ent); + *root = *sim_ent_nil(); + root->ss = ss; + root->handle = SIM_ENT_ROOT_HANDLE; + root->valid = true; + root->is_root = true; + ++ss->num_ents_allocated; + ++ss->num_ents_reserved; + } else { + for (u64 i = 0; i < ss->num_ents_reserved; ++i) { + struct sim_ent *dst_ent = &ss->ents[i]; + struct sim_ent *src_ent = &src->ents[i]; + *dst_ent = *src_ent; + dst_ent->ss = ss; + } + } + + /* Release duplicate tick if it exists */ + { + struct sim_snapshot *existing = sim_snapshot_from_tick(client, tick); + if (existing->valid) { + sim_snapshot_release(existing); + } + } + + /* Linear search to insert snapshot in tick order */ + { + struct sim_snapshot *prev = sim_snapshot_from_tick(client, client->last_tick); + while (prev->valid) { + if (prev->tick < tick) { + break; + } + prev = sim_snapshot_from_tick(client, prev->prev_tick); + } + if (prev->valid) { + struct sim_snapshot *next = sim_snapshot_from_tick(client, prev->next_tick); + if (next->valid) { + next->prev_tick = tick; + } else { + client->last_tick = tick; + } + ss->next_tick = prev->next_tick; + ss->prev_tick = prev->tick; + prev->next_tick = ss->tick; + } else { + struct sim_snapshot *first = sim_snapshot_from_tick(client, client->first_tick); + if (first->valid) { + ss->next_tick = first->tick; + first->prev_tick = tick; + } else { + client->last_tick = tick; + } + ss->next_tick = client->first_tick; + client->first_tick = tick; + } + } + + /* Insert into lookup */ + { + u64 bucket_index = tick % client->num_snapshot_lookup_buckets; + struct sim_snapshot_lookup_bucket *bucket = &client->snapshot_lookup_buckets[bucket_index]; + if (bucket->last) { + bucket->last->next_in_bucket = ss; + ss->prev_in_bucket = bucket->last; + } else { + bucket->first = ss; + } + bucket->last = ss; + } + + return ss; +} + +void sim_snapshot_release(struct sim_snapshot *ss) +{ + struct sim_client *client = ss->client; + + /* Remove from lookup */ + { + u64 bucket_index = ss->tick % client->num_snapshot_lookup_buckets; + struct sim_snapshot_lookup_bucket *bucket = &client->snapshot_lookup_buckets[bucket_index]; + struct sim_snapshot *prev = ss->prev_in_bucket; + struct sim_snapshot *next = ss->next_in_bucket; + if (prev) { + prev->next_in_bucket = next; + } else { + bucket->first = next; + } + if (next) { + next->prev_in_bucket = prev; + } else { + bucket->last = prev; + } + } + + /* Remove from snapshot list */ + { + struct sim_snapshot *prev = sim_snapshot_from_tick(client, ss->prev_tick); + struct sim_snapshot *next = sim_snapshot_from_tick(client, ss->next_tick); + if (prev->valid) { + prev->next_tick = next->tick; + } else { + client->first_tick = next->tick; + } + if (next->valid) { + next->prev_tick = prev->tick; + } else { + client->last_tick = prev->tick; + } + } + + ss->valid = false; + ss->next_free = client->first_free_snapshot; + client->first_free_snapshot = ss; + --client->num_ticks; +} + +/* Release all snapshots for client with tick in range [start, end] */ +void sim_snapshot_release_ticks_in_range(struct sim_client *client, u64 start, u64 end) +{ + if (start > end) { + u64 swp = start; + start = end; + end = swp; + } + + struct sim_snapshot *ss = sim_snapshot_from_tick(client, client->first_tick); + while (ss->valid) { + u64 tick = ss->tick; + u64 next_tick = ss->next_tick; + if (tick >= start) { + if (tick <= end) { + sim_snapshot_release(ss); + } else { + break; + } + } + ss = sim_snapshot_from_tick(client, next_tick); + } +} + +/* ========================== * + * Snapshot lookup + * ========================== */ + +struct sim_snapshot *sim_snapshot_from_tick(struct sim_client *client, u64 tick) +{ + struct sim_snapshot *ss = sim_snapshot_nil(); + if (tick > 0) { + u64 bucket_index = tick % client->num_snapshot_lookup_buckets; + struct sim_snapshot_lookup_bucket *bucket = &client->snapshot_lookup_buckets[bucket_index]; + for (struct sim_snapshot *search = bucket->first; search; search = search->next_in_bucket) { + if (search->tick == tick) { + ss = search; + break; + } + } + } + return ss; +} + +/* Returns the snapshot at nearest valid tick <= supplied tick */ +struct sim_snapshot *sim_snapshot_from_closest_tick_lte(struct sim_client *client, u64 tick) +{ + struct sim_snapshot *ss = sim_snapshot_from_tick(client, tick); + if (!ss->valid) { + /* Degenerate to linear search */ + ss = sim_snapshot_from_tick(client, client->last_tick); + while (ss->valid) { + if (ss->tick <= tick) { + break; + } + ss = sim_snapshot_from_tick(client, ss->prev_tick); + } + } + return ss; +} + +/* ========================== * + * Snapshot lerp + * ========================== */ + +struct sim_snapshot *sim_snapshot_alloc_from_lerp(struct sim_client *client, struct sim_snapshot *ss0, struct sim_snapshot *ss1, f64 blend) +{ + __prof; + + /* New snapshot will be allocated with same tick as ss0 or ss1, so the result should go into a different client */ + ASSERT(ss0->client != client && ss1->client != client); + + struct sim_snapshot *ss; + b32 should_blend = true; + if (ss0->continuity_gen == ss1->continuity_gen && 0 < blend && blend < 1) { + ss = sim_snapshot_alloc(client, ss0, ss0->tick); + } else if (math_round_to_int64(blend) <= 0) { + ss = sim_snapshot_alloc(client, ss0, ss0->tick); + should_blend = false; + } else { + ss = sim_snapshot_alloc(client, ss1, ss1->tick); + should_blend = false; + } + + if (!ss0->valid || !ss1->valid) { + /* New snapshot allocation caused one of the src snapshots original to release. + * ss0 & ss1 should be from a separate client than the allocating one. */ + ASSERT(false); + } + + if (should_blend) { + /* Blend time */ + ss->real_dt_ns = math_lerp_i64(ss0->real_dt_ns, ss1->real_dt_ns, blend); + ss->real_time_ns = math_lerp_i64(ss0->real_time_ns, ss1->real_time_ns, blend); + ss->world_timescale = math_lerp_f64(ss0->world_timescale, ss1->world_timescale, blend); + ss->world_dt_ns = math_lerp_i64(ss0->world_dt_ns, ss1->world_dt_ns, blend); + ss->world_time_ns = math_lerp_i64(ss0->world_time_ns, ss1->world_time_ns, blend); + + /* Blend entities */ + { + __profscope(snapshot_lerp_entities); + u64 num_entities = min_u64(ss0->num_ents_reserved, ss1->num_ents_reserved); + for (u64 i = 0; i < num_entities; ++i) { + struct sim_ent *e = &ss->ents[i]; + struct sim_ent *e0 = &ss0->ents[i]; + struct sim_ent *e1 = &ss1->ents[i]; + sim_ent_lerp(e, e0, e1, blend); + } + } + } + + + return ss; +} + +/* ========================== * + * Snapshot encode + * ========================== */ + +void sim_snapshot_encode(struct bitbuff_writer *bw, struct sim_client *receiver, struct sim_snapshot *ss0, struct sim_snapshot *ss1) +{ + __prof; + + /* TODO: Don't encode this */ + bw_write_bit(bw, ss1->is_master); + + bw_write_iv(bw, ss1->real_dt_ns); + bw_write_iv(bw, ss1->real_time_ns); + + bw_write_f64(bw, ss1->world_timescale); + bw_write_iv(bw, ss1->world_dt_ns); + bw_write_iv(bw, ss1->world_time_ns); + + bw_write_uv(bw, ss1->continuity_gen); + bw_write_uv(bw, ss1->phys_iteration); + + bw_write_uv(bw, receiver->handle.gen); + bw_write_uv(bw, receiver->handle.idx); + + /* Ents */ + if (ss1->num_ents_allocated == ss0->num_ents_allocated) { bw_write_bit(bw, 0); + } else { + bw_write_bit(bw, 1); + bw_write_uv(bw, ss1->num_ents_allocated); + } + if (ss1->num_ents_reserved == ss0->num_ents_reserved) { + bw_write_bit(bw, 0); + } else { + bw_write_bit(bw, 1); + bw_write_uv(bw, ss1->num_ents_reserved); + } + for (u64 i = 0; i < ss1->num_ents_reserved; ++i) { + struct sim_ent *e0 = sim_ent_nil(); + if (i < ss0->num_ents_reserved) { + e0 = &ss0->ents[i]; + } + struct sim_ent *e1 = &ss1->ents[i]; + sim_ent_encode(bw, e0, e1); } } -void sim_cmd_frames_decode(struct arena *arena, struct host_event_array host_events, struct sim_cmd_frame_list *frames_out) +/* ========================== * + * Snapshot decode + * ========================== */ + +void sim_snapshot_decode(struct bitbuff_reader *br, struct sim_snapshot *ss) { __prof; - for (u64 i = 0; i < host_events.count; ++i) { - struct host_event host_event = host_events.events[i]; - enum host_event_kind host_event_kind = host_event.kind; - switch (host_event_kind) { - case HOST_EVENT_KIND_CHANNEL_OPENED: - { - /* Create a stand-alone cmd frame for connecting */ - struct sim_cmd_frame *frame = arena_push_zero(arena, struct sim_cmd_frame); - 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; + /* TODO: Don't encode this */ + ss->is_master = br_read_bit(br); - frame->first = cmd; - frame->last = cmd; + ss->real_dt_ns = br_read_iv(br); + ss->real_time_ns = br_read_iv(br); - if (frames_out->last) { - frames_out->last->next = frame; - } else { - frames_out->first = frame; - } - frames_out->last = frame; - } break; + ss->world_timescale = br_read_f64(br); + ss->world_dt_ns = br_read_iv(br); + ss->world_time_ns = br_read_iv(br); - case HOST_EVENT_KIND_CHANNEL_CLOSED: - { - /* Create a stand-alone cmd frame for disconnecting */ - struct sim_cmd_frame *frame = arena_push_zero(arena, struct sim_cmd_frame); - frame->sender_channel = host_event.channel_id; + ss->continuity_gen = br_read_uv(br); + ss->phys_iteration = br_read_uv(br); - struct sim_cmd *cmd = arena_push_zero(arena, struct sim_cmd); - cmd->kind = SIM_CMD_KIND_SIM_CLIENT_DISCONNECT; - cmd->disconnect_reason = LIT("Connection lost"); + ss->local_client.gen = br_read_uv(br); + ss->local_client.idx = br_read_uv(br); - frame->first = cmd; - frame->last = cmd; - - if (frames_out->last) { - frames_out->last->next = frame; - } else { - frames_out->first = frame; - } - frames_out->last = frame; - } break; - - case HOST_EVENT_KIND_MSG: - { - struct bitbuff bb = bitbuff_from_string(host_event.msg); - struct bitbuff_reader br = br_from_bitbuff(&bb); - - struct sim_cmd_frame *frame = arena_push_zero(arena, struct sim_cmd_frame); - frame->sender_channel = host_event.channel_id; - frame->tick = br_read_uv(&br); - frame->ack = br_read_uv(&br); - - while (br_read_bit(&br)) { - struct sim_cmd *cmd = arena_push_zero(arena, struct sim_cmd); - cmd->kind = br_read_ibits(&br, 8); - switch (cmd->kind) { - case SIM_CMD_KIND_CLIENT_CONTROL: - { - cmd->control.move.x = br_read_f32(&br); - cmd->control.move.y = br_read_f32(&br); - cmd->control.focus.x = br_read_f32(&br); - cmd->control.focus.y = br_read_f32(&br); - cmd->control.flags = br_read_ubits(&br, 32); -#if COLLIDER_DEBUG - cmd->collider_gjk_steps = br_read_ubits(&br, 32); -#endif - } break; - - case SIM_CMD_KIND_SNAPSHOT: - { - cmd->snapshot_tick_start = br_read_uv(&br); - cmd->snapshot_tick_end = br_read_uv(&br); - cmd->snapshot_encoded = br_read_string(arena, &br); - } break; - - default: break; - } - - if (frame->last) { - frame->last->next = cmd; - } else { - frame->first = cmd; - } - frame->last = cmd; - } - - if (frames_out->last) { - frames_out->last->next = frame; - } else { - frames_out->first = frame; - } - frames_out->last = frame; - } break; - - default: break; + /* Ents */ + if (br_read_bit(br)) { + ss->num_ents_allocated = br_read_uv(br); + } + if (br_read_bit(br)) { + u64 old_num_ents_reserved = ss->num_ents_reserved; + ss->num_ents_reserved = br_read_uv(br); + i64 reserve_diff = (i64)ss->num_ents_reserved - (i64)old_num_ents_reserved; + if (reserve_diff > 0) { + arena_push_array(&ss->ents_arena, struct sim_ent, reserve_diff); + for (u64 i = old_num_ents_reserved; i < ss->num_ents_reserved; ++i) { + struct sim_ent *e = &ss->ents[i]; + *e = *sim_ent_nil(); + e->ss = ss; + } } } + for (u64 i = 0; i < ss->num_ents_reserved; ++i) { + struct sim_ent *e = &ss->ents[i]; + sim_ent_decode(br, e); + } } diff --git a/src/sim.h b/src/sim.h index 569fa43b..b413c16c 100644 --- a/src/sim.h +++ b/src/sim.h @@ -1,39 +1,7 @@ #ifndef SIM_H #define SIM_H -#include "bitbuff.h" - -struct sprite_startup_receipt; -struct phys_startup_receipt; -struct host_startup_receipt; -struct sim_snapshot_startup_receipt; - -/* ========================== * - * Control - * ========================== */ - -enum sim_control_flag { - SIM_CONTROL_FLAG_NONE = 0, - SIM_CONTROL_FLAG_FIRING = 1 << 0, - - /* Testing */ - SIM_CONTROL_FLAG_CLEAR_ALL = 1 << 1, - SIM_CONTROL_FLAG_SPAWN_TEST = 1 << 2, - SIM_CONTROL_FLAG_PAUSE = 1 << 3, - SIM_CONTROL_FLAG_STEP = 1 << 4, - - SIM_CONTROL_FLAG_DRAGGING = 1 << 5, -}; - -struct sim_control { - struct v2 move; /* Movement direction vector (speed of 0 -> 1) */ - struct v2 focus; /* Focus direction vector (where does the controller want to look) */ - u32 flags; -}; - -/* ========================== * - * Layers - * ========================== */ +#define SIM_CLIENT_NIL_HANDLE ((struct sim_client_handle) { .gen = 0, .idx = 0 }) /* Absolute layers */ #define SIM_LAYER_FLOOR_DECALS (-300) @@ -45,8 +13,223 @@ struct sim_control { #define SIM_LAYER_RELATIVE_DEFAULT (0) #define SIM_LAYER_RELATIVE_WEAPON (1) +/* ========================== * + * Startup + * ========================== */ + +struct sim_startup_receipt { i32 _; }; +struct sim_startup_receipt sim_startup(void); + +/* ========================== * + * Client store + * ========================== */ + +struct sim_client_lookup_bucket { + struct sim_client_handle first; + struct sim_client_handle last; +}; + +struct sim_client_store { + b32 valid; + struct arena arena; + + /* Client lookup */ + struct sim_client_lookup_bucket *client_lookup_buckets; + u64 num_client_lookup_buckets; + + /* Clients */ + struct arena clients_arena; + struct sim_client *clients; + struct sim_client_handle first_free_client; + u64 num_clients_allocated; + u64 num_clients_reserved; +}; + +INLINE struct sim_client_store *sim_client_store_nil(void) +{ + extern READONLY struct sim_client_store **_g_sim_client_store_nil; + return *_g_sim_client_store_nil; +} + +struct sim_client_store *sim_client_store_alloc(void); +void sim_client_store_release(struct sim_client_store *store); + +/* ========================== * + * Client + * ========================== */ + struct sim_snapshot; -struct sim_snapshot_store; -struct sim_snapshot *sim_step(struct sim_snapshot_store *snapshot_store, struct sim_snapshot *prev_snapshot, struct sim_cmd_frame_list input_frames, i64 real_dt_ns); + +enum sim_client_kind { + SIM_CLIENT_KIND_INVALID, + SIM_CLIENT_KIND_USER, + SIM_CLIENT_KIND_LOCAL_SIM, + SIM_CLIENT_KIND_SLAVE_SIM, + SIM_CLIENT_KIND_MASTER_SIM, +}; + +struct sim_snapshot_lookup_bucket { + struct sim_snapshot *first; + struct sim_snapshot *last; +}; + +struct sim_client { + b32 valid; + enum sim_client_kind kind; + struct sim_client_handle handle; + struct sim_client_store *store; + + struct arena snapshots_arena; + + struct host_channel_id channel_id; + u64 channel_hash; + + struct sim_client_handle next_free; + struct sim_client_handle next_in_bucket; + struct sim_client_handle prev_in_bucket; + + /* This is the last confirmed tick of ours that we know this client has received */ + u64 ack; + + /* This is the last confirmed client tick that the client knows we have received */ + u64 reverse_ack; + + /* Snapshots sorted by tick (low to high) */ + u64 first_tick; + u64 last_tick; + u64 num_ticks; + struct sim_snapshot *first_free_snapshot; + + /* Tick -> snapshot lookup */ + u64 num_snapshot_lookup_buckets; + struct sim_snapshot_lookup_bucket *snapshot_lookup_buckets; +}; + +INLINE struct sim_client *sim_client_nil(void) +{ + extern READONLY struct sim_client **_g_sim_client_nil; + return *_g_sim_client_nil; +} + +struct sim_client *sim_client_alloc(struct sim_client_store *store, enum sim_client_kind kind); +void sim_client_release(struct sim_client *client); + +struct sim_client *sim_client_from_channel_id(struct sim_client_store *store, struct host_channel_id channel_id); +void sim_client_set_channel_id(struct sim_client *client, struct host_channel_id channel_id); +struct sim_client *sim_client_from_handle(struct sim_client_store *store, struct sim_client_handle handle); + +/* ========================== * + * Snapshot + * ========================== */ + +enum sim_control_flag { + SIM_CONTROL_FLAG_NONE = 0, + SIM_CONTROL_FLAG_FIRING = 1 << 0, + + /* Testing */ + SIM_CONTROL_FLAG_CLEAR_ALL = 1 << 1, + SIM_CONTROL_FLAG_SPAWN_TEST = 1 << 2, + SIM_CONTROL_FLAG_PAUSE = 1 << 3, + SIM_CONTROL_FLAG_STEP = 1 << 4, + + SIM_CONTROL_FLAG_DRAGGING = 1 << 5, +}; + +struct sim_control { + struct v2 move; /* Movement direction vector (speed of 0 -> 1) */ + struct v2 focus; /* Focus direction vector (where does the controller want to look) */ + u32 flags; +}; + +struct sim_snapshot { + b32 valid; + u64 tick; + struct sim_client *client; + struct sim_snapshot *next_free; + struct sim_snapshot *next_in_bucket; + struct sim_snapshot *prev_in_bucket; + u64 prev_tick; + u64 next_tick; + + struct arena arena; + + struct sim_client_handle producer_client; + b32 producer_client_is_local; + + /* ====================================================================== */ + /* SIM_SNAPSHOT_FLAG_CONTROL */ + + struct sim_control control; + + /* ====================================================================== */ + /* SIM_SNAPSHOT_FLAG_STATE */ + + /* Was this snapshot created by the master sim */ + b32 is_master; + + /* Time of local snapshot publish to user thread */ + i64 publish_dt_ns; + i64 publish_time_ns; + + /* Real time (guaranteed to increase by real_dt_ns each sim step) */ + i64 real_dt_ns; + i64 real_time_ns; + + /* World time (affected by timescale) */ + f64 world_timescale; + i64 world_dt_ns; + i64 world_time_ns; + + /* If != previous tick's continuity then don't lerp */ + u64 continuity_gen; + + /* The last physics iteration (used for tracking contact lifetime) */ + u64 phys_iteration; + + /* The receiver's client handle on the sender's machine (use this to find local client ent in snapshot) */ + struct sim_client_handle local_client; + + /* Entities */ + struct arena ents_arena; + struct sim_ent *ents; + struct sim_ent_handle first_free_ent; + u64 num_ents_allocated; + u64 num_ents_reserved; +}; + +struct sim_snapshot_list_node { + struct sim_snapshot *ss; + struct sim_snapshot_list_node *next; +}; + +struct sim_snapshot_list { + struct sim_snapshot_list_node *first; + struct sim_snapshot_list_node *last; +}; + +INLINE struct sim_snapshot *sim_snapshot_nil(void) +{ + extern READONLY struct sim_snapshot **_g_sim_snapshot_nil; + return *_g_sim_snapshot_nil; +} + +struct bitbuff_writer; +struct bitbuff_reader; + +/* Alloc */ +struct sim_snapshot *sim_snapshot_alloc(struct sim_client *client, struct sim_snapshot *src, u64 tick); +void sim_snapshot_release(struct sim_snapshot *sim_snapshot); +void sim_snapshot_release_ticks_in_range(struct sim_client *client, u64 start, u64 end); + +/* Lookup */ +struct sim_snapshot *sim_snapshot_from_tick(struct sim_client *client, u64 tick); +struct sim_snapshot *sim_snapshot_from_closest_tick_lte(struct sim_client *client, u64 tick); + +/* Lerp */ +struct sim_snapshot *sim_snapshot_alloc_from_lerp(struct sim_client *client, struct sim_snapshot *ss0, struct sim_snapshot *ss1, f64 blend); + +/* Encode / decode */ +void sim_snapshot_encode(struct bitbuff_writer *bw, struct sim_client *receiver, struct sim_snapshot *ss0, struct sim_snapshot *ss1); +void sim_snapshot_decode(struct bitbuff_reader *br, struct sim_snapshot *ss); #endif diff --git a/src/sim_client.c b/src/sim_client.c deleted file mode 100644 index e3c0053e..00000000 --- a/src/sim_client.c +++ /dev/null @@ -1,189 +0,0 @@ -#include "sim_client.h" -#include "sim.h" -#include "sim_snapshot.h" -#include "host.h" -#include "arena.h" -#include "util.h" -#include "arena.h" - -/* ========================== * - * Client - * ========================== */ - -INTERNAL u64 hash_from_channel_id(struct host_channel_id channel_id) -{ - return hash_fnv64(HASH_FNV64_BASIS, STRING_FROM_STRUCT(&channel_id)); -} - -struct sim_client *sim_client_from_handle(struct sim_snapshot *ss, struct sim_client_handle handle) -{ - if (handle.gen != 0 && handle.idx < ss->num_clients_reserved) { - struct sim_client *client = &ss->clients[handle.idx]; - if (client->handle.gen == handle.gen) { - return client; - } - } - return sim_client_nil(); -} - -struct sim_client *sim_client_from_channel_id(struct sim_snapshot *ss, struct host_channel_id channel_id) -{ - struct sim_client *res = sim_client_nil(); - u64 channel_hash = hash_from_channel_id(channel_id); - u64 bucket_index = channel_hash % ss->num_client_lookup_buckets; - struct sim_client_lookup_bucket *bucket = &ss->client_lookup_buckets[bucket_index]; - for (struct sim_client *client = sim_client_from_handle(ss, bucket->first); client->valid; client = sim_client_from_handle(ss, client->next_in_bucket)) { - if (client->channel_hash == channel_hash) { - res = client; - break; - } - } - return res; -} - -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); - if (client->valid) { - ss->first_free_client = client->next_free; - handle = client->handle; - ++handle.gen; - } else { - client = arena_push(&ss->clients_arena, struct sim_client); - handle.gen = 1; - handle.idx = ss->num_clients_reserved; - ++ss->num_clients_reserved; - } - ++ss->num_clients_allocated; - *client = *sim_client_nil(); - client->ss = ss; - client->valid = true; - client->kind = kind; - client->handle = handle; - - /* Insert into channel lookup */ - sim_client_set_channel_id(client, channel_id); - - return client; -} - -void sim_client_release(struct sim_client *client) -{ - struct sim_snapshot *ss = client->ss; - client->valid = false; - ++client->handle.gen; - client->next_free = ss->first_free_client; - ss->first_free_client = client->handle; - --ss->num_clients_allocated; - - /* Remove from channel lookup */ - sim_client_set_channel_id(client, HOST_CHANNEL_ID_NIL); -} - -void sim_client_set_channel_id(struct sim_client *client, struct host_channel_id channel_id) -{ - struct sim_snapshot *ss = client->ss; - struct host_channel_id old_channel_id = client->channel_id; - - /* Remove from channel lookup */ - if (!host_channel_id_is_nil(old_channel_id)) { - 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); - struct sim_client *next = sim_client_from_handle(ss, client->next_in_bucket); - if (prev->valid) { - prev->next_in_bucket = next->handle; - } else { - bucket->first = next->handle; - } - if (next->valid) { - next->prev_in_bucket = prev->handle; - } else { - bucket->last = prev->handle; - } - } - - /* Insert into channel lookup */ - u64 channel_hash = hash_from_channel_id(channel_id); - client->channel_id = channel_id; - client->channel_hash = channel_hash; - if (!host_channel_id_is_nil(channel_id)) { - u64 bucket_index = channel_hash % ss->num_client_lookup_buckets; - struct sim_client_lookup_bucket *bucket = &ss->client_lookup_buckets[bucket_index]; - { - struct sim_client *prev_in_bucket = sim_client_from_handle(ss, bucket->last); - if (prev_in_bucket->valid) { - prev_in_bucket->next_in_bucket = client->handle; - client->prev_in_bucket = prev_in_bucket->handle; - } else { - bucket->first = client->handle; - } - bucket->last = client->handle; - } - } -} - -/* ========================== * - * Lerp - * ========================== */ - -void sim_client_lerp(struct sim_client *c, struct sim_client *c0, struct sim_client *c1, f64 blend) -{ - if (c0->valid && c1->valid && c0->handle.gen == c1->handle.gen) { - c0->cursor_pos = v2_lerp(c0->cursor_pos, c1->cursor_pos, blend); - c->control.move = v2_lerp(c0->control.move, c1->control.move, blend); - c->control.focus = v2_lerp(c0->control.focus, c1->control.focus, blend); - } -} - -/* ========================== * - * Encode - * ========================== */ - -void sim_client_encode(struct bitbuff_writer *bw, struct sim_client *c0, struct sim_client *c1) -{ - struct sim_snapshot *ss = c1->ss; - - /* TODO: Granular delta encoding */ - - u64 pos = 0; - c1->ss = c0->ss; - while (pos < sizeof(*c1)) { - u64 chunk_size = min_u64(pos + 8, sizeof(*c1)) - pos; - u8 *chunk0 = (u8 *)c0 + pos; - u8 *chunk1 = (u8 *)c1 + pos; - if (MEMEQ(chunk0, chunk1, chunk_size)) { - bw_write_bit(bw, 0); - } else { - bw_write_bit(bw, 1); - u64 bits = 0; - MEMCPY(&bits, chunk1, chunk_size); - bw_write_ubits(bw, bits, 64); - } - pos += 8; - } - c1->ss = ss; -} - -/* ========================== * - * Decode - * ========================== */ - -void sim_client_decode(struct bitbuff_reader *br, struct sim_client *e) -{ - struct sim_snapshot *ss = e->ss; - - u64 pos = 0; - while (pos < sizeof(*e)) { - u8 *chunk = (u8 *)e + pos; - if (br_read_bit(br)) { - u64 chunk_size = min_u64(pos + 8, sizeof(*e)) - pos; - u64 bits = br_read_ubits(br, 64); - MEMCPY(chunk, &bits, chunk_size); - } - pos += 8; - } - - e->ss = ss; -} diff --git a/src/sim_client.h b/src/sim_client.h deleted file mode 100644 index 34ccf767..00000000 --- a/src/sim_client.h +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef SIM_CLIENT_H -#define SIM_CLIENT_H - -#include "sim_ent.h" - -#define SIM_CLIENT_NIL_HANDLE ((struct sim_client_handle) { .gen = 0, .idx = 0 }) - -struct sim_client_channel_lookup_bucket; -struct sim_snapshot; - -enum sim_client_kind { - SIM_CLIENT_KIND_INVALID, - SIM_CLIENT_KIND_SIM_SLAVE, - SIM_CLIENT_KIND_SIM_MASTER -}; - -struct sim_client_lookup_bucket { - struct sim_client_handle first; - struct sim_client_handle last; -}; - -struct sim_client { - b32 valid; - enum sim_client_kind kind; - struct sim_client_handle handle; - struct sim_snapshot *ss; - - struct host_channel_id channel_id; - u64 channel_hash; - - struct sim_client_handle next_free; - struct sim_client_handle next_in_bucket; - struct sim_client_handle prev_in_bucket; - - /* This is the last tick we know that this client has received */ - u64 ack; - - struct v2 cursor_pos; - struct sim_ent_handle camera_ent; - struct sim_ent_handle control_ent; - - struct sim_control control; - - b32 dbg_drag_start; - b32 dbg_drag_stop; - struct sim_ent_handle dbg_drag_joint_ent; -}; - -INLINE struct sim_client *sim_client_nil(void) -{ - extern READONLY struct sim_client **_g_sim_client_nil; - return *_g_sim_client_nil; -} - -/* ========================== * - * Client - * ========================== */ - -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, enum sim_client_kind kind); -void sim_client_release(struct sim_client *client); -void sim_client_set_channel_id(struct sim_client *client, struct host_channel_id channel_id); - -/* ========================== * - * Lerp - * ========================== */ - -void sim_client_lerp(struct sim_client *c, struct sim_client *c0, struct sim_client *c1, f64 blend); - -/* ========================== * - * Encode - * ========================== */ - -struct bitbuff_writer; -struct bitbuff_reader; - -void sim_client_encode(struct bitbuff_writer *bw, struct sim_client *c0, struct sim_client *c1); -void sim_client_decode(struct bitbuff_reader *br, struct sim_client *c); - -#endif diff --git a/src/sim_ent.c b/src/sim_ent.c index 483575f8..af6d769e 100644 --- a/src/sim_ent.c +++ b/src/sim_ent.c @@ -1,10 +1,10 @@ #include "sim_ent.h" #include "sim.h" -#include "sim_snapshot.h" #include "math.h" +#include "bitbuff.h" /* ========================== * - * Entity allocation + * Ent allocation * ========================== */ INTERNAL struct sim_ent *sim_ent_alloc_internal(struct sim_snapshot *ss) @@ -68,7 +68,7 @@ void sim_ent_release(struct sim_ent *ent) } /* ========================== * - * Query + * Ent query * ========================== */ /* Returns a valid ent or read-only nil ent. Always safe to read result, need to check `valid` to write. */ @@ -119,7 +119,79 @@ struct sim_ent *sim_ent_find_first_match_all(struct sim_snapshot *ss, struct sim } /* ========================== * - * Xform + * Ent tree + * ========================== */ + +void sim_ent_link_parent(struct sim_ent *ent, struct sim_ent *parent) +{ + struct sim_snapshot *ss = ent->ss; + + if (ent->parent.gen) { + /* Unlink from current parent */ + sim_ent_unlink_from_parent(ent); + } + + struct sim_ent_handle handle = ent->handle; + struct sim_ent_handle parent_handle = parent->handle; + + ent->parent = parent_handle; + + struct sim_ent_handle last_child_handle = parent->last; + struct sim_ent *last_child = sim_ent_from_handle(ss, last_child_handle); + if (last_child->valid) { + ent->prev = last_child_handle; + last_child->next = handle; + } else { + parent->first = handle; + } + parent->last = handle; + + if (parent->is_root) { + ent->is_top = true; + ent->top = handle; + } else { + ent->top = parent->top; + } +} + +/* NOTE: Entity will be dangling after calling this, should re-link to root ent. */ +void sim_ent_unlink_from_parent(struct sim_ent *ent) +{ + struct sim_snapshot *ss = ent->ss; + + struct sim_ent_handle parent_handle = ent->parent; + struct sim_ent *parent = sim_ent_from_handle(ss, parent_handle); + struct sim_ent *prev = sim_ent_from_handle(ss, ent->prev); + struct sim_ent *next = sim_ent_from_handle(ss, ent->next); + + /* Unlink from parent & siblings */ + if (prev->valid) { + prev->next = next->handle; + } else { + parent->first = next->handle; + } + if (next->valid) { + next->prev = prev->handle; + } else { + parent->last = prev->handle; + } + ent->prev = sim_ent_nil()->handle; + ent->next = sim_ent_nil()->handle; +} + +/* ========================== * + * Activate + * ========================== */ + +void sim_ent_activate(struct sim_ent *ent, u64 current_tick) +{ + sim_ent_enable_prop(ent, SIM_ENT_PROP_ACTIVE); + ent->activation_tick = current_tick; + ++ent->continuity_gen; +} + +/* ========================== * + * Ent xform * ========================== */ INTERNAL void sim_ent_mark_child_xforms_dirty(struct sim_snapshot *ss, struct sim_ent *ent) @@ -210,7 +282,7 @@ void sim_ent_set_local_xform(struct sim_ent *ent, struct xform xf) } /* ========================== * - * Movement + * Ent movement * ========================== */ void sim_ent_apply_linear_impulse(struct sim_ent *ent, struct v2 impulse, struct v2 point) @@ -254,190 +326,7 @@ void sim_ent_apply_torque(struct sim_ent *ent, f32 torque) } /* ========================== * - * Tree - * ========================== */ - -void sim_ent_link_parent(struct sim_ent *ent, struct sim_ent *parent) -{ - struct sim_snapshot *ss = ent->ss; - - if (ent->parent.gen) { - /* Unlink from current parent */ - sim_ent_unlink_from_parent(ent); - } - - struct sim_ent_handle handle = ent->handle; - struct sim_ent_handle parent_handle = parent->handle; - - ent->parent = parent_handle; - - struct sim_ent_handle last_child_handle = parent->last; - struct sim_ent *last_child = sim_ent_from_handle(ss, last_child_handle); - if (last_child->valid) { - ent->prev = last_child_handle; - last_child->next = handle; - } else { - parent->first = handle; - } - parent->last = handle; - - if (parent->is_root) { - ent->is_top = true; - ent->top = handle; - } else { - ent->top = parent->top; - } -} - -/* NOTE: Entity will be dangling after calling this, should re-link to root ent. */ -void sim_ent_unlink_from_parent(struct sim_ent *ent) -{ - struct sim_snapshot *ss = ent->ss; - - struct sim_ent_handle parent_handle = ent->parent; - struct sim_ent *parent = sim_ent_from_handle(ss, parent_handle); - struct sim_ent *prev = sim_ent_from_handle(ss, ent->prev); - struct sim_ent *next = sim_ent_from_handle(ss, ent->next); - - /* Unlink from parent & siblings */ - if (prev->valid) { - prev->next = next->handle; - } else { - parent->first = next->handle; - } - if (next->valid) { - next->prev = prev->handle; - } else { - parent->last = prev->handle; - } - ent->prev = sim_ent_nil()->handle; - ent->next = sim_ent_nil()->handle; -} - -/* ========================== * - * Entity lookup - * ========================== */ - -struct sim_ent_lookup sim_ent_lookup_alloc(u64 num_buckets) -{ - ASSERT(num_buckets > 0); - struct sim_ent_lookup l = ZI; - l.arena = arena_alloc(GIGABYTE(64)); - l.buckets = arena_push_array_zero(&l.arena, struct sim_ent_lookup_bucket, num_buckets); - l.num_buckets = num_buckets; - return l; -} - -void sim_ent_lookup_release(struct sim_ent_lookup *l) -{ - arena_release(&l->arena); -} - -struct sim_ent_lookup_entry *sim_ent_lookup_get(struct sim_ent_lookup *l, struct sim_ent_lookup_key key) -{ - u64 index = key.hash % l->num_buckets; - struct sim_ent_lookup_bucket *bucket = &l->buckets[index]; - struct sim_ent_lookup_entry *res = NULL; - for (struct sim_ent_lookup_entry *e = bucket->first; e; e = e->next) { - if (e->key.hash == key.hash) { - res = e; - break; - } - } - return res; -} - -void sim_ent_lookup_set(struct sim_ent_lookup *l, struct sim_ent_lookup_key key, struct sim_ent_handle handle) -{ - u64 index = key.hash % l->num_buckets; - struct sim_ent_lookup_bucket *bucket = &l->buckets[index]; - - struct sim_ent_lookup_entry *prev = NULL; - struct sim_ent_lookup_entry **slot = &bucket->first; - while (*slot) { - if ((*slot)->key.hash == key.hash) { - break; - } - prev = *slot; - slot = &(*slot)->next; - } - - struct sim_ent_lookup_entry *entry = *slot; - if (entry) { - /* Set existing entry */ - entry->ent = handle; - } else { - /* Allocate entry */ - if (l->first_free_entry) { - entry = l->first_free_entry; - l->first_free_entry->prev = NULL; - l->first_free_entry = entry->next; - } else { - entry = arena_push(&l->arena, struct sim_ent_lookup_entry); - } - MEMZERO_STRUCT(entry); - - entry->key = key; - entry->ent = handle; - if (prev) { - entry->prev = prev; - prev->next = entry; - } - - bucket->last = entry; - *slot = entry; - } -} - -void sim_ent_lookup_remove(struct sim_ent_lookup *l, struct sim_ent_lookup_entry *entry) -{ - struct sim_ent_lookup_bucket *bucket = &l->buckets[entry->key.hash % l->num_buckets]; - struct sim_ent_lookup_entry *prev = entry->prev; - struct sim_ent_lookup_entry *next = entry->next; - - if (prev) { - prev->next = next; - } else { - bucket->first = next; - } - - if (next) { - next->prev = prev; - } else { - bucket->last = prev; - } - - if (l->first_free_entry) { - l->first_free_entry->prev = entry; - } - entry->next = l->first_free_entry; - entry->prev = NULL; - l->first_free_entry = entry; -} - -struct sim_ent_lookup_key sim_ent_lookup_key_from_two_handles(struct sim_ent_handle h0, struct sim_ent_handle h1) -{ - struct sim_ent_lookup_key key = ZI; - struct string b0 = STRING_FROM_STRUCT(&h0); - struct string b1 = STRING_FROM_STRUCT(&h1); - key.hash = hash_fnv64(HASH_FNV64_BASIS, b0); - key.hash = hash_fnv64(key.hash, b1); - return key; -} - -/* ========================== * - * Activate - * ========================== */ - -void sim_ent_activate(struct sim_ent *ent, u64 current_tick) -{ - sim_ent_enable_prop(ent, SIM_ENT_PROP_ACTIVE); - ent->activation_tick = current_tick; - ++ent->continuity_gen; -} - -/* ========================== * - * Lerp + * Ent lerp * ========================== */ void sim_ent_lerp(struct sim_ent *e, struct sim_ent *e0, struct sim_ent *e1, f64 blend) @@ -477,7 +366,7 @@ void sim_ent_lerp(struct sim_ent *e, struct sim_ent *e0, struct sim_ent *e1, f64 } /* ========================== * - * Encode + * Ent encode * ========================== */ void sim_ent_encode(struct bitbuff_writer *bw, struct sim_ent *e0, struct sim_ent *e1) @@ -506,7 +395,7 @@ void sim_ent_encode(struct bitbuff_writer *bw, struct sim_ent *e0, struct sim_en } /* ========================== * - * Decode + * Ent decode * ========================== */ void sim_ent_decode(struct bitbuff_reader *br, struct sim_ent *e) diff --git a/src/sim_ent.h b/src/sim_ent.h index a26dddd6..4feeddbe 100644 --- a/src/sim_ent.h +++ b/src/sim_ent.h @@ -20,6 +20,9 @@ enum sim_ent_prop { SIM_ENT_PROP_RELEASE_THIS_TICK, SIM_ENT_PROP_RELEASE_NEXT_TICK, + SIM_ENT_PROP_CLIENT, + SIM_ENT_PROP_LOCAL_CLIENT, + SIM_ENT_PROP_PHYSICAL_DYNAMIC, SIM_ENT_PROP_PHYSICAL_KINEMATIC, @@ -54,29 +57,6 @@ enum sim_ent_prop { SIM_ENT_PROP_COUNT }; -struct sim_ent_lookup_key { - u64 hash; -}; - -struct sim_ent_lookup_entry { - struct sim_ent_lookup_key key; - struct sim_ent_handle ent; - struct sim_ent_lookup_entry *next; - struct sim_ent_lookup_entry *prev; -}; - -struct sim_ent_lookup_bucket { - struct sim_ent_lookup_entry *first; - struct sim_ent_lookup_entry *last; -}; - -struct sim_ent_lookup { - struct arena arena; - struct sim_ent_lookup_bucket *buckets; - u64 num_buckets; - struct sim_ent_lookup_entry *first_free_entry; -}; - struct sim_ent { /* ====================================================================== */ /* Metadata */ @@ -154,12 +134,31 @@ struct sim_ent { /* SIM_ENT_PROP_MOUSE_JOINT */ struct phys_mouse_joint mouse_joint_data; + /* ====================================================================== */ + /* Client */ + + /* SIM_ENT_PROP_CLIENT */ + + /* FIXME: Lerp */ + + struct sim_client_handle client_handle; + + struct sim_control client_control; + struct v2 client_cursor_pos; + + struct sim_ent_handle client_control_ent; + struct sim_ent_handle client_camera_ent; + + struct sim_ent_handle client_dbg_drag_joint_ent; + b32 client_dbg_drag_start; + b32 client_dbg_drag_stop; + /* ====================================================================== */ /* Control */ /* SIM_ENT_PROP_CONTROLLED */ - struct sim_client_handle controlling_client; + struct sim_ent_handle controlling_client; f32 control_force; /* How much force is applied to achieve desired control movement */ f32 control_force_max_speed; /* Maximum linear velocity achieved by force (m/s) */ @@ -362,6 +361,18 @@ INLINE b32 sim_ent_is_valid_and_active(struct sim_ent *ent) struct sim_ent *sim_ent_alloc(struct sim_ent *parent); void sim_ent_release(struct sim_ent *ent); +/* Query */ +struct sim_ent *sim_ent_from_handle(struct sim_snapshot *ss, struct sim_ent_handle handle); +struct sim_ent *sim_ent_find_first_match_one(struct sim_snapshot *ss, enum sim_ent_prop prop); +struct sim_ent *sim_ent_find_first_match_all(struct sim_snapshot *ss, struct sim_ent_prop_array props); + +/* Tree */ +void sim_ent_link_parent(struct sim_ent *parent, struct sim_ent *child); +void sim_ent_unlink_from_parent(struct sim_ent *ent); + +/* Activate */ +void sim_ent_activate(struct sim_ent *ent, u64 current_tick); + /* Xform */ struct xform sim_ent_get_xform(struct sim_ent *ent); struct xform sim_ent_get_local_xform(struct sim_ent *ent); @@ -377,26 +388,6 @@ void sim_ent_apply_force_to_center(struct sim_ent *ent, struct v2 force); void sim_ent_apply_angular_impulse(struct sim_ent *ent, f32 impulse); void sim_ent_apply_torque(struct sim_ent *ent, f32 torque); -/* Query */ -struct sim_ent *sim_ent_from_handle(struct sim_snapshot *ss, struct sim_ent_handle handle); -struct sim_ent *sim_ent_find_first_match_one(struct sim_snapshot *ss, enum sim_ent_prop prop); -struct sim_ent *sim_ent_find_first_match_all(struct sim_snapshot *ss, struct sim_ent_prop_array props); - -/* Tree */ -void sim_ent_link_parent(struct sim_ent *parent, struct sim_ent *child); -void sim_ent_unlink_from_parent(struct sim_ent *ent); - -/* Lookup */ -struct sim_ent_lookup sim_ent_lookup_alloc(u64 num_buckets); -void sim_ent_lookup_release(struct sim_ent_lookup *l); -struct sim_ent_lookup_entry *sim_ent_lookup_get(struct sim_ent_lookup *l, struct sim_ent_lookup_key key); -void sim_ent_lookup_set(struct sim_ent_lookup *l, struct sim_ent_lookup_key key, struct sim_ent_handle handle); -void sim_ent_lookup_remove(struct sim_ent_lookup *l, struct sim_ent_lookup_entry *entry); -struct sim_ent_lookup_key sim_ent_lookup_key_from_two_handles(struct sim_ent_handle h0, struct sim_ent_handle h1); - -/* Activate */ -void sim_ent_activate(struct sim_ent *ent, u64 current_tick); - /* Lerp */ void sim_ent_lerp(struct sim_ent *e, struct sim_ent *e0, struct sim_ent *e1, f64 blend); diff --git a/src/sim_snapshot.c b/src/sim_snapshot.c deleted file mode 100644 index 17abb1a4..00000000 --- a/src/sim_snapshot.c +++ /dev/null @@ -1,575 +0,0 @@ -#include "sim_snapshot.h" -#include "sim.h" -#include "sim_ent.h" -#include "sim_client.h" -#include "arena.h" -#include "scratch.h" - -#define TICK_LOOKUP_BUCKETS 127 -#define CLIENT_LOOKUP_BUCKETS 127 - -struct sim_snapshot_lookup_bucket { - struct sim_snapshot *first; - struct sim_snapshot *last; -}; - -/* ========================== * - * Startup - * ========================== */ - -GLOBAL struct { - struct arena nil_arena; - struct sim_snapshot_store *nil_snapshot_store; - struct sim_snapshot *nil_snapshot; - struct sim_ent *nil_ent; - struct sim_client *nil_client; -} G = ZI, DEBUG_ALIAS(G, G_sim_snapshot); - -/* Accessed via `sim_snapshot_store_nil()` */ -READONLY struct sim_snapshot_store **_g_sim_snapshot_store_nil = &G.nil_snapshot_store; - -/* Accessed via `sim_snapshot_nil()` */ -READONLY struct sim_snapshot **_g_sim_snapshot_nil = &G.nil_snapshot; - -/* Accessed via `sim_ent_nil()` */ -READONLY struct sim_ent **_g_sim_ent_nil = &G.nil_ent; - -/* Accessed via `sim_client_nil()` */ -READONLY struct sim_client **_g_sim_client_nil = &G.nil_client; - -struct sim_snapshot_startup_receipt sim_snapshot_startup(void) -{ - G.nil_arena = arena_alloc(GIGABYTE(1)); - - /* Nil snapshot store */ - G.nil_snapshot_store = arena_push_zero(&G.nil_arena, struct sim_snapshot_store); - G.nil_snapshot_store->valid = false; - - /* Nil snapshot */ - G.nil_snapshot = arena_push_zero(&G.nil_arena, struct sim_snapshot); - G.nil_snapshot->valid = false; - G.nil_snapshot->store = sim_snapshot_store_nil(); - - /* Nil ent */ - G.nil_ent = arena_push_zero(&G.nil_arena, struct sim_ent); - G.nil_ent->ss = sim_snapshot_nil(); - G.nil_ent->valid = false; - G.nil_ent->handle = SIM_ENT_NIL_HANDLE; - G.nil_ent->local_xform = XFORM_IDENT; - G.nil_ent->cached_global_xform = XFORM_IDENT; - G.nil_ent->cached_global_xform_dirty = false; - G.nil_ent->friction = 0.5f; - G.nil_ent->mass_unscaled = 1; - G.nil_ent->inertia_unscaled = 1; - G.nil_ent->sprite_local_xform = XFORM_IDENT; - G.nil_ent->sprite_tint = COLOR_WHITE; - - /* Nil client */ - G.nil_client = arena_push_zero(&G.nil_arena, struct sim_client); - G.nil_client->ss = sim_snapshot_nil(); - G.nil_client->valid = false; - - /* Lock nil arena */ - arena_set_readonly(&G.nil_arena); - return (struct sim_snapshot_startup_receipt ) { 0 }; -} - -/* ========================== * - * Store - * ========================== */ - -struct sim_snapshot_store *sim_snapshot_store_alloc(void) -{ - __prof; - struct sim_snapshot_store *store; - { - struct arena arena = arena_alloc(GIGABYTE(64)); - store = arena_push_zero(&arena, struct sim_snapshot_store); - store->arena = arena; - } - store->valid = true; - store->num_lookup_buckets = TICK_LOOKUP_BUCKETS; - store->lookup_buckets = arena_push_array_zero(&store->arena, struct sim_snapshot_lookup_bucket, store->num_lookup_buckets); - return store; -} - -void sim_snapshot_store_release(struct sim_snapshot_store *store) -{ - __prof; - /* Release snapshot internal memory */ - for (u64 i = 0; i < store->num_lookup_buckets; ++i) { - struct sim_snapshot_lookup_bucket *bucket = &store->lookup_buckets[i]; - struct sim_snapshot *ss = bucket->first; - while (ss) { - struct sim_snapshot *next = ss->next_in_bucket; - arena_release(&ss->clients_arena); - arena_release(&ss->ents_arena); - arena_release(&ss->arena); - ss = next; - } - } - { - struct sim_snapshot *ss = store->first_free_snapshot; - while (ss) { - struct sim_snapshot *next = ss->next_free; - arena_release(&ss->clients_arena); - arena_release(&ss->ents_arena); - arena_release(&ss->arena); - ss = next; - } - } - /* Release store */ - arena_release(&store->arena); -} - -/* Releases all snapshots with tick in range [start, end] */ -void sim_snapshot_store_release_ticks_in_range(struct sim_snapshot_store *store, u64 start, u64 end) -{ - if (start > end) { - u64 swp = start; - start = end; - end = swp; - } - - struct sim_snapshot *ss = sim_snapshot_from_tick(store, store->first_tick); - while (ss->valid) { - u64 tick = ss->tick; - u64 next_tick = ss->next_tick; - if (tick >= start) { - if (tick <= end) { - sim_snapshot_release(ss); - } else { - break; - } - } - ss = sim_snapshot_from_tick(store, next_tick); - } -} - -/* ========================== * - * Alloc - * ========================== */ - -/* Produces a new snapshot at `tick` with data copied from `src` snapshot. */ -struct sim_snapshot *sim_snapshot_alloc(struct sim_snapshot_store *store, struct sim_snapshot *src, u64 tick) -{ - if (tick == 0) { - return sim_snapshot_nil(); - } - - struct sim_snapshot *ss; - { - struct arena arena = ZI; - struct arena clients_arena = ZI; - struct arena ents_arena = ZI; - { - ss = store->first_free_snapshot; - if (ss) { - /* Re-use existing snasphot arenas */ - store->first_free_snapshot = ss->next_free; - ents_arena = ss->ents_arena; - clients_arena = ss->clients_arena; - arena = ss->arena; - } else { - /* Arenas allocated here will be released along with the entire snasphot store */ - arena = arena_alloc(GIGABYTE(8)); - clients_arena = arena_alloc(GIGABYTE(8)); - ents_arena = arena_alloc(GIGABYTE(8)); - } - } - arena_reset(&arena); - ss = arena_push_zero(&arena, struct sim_snapshot); - ss->arena = arena; - - ss->clients_arena = clients_arena; - arena_reset(&ss->clients_arena); - - ss->ents_arena = ents_arena; - arena_reset(&ss->ents_arena); - } - - ss->tick = tick; - ss->valid = true; - ss->store = store; - ++store->num_ticks; - - /* Copy src info */ - ss->is_master = src->is_master; - ss->real_dt_ns = src->real_dt_ns; - ss->real_time_ns = src->real_time_ns; - ss->world_timescale = src->world_timescale; - ss->world_dt_ns = src->world_dt_ns; - ss->world_time_ns = src->world_time_ns; - ss->continuity_gen = src->continuity_gen; - ss->local_client = src->local_client; - ss->phys_iteration = src->phys_iteration; - - /* Copy client lookup buckets */ - if (src->num_client_lookup_buckets > 0) { - ss->num_client_lookup_buckets = src->num_client_lookup_buckets; - ss->client_lookup_buckets = arena_push_array(&ss->arena, struct sim_client_lookup_bucket, ss->num_client_lookup_buckets); - MEMCPY(ss->client_lookup_buckets, src->client_lookup_buckets, sizeof(*ss->client_lookup_buckets) * ss->num_client_lookup_buckets); - } else { - ss->num_client_lookup_buckets = CLIENT_LOOKUP_BUCKETS; - ss->client_lookup_buckets = arena_push_array_zero(&ss->arena, struct sim_client_lookup_bucket, ss->num_client_lookup_buckets); - } - - /* Copy clients */ - ss->first_free_client = src->first_free_client; - ss->num_clients_allocated = src->num_clients_allocated; - ss->num_clients_reserved = src->num_clients_reserved; - ss->clients = arena_push_array(&ss->clients_arena, struct sim_client, ss->num_clients_reserved); - for (u64 i = 0; i < ss->num_clients_reserved; ++i) { - struct sim_client *dst_client = &ss->clients[i]; - struct sim_client *src_client = &src->clients[i]; - *dst_client = *src_client; - dst_client->ss = ss; - } - - /* Copy entities */ - ss->first_free_ent = src->first_free_ent; - ss->num_ents_allocated = src->num_ents_allocated; - ss->num_ents_reserved = src->num_ents_reserved; - ss->ents = arena_push_array(&ss->ents_arena, struct sim_ent, ss->num_ents_reserved); - if (ss->num_ents_reserved == 0) { - /* Create root ent if copying from nil store */ - struct sim_ent *root = arena_push(&ss->ents_arena, struct sim_ent); - *root = *sim_ent_nil(); - root->ss = ss; - root->handle = SIM_ENT_ROOT_HANDLE; - root->valid = true; - root->is_root = true; - ++ss->num_ents_allocated; - ++ss->num_ents_reserved; - } else { - for (u64 i = 0; i < ss->num_ents_reserved; ++i) { - struct sim_ent *dst_ent = &ss->ents[i]; - struct sim_ent *src_ent = &src->ents[i]; - *dst_ent = *src_ent; - dst_ent->ss = ss; - } - } - - /* Release duplicate tick if it exists */ - { - struct sim_snapshot *existing = sim_snapshot_from_tick(store, tick); - if (existing->valid) { - sim_snapshot_release(existing); - } - } - - /* Linear search to insert snapshot in tick order */ - { - struct sim_snapshot *prev = sim_snapshot_from_tick(store, store->last_tick); - while (prev->valid) { - if (prev->tick < tick) { - break; - } - prev = sim_snapshot_from_tick(store, prev->prev_tick); - } - if (prev->valid) { - struct sim_snapshot *next = sim_snapshot_from_tick(store, prev->next_tick); - if (next->valid) { - next->prev_tick = tick; - } else { - store->last_tick = tick; - } - ss->next_tick = prev->next_tick; - ss->prev_tick = prev->tick; - prev->next_tick = ss->tick; - } else { - struct sim_snapshot *first = sim_snapshot_from_tick(store, store->first_tick); - if (first->valid) { - ss->next_tick = first->tick; - first->prev_tick = tick; - } else { - store->last_tick = tick; - } - ss->next_tick = store->first_tick; - store->first_tick = tick; - } - } - - /* Insert into lookup */ - { - u64 bucket_index = tick % store->num_lookup_buckets; - struct sim_snapshot_lookup_bucket *bucket = &store->lookup_buckets[bucket_index]; - if (bucket->last) { - bucket->last->next_in_bucket = ss; - ss->prev_in_bucket = bucket->last; - } else { - bucket->first = ss; - } - bucket->last = ss; - } - - return ss; -} - -void sim_snapshot_release(struct sim_snapshot *ss) -{ - struct sim_snapshot_store *store = ss->store; - - /* Remove from lookup */ - { - u64 bucket_index = ss->tick % store->num_lookup_buckets; - struct sim_snapshot_lookup_bucket *bucket = &store->lookup_buckets[bucket_index]; - struct sim_snapshot *prev = ss->prev_in_bucket; - struct sim_snapshot *next = ss->next_in_bucket; - if (prev) { - prev->next_in_bucket = next; - } else { - bucket->first = next; - } - if (next) { - next->prev_in_bucket = prev; - } else { - bucket->last = prev; - } - } - - /* Remove from snapshot list */ - { - struct sim_snapshot *prev = sim_snapshot_from_tick(store, ss->prev_tick); - struct sim_snapshot *next = sim_snapshot_from_tick(store, ss->next_tick); - if (prev->valid) { - prev->next_tick = next->tick; - } else { - store->first_tick = next->tick; - } - if (next->valid) { - next->prev_tick = prev->tick; - } else { - store->last_tick = prev->tick; - } - } - - ss->valid = false; - ss->next_free = store->first_free_snapshot; - store->first_free_snapshot = ss; - --store->num_ticks; -} - -/* ========================== * - * Lookup - * ========================== */ - -struct sim_snapshot *sim_snapshot_from_tick(struct sim_snapshot_store *store, u64 tick) -{ - struct sim_snapshot *ss = sim_snapshot_nil(); - if (tick > 0) { - u64 bucket_index = tick % store->num_lookup_buckets; - struct sim_snapshot_lookup_bucket *bucket = &store->lookup_buckets[bucket_index]; - for (struct sim_snapshot *search = bucket->first; search; search = search->next_in_bucket) { - if (search->tick == tick) { - ss = search; - break; - } - } - } - return ss; -} - -/* ========================== * - * Lerp - * ========================== */ - -struct sim_snapshot *sim_snapshot_alloc_from_lerp(struct sim_snapshot_store *store, struct sim_snapshot *ss0, struct sim_snapshot *ss1, f64 blend) -{ - __prof; - - /* New snapshot will be allocated with same tick as ss0 or ss1, so the result should go into a different store */ - ASSERT(ss0->store != store && ss1->store != store); - - struct sim_snapshot *ss; - b32 should_blend = true; - if (ss0->continuity_gen == ss1->continuity_gen && 0 < blend && blend < 1) { - ss = sim_snapshot_alloc(store, ss0, ss0->tick); - } else if (math_round_to_int64(blend) <= 0) { - ss = sim_snapshot_alloc(store, ss0, ss0->tick); - should_blend = false; - } else { - ss = sim_snapshot_alloc(store, ss1, ss1->tick); - should_blend = false; - } - - if (!ss0->valid || !ss1->valid) { - /* New snapshot allocation caused one of the src snapshots original to release. - * ss0 & ss1 should be from a separate store than the allocating one. */ - ASSERT(false); - } - - if (should_blend) { - /* Blend time */ - ss->real_dt_ns = math_lerp_i64(ss0->real_dt_ns, ss1->real_dt_ns, blend); - ss->real_time_ns = math_lerp_i64(ss0->real_time_ns, ss1->real_time_ns, blend); - ss->world_timescale = math_lerp_f64(ss0->world_timescale, ss1->world_timescale, blend); - ss->world_dt_ns = math_lerp_i64(ss0->world_dt_ns, ss1->world_dt_ns, blend); - ss->world_time_ns = math_lerp_i64(ss0->world_time_ns, ss1->world_time_ns, blend); - - /* Blend clients */ - { - __profscope(snapshot_lerp_clients); - u64 num_clients = min_u64(ss0->num_clients_reserved, ss1->num_clients_reserved); - for (u64 i = 0; i < num_clients; ++i) { - struct sim_client *c = &ss->clients[i]; - struct sim_client *c0 = &ss0->clients[i]; - struct sim_client *c1 = &ss1->clients[i]; - sim_client_lerp(c, c0, c1, blend); - } - } - - /* Blend entities */ - { - __profscope(snapshot_lerp_entities); - u64 num_entities = min_u64(ss0->num_ents_reserved, ss1->num_ents_reserved); - for (u64 i = 0; i < num_entities; ++i) { - struct sim_ent *e = &ss->ents[i]; - struct sim_ent *e0 = &ss0->ents[i]; - struct sim_ent *e1 = &ss1->ents[i]; - sim_ent_lerp(e, e0, e1, blend); - } - } - } - - - return ss; -} - -/* ========================== * - * Encode - * ========================== */ - -void sim_snapshot_encode(struct bitbuff_writer *bw, struct sim_client *receiver, struct sim_snapshot *ss0, struct sim_snapshot *ss1) -{ - __prof; - - /* TODO: Don't encode this */ - bw_write_bit(bw, ss1->is_master); - - bw_write_iv(bw, ss1->real_dt_ns); - bw_write_iv(bw, ss1->real_time_ns); - - bw_write_f64(bw, ss1->world_timescale); - bw_write_iv(bw, ss1->world_dt_ns); - bw_write_iv(bw, ss1->world_time_ns); - - bw_write_uv(bw, ss1->continuity_gen); - bw_write_uv(bw, ss1->phys_iteration); - - bw_write_uv(bw, receiver->handle.gen); - bw_write_uv(bw, receiver->handle.idx); - - /* Clients */ - if (ss1->num_clients_allocated == ss0->num_clients_allocated) { - bw_write_bit(bw, 0); - } else { - bw_write_bit(bw, 1); - bw_write_uv(bw, ss1->num_clients_allocated); - } - if (ss1->num_clients_reserved == ss0->num_clients_reserved) { - bw_write_bit(bw, 0); - } else { - bw_write_bit(bw, 1); - bw_write_uv(bw, ss1->num_clients_reserved); - } - for (u64 i = 0; i < ss1->num_clients_reserved; ++i) { - struct sim_client *c0 = sim_client_nil(); - if (i < ss0->num_clients_reserved) { - c0 = &ss0->clients[i]; - } - struct sim_client *c1 = &ss1->clients[i]; - sim_client_encode(bw, c0, c1); - } - - /* Ents */ - if (ss1->num_ents_allocated == ss0->num_ents_allocated) { - bw_write_bit(bw, 0); - } else { - bw_write_bit(bw, 1); - bw_write_uv(bw, ss1->num_ents_allocated); - } - if (ss1->num_ents_reserved == ss0->num_ents_reserved) { - bw_write_bit(bw, 0); - } else { - bw_write_bit(bw, 1); - bw_write_uv(bw, ss1->num_ents_reserved); - } - for (u64 i = 0; i < ss1->num_ents_reserved; ++i) { - struct sim_ent *e0 = sim_ent_nil(); - if (i < ss0->num_ents_reserved) { - e0 = &ss0->ents[i]; - } - struct sim_ent *e1 = &ss1->ents[i]; - sim_ent_encode(bw, e0, e1); - } -} - -/* ========================== * - * Decode - * ========================== */ - -void sim_snapshot_decode(struct bitbuff_reader *br, struct sim_snapshot *ss) -{ - __prof; - - /* TODO: Don't encode this */ - ss->is_master = br_read_bit(br); - - ss->real_dt_ns = br_read_iv(br); - ss->real_time_ns = br_read_iv(br); - - ss->world_timescale = br_read_f64(br); - ss->world_dt_ns = br_read_iv(br); - ss->world_time_ns = br_read_iv(br); - - ss->continuity_gen = br_read_uv(br); - ss->phys_iteration = br_read_uv(br); - - ss->local_client.gen = br_read_uv(br); - ss->local_client.idx = br_read_uv(br); - - /* Clients */ - if (br_read_bit(br)) { - ss->num_clients_allocated = br_read_uv(br); - } - if (br_read_bit(br)) { - u64 old_num_clients_reserved = ss->num_clients_reserved; - ss->num_clients_reserved = br_read_uv(br); - i64 reserve_diff = (i64)ss->num_clients_reserved - (i64)old_num_clients_reserved; - if (reserve_diff > 0) { - arena_push_array(&ss->clients_arena, struct sim_client, reserve_diff); - for (u64 i = old_num_clients_reserved; i < ss->num_clients_reserved; ++i) { - struct sim_client *c = &ss->clients[i]; - *c = *sim_client_nil(); - c->ss = ss; - } - } - } - for (u64 i = 0; i < ss->num_clients_reserved; ++i) { - struct sim_client *c = &ss->clients[i]; - sim_client_decode(br, c); - } - - /* Ents */ - if (br_read_bit(br)) { - ss->num_ents_allocated = br_read_uv(br); - } - if (br_read_bit(br)) { - u64 old_num_ents_reserved = ss->num_ents_reserved; - ss->num_ents_reserved = br_read_uv(br); - i64 reserve_diff = (i64)ss->num_ents_reserved - (i64)old_num_ents_reserved; - if (reserve_diff > 0) { - arena_push_array(&ss->ents_arena, struct sim_ent, reserve_diff); - for (u64 i = old_num_ents_reserved; i < ss->num_ents_reserved; ++i) { - struct sim_ent *e = &ss->ents[i]; - *e = *sim_ent_nil(); - e->ss = ss; - } - } - } - for (u64 i = 0; i < ss->num_ents_reserved; ++i) { - struct sim_ent *e = &ss->ents[i]; - sim_ent_decode(br, e); - } -} diff --git a/src/sim_snapshot.h b/src/sim_snapshot.h deleted file mode 100644 index f1e219e1..00000000 --- a/src/sim_snapshot.h +++ /dev/null @@ -1,160 +0,0 @@ -#ifndef SIM_SNAPSHOT_H -#define SIM_SNAPSHOT_H - -#include "sim_ent.h" -#include "sim_client.h" - -#if 0 -enum sim_snapshot_flag { - SIM_SNAPSHOT_FLAG_NONE = 0, - - /* The snapshot contains client control data */ - SIM_SNAPSHOT_FLAG_CONTROL = (1 << 0), - - /* The snapshot contains entity state data */ - SIM_SNAPSHOT_FLAG_STATE = (1 << 1) -}; -#endif - -struct sim_snapshot_store { - b32 valid; - struct arena arena; - - /* Snapshots sorted by tick (low to high) */ - u64 first_tick; - u64 last_tick; - u64 num_ticks; - - struct sim_snapshot *first_free_snapshot; - - /* Tick -> snapshot lookup */ - u64 num_lookup_buckets; - struct sim_snapshot_lookup_bucket *lookup_buckets; -}; - -struct sim_snapshot { - b32 valid; - u64 tick; - u64 ack; - struct sim_snapshot_store *store; - struct sim_snapshot *next_free; - struct sim_snapshot *next_in_bucket; - struct sim_snapshot *prev_in_bucket; - u64 prev_tick; - u64 next_tick; - - struct arena arena; - - /* ====================================================================== */ - /* SIM_SNAPSHOT_FLAG_CONTROL */ - - struct sim_control control; - - /* ====================================================================== */ - /* SIM_SNAPSHOT_FLAG_STATE */ - - /* Was this snapshot created by the master sim */ - b32 is_master; - - /* Time of local snapshot publish to user thread */ - i64 publish_dt_ns; - i64 publish_time_ns; - - /* Real time (guaranteed to increase by real_dt_ns each sim step) */ - i64 real_dt_ns; - i64 real_time_ns; - - /* World time (affected by timescale) */ - f64 world_timescale; - i64 world_dt_ns; - i64 world_time_ns; - - /* If != previous tick's continuity then don't lerp */ - u64 continuity_gen; - - /* The last physics iteration (used for tracking contact lifetime) */ - u64 phys_iteration; - - /* Which client in the client store represents the local host in the original snapshot */ - struct sim_client_handle local_client; - - /* Client lookup */ - struct sim_client_lookup_bucket *client_lookup_buckets; - u64 num_client_lookup_buckets; - - /* Clients */ - struct arena clients_arena; - struct sim_client *clients; - struct sim_client_handle first_free_client; - u64 num_clients_allocated; - u64 num_clients_reserved; - - /* Entities */ - struct arena ents_arena; - struct sim_ent *ents; - struct sim_ent_handle first_free_ent; - u64 num_ents_allocated; - u64 num_ents_reserved; -}; - -#if 0 -struct sim_snapshot_encoded { - u64 base_tick; - u64 tick; - struct string data; /* Contains snapshot deltas for `tick` from `base_tick` */ -}; -#endif - -/* ========================== * - * Startup - * ========================== */ - -struct sim_snapshot_startup_receipt { i32 _; }; -struct sim_snapshot_startup_receipt sim_snapshot_startup(void); - -/* ========================== * - * Store - * ========================== */ - -struct sim_snapshot_store *sim_snapshot_store_alloc(void); -void sim_snapshot_store_release(struct sim_snapshot_store *store); -void sim_snapshot_store_release_ticks_in_range(struct sim_snapshot_store *store, u64 start, u64 end); - -/* ========================== * - * Nil - * ========================== */ - -INLINE struct sim_snapshot *sim_snapshot_nil(void) -{ - extern READONLY struct sim_snapshot **_g_sim_snapshot_nil; - return *_g_sim_snapshot_nil; -} - -INLINE struct sim_snapshot_store *sim_snapshot_store_nil(void) -{ - extern READONLY struct sim_snapshot_store **_g_sim_snapshot_store_nil; - return *_g_sim_snapshot_store_nil; -} - -/* ========================== * - * Snapshot - * ========================== */ - -struct bitbuff_writer; -struct bitbuff_reader; - -/* Alloc */ -struct sim_snapshot *sim_snapshot_alloc(struct sim_snapshot_store *store, struct sim_snapshot *src, u64 tick); -void sim_snapshot_release(struct sim_snapshot *sim_snapshot); - -/* Lookup */ -struct sim_snapshot *sim_snapshot_from_tick(struct sim_snapshot_store *store, u64 tick); - -/* Lerp */ -struct sim_snapshot *sim_snapshot_alloc_from_lerp(struct sim_snapshot_store *store, struct sim_snapshot *ss0, struct sim_snapshot *ss1, f64 blend); - -/* Encode / decode */ -void sim_snapshot_encode(struct bitbuff_writer *bw, struct sim_client *receiver, struct sim_snapshot *ss0, struct sim_snapshot *ss1); -void sim_snapshot_decode(struct bitbuff_reader *br, struct sim_snapshot *ss); - -#endif diff --git a/src/sim_step.c b/src/sim_step.c new file mode 100644 index 00000000..fb37ae12 --- /dev/null +++ b/src/sim_step.c @@ -0,0 +1,1459 @@ +#include "sim_step.h" +#include "sim_ent.h" +#include "sim.h" +#include "sys.h" +#include "util.h" +#include "sprite.h" +#include "math.h" +#include "scratch.h" +#include "atomic.h" +#include "app.h" +#include "log.h" +#include "phys.h" +#include "collider.h" +#include "rng.h" +#include "space.h" +#include "bitbuff.h" +#include "host.h" + +/* ========================== * + * Ent lookup + * ========================== */ + +struct sim_lookup sim_lookup_alloc(u64 num_buckets) +{ + ASSERT(num_buckets > 0); + struct sim_lookup l = ZI; + l.arena = arena_alloc(GIGABYTE(64)); + l.num_buckets = num_buckets; + sim_lookup_reset(&l); + return l; +} + +void sim_lookup_release(struct sim_lookup *l) +{ + arena_release(&l->arena); +} + +void sim_lookup_reset(struct sim_lookup *l) +{ + arena_reset(&l->arena); + l->buckets = arena_push_array_zero(&l->arena, struct sim_lookup_bucket, l->num_buckets); + l->first_free_entry = NULL; +} + +struct sim_lookup_entry *sim_lookup_get(struct sim_lookup *l, struct sim_lookup_key key) +{ + u64 index = key.hash % l->num_buckets; + struct sim_lookup_bucket *bucket = &l->buckets[index]; + struct sim_lookup_entry *res = NULL; + for (struct sim_lookup_entry *e = bucket->first; e; e = e->next) { + if (e->key.hash == key.hash) { + res = e; + break; + } + } + return res; +} + +void sim_lookup_set(struct sim_lookup *l, struct sim_lookup_key key, struct sim_ent_handle handle) +{ + u64 index = key.hash % l->num_buckets; + struct sim_lookup_bucket *bucket = &l->buckets[index]; + + struct sim_lookup_entry *prev = NULL; + struct sim_lookup_entry **slot = &bucket->first; + while (*slot) { + if ((*slot)->key.hash == key.hash) { + break; + } + prev = *slot; + slot = &(*slot)->next; + } + + struct sim_lookup_entry *entry = *slot; + if (entry) { + /* Set existing entry */ + entry->ent = handle; + } else { + /* Allocate entry */ + if (l->first_free_entry) { + entry = l->first_free_entry; + l->first_free_entry->prev = NULL; + l->first_free_entry = entry->next; + } else { + entry = arena_push(&l->arena, struct sim_lookup_entry); + } + MEMZERO_STRUCT(entry); + + entry->key = key; + entry->ent = handle; + if (prev) { + entry->prev = prev; + prev->next = entry; + } + + bucket->last = entry; + *slot = entry; + } +} + +void sim_lookup_remove(struct sim_lookup *l, struct sim_lookup_entry *entry) +{ + struct sim_lookup_bucket *bucket = &l->buckets[entry->key.hash % l->num_buckets]; + struct sim_lookup_entry *prev = entry->prev; + struct sim_lookup_entry *next = entry->next; + + if (prev) { + prev->next = next; + } else { + bucket->first = next; + } + + if (next) { + next->prev = prev; + } else { + bucket->last = prev; + } + + if (l->first_free_entry) { + l->first_free_entry->prev = entry; + } + entry->next = l->first_free_entry; + entry->prev = NULL; + l->first_free_entry = entry; +} + +struct sim_lookup_key sim_lookup_key_from_two_handles(struct sim_ent_handle h0, struct sim_ent_handle h1) +{ + struct sim_lookup_key key = ZI; + struct string b0 = STRING_FROM_STRUCT(&h0); + struct string b1 = STRING_FROM_STRUCT(&h1); + key.hash = hash_fnv64(HASH_FNV64_BASIS, b0); + key.hash = hash_fnv64(key.hash, b1); + return key; +} + +struct sim_lookup_key sim_lookup_key_from_client_handle(struct sim_client_handle handle) +{ + struct sim_lookup_key key = ZI; + key.hash = hash_fnv64(HASH_FNV64_BASIS, STRING_FROM_STRUCT(&handle)); + return key; +} + +/* ========================== * + * Sim accel + * ========================== */ + +struct sim_accel sim_accel_alloc(void) +{ + struct sim_accel accel = ZI; + accel.space = space_alloc(1, 256); + accel.client_lookup = sim_lookup_alloc(4096); + accel.contact_lookup = sim_lookup_alloc(4096); +#if COLLIDER_DEBUG + accel.collision_debug_lookup = sim_lookup_alloc(4096); +#endif + return accel; +} + +void sim_accel_release(struct sim_accel *accel) +{ +#if COLLIDER_DEBUG + sim_lookup_release(&accel->collision_debug_lookup); +#endif + sim_lookup_release(&accel->contact_lookup); + sim_lookup_release(&accel->client_lookup); + space_release(accel->space); +} + +void sim_accel_rebuild(struct sim_snapshot *ss, struct sim_accel *accel) +{ + /* FIXME: Rebuild collision debug lookup */ + + space_reset(accel->space); + sim_lookup_reset(&accel->client_lookup); + sim_lookup_reset(&accel->contact_lookup); + + /* Reset ent space handles */ + 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; + MEMZERO_STRUCT(&ent->space_handle); + } + + /* NOTE: Not rebuilding space since it'll happen during phys step */ + + /* Rebuild lookup tables */ + 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; + if (sim_ent_has_prop(ent, SIM_ENT_PROP_CLIENT)) { + struct sim_lookup_key key = sim_lookup_key_from_client_handle(ent->client_handle); + sim_lookup_set(&accel->client_lookup, key, ent->handle); + } + if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTACT_CONSTRAINT)) { + struct sim_lookup_key contact_lookup_key = sim_lookup_key_from_two_handles(ent->contact_constraint_data.e0, ent->contact_constraint_data.e1); + sim_lookup_set(&accel->contact_lookup, contact_lookup_key, ent->handle); + } + } +} + +/* ========================== * + * Test + * ========================== */ + +/* TODO: Remove this */ + +INTERNAL void spawn_test_entities(struct sim_snapshot *world, struct v2 offset) +{ + struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE); + root->mass_unscaled = F32_INFINITY; + root->inertia_unscaled = F32_INFINITY; + + /* Enemy */ + { + struct sim_ent *e = sim_ent_alloc(root); + + struct v2 pos = V2(1, -2); + pos = v2_add(pos, offset); + f32 r = 0; + struct v2 size = V2(1, 1); + struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size); + sim_ent_set_xform(e, xf); + + e->sprite = sprite_tag_from_path(LIT("res/graphics/tim.ase")); + e->sprite_collider_slice = LIT("shape"); + e->layer = SIM_LAYER_SHOULDERS; + + sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC); + e->mass_unscaled = 10; + e->inertia_unscaled = 10; + e->linear_ground_friction = 250; + e->angular_ground_friction = 200; + } + + /* Big box */ +#if 1 + { + struct sim_ent *e = sim_ent_alloc(root); + + struct v2 pos = V2(1, -0.5); + pos = v2_add(pos, offset); + f32 r = 0; + struct v2 size = V2(0.5, 0.25); + struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size); + sim_ent_set_xform(e, xf); + + e->sprite = sprite_tag_from_path(LIT("res/graphics/box.ase")); + e->sprite_collider_slice = LIT("shape"); + e->layer = SIM_LAYER_SHOULDERS; + + sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC); + e->mass_unscaled = 100; + e->inertia_unscaled = 50; + e->linear_ground_friction = 100; + e->angular_ground_friction = 50; + } +#endif + + /* Tiny box */ +#if 0 + { + struct sim_ent *e = sim_ent_alloc(root); + + struct v2 pos = V2(1, -0.5); + pos = v2_add(pos, offset); + f32 r = PI / 4; + struct v2 size = V2(0.5, 0.25); + struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size); + sim_ent_set_xform(e, xf); + + e->sprite = sprite_tag_from_path(LIT("res/graphics/bullet.ase")); + e->sprite_collider_slice = LIT("shape"); + e->layer = SIM_LAYER_SHOULDERS; + + sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC); + e->mass_unscaled = 0.5; + e->inertia_unscaled = 1000; + e->linear_ground_friction = 0.001; + } +#endif +} + +INTERNAL struct sim_ent *spawn_test_player(struct sim_snapshot *world) +{ + struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE); + + /* Player */ + struct sim_ent *player_ent = sim_ent_nil(); + //if (!ctx->extra_spawn) { + { + + struct sim_ent *e = sim_ent_alloc(root); + + struct v2 pos = V2(1, -1); + + //struct v2 size = V2(0.5, 0.5); + //struct v2 size = V2(0.5, 0.25); + struct v2 size = V2(1.0, 1.0); + + //f32 r = PI / 4; + f32 r = 0; + + { + sim_ent_enable_prop(e, SIM_ENT_PROP_TEST); + e->sprite = sprite_tag_from_path(LIT("res/graphics/tim.ase")); + e->mass_unscaled = 10; + e->inertia_unscaled = 5; + } + + //e->sprite = sprite_tag_from_path(LIT("res/graphics/box_rounded.ase")); + //e->sprite_span_name = LIT("idle.unarmed"); + //e->sprite_span_name = LIT("idle.one_handed"); + e->sprite_span_name = LIT("idle.two_handed"); + e->layer = SIM_LAYER_SHOULDERS; + + e->local_collider.points[0] = V2(0, 0); + e->local_collider.count = 1; + e->local_collider.radius = 0.15f; + + struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size); + //xf.bx.y = -1.f; + + sim_ent_set_xform(e, xf); + + e->linear_ground_friction = 250; + e->angular_ground_friction = 200; + + e->friction = 0; + + //e->control_force = 500; + e->control_force = 500; + e->control_force_max_speed = 4; + + //e->control_torque = 5000; + e->control_torque = F32_INFINITY; + + + sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC); + + player_ent = e; + } + + /* Player weapon */ + if (player_ent->valid) { + struct sim_ent *e = sim_ent_alloc(player_ent); + e->sprite = sprite_tag_from_path(LIT("res/graphics/gun.ase")); + + sim_ent_enable_prop(e, SIM_ENT_PROP_ATTACHED); + e->attach_slice = LIT("attach.wep"); + e->layer = SIM_LAYER_RELATIVE_WEAPON; + + sim_ent_enable_prop(e, SIM_ENT_PROP_WEAPON); + e->trigger_delay = 1.0f / 10.0f; + //e->trigger_delay = 1.0f / 100.0f; + + player_ent->equipped = e->handle; + } + + return player_ent; +} + +INTERNAL struct sim_ent *spawn_test_player_camera(struct sim_snapshot *world, struct sim_ent *player_ent) +{ + struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE); + + struct sim_ent *camera_ent = sim_ent_nil(); + if (player_ent->valid) { + camera_ent = sim_ent_alloc(root); + sim_ent_set_xform(camera_ent, XFORM_IDENT); + + sim_ent_enable_prop(camera_ent, SIM_ENT_PROP_CAMERA); + sim_ent_enable_prop(camera_ent, SIM_ENT_PROP_CAMERA_ACTIVE); + camera_ent->camera_follow = player_ent->handle; + + f32 width = (f32)DEFAULT_CAMERA_WIDTH; + f32 height = (f32)DEFAULT_CAMERA_HEIGHT; + camera_ent->camera_quad_xform = XFORM_TRS(.s = V2(width, height)); + } + + return camera_ent; +} + +INTERNAL void test_clear_level(struct sim_snapshot *world) +{ + for (u64 j = 0; j < world->num_ents_reserved; ++j) { + struct sim_ent *ent = &world->ents[j]; + if (ent->valid) { + sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE_NEXT_TICK); + } + } +} + +/* ========================== * + * Release entities + * ========================== */ + +#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 = 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 < 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; + } + } + + /* Release references */ + for (u64 i = 0; i < ents_to_release_count; ++i) { + struct sim_ent *ent = ents_to_release[i]; + /* Release space entry */ + { + struct space_entry *space_entry = space_entry_from_handle(space, ent->space_handle); + if (space_entry->valid) { + space_entry_release(space_entry); + } + } + } + + /* 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); +} +#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 + * ========================== */ + +INTERNAL PHYS_COLLISION_CALLBACK_FUNC_DEF(on_collision, collision_data_array, step_ctx) +{ + struct sim_snapshot *world = step_ctx->world; + struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE); + + for (u64 i = 0; i < collision_data_array.count; ++i) { + struct phys_collision_data *data = &collision_data_array.a[i]; + + struct phys_contact_constraint *constraint = data->constraint; + struct sim_ent *e0 = sim_ent_from_handle(world, data->e0); + struct sim_ent *e1 = sim_ent_from_handle(world, data->e1); + + if (sim_ent_is_valid_and_active(e0) && sim_ent_is_valid_and_active(e1)) { + /* Bullet hit entity */ + if (sim_ent_has_prop(e0, SIM_ENT_PROP_BULLET) || sim_ent_has_prop(e1, SIM_ENT_PROP_BULLET)) { + struct v2 normal = data->normal; /* Impact normal */ + struct v2 vrel = v2_neg(data->vrel); /* Impact velocity */ + + struct sim_ent *target = e0; + struct sim_ent *bullet = e1; + if (sim_ent_has_prop(e0, SIM_ENT_PROP_BULLET)) { + target = e1; + bullet = e0; + normal = v2_neg(normal); + vrel = v2_neg(vrel); + } + struct sim_ent *src = sim_ent_from_handle(world, bullet->bullet_src); + + if (bullet->bullet_has_hit || sim_ent_handle_eq(src->top, target->top)) { + /* Ignore collision if bullet already spent or if weapon and + * target share same top level parent */ + /* NOTE: Since bullet is most likely just a sensor skip_solve is probably already true */ + constraint->skip_solve = true; + } else { + struct v2 point = data->point; + + /* Update bullet */ + bullet->bullet_has_hit = true; + sim_ent_enable_prop(bullet, SIM_ENT_PROP_RELEASE_THIS_TICK); + + /* Update tracer */ + struct sim_ent *tracer = sim_ent_from_handle(world, bullet->bullet_tracer); + if (sim_ent_is_valid_and_active(tracer)) { + struct xform xf = sim_ent_get_xform(tracer); + xf.og = point; + sim_ent_set_xform(tracer, xf); + sim_ent_set_linear_velocity(tracer, V2(0, 0)); + } + + /* Update target */ + struct v2 knockback = v2_mul(v2_norm(vrel), bullet->bullet_knockback); + sim_ent_apply_linear_impulse(target, knockback, point); + + /* Create test blood */ + /* TODO: Remove this */ + { + struct xform xf = XFORM_TRS(.t = point, .r = rng_rand_f32(0, TAU)); + struct sim_ent *decal = sim_ent_alloc(root); + decal->sprite = sprite_tag_from_path(LIT("res/graphics/blood.ase")); + decal->sprite_tint = RGBA_32_F(1, 1, 1, 0.25f); + decal->layer = SIM_LAYER_FLOOR_DECALS; + sim_ent_set_xform(decal, xf); + + f32 perp_range = 0.5; + struct v2 linear_velocity = v2_mul(normal, 0.5); + linear_velocity = v2_add(linear_velocity, v2_mul(v2_perp(normal), rng_rand_f32(-perp_range, perp_range))); + + f32 angular_velocity_range = 5; + f32 angular_velocity = rng_rand_f32(-angular_velocity_range, angular_velocity_range); + + sim_ent_enable_prop(decal, SIM_ENT_PROP_PHYSICAL_KINEMATIC); + sim_ent_set_linear_velocity(decal, linear_velocity); + sim_ent_set_angular_velocity(decal, angular_velocity); + + decal->linear_damping = 5.0f; + decal->angular_damping = 5.0f; + } + } + } + } + } +} + +/* ========================== * + * Update + * ========================== */ + +void sim_step(struct sim_step_ctx *step_ctx) +{ + __prof; + struct temp_arena scratch = scratch_begin_no_conflict(); + + struct sim_snapshot *world = step_ctx->world; + struct sim_accel *accel = step_ctx->accel; + struct sim_snapshot_list *cmd_snapshots = step_ctx->cmd_snapshots; + i64 real_dt_ns = step_ctx->real_dt_ns; + + /* ========================== * + * Begin frame + * ========================== */ + + sim_accel_rebuild(world, step_ctx->accel); + + //sys_sleep_precise(rng_rand_f32(0, 0.050)); + //sys_sleep_precise(0.050); + + world->real_dt_ns = max_i64(0, real_dt_ns); + world->real_time_ns += world->real_dt_ns; + + world->world_timescale = SIM_TIMESCALE; + world->world_dt_ns = max_i64(0, real_dt_ns * world->world_timescale); + world->world_time_ns += world->world_dt_ns; + + f64 real_dt = SECONDS_FROM_NS(world->real_dt_ns); + f64 real_time = SECONDS_FROM_NS(world->real_time_ns); + f64 world_dt = SECONDS_FROM_NS(world->world_dt_ns); + f64 world_time = SECONDS_FROM_NS(world->world_time_ns); + (UNUSED)real_dt; + (UNUSED)real_time; + (UNUSED)world_dt; + (UNUSED)world_time; + + struct sprite_scope *sprite_frame_scope = sprite_scope_begin(); + + struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE); + + if (world->is_master) { + /* ========================== * + * Spawn test entities + * ========================== */ + + /* TODO: remove this (testing) */ + /* 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); + + /* ========================== * + * Activate entities + * ========================== */ + + for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { + struct sim_ent *ent = &world->ents[ent_index]; + if (!ent->valid) continue; + + if (!sim_ent_has_prop(ent, SIM_ENT_PROP_ACTIVE)) { + u64 atick = ent->activation_tick; + if (atick != 0 || world->tick >= atick) { + sim_ent_activate(ent, world->tick); + } + } + } + + /* ========================== * + * Reset triggered entities + * ========================== */ + + for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { + struct sim_ent *ent = &world->ents[ent_index]; + if (!sim_ent_is_valid_and_active(ent)) continue; + + if (sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGER_NEXT_TICK)) { + sim_ent_disable_prop(ent, SIM_ENT_PROP_TRIGGER_NEXT_TICK); + sim_ent_enable_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK); + } else if (sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK)) { + sim_ent_disable_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK); + } + } + + /* ========================== * + * Process client cmds + * ========================== */ + + for (struct sim_snapshot_list_node *n = cmd_snapshots->first; n; n = n->next) { + struct sim_snapshot *cmd_snapshot = n->ss; + struct sim_client_handle client_handle = cmd_snapshot->producer_client; + + struct sim_lookup_key key = sim_lookup_key_from_client_handle(client_handle); + struct sim_lookup_entry *client_entry = sim_lookup_get(&accel->client_lookup, key); + struct sim_ent *client_ent = client_entry ? sim_ent_from_handle(world, client_entry->ent) : sim_ent_nil(); + + /* Create client ent if it doesn't exist */ + /* FIXME: Client ent never released upon disconnect */ + if (!client_ent->valid) { + client_ent = sim_ent_alloc(root); + client_ent->client_handle = client_handle; + sim_ent_enable_prop(client_ent, SIM_ENT_PROP_CLIENT); + if (cmd_snapshot->producer_client_is_local) { + sim_ent_enable_prop(client_ent, SIM_ENT_PROP_LOCAL_CLIENT); + } + sim_ent_enable_prop(client_ent, SIM_ENT_PROP_ACTIVE); + /* FIXME: Remove from lookup table when client disconnects */ + sim_lookup_set(&accel->client_lookup, key, client_ent->handle); + } + + /* Process client control */ + /* TODO: Create client cmd ents here and process them separately */ + struct sim_control old_control = client_ent->client_control; + struct sim_control *control = &client_ent->client_control; + *control = cmd_snapshot->control; + { + client_ent->client_dbg_drag_start = false; + client_ent->client_dbg_drag_stop = false; + + if (v2_len_sq(control->move) > 1) { + /* Cap movement vector magnitude at 1 */ + control->move = v2_norm(control->move); + } + + /* Determine cursor pos from focus */ + { + struct sim_ent_handle client_control_ent_handle = client_ent->client_control_ent; + struct sim_ent *client_control_ent = sim_ent_from_handle(world, client_control_ent_handle); + if (client_control_ent->valid || sim_ent_handle_eq(client_control_ent_handle, SIM_ENT_NIL_HANDLE)) { + /* 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); + } + } + + u32 flags = control->flags; + if (flags & SIM_CONTROL_FLAG_DRAGGING) { + if (!(old_control.flags & SIM_CONTROL_FLAG_DRAGGING)) { + client_ent->client_dbg_drag_start = true; + } + } else { + if (old_control.flags & SIM_CONTROL_FLAG_DRAGGING) { + client_ent->client_dbg_drag_stop = true; + } + } + if (flags & SIM_CONTROL_FLAG_CLEAR_ALL) { + if (!(old_control.flags & SIM_CONTROL_FLAG_CLEAR_ALL)) { + test_clear_level(world); + } + } + if (flags & SIM_CONTROL_FLAG_SPAWN_TEST) { + if (!(old_control.flags & SIM_CONTROL_FLAG_SPAWN_TEST)) { + logf_info("Spawning (test)"); + u32 count = 1; + f32 spread = 1; + for (u32 j = 0; j < count; ++j) { + spawn_test_entities(world, V2(0, (((f32)j / (f32)count) - 0.5) * spread)); + } + } + } + } + } + + /* ========================== * + * Create client player ents + * ========================== */ + + 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)) continue; + if (sim_ent_has_prop(ent, SIM_ENT_PROP_CLIENT)) { + /* FIXME: Ents never released when client disconnects */ + struct sim_ent *control_ent = sim_ent_from_handle(world, ent->client_control_ent); + if (!control_ent->valid) { + control_ent = spawn_test_player(world); + sim_ent_enable_prop(control_ent, SIM_ENT_PROP_CONTROLLED); + ent->client_control_ent = control_ent->handle; + control_ent->controlling_client = ent->handle; + } + struct sim_ent *camera_ent = sim_ent_from_handle(world, ent->client_camera_ent); + if (!camera_ent->valid) { + camera_ent = spawn_test_player_camera(world, control_ent); + ent->client_camera_ent = camera_ent->handle; + } + } + } + + /* ========================== * + * Update entity control from client control + * ========================== */ + + for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { + struct sim_ent *ent = &world->ents[ent_index]; + if (!sim_ent_is_valid_and_active(ent)) continue; + + if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) { + struct sim_ent *client_ent = sim_ent_from_handle(world, ent->controlling_client); + if (client_ent->valid) { + ent->control = client_ent->client_control; + /* TODO: Move this */ + if (ent->control.flags & SIM_CONTROL_FLAG_FIRING) { + sim_ent_enable_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED); + } else { + sim_ent_disable_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED); + } + } + } + } + + /* ========================== * + * Update entities from sprite + * ========================== */ + + for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { + struct sim_ent *ent = &world->ents[ent_index]; + if (!sim_ent_is_valid_and_active(ent)) continue; + if (sprite_tag_is_nil(ent->sprite)) continue; + + struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, ent->sprite); + + /* 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 = 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; + ++frame_index; + if (frame_index > span.end) { + /* Loop animation */ + 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_frame = frame_index; + } + + /* Update sprite local xform */ + { + struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, LIT("pivot"), ent->animation_frame); + struct v2 sprite_size = v2_div(sheet->frame_size, (f32)IMAGE_PIXELS_PER_UNIT); + + struct v2 dir = v2_mul_v2(sprite_size, slice.dir); + f32 rot = v2_angle(dir) + PI / 2; + + struct xform xf = XFORM_IDENT; + xf = xform_rotated(xf, -rot); + xf = xform_scaled(xf, sprite_size); + xf = xform_translated(xf, v2_neg(slice.center)); + ent->sprite_local_xform = xf; + } + + /* Update collider from sprite */ + if (ent->sprite_collider_slice.len > 0) { + struct xform cxf = ent->sprite_local_xform; + + struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, ent->sprite_collider_slice, ent->animation_frame); + ent->local_collider = collider_from_quad(xform_mul_quad(cxf, quad_from_rect(slice.rect))); + } + + /* Test collider */ + #if 0 + if (sim_ent_has_prop(ent, SIM_ENT_PROP_TEST)) { + //if ((true)) { + #if 0 + ent->local_collider.points[0] = V2(0, 0); + ent->local_collider.count = 1; + ent->local_collider.radius = 0.5; + #elif 0 + ent->local_collider.points[0] = v2_with_len(V2(0.08f, 0.17f), 0.15f); + ent->local_collider.points[1] = v2_with_len(V2(-0.07f, -0.2f), 0.15f); + ent->local_collider.count = 2; + ent->local_collider.radius = 0.075f; + #elif 1 + #if 0 + /* "Bad" winding order */ + ent->local_collider.points[0] = V2(-0.15, 0.15); + ent->local_collider.points[1] = V2(0.15, 0.15); + ent->local_collider.points[2] = V2(0, -0.15); + #else + ent->local_collider.points[0] = V2(0, -0.15); + ent->local_collider.points[1] = V2(0.15, 0.15); + ent->local_collider.points[2] = V2(-0.15, 0.15); + #endif + ent->local_collider.count = 3; + ent->local_collider.radius = 0.25; + //ent->local_collider.radius = math_fabs(math_sin(ctx->tick.time) / 3); + #else + //ent->local_collider.radius = 0.5; + ent->local_collider.radius = 0.25; + //ent->local_collider.radius = 0.; + #endif + } + #endif + } + + /* ========================== * + * Update attachments + * ========================== */ + + for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { + struct sim_ent *ent = &world->ents[ent_index]; + if (!sim_ent_is_valid_and_active(ent)) continue; + if (!sim_ent_has_prop(ent, SIM_ENT_PROP_ATTACHED)) continue; + + struct sim_ent *parent = sim_ent_from_handle(world, ent->parent); + struct sprite_tag parent_sprite = parent->sprite; + struct sprite_sheet *parent_sheet = sprite_sheet_from_tag_await(sprite_frame_scope, parent_sprite); + + struct xform parent_sprite_xf = parent->sprite_local_xform; + + struct sprite_sheet_slice attach_slice = sprite_sheet_get_slice(parent_sheet, ent->attach_slice, parent->animation_frame); + struct v2 attach_pos = xform_mul_v2(parent_sprite_xf, attach_slice.center); + struct v2 attach_dir = xform_basis_mul_v2(parent_sprite_xf, attach_slice.dir); + + struct xform xf = sim_ent_get_local_xform(ent); + xf.og = attach_pos; + xf = xform_basis_with_rotation_world(xf, v2_angle(attach_dir) + PI / 2); + sim_ent_set_local_xform(ent, xf); + } + + /* ========================== * + * Test + * ========================== */ + + #if 0 + 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; + + #if 0 + if (!ent->test_initialized) { + ent->test_initialized = true; + ent->test_start_local_xform = sim_ent_get_local_xform(ent); + ent->test_start_sprite_xform = ent->sprite_local_xform; + } + + f32 t = (f32)time; + struct v2 og = v2_mul(V2(math_cos(t), math_sin(t)), 3); + f32 r = t * 3; + struct v2 s = V2(1 + (math_fabs(math_sin(t * 5)) * 3), 1); + (UNUSED)og; + (UNUSED)r; + (UNUSED)s; + + og = v2_add(og, ent->test_start_local_xform.og); + r += xform_get_rotation(ent->test_start_local_xform); + s = v2_add(s, xform_get_scale(ent->test_start_local_xform)); + + struct xform xf = sim_ent_get_local_xform(ent); + xf.og = og; + xf = xform_rotated_to(xf, r); + xf = xform_scaled_to(xf, s); + sim_ent_set_local_xform(ent, xf); + #else + f32 t = (f32)time; + struct v2 og = v2_mul(V2(math_cos(t), math_sin(t)), 3); + f32 rot = t * PI / 3; + struct v2 scale = V2(1 + (math_fabs(math_sin(t * 5)) * 3), 1); + (UNUSED)og; + (UNUSED)rot; + (UNUSED)scale; + + struct xform xf = sim_ent_get_local_xform(ent); + xf = xform_rotated_to(xf, rot); + xf = xform_scaled_to(xf, scale); + sim_ent_set_local_xform(ent, xf); + #endif + } + #endif + + /* ========================== * + * Trigger equipped + * ========================== */ + + for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { + struct sim_ent *ent = &world->ents[ent_index]; + if (!sim_ent_is_valid_and_active(ent)) continue; + + if (sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED)) { + struct sim_ent *eq = sim_ent_from_handle(world, ent->equipped); + if (sim_ent_is_valid_and_active(eq)) { + sim_ent_enable_prop(eq, SIM_ENT_PROP_TRIGGERED_THIS_TICK); + } + } + } + + /* ========================== * + * Process triggered entities + * ========================== */ + + for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { + struct sim_ent *ent = &world->ents[ent_index]; + if (!sim_ent_is_valid_and_active(ent)) continue; + if (!sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK)) continue; + if ((world_time - ent->last_triggered < ent->trigger_delay) && ent->last_triggered != 0) continue; + + ent->last_triggered = world_time; + + /* Fire weapon */ + if (sim_ent_has_prop(ent, SIM_ENT_PROP_WEAPON)) { + struct sprite_tag sprite = ent->sprite; + u32 animation_frame = ent->animation_frame; + struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, sprite); + + struct xform sprite_local_xform = ent->sprite_local_xform; + + struct sprite_sheet_slice out_slice = sprite_sheet_get_slice(sheet, LIT("out"), animation_frame); + struct v2 rel_pos = xform_mul_v2(sprite_local_xform, out_slice.center); + struct v2 rel_dir = xform_basis_mul_v2(sprite_local_xform, out_slice.dir); + + /* Spawn bullet */ + struct sim_ent *bullet; + { + bullet = sim_ent_alloc(root); + + bullet->bullet_src = ent->handle; + bullet->bullet_src_pos = rel_pos; + bullet->bullet_src_dir = rel_dir; + //bullet->bullet_impulse = 0.75f; + bullet->bullet_impulse = 2.0f; + bullet->bullet_knockback = 10; + bullet->mass_unscaled = 0.04f; + bullet->inertia_unscaled = 0.00001f; + bullet->layer = SIM_LAYER_BULLETS; + + #if 1 + /* Point collider */ + bullet->local_collider.points[0] = V2(0, 0); + bullet->local_collider.count = 1; + #else + bullet->sprite = sprite_tag_from_path(LIT("res/graphics/bullet.ase")); + bullet->sprite_collider_slice = LIT("shape"); + #endif + + sim_ent_enable_prop(bullet, SIM_ENT_PROP_BULLET); + sim_ent_enable_prop(bullet, SIM_ENT_PROP_SENSOR); + } + + /* Spawn tracer */ + { + struct sim_ent *tracer = sim_ent_alloc(root); + tracer->tracer_fade_duration = 0.025f; + tracer->layer = SIM_LAYER_TRACERS; + sim_ent_enable_prop(tracer, SIM_ENT_PROP_TRACER); + + bullet->bullet_tracer = tracer->handle; + } + } + } + + /* ========================== * + * Create motor joints from control move + * ========================== */ + + for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { + struct sim_ent *ent = &world->ents[ent_index]; + if (!sim_ent_is_valid_and_active(ent)) continue; + + if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) { + struct sim_ent *joint_ent = sim_ent_from_handle(world, ent->move_joint); + if (!sim_ent_is_valid_and_active(joint_ent)) { + joint_ent = sim_ent_alloc(root); + joint_ent->mass_unscaled = F32_INFINITY; + joint_ent->inertia_unscaled = F32_INFINITY; + sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_MOTOR_JOINT); + sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_ACTIVE); + ent->move_joint = joint_ent->handle; + + struct phys_motor_joint_def def = ZI; + def.e0 = joint_ent->handle; /* Re-using joint entity as e0 */ + def.e1 = ent->handle; + def.correction_rate = 0; + def.max_force = ent->control_force; + def.max_torque = 0; + joint_ent->motor_joint_data = phys_motor_joint_from_def(def); + } + + sim_ent_set_xform(joint_ent, XFORM_IDENT); /* Reset joint ent position */ + sim_ent_set_linear_velocity(joint_ent, v2_mul(v2_clamp_len(ent->control.move, 1), ent->control_force_max_speed)); + } + } + + /* ========================== * + * Create motor joints from control focus (aim) + * ========================== */ + + #if SIM_PLAYER_AIM + for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { + struct sim_ent *ent = &world->ents[ent_index]; + if (!sim_ent_is_valid_and_active(ent)) continue; + + if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) { + struct xform xf = sim_ent_get_xform(ent); + struct xform sprite_xf = xform_mul(xf, ent->sprite_local_xform); + + /* Retrieve / create aim joint */ + struct sim_ent *joint_ent = sim_ent_from_handle(world, ent->aim_joint); + if (!sim_ent_is_valid_and_active(joint_ent)) { + joint_ent = sim_ent_alloc(root); + joint_ent->mass_unscaled = F32_INFINITY; + joint_ent->inertia_unscaled = F32_INFINITY; + sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_PHYSICAL_KINEMATIC); /* Since we'll be setting velocity manually */ + sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_MOTOR_JOINT); + sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_ACTIVE); + ent->aim_joint = joint_ent->handle; + + struct phys_motor_joint_def def = ZI; + def.e0 = joint_ent->handle; /* Re-using joint entity as e0 */ + def.e1 = ent->handle; + def.max_force = 0; + def.max_torque = ent->control_torque; + joint_ent->motor_joint_data = phys_motor_joint_from_def(def); + } + + /* Set correction rate dynamically since motor velocity is only set for one frame */ + joint_ent->motor_joint_data.correction_rate = 10 * world_dt; + + + /* Solve for final angle using law of sines */ + f32 new_angle; + { + struct v2 ent_pos = xf.og; + struct v2 focus_pos = v2_add(ent_pos, ent->control.focus); + + struct v2 sprite_hold_pos; + struct v2 sprite_hold_dir; + { + struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, ent->sprite); + struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, LIT("attach.wep"), ent->animation_frame); + sprite_hold_pos = slice.center; + sprite_hold_dir = slice.dir; + } + + struct v2 hold_dir = xform_basis_mul_v2(sprite_xf, sprite_hold_dir); + struct v2 hold_pos = xform_mul_v2(sprite_xf, sprite_hold_pos); + if (v2_eq(hold_pos, ent_pos)) { + /* If hold pos is same as origin (E.G if pivot is being used as hold pos), then move hold pos forward a tad to avoid issue */ + sprite_hold_pos = v2_add(sprite_hold_pos, V2(0, -1)); + hold_pos = xform_mul_v2(sprite_xf, sprite_hold_pos); + } + + f32 forward_hold_angle_offset; + { + struct xform xf_unrotated = xform_basis_with_rotation_world(xf, 0); + struct v2 hold_pos_unrotated = xform_mul_v2(xf_unrotated, xform_mul_v2(ent->sprite_local_xform, sprite_hold_pos)); + forward_hold_angle_offset = v2_angle_from_dirs(V2(0, -1), v2_sub(hold_pos_unrotated, xf_unrotated.og)); + } + + struct v2 hold_ent_dir = v2_sub(ent_pos, hold_pos); + struct v2 focus_ent_dir = v2_sub(ent_pos, focus_pos); + + f32 hold_ent_len = v2_len(hold_ent_dir); + f32 focus_ent_len = v2_len(focus_ent_dir); + + f32 final_hold_angle_btw_ent_and_focus = v2_angle_from_dirs(hold_ent_dir, hold_dir); + f32 final_focus_angle_btw_ent_and_hold = math_asin((math_sin(final_hold_angle_btw_ent_and_focus) * hold_ent_len) / focus_ent_len); + f32 final_ent_angle_btw_focus_and_hold = PI - (final_focus_angle_btw_ent_and_hold + final_hold_angle_btw_ent_and_focus); + + new_angle = math_unwind_angle(v2_angle_from_dirs(V2(0, -1), v2_sub(focus_pos, ent_pos)) + final_ent_angle_btw_focus_and_hold - forward_hold_angle_offset); + } + + f32 new_vel = 0; + if (!F32_IS_NAN(new_angle)) { + const f32 angle_error_allowed = 0.001f; + struct xform joint_xf = sim_ent_get_xform(joint_ent); + f32 diff = math_unwind_angle(new_angle - xform_get_rotation(joint_xf)); + if (math_fabs(diff) > angle_error_allowed) { + /* Instantly snap joint ent to new angle */ + new_vel = diff / real_dt; + } + } + sim_ent_set_angular_velocity(joint_ent, new_vel); + } + } + #endif + + /* ========================== * + * Create motor joints from ground friction (gravity) + * ========================== */ + + for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { + struct sim_ent *ent = &world->ents[ent_index]; + if (!sim_ent_is_valid_and_active(ent)) continue; + if (!sim_ent_has_prop(ent, SIM_ENT_PROP_PHYSICAL_DYNAMIC)) continue; + + struct sim_ent *joint_ent = sim_ent_from_handle(world, ent->ground_friction_joint); + + struct phys_motor_joint_def def = ZI; + def.e0 = root->handle; + def.e1 = ent->handle; + def.correction_rate = 0; + def.max_force = ent->linear_ground_friction; + def.max_torque = ent->angular_ground_friction; + if (joint_ent->motor_joint_data.max_force != def.max_force || joint_ent->motor_joint_data.max_torque != def.max_torque) { + if (!sim_ent_is_valid_and_active(joint_ent)) { + joint_ent = sim_ent_alloc(root); + sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_MOTOR_JOINT); + sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_ACTIVE); + joint_ent->motor_joint_data = phys_motor_joint_from_def(def); + ent->ground_friction_joint = joint_ent->handle; + } + } + } + + /* ========================== * + * Create mouse joints from client debug drag + * ========================== */ + + for (u64 i = 0; i < world->num_ents_reserved; ++i) { + struct sim_ent *client_ent = &world->ents[i]; + if (!sim_ent_is_valid_and_active(client_ent)) continue; + if (!sim_ent_has_prop(client_ent, SIM_ENT_PROP_CLIENT)) continue; + + struct v2 cursor = client_ent->client_cursor_pos; + b32 start_dragging = client_ent->client_dbg_drag_start; + b32 stop_dragging = client_ent->client_dbg_drag_stop; + + struct sim_ent *joint_ent = sim_ent_from_handle(world, client_ent->client_dbg_drag_joint_ent); + struct sim_ent *target_ent = sim_ent_from_handle(world, joint_ent->mouse_joint_data.target); + + if (start_dragging) { + struct xform mouse_xf = xform_from_pos(cursor); + struct collider_shape mouse_shape = ZI; + mouse_shape.points[0] = V2(0, 0); + mouse_shape.count = 1; + + 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; + if (!sim_ent_has_prop(ent, SIM_ENT_PROP_PHYSICAL_DYNAMIC)) continue; + + struct collider_shape ent_collider = ent->local_collider; + if (ent_collider.count > 0) { + struct xform ent_xf = sim_ent_get_xform(ent); + /* TODO: Can just use boolean GJK */ + struct collider_collision_points_result res = collider_collision_points(&ent_collider, &mouse_shape, ent_xf, mouse_xf); + if (res.num_points > 0) { + target_ent = ent; + break; + } + } + } + } + if (stop_dragging) { + target_ent = sim_ent_nil(); + } + + if (sim_ent_is_valid_and_active(target_ent)) { + if (!sim_ent_is_valid_and_active(joint_ent)) { + /* FIXME: Joint ent may never release */ + joint_ent = sim_ent_alloc(root); + joint_ent->mass_unscaled = F32_INFINITY; + joint_ent->inertia_unscaled = F32_INFINITY; + client_ent->client_dbg_drag_joint_ent = joint_ent->handle; + sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_MOUSE_JOINT); + sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_ACTIVE); + } + struct xform xf = sim_ent_get_xform(target_ent); + f32 mass = target_ent->mass_unscaled * math_fabs(xform_get_determinant(xf)); + + struct phys_mouse_joint_def def = ZI; + def.target = target_ent->handle; + if (sim_ent_handle_eq(joint_ent->mouse_joint_data.target, target_ent->handle)) { + def.point_local_start = joint_ent->mouse_joint_data.point_local_start; + } else { + def.point_local_start = xform_invert_mul_v2(xf, cursor); + } + def.point_local_end = xform_invert_mul_v2(xf, cursor); + def.max_force = mass * 1000; + joint_ent->mouse_joint_data = phys_mouse_joint_from_def(def); + } else if (sim_ent_is_valid_and_active(joint_ent)) { + joint_ent->mouse_joint_data.target = target_ent->handle; + } + } + + /* ========================== * + * Physics step + * ========================== */ + + { + struct phys_step_ctx phys = ZI; + phys.sim_step_ctx = step_ctx; + phys.pre_solve_callback = on_collision; + phys_step(&phys, world_dt); + } + + /* ========================== * + * Update tracers + * ========================== */ + + for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { + struct sim_ent *ent = &world->ents[ent_index]; + if (!sim_ent_is_valid_and_active(ent)) continue; + if (!sim_ent_has_prop(ent, SIM_ENT_PROP_TRACER)) continue; + + struct v2 end = sim_ent_get_xform(ent).og; + + struct v2 tick_velocity = v2_mul(ent->tracer_start_velocity, world_dt); + struct v2 gradient_start = v2_add(ent->tracer_gradient_start, tick_velocity); + struct v2 gradient_end = v2_add(ent->tracer_gradient_end, tick_velocity); + + if (v2_dot(tick_velocity, v2_sub(gradient_start, end)) > 0) { + /* Tracer has disappeared */ + sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE_NEXT_TICK); + } + + ent->tracer_gradient_start = gradient_start; + ent->tracer_gradient_end = gradient_end; + } + + /* ========================== * + * Initialize bullet kinematics from sources + * ========================== */ + + for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { + struct sim_ent *ent = &world->ents[ent_index]; + if (!sim_ent_is_valid_and_active(ent)) continue; + if (!sim_ent_has_prop(ent, SIM_ENT_PROP_BULLET)) continue; + + if (ent->activation_tick == world->tick) { + struct sim_ent *src = sim_ent_from_handle(world, ent->bullet_src); + struct xform src_xf = sim_ent_get_xform(src); + + struct v2 pos = xform_mul_v2(src_xf, ent->bullet_src_pos); + struct v2 impulse = xform_basis_mul_v2(src_xf, ent->bullet_src_dir); + impulse = v2_with_len(impulse, ent->bullet_impulse); + + #if 0 + /* Add shooter velocity to bullet */ + { + /* TODO: Add angular velocity as well? */ + struct sim_ent *top = sim_ent_from_handle(ss_blended, src->top); + impulse = v2_add(impulse, v2_mul(top->linear_velocity, dt)); + } + #endif + + struct xform xf = XFORM_TRS(.t = pos, .r = v2_angle(impulse) + PI / 2); + sim_ent_set_xform(ent, xf); + sim_ent_enable_prop(ent, SIM_ENT_PROP_PHYSICAL_KINEMATIC); + + sim_ent_apply_linear_impulse_to_center(ent, impulse); + + /* Initialize tracer */ + struct sim_ent *tracer = sim_ent_from_handle(world, ent->bullet_tracer); + if (sim_ent_is_valid_and_active(tracer)) { + sim_ent_set_xform(tracer, xf); + sim_ent_enable_prop(tracer, SIM_ENT_PROP_PHYSICAL_KINEMATIC); + sim_ent_set_linear_velocity(tracer, ent->linear_velocity); + tracer->tracer_start = pos; + tracer->tracer_start_velocity = ent->linear_velocity; + tracer->tracer_gradient_end = pos; + tracer->tracer_gradient_start = v2_sub(pos, v2_mul(ent->linear_velocity, tracer->tracer_fade_duration)); + } + + /* Spawn quake */ + { + struct sim_ent *quake = sim_ent_alloc(root); + sim_ent_set_xform(quake, XFORM_POS(pos)); + quake->quake_intensity = 0.2f; + quake->quake_fade = quake->quake_intensity / 0.1f; + sim_ent_enable_prop(quake, SIM_ENT_PROP_QUAKE); + } + } + } + + /* ========================== * + * Update cameras + * ========================== */ + + for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { + struct sim_ent *ent = &world->ents[ent_index]; + if (!sim_ent_is_valid_and_active(ent)) continue; + if (!sim_ent_has_prop(ent, SIM_ENT_PROP_CAMERA)) continue; + + struct xform xf = sim_ent_get_xform(ent); + + /* Camera follow */ + { + struct sim_ent *follow = sim_ent_from_handle(world, ent->camera_follow); + + f32 aspect_ratio = 1.0; + { + struct xform quad_xf = xform_mul(sim_ent_get_xform(ent), ent->camera_quad_xform); + struct v2 camera_size = xform_get_scale(quad_xf); + if (!v2_is_zero(camera_size)) { + aspect_ratio = camera_size.x / camera_size.y; + } + } + f32 ratio_y = 0.33f; + f32 ratio_x = ratio_y / aspect_ratio; + struct v2 camera_focus_dir = v2_mul_v2(follow->control.focus, V2(ratio_x, ratio_y)); + struct v2 camera_focus_pos = v2_add(sim_ent_get_xform(follow).og, camera_focus_dir); + ent->camera_xform_target = xf; + ent->camera_xform_target.og = camera_focus_pos; + + /* Lerp camera */ + if (ent->camera_applied_lerp_continuity_gen_plus_one == ent->camera_lerp_continuity_gen + 1) { + f32 t = 1 - math_pow(2.f, -20.f * (f32)world_dt); + xf = xform_lerp(xf, ent->camera_xform_target, t); + } else { + /* Skip lerp */ + xf = ent->camera_xform_target; + } + ent->camera_applied_lerp_continuity_gen_plus_one = ent->camera_lerp_continuity_gen + 1; + } + + /* Camera shake */ + { + /* TODO: Update based on distance to quake */ + ent->shake = 0; + for (u64 quake_ent_index = 0; quake_ent_index < world->num_ents_reserved; ++quake_ent_index) { + struct sim_ent *quake = &world->ents[quake_ent_index]; + if (!sim_ent_is_valid_and_active(quake)) continue; + if (!sim_ent_has_prop(quake, SIM_ENT_PROP_QUAKE)) continue; + ent->shake += quake->quake_intensity; + } + } + + sim_ent_set_xform(ent, xf); + } + + /* ========================== * + * Update quakes + * ========================== */ + + for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { + struct sim_ent *ent = &world->ents[ent_index]; + if (!sim_ent_is_valid_and_active(ent)) continue; + if (!sim_ent_has_prop(ent, SIM_ENT_PROP_QUAKE)) continue; + + ent->quake_intensity = max_f32(0, ent->quake_intensity - (ent->quake_fade * world_dt)); + if (ent->quake_intensity <= 0) { + sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE_NEXT_TICK); + } + } + + /* ========================== * + * Update relative layers + * ========================== */ + + { + struct temp_arena temp = arena_temp_begin(scratch.arena); + + struct sim_ent **stack = arena_push(temp.arena, struct sim_ent *); + u64 stack_count = 1; + *stack = root; + + while (stack_count > 0) { + struct sim_ent *parent; + arena_pop(temp.arena, struct sim_ent *, &parent); + --stack_count; + + i32 parent_layer = parent->final_layer; + for (struct sim_ent *child = sim_ent_from_handle(world, parent->first); child->valid; child = sim_ent_from_handle(world, child->next)) { + if (sim_ent_is_valid_and_active(child)) { + child->final_layer = parent_layer + child->layer; + *arena_push(temp.arena, struct sim_ent *) = child; + ++stack_count; + } + } + } + + arena_temp_end(temp); + } + + /* ========================== * + * Release entities + * ========================== */ + + release_entities_with_prop(world, SIM_ENT_PROP_RELEASE_THIS_TICK); + } + + /* ========================== * + * End frame + * ========================== */ + + sprite_scope_end(sprite_frame_scope); + + scratch_end(scratch); +} diff --git a/src/sim_step.h b/src/sim_step.h new file mode 100644 index 00000000..a10ff44b --- /dev/null +++ b/src/sim_step.h @@ -0,0 +1,83 @@ +#ifndef SIM_STEP_H +#define SIM_STEP_H + +struct space; +struct sim_snapshot; +struct sim_snapshot_list; +struct sim_lookup; + +/* ========================== * + * Sim lookup + * ========================== */ + + /* Structure used to accelerate up entity lookup (rebuilt every step) */ + /* TODO: Remove this and do something better. Just a hack to de-couple old sim ctx from step. */ + +struct sim_lookup_key { + u64 hash; +}; + +struct sim_lookup_entry { + struct sim_lookup_key key; + struct sim_ent_handle ent; + struct sim_lookup_entry *next; + struct sim_lookup_entry *prev; +}; + +struct sim_lookup_bucket { + struct sim_lookup_entry *first; + struct sim_lookup_entry *last; +}; + +struct sim_lookup { + struct arena arena; + struct sim_lookup_bucket *buckets; + u64 num_buckets; + struct sim_lookup_entry *first_free_entry; +}; + +struct sim_lookup sim_lookup_alloc(u64 num_buckets); +void sim_lookup_release(struct sim_lookup *l); +void sim_lookup_reset(struct sim_lookup *l); + +struct sim_lookup_entry *sim_lookup_get(struct sim_lookup *l, struct sim_lookup_key key); +void sim_lookup_set(struct sim_lookup *l, struct sim_lookup_key key, struct sim_ent_handle handle); +void sim_lookup_remove(struct sim_lookup *l, struct sim_lookup_entry *entry); + +struct sim_lookup_key sim_lookup_key_from_two_handles(struct sim_ent_handle h0, struct sim_ent_handle h1); +struct sim_lookup_key sim_lookup_key_from_client_handle(struct sim_client_handle handle); + +/* ========================== * + * Sim accel + * ========================== */ + + /* Structure used to accelerate up entity lookup (rebuilt every step) */ + /* TODO: Remove this and do something better. Just a hack to de-couple old sim ctx from step. */ + +struct sim_accel { + struct space *space; + struct sim_lookup client_lookup; + struct sim_lookup contact_lookup; +#if COLLIDER_DEBUG + struct sim_lookup collision_debug_lookup; +#endif +}; + +struct sim_accel sim_accel_alloc(void); +void sim_accel_release(struct sim_accel *accel); +void sim_accel_rebuild(struct sim_snapshot *ss, struct sim_accel *accel); + +/* ========================== * + * Sim step + * ========================== */ + +struct sim_step_ctx { + struct sim_accel *accel; + struct sim_snapshot *world; + struct sim_snapshot_list *cmd_snapshots; + i64 real_dt_ns; +}; + +void sim_step(struct sim_step_ctx *step_ctx); + +#endif diff --git a/src/space.c b/src/space.c index ecba51cb..3aeed8e9 100644 --- a/src/space.c +++ b/src/space.c @@ -22,11 +22,14 @@ READONLY struct space _g_space_nil = { .valid = false }; * For example, at `num_buckets_sqrt` = 256 (65536 buckets), tiles <1, 1>, <1, 257>, and <257, 257> will collide. */ struct space *space_alloc(f32 cell_size, u32 num_buckets_sqrt) { - struct arena arena = arena_alloc(GIGABYTE(64)); - struct space *space = arena_push_zero(&arena, struct space); + struct space *space; + { + struct arena arena = arena_alloc(GIGABYTE(64)); + space = arena_push_zero(&arena, struct space); + space->entry_arena = arena; + } space->valid = true; - space->entry_arena = arena; space->entries = arena_dry_push(&space->entry_arena, struct space_entry); space->cell_arena = arena_alloc(GIGABYTE(64)); @@ -44,6 +47,17 @@ void space_release(struct space *space) arena_release(&space->entry_arena); } +void space_reset(struct space *space) +{ + arena_pop_to(&space->entry_arena, (u64)space->entries - (u64)space->entry_arena.base); + arena_reset(&space->cell_arena); + space->buckets = arena_push_array_zero(&space->cell_arena, struct space_cell_bucket, space->num_buckets);; + space->num_entries_reserved = 0; + space->first_free_cell = NULL; + space->first_free_cell_node = NULL; + space->first_free_entry = NULL; +} + struct space *space_from_entry(struct space_entry *entry) { if (entry->valid) { diff --git a/src/space.h b/src/space.h index 6e6cd421..d4918d41 100644 --- a/src/space.h +++ b/src/space.h @@ -106,6 +106,8 @@ INLINE struct space *space_nil(void) struct space *space_alloc(f32 cell_size, u32 num_buckets_sqrt); void space_release(struct space *space); +void space_reset(struct space *space); + struct space *space_from_entry(struct space_entry *entry); /* ========================== * diff --git a/src/user.c b/src/user.c index cd944a16..89f3f6d3 100644 --- a/src/user.c +++ b/src/user.c @@ -2,7 +2,7 @@ #include "app.h" #include "sim.h" #include "sim_ent.h" -#include "sim_snapshot.h" +#include "sim_step.h" #include "renderer.h" #include "font.h" #include "sprite.h" @@ -48,12 +48,10 @@ GLOBAL struct { struct sys_window *window; struct string connect_address_str; - 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; + struct sim_client_store *user_client_store; + struct sim_client *user_unblended_snapshots_client; /* Contains buffered snapshots received from sim */ + struct sim_client *user_blended_snapshots_client; /* Contains single world snapshot from result of blending sim snapshots */ + struct sim_snapshot *ss_blended; /* Points to blended snapshot in blended snapshots client */ /* Usage stats */ i64 last_second_reset_ns; @@ -93,8 +91,9 @@ GLOBAL struct { u64 user_sim_cmd_ack; /* Local sim -> user */ - struct sys_mutex local_sim_ss_mutex; - struct sim_snapshot_store *local_sim_ss_store; + struct sys_mutex local_sim_to_user_mutex; + struct sim_client_store *local_sim_to_user_client_store; + struct sim_client *local_sim_to_user_client; /* Rolling window of local sim publish times */ i64 last_snapshot_published_at_ns; @@ -180,7 +179,7 @@ struct user_startup_receipt user_startup(struct work_startup_receipt *work_sr, struct mixer_startup_receipt *mixer_sr, struct phys_startup_receipt *phys_sr, struct host_startup_receipt *host_sr, - struct sim_snapshot_startup_receipt *sim_snapshot_sr, + struct sim_startup_receipt *sim_sr, struct string connect_address_str, struct sys_window *window) { @@ -194,30 +193,31 @@ struct user_startup_receipt user_startup(struct work_startup_receipt *work_sr, (UNUSED)mixer_sr; (UNUSED)phys_sr; (UNUSED)host_sr; - (UNUSED)sim_snapshot_sr; + (UNUSED)sim_sr; G.arena = arena_alloc(GIGABYTE(64)); + G.real_time_ns = sys_time_ns(); /* TODO: Remove this */ G.connect_address_str = string_copy(&G.arena, connect_address_str); - /* 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)); - /* User sim control */ - G.user_sim_cmd_mutex = sys_mutex_alloc(); + /* Snapshot store */ + G.user_client_store = sim_client_store_alloc(); + G.user_unblended_snapshots_client = sim_client_alloc(G.user_client_store, SIM_CLIENT_KIND_USER); + G.user_blended_snapshots_client = sim_client_alloc(G.user_client_store, SIM_CLIENT_KIND_USER); + G.ss_blended = sim_snapshot_nil(); /* Local sim snapshot store */ - G.local_sim_ss_mutex = sys_mutex_alloc(); - G.local_sim_ss_store = sim_snapshot_store_alloc(); + G.local_sim_to_user_mutex = sys_mutex_alloc(); + G.local_sim_to_user_client_store = sim_client_store_alloc(); + G.local_sim_to_user_client = sim_client_alloc(G.local_sim_to_user_client_store, SIM_CLIENT_KIND_LOCAL_SIM); - G.encoder_bitbuff = bitbuff_alloc(GIGABYTE(64)); + /* User sim control */ + G.user_sim_cmd_mutex = sys_mutex_alloc(); G.world_to_ui_xf = XFORM_IDENT; G.world_cmd_buffer = renderer_cmd_buffer_alloc(); @@ -225,8 +225,6 @@ struct user_startup_receipt user_startup(struct work_startup_receipt *work_sr, G.final_cmd_buffer = renderer_cmd_buffer_alloc(); G.backbuffer_cmd_buffer = renderer_cmd_buffer_alloc(); - G.real_time_ns = sys_time_ns(); - G.window = window; sys_window_register_event_callback(G.window, &window_event_callback); @@ -325,8 +323,8 @@ INTERNAL void debug_draw_movement(struct sim_ent *ent) u32 color_vel = RGBA_32_F(1, 0.5, 0, 1); - struct xform xf = sim_ent_get_xform(ent); - struct v2 velocity = ent->linear_velocity; + struct xform xf = sim_ent_get_xform(ent); + struct v2 velocity = ent->linear_velocity; struct v2 pos = xform_mul_v2(G.world_to_ui_xf, xf.og); struct v2 vel_ray = xform_basis_mul_v2(G.world_to_ui_xf, velocity); @@ -393,22 +391,17 @@ INTERNAL void user_update(void) * ========================== */ { - 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; + struct sys_lock lock = sys_mutex_lock_e(&G.local_sim_to_user_mutex); + u64 old_last_tick = G.user_unblended_snapshots_client->last_tick; + u64 last_tick = G.local_sim_to_user_client->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); - -#if 0 - G.last_snapshot_received_at_ns = G.real_time_ns; -#else + struct sim_snapshot *src = sim_snapshot_from_tick(G.local_sim_to_user_client, last_tick); + sim_snapshot_alloc(G.user_unblended_snapshots_client, src, src->tick); G.last_snapshot_published_at_ns = src->publish_time_ns; G.snapshot_publish_dts_ns[G.snapshot_publish_dts_index++] = src->publish_dt_ns; if (G.snapshot_publish_dts_index >= (i64)ARRAY_COUNT(G.snapshot_publish_dts_ns)) { G.snapshot_publish_dts_index = 0; } -#endif } sys_mutex_unlock(&lock); } @@ -450,7 +443,7 @@ INTERNAL void user_update(void) } /* 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.user_unblended_snapshots_client, G.user_unblended_snapshots_client->last_tick); G.local_sim_last_known_time_ns = newest_snapshot->real_time_ns; G.local_sim_last_known_tick = newest_snapshot->tick; u64 keep_unblended_tick = newest_snapshot->tick; @@ -474,7 +467,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.unblended_snapshot_store, G.unblended_snapshot_store->first_tick); + struct sim_snapshot *ss = sim_snapshot_from_tick(G.user_unblended_snapshots_client, G.user_unblended_snapshots_client->first_tick); while (ss->valid) { u64 next_tick = ss->next_tick; i64 ss_time_ns = ss->real_time_ns; @@ -484,7 +477,7 @@ INTERNAL void user_update(void) if (ss_time_ns > G.render_time_ns && ss_time_ns < right_snapshot->real_time_ns) { right_snapshot = ss; } - ss = sim_snapshot_from_tick(G.unblended_snapshot_store, next_tick); + ss = sim_snapshot_from_tick(G.user_unblended_snapshots_client, next_tick); } } @@ -495,11 +488,11 @@ INTERNAL void user_update(void) /* Create world from blended snapshots */ if (left_snapshot->valid && right_snapshot->valid) { 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.user_blended_snapshots_client, left_snapshot, right_snapshot, blend); } else if (left_snapshot->valid) { - G.ss_blended = sim_snapshot_alloc(G.blended_snapshot_store, left_snapshot, left_snapshot->tick); + G.ss_blended = sim_snapshot_alloc(G.user_blended_snapshots_client, left_snapshot, left_snapshot->tick); } else if (right_snapshot->valid) { - G.ss_blended = sim_snapshot_alloc(G.blended_snapshot_store, right_snapshot, right_snapshot->tick); + G.ss_blended = sim_snapshot_alloc(G.user_blended_snapshots_client, right_snapshot, right_snapshot->tick); } #else /* Interp disabled, just copy latest snapshot */ @@ -509,24 +502,24 @@ INTERNAL void user_update(void) if (G.ss_blended->valid) { sim_snapshot_release(G.ss_blended); } - G.ss_blended = sim_snapshot_alloc(G.blended_snapshot_store, newest_snapshot, newest_snapshot->tick); + G.ss_blended = sim_snapshot_alloc(G.user_blended_snapshots_client, newest_snapshot, newest_snapshot->tick); } #endif /* Release unneeded unblended sim snapshots */ if (keep_unblended_tick > 0) { - sim_snapshot_store_release_ticks_in_range(G.unblended_snapshot_store, 0, keep_unblended_tick - 1); + sim_snapshot_release_ticks_in_range(G.user_unblended_snapshots_client, 0, keep_unblended_tick - 1); } /* Release unused blended snapshots */ { - struct sim_snapshot *ss = sim_snapshot_from_tick(G.blended_snapshot_store, G.blended_snapshot_store->first_tick); + struct sim_snapshot *ss = sim_snapshot_from_tick(G.user_blended_snapshots_client, G.user_blended_snapshots_client->first_tick); while (ss->valid) { u64 next_tick = ss->next_tick; if (ss != G.ss_blended) { sim_snapshot_release(ss); } - ss = sim_snapshot_from_tick(G.blended_snapshot_store, next_tick); + ss = sim_snapshot_from_tick(G.user_blended_snapshots_client, next_tick); } } } @@ -624,9 +617,23 @@ INTERNAL void user_update(void) * Find local entities * ========================== */ - 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); + struct sim_ent *local_client_ent = sim_ent_nil(); + { + struct sim_client_handle local_client_handle = G.ss_blended->local_client; + 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; + if (sim_ent_has_prop(ent, SIM_ENT_PROP_CLIENT)) { + struct sim_client_handle h = ent->client_handle; + if (h.gen != 0 && h.gen == local_client_handle.gen && h.idx == local_client_handle.idx) { + local_client_ent = ent; + break; + } + } + } + } + struct sim_ent *local_player = sim_ent_from_handle(G.ss_blended, local_client_ent->client_control_ent); + struct sim_ent *local_camera = sim_ent_from_handle(G.ss_blended, local_client_ent->client_camera_ent); /* ========================== * * Apply shake @@ -1426,7 +1433,6 @@ INTERNAL void user_update(void) 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); } @@ -1561,19 +1567,6 @@ INTERNAL void user_update(void) } -#if 0 - /* Update network usage stats */ - G.client_bytes_read.last_second_end = G.host->bytes_received; - G.client_bytes_sent.last_second_end = G.host->bytes_sent; - if (G.real_time_ns - G.last_second_reset_ns > NS_FROM_SECONDS(1)) { - G.last_second_reset_ns = G.real_time_ns; - G.client_bytes_read.last_second = G.client_bytes_read.last_second_end - G.client_bytes_read.last_second_start; - G.client_bytes_sent.last_second = G.client_bytes_sent.last_second_end - G.client_bytes_sent.last_second_start; - G.client_bytes_read.last_second_start = G.client_bytes_read.last_second_end; - G.client_bytes_sent.last_second_start = G.client_bytes_sent.last_second_end; - } -#endif - /* ========================== * * Render * ========================== */ @@ -1703,9 +1696,6 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_thread_entry_point, arg) * Local sim thread * ========================== */ -#if 1 - - INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) { #if 0 @@ -1727,34 +1717,10 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) } else { is_master = true; } - - - - - - - - - - - - - - - - - - - - struct bitbuff msg_writer_bb = bitbuff_alloc(GIGABYTE(64)); struct bitbuff snapshot_writer_bb = bitbuff_alloc(GIGABYTE(64)); - - - - - + struct sim_accel accel = sim_accel_alloc(); struct sim_client_store *store = sim_client_store_alloc(); struct sim_client *local_client = sim_client_alloc(store, SIM_CLIENT_KIND_LOCAL_SIM); @@ -1770,7 +1736,7 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) struct temp_arena scratch = scratch_begin_no_conflict(); { __profscope(local_sim_sleep); - sleep_frame(last_tick_ns, target_dt_ns); + sleep_frame(last_tick_ns, compute_dt_ns); last_tick_ns = sys_time_ns(); } ++step_tick; @@ -1780,8 +1746,11 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) struct sim_snapshot *prev_user_cmd_ss = sim_snapshot_from_tick(user_client, user_client->last_tick); struct sim_snapshot *user_cmd_ss = sim_snapshot_alloc(user_client, prev_user_cmd_ss, step_tick); struct sys_lock lock = sys_mutex_lock_e(&G.user_sim_cmd_mutex); - user_cmd_ss->ack = G.user_sim_cmd_ack; + user_cmd_ss->producer_client = user_client->handle; + user_cmd_ss->producer_client_is_local = true; user_cmd_ss->control = G.user_sim_cmd_control; + user_client->ack = G.user_sim_cmd_ack; + user_client->reverse_ack = step_tick; ++G.user_sim_cmd_gen; sys_mutex_unlock(&lock); } @@ -1810,7 +1779,7 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) } } break; - case HOST_EVENT_KIND_CHANNEL_MSG: + case HOST_EVENT_KIND_MSG: { if (client->valid) { struct bitbuff msg_bb = bitbuff_from_string(event->msg); @@ -1869,6 +1838,8 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) } } } break; + + default: break; } } } @@ -1882,7 +1853,7 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) u64 ack = client->ack; u64 reverse_ack = client->reverse_ack; if (reverse_ack > 1) { - sim_client_release_snapshot_ticks_in_range(client, 0, reverse_ack - 1); + sim_snapshot_release_ticks_in_range(client, 0, reverse_ack - 1); } if (ack < oldest_client_ack || oldest_client_ack == 0) { oldest_client_ack = ack; @@ -1890,7 +1861,7 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) } } if (oldest_client_ack > 1) { - sim_client_release_snapshot_ticks_in_range(local_client, 0, oldest_client_ack - 1); + sim_snapshot_release_ticks_in_range(local_client, 0, oldest_client_ack - 1); } /* Pick client snapshot cmds to feed to sim step */ @@ -1916,13 +1887,22 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) if (is_master) { struct sim_snapshot *prev_step_ss = sim_snapshot_from_tick(local_client, step_tick - 1); step_ss = sim_snapshot_alloc(local_client, prev_step_ss, step_tick); + step_ss->is_master = true; } else { struct sim_snapshot *newest_master_ss = sim_snapshot_from_tick(master_client, master_client->last_tick); step_ss = sim_snapshot_alloc(local_client, newest_master_ss, step_tick); + step_ss->is_master = false; } /* Step */ - sim_step(step_ss, client_step_cmds, step_dt_ns); + { + struct sim_step_ctx step_ctx = ZI; + step_ctx.accel = &accel; + step_ctx.world = step_ss; + step_ctx.cmd_snapshots = &client_step_cmds; + step_ctx.real_dt_ns = step_dt_ns; + sim_step(&step_ctx); + } /* Publish snapshot to clients */ for (u64 i = 0; i < store->num_clients_reserved; ++i) { @@ -1959,13 +1939,13 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) case SIM_CLIENT_KIND_MASTER_SIM: { - u64 ack = client->last_tick; + u64 base_tick = client->last_tick; struct sim_snapshot *base_ss = sim_snapshot_from_tick(client, base_tick); if (base_ss->tick == base_tick) { u64 ack = client->last_tick; u64 reverse_ack = client->ack; - bw_write_uv(&bw, ack); - bw_write_uv(&bw, reverse_ack); + bw_write_uv(&msg_bw, ack); + bw_write_uv(&msg_bw, reverse_ack); /* Write all user cmd snapshots since last client ack */ struct sim_snapshot *send_ss = sim_snapshot_from_tick(user_client, base_ss->tick + 1); while (send_ss->valid) { @@ -1973,6 +1953,11 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) bw_write_uv(&snapshot_bw, base_ss->tick); bw_write_uv(&snapshot_bw, step_ss->tick); sim_snapshot_encode(&snapshot_bw, client, base_ss, step_ss); + + struct string encoded_tmp = ZI; + encoded_tmp.len = bw_num_bytes_written(&snapshot_bw); + encoded_tmp.text = bw_get_written_raw(&snapshot_bw); + bw_write_uv(&msg_bw, encoded_tmp.len); bw_write_bytes(&msg_bw, encoded_tmp); } @@ -1986,12 +1971,14 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) case SIM_CLIENT_KIND_USER: { /* TODO: Double buffer */ - struct sys_lock lock = sys_mutex_lock_e(&G.local_to_user_snapshot_mutex); - struct sim_snapshot *pub_ss = sim_snapshot_alloc(G.local_to_user_snapshot_client, step_ss, step_ss->tick); + struct sys_lock lock = sys_mutex_lock_e(&G.local_sim_to_user_mutex); + struct sim_snapshot *pub_ss = sim_snapshot_alloc(G.local_sim_to_user_client, step_ss, step_ss->tick); + pub_ss->local_client = client->handle; i64 publish_ns = sys_time_ns(); pub_ss->publish_dt_ns = publish_ns - last_publish_ns; pub_ss->publish_time_ns = publish_ns; last_publish_ns = publish_ns; + sim_snapshot_release_ticks_in_range(G.local_sim_to_user_client, 0, step_ss->tick - 1); sys_mutex_unlock(&lock); } break; @@ -2005,316 +1992,9 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) scratch_end(scratch); } - - - - - - - - - - sim_snapshot_store_release(store); + sim_client_store_release(store); + sim_accel_release(&accel); bitbuff_release(&snapshot_writer_bb); bitbuff_release(&msg_writer_bb); host_release(host); } - -#else - - -INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg) -{ -#if 0 - struct host_listen_address local_listen_addr = host_listen_address_from_local_name(LIT("LOCAL_SIM")); - struct host_listen_address net_listen_addr = host_listen_address_from_net_port(12345); - //struct host *host = host_alloc(); - /* TODO: Host system should allocate & copy string stored in local_listen_addr */ - //host_listen(host, local_listen_addr); - //host_listen(host, net_listen_addr); -#else - struct host *host = host_alloc(12345); -#endif - (UNUSED)arg; - - b32 is_master = false; - if (G.connect_address_str.len > 0) { - struct sock_address addr = sock_address_from_string(G.connect_address_str); - host_queue_connect_to_address(host, addr); - } else { - is_master = true; - } - - - - - - - - - - - - - - - - - - - - - - struct bitbuff encoder_bitbuff = bitbuff_alloc(GIGABYTE(64)); - - struct sim_snapshot_store *local_snapshot_store = sim_snapshot_store_alloc(); - struct sim_snapshot_store *remote_snapshot_store = sim_snapshot_store_alloc(); - u64 oldest_needed_remote_tick = 0; - u64 remote_ack = 0; - (UNUSED)remote_ack; - - struct sim_snapshot *local_ss_prev = sim_snapshot_nil(); - - - i64 last_publish_ns = 0; - 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(); - } - - /* Release old snapshots */ - /* TODO: Remove this */ - { - u64 keep_count = 100; - u64 keep_tick = local_ss_prev->tick > keep_count ? local_ss_prev->tick - keep_count : 0; - if (keep_tick > 0) { - sim_snapshot_store_release_ticks_in_range(local_snapshot_store, 0, keep_tick); - } - } - - /* Retrieve cmds */ - struct sim_cmd_frame_list input_cmds = ZI; - struct sim_cmd_frame *user_cmd_frame; - { - /* Grab snapshots 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, &input_cmds); - } - - /* Generate user sim cmd from user thread */ - { - 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 = local_ss_prev->tick + 1; - user_cmd_frame->ack = G.user_sim_cmd_ack; - user_cmd_frame->sender_is_local = true; - - 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_frame->first = user_cmd; - user_cmd_frame->last = user_cmd; - ++G.user_sim_cmd_gen; - 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; - } - - - for (struct sim_cmd_frame *frame = input_cmds.first; frame; frame = frame->next) { - /* FIXME: Verify cmd is from master */ - if (!is_master && !frame->sender_is_local) { - struct sim_cmd *snapshot_cmd = NULL; - - /* Grab newest snapshot cmd */ - for (struct sim_cmd *cmd = frame->first; cmd; cmd = cmd->next) { - if (cmd->kind == SIM_CMD_KIND_SNAPSHOT && (!snapshot_cmd || cmd->snapshot_tick_end > snapshot_cmd->snapshot_tick_end)) { - snapshot_cmd = cmd; - } - } - - /* Decode newest snapshot cmd */ - if (snapshot_cmd && snapshot_cmd->snapshot_tick_end > remote_snapshot_store->last_tick) { - remote_ack = frame->ack; - - u64 ss0_tick = snapshot_cmd->snapshot_tick_start; - u64 ss1_tick = snapshot_cmd->snapshot_tick_end; - struct sim_snapshot *ss0 = sim_snapshot_from_tick(remote_snapshot_store, ss0_tick); - struct sim_snapshot *ss1 = sim_snapshot_from_tick(remote_snapshot_store, ss1_tick); - if (ss0->tick == ss0_tick) { - if (!ss1->valid) { - /* Decode remote snapshot */ - ss1 = sim_snapshot_alloc(remote_snapshot_store, ss0, ss1_tick); - struct bitbuff bb = bitbuff_from_string(snapshot_cmd->snapshot_encoded); - struct bitbuff_reader br = br_from_bitbuff(&bb); - sim_snapshot_decode(&br, ss1); - - if (ss0->tick > oldest_needed_remote_tick) { - oldest_needed_remote_tick = ss0->tick; - } - - /* Set master client channel id */ - for (u64 i = 0; i < ss1->num_clients_reserved; ++i) { - struct sim_client *client = &ss1->clients[i]; - if (client->valid && client->kind == SIM_CLIENT_KIND_SIM_MASTER) { - sim_client_set_channel_id(client, frame->sender_channel); - } - } - - local_ss_prev = sim_snapshot_alloc(local_snapshot_store, ss1, ss1_tick + 20); - local_ss_prev->is_master = is_master; - } - } else { - /* User should always have src tick present */ - ASSERT(false); - } - } - } - } - - /* Release old remote ticks */ - if (oldest_needed_remote_tick > 0) { - sim_snapshot_store_release_ticks_in_range(remote_snapshot_store, 0, oldest_needed_remote_tick - 1); - } - - /* Step */ - struct sim_snapshot *local_ss = sim_step(local_snapshot_store, local_ss_prev, input_cmds, target_dt_ns); - local_ss->is_master = is_master; - - /* Publish snapshot to user */ - /* TODO: Double buffer */ - { - struct sys_lock lock = sys_mutex_lock_e(&G.local_sim_ss_mutex); - struct sim_snapshot *pub_ss = sim_snapshot_alloc(G.local_sim_ss_store, local_ss, local_ss->tick); - sim_snapshot_store_release_ticks_in_range(G.local_sim_ss_store, 0, pub_ss->tick - 1); - i64 publish_ns = sys_time_ns(); - pub_ss->publish_dt_ns = publish_ns - last_publish_ns; - pub_ss->publish_time_ns = publish_ns; - last_publish_ns = publish_ns; - sys_mutex_unlock(&lock); - } - - if (local_ss->is_master) { - /* Publish snapshot cmds to slave clients */ - u64 oldest_ack_tick = 0; - for (u64 i = 0; i < local_ss->num_clients_reserved; ++i) { - struct sim_client *client = &local_ss->clients[i]; - if (client->valid && client->kind == SIM_CLIENT_KIND_SIM_SLAVE) { - struct temp_arena temp = arena_temp_begin(scratch.arena); - - if (oldest_ack_tick == 0 || client->ack < oldest_ack_tick) { - oldest_ack_tick = client->ack; - } - - struct sim_snapshot *ss0 = sim_snapshot_from_tick(local_snapshot_store, client->ack); - struct sim_snapshot *ss1 = local_ss; - - /* Create & encode snapshot cmd */ - struct sim_cmd snapshot_cmd = ZI; - { - snapshot_cmd.kind = SIM_CMD_KIND_SNAPSHOT; - snapshot_cmd.snapshot_tick_start = ss0->tick; - snapshot_cmd.snapshot_tick_end = ss1->tick; - { - struct bitbuff_writer bw = bw_from_bitbuff(&encoder_bitbuff); - sim_snapshot_encode(&bw, client, ss0, ss1); - snapshot_cmd.snapshot_encoded = bw_get_written(temp.arena, &bw); - } - } - - struct sim_cmd_frame snapshot_cmd_frame = ZI; - snapshot_cmd_frame.tick = ss1->tick; - snapshot_cmd_frame.ack = client->ack; - snapshot_cmd_frame.first = &snapshot_cmd; - snapshot_cmd_frame.last = &snapshot_cmd; - - struct sim_cmd_frame_list cmd_frames = ZI; - cmd_frames.first = &snapshot_cmd_frame; - cmd_frames.last = &snapshot_cmd_frame; - - /* Encode cmds */ - struct string cmds_msg = ZI; - { - struct bitbuff_writer bw = bw_from_bitbuff(&encoder_bitbuff); - sim_cmd_frames_encode(&bw, cmd_frames); - cmds_msg = bw_get_written(temp.arena, &bw); - } - - host_queue_write(host, client->channel_id, cmds_msg, 0); - - arena_temp_end(temp); - } - } - } else { - /* Publish user cmd to master client */ - for (u64 i = 0; i < local_ss->num_clients_reserved; ++i) { - struct sim_client *client = &local_ss->clients[i]; - if (client->valid && client->kind == SIM_CLIENT_KIND_SIM_MASTER) { - struct temp_arena temp = arena_temp_begin(scratch.arena); - - user_cmd_frame->ack = remote_snapshot_store->last_tick; - user_cmd_frame->sender_is_local = false; - - struct sim_cmd_frame_list l = ZI; - l.first = user_cmd_frame; - l.last = user_cmd_frame; - - /* Encode cmds */ - struct string cmds_msg = ZI; - { - struct bitbuff_writer bw = bw_from_bitbuff(&encoder_bitbuff); - /* FIXME: Ack tick */ - sim_cmd_frames_encode(&bw, l); - cmds_msg = bw_get_written(temp.arena, &bw); - } - - host_queue_write(host, client->channel_id, cmds_msg, 0); - - arena_temp_end(temp); - } - } - } - - /* Send host messages */ - host_update(host); - __profframe("Local sim"); - - { - /* Update network usage stats */ - i64 stat_now_ns = sys_time_ns(); - G.client_bytes_read.last_second_end = host->bytes_received; - G.client_bytes_sent.last_second_end = host->bytes_sent; - if (stat_now_ns - G.last_second_reset_ns > NS_FROM_SECONDS(1)) { - G.last_second_reset_ns = stat_now_ns; - G.client_bytes_read.last_second = G.client_bytes_read.last_second_end - G.client_bytes_read.last_second_start; - G.client_bytes_sent.last_second = G.client_bytes_sent.last_second_end - G.client_bytes_sent.last_second_start; - G.client_bytes_read.last_second_start = G.client_bytes_read.last_second_end; - G.client_bytes_sent.last_second_start = G.client_bytes_sent.last_second_end; - } - } - - scratch_end(scratch); - - if (local_ss->is_master) { - local_ss_prev = local_ss; - } - } - - sim_snapshot_store_release(local_snapshot_store); - bitbuff_release(&encoder_bitbuff); - host_release(host); -} - -#endif diff --git a/src/user.h b/src/user.h index f700e12f..78e248fe 100644 --- a/src/user.h +++ b/src/user.h @@ -12,7 +12,7 @@ struct sound_startup_receipt; struct mixer_startup_receipt; struct phys_startup_receipt; struct host_startup_receipt; -struct sim_snapshot_startup_receipt; +struct sim_startup_receipt; enum user_bind_kind { USER_BIND_KIND_NONE, @@ -61,7 +61,7 @@ struct user_startup_receipt user_startup(struct work_startup_receipt *work_sr, struct mixer_startup_receipt *mixer_sr, struct phys_startup_receipt *phys_sr, struct host_startup_receipt *host_sr, - struct sim_snapshot_startup_receipt *sim_snapshot_sr, + struct sim_startup_receipt *sim_sr, struct string connect_address_str, struct sys_window *window);