re-add lerping via snapshot interpolation

This commit is contained in:
jacob 2025-02-10 15:22:19 -06:00
parent cd38ab4fbf
commit f04a81fbb0
17 changed files with 906 additions and 459 deletions

BIN
res/graphics/box.ase (Stored with Git LFS)

Binary file not shown.

View File

@ -6,6 +6,7 @@
#include "work.h"
#include "user.h"
#include "sim.h"
#include "sim_snapshot.h"
#include "playback.h"
#include "log.h"
#include "resource.h"
@ -224,7 +225,10 @@ 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 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, args_str, &window);
struct sim_ent_startup_receipt sim_ent_sr = sim_ent_startup();
struct sim_client_startup_receipt sim_client_sr = sim_client_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_ent_sr, &sim_client_sr, &sim_snapshot_sr, args_str, &window);
struct playback_startup_receipt playback_sr = playback_startup(&mixer_sr);
(UNUSED)user_sr;

View File

@ -33,7 +33,7 @@
#define SPACE_CELL_BUCKETS_SQRT (256)
#define SPACE_CELL_SIZE 1.0f
#define SIM_FPS 50.0
#define SIM_TICKS_PER_SECOND 100.0
#define SIM_TIMESCALE 1
#define SIM_PHYSICS_SUBSTEPS 4
@ -51,10 +51,10 @@
#define SIM_MAX_ANGULAR_VELOCITY F32_INFINITY
/* How many ticks back in time should the user blend between?
* <Delay ms> = <USER_INTERP_OFFSET_TICK_RATIO> * <Sim tick rate>
* <Delay ms> = <USER_INTERP_RATIO> * <Tick interval>
* E.g: At 1.5, the user thread will render 75ms back in time (if sim thread runs at 50FPS)
*/
#define USER_INTERP_OFFSET_TICK_RATIO 1.1
#define USER_INTERP_RATIO 1.1
#define USER_INTERP_ENABLED 1
#define COLLIDER_DEBUG 0

View File

@ -18,7 +18,7 @@ GLOBAL struct {
struct phys_startup_receipt phys_startup(void)
{
/* Initialize constants */
const f32 substep_dt = (1.f / ((f32)SIM_FPS * (f32)SIM_PHYSICS_SUBSTEPS));
const f32 substep_dt = (1.f / ((f32)SIM_TICKS_PER_SECOND * (f32)SIM_PHYSICS_SUBSTEPS));
const f32 contact_frequency = (1.f / substep_dt) / 8.f;
const f32 contact_damping_ratio = 10;
@ -48,7 +48,7 @@ struct phys_collision_data_array phys_create_and_update_contacts(struct arena *a
struct sim_ent_lookup *debug_lookup = ctx->debug_lookup;
struct space *space = ctx->space;
struct sim_ent_store *store = ctx->store;
struct sim_ent *root = sim_ent_from_handle(store, store->root);
struct sim_ent *root = sim_ent_from_handle(store, SIM_ENT_ROOT_HANDLE);
for (u64 check0_index = 0; check0_index < store->num_reserved; ++check0_index) {
struct sim_ent *check0 = &store->entities[check0_index];

117
src/sim.c
View File

@ -26,6 +26,9 @@
struct sim_ctx *sim_ctx_alloc(struct sprite_startup_receipt *sprite_sr,
struct phys_startup_receipt *phys_sr,
struct host_startup_receipt *host_sr,
struct sim_ent_startup_receipt *sim_ent_sr,
struct sim_client_startup_receipt *sim_client_sr,
struct sim_snapshot_startup_receipt *sim_snapshot_sr,
u16 host_port)
{
struct arena arena = arena_alloc(GIGABYTE(64));
@ -35,6 +38,9 @@ struct sim_ctx *sim_ctx_alloc(struct sprite_startup_receipt *sprite_sr,
(UNUSED)sprite_sr;
(UNUSED)phys_sr;
(UNUSED)host_sr;
(UNUSED)sim_ent_sr;
(UNUSED)sim_client_sr;
(UNUSED)sim_snapshot_sr;
/* Intialize host */
ctx->host = host_alloc(host_port);
@ -46,9 +52,9 @@ struct sim_ctx *sim_ctx_alloc(struct sprite_startup_receipt *sprite_sr,
#endif
ctx->space = space_alloc(SPACE_CELL_SIZE, SPACE_CELL_BUCKETS_SQRT);
/* Create emtpy snapshot */
sim_snapshot_alloc(&ctx->world);
ctx->world.world_timescale = SIM_TIMESCALE;
/* Create snapshot store */
ctx->snapshot_store = sim_snapshot_store_alloc();
ctx->world = sim_snapshot_nil();
return ctx;
}
@ -57,8 +63,8 @@ void sim_ctx_release(struct sim_ctx *ctx)
{
__prof;
/* Release snapshot */
sim_snapshot_release(&ctx->world);
/* Release snapshot store */
sim_snapshot_store_release(ctx->snapshot_store);
/* Release bookkeeping */
space_release(ctx->space);
@ -79,7 +85,7 @@ void sim_ctx_release(struct sim_ctx *ctx)
INTERNAL void spawn_test_entities(struct sim_ctx *ctx)
{
struct sim_ent *root = sim_ent_from_handle(ctx->world.ent_store, ctx->world.ent_store->root);
struct sim_ent *root = sim_ent_from_handle(ctx->world->ent_store, SIM_ENT_ROOT_HANDLE);
root->mass_unscaled = F32_INFINITY;
root->inertia_unscaled = F32_INFINITY;
@ -104,9 +110,8 @@ INTERNAL void spawn_test_entities(struct sim_ctx *ctx)
e->angular_ground_friction = 200;
}
/* Big box */
#if 0
#if 1
{
struct sim_ent *e = sim_ent_alloc(root);
@ -122,9 +127,9 @@ INTERNAL void spawn_test_entities(struct sim_ctx *ctx)
sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC);
e->mass_unscaled = 100;
e->inertia_unscaled = 10;
e->inertia_unscaled = 50;
e->linear_ground_friction = 100;
e->angular_ground_friction = 5;
e->angular_ground_friction = 50;
}
#endif
@ -153,8 +158,8 @@ INTERNAL void spawn_test_entities(struct sim_ctx *ctx)
INTERNAL struct sim_ent *spawn_test_player(struct sim_ctx *ctx)
{
struct sim_ent_store *store = ctx->world.ent_store;
struct sim_ent *root = sim_ent_from_handle(store, store->root);
struct sim_ent_store *store = ctx->world->ent_store;
struct sim_ent *root = sim_ent_from_handle(store, SIM_ENT_ROOT_HANDLE);
/* Player */
struct sim_ent *player_ent = sim_ent_nil();
@ -219,8 +224,8 @@ INTERNAL struct sim_ent *spawn_test_player(struct sim_ctx *ctx)
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;
e->trigger_delay = 1.0f / 10.0f;
//e->trigger_delay = 1.0f / 100.0f;
player_ent->equipped = e->handle;
}
@ -230,8 +235,8 @@ INTERNAL struct sim_ent *spawn_test_player(struct sim_ctx *ctx)
INTERNAL struct sim_ent *spawn_test_player_camera(struct sim_ctx *ctx, struct sim_ent *player_ent)
{
struct sim_ent_store *store = ctx->world.ent_store;
struct sim_ent *root = sim_ent_from_handle(store, store->root);
struct sim_ent_store *store = ctx->world->ent_store;
struct sim_ent *root = sim_ent_from_handle(store, SIM_ENT_ROOT_HANDLE);
struct sim_ent *camera_ent = sim_ent_nil();
if (player_ent->valid) {
@ -257,7 +262,7 @@ INTERNAL struct sim_ent *spawn_test_player_camera(struct sim_ctx *ctx, struct si
INTERNAL void release_entities_with_prop(struct sim_ctx *ctx, enum sim_ent_prop prop)
{
struct temp_arena scratch = scratch_begin_no_conflict();
struct sim_ent_store *store = ctx->world.ent_store;
struct sim_ent_store *store = ctx->world->ent_store;
struct space *space = ctx->space;
struct sim_ent **ents_to_release = arena_dry_push(scratch.arena, struct sim_ent *);
@ -304,8 +309,8 @@ INTERNAL void release_entities_with_prop(struct sim_ctx *ctx, enum sim_ent_prop
INTERNAL PHYS_COLLISION_CALLBACK_FUNC_DEF(on_collision, collision_data_array, udata)
{
struct sim_ctx *ctx = (struct sim_ctx *)udata;
struct sim_ent_store *store = ctx->world.ent_store;
struct sim_ent *root = sim_ent_from_handle(store, store->root);
struct sim_ent_store *store = ctx->world->ent_store;
struct sim_ent *root = sim_ent_from_handle(store, SIM_ENT_ROOT_HANDLE);
for (u64 i = 0; i < collision_data_array.count; ++i) {
struct phys_collision_data *data = &collision_data_array.a[i];
@ -405,31 +410,47 @@ void sim_update(struct sim_ctx *ctx, i64 target_dt_ns)
* Begin frame
* ========================== */
++ctx->world.tick;
struct sim_snapshot *prev_snapshot = ctx->world;
ctx->world = sim_snapshot_alloc(ctx->snapshot_store, prev_snapshot, prev_snapshot->tick + 1);
ctx->world.real_dt_ns = max_i64(0, target_dt_ns);
ctx->world.real_time_ns += ctx->world.real_dt_ns;
/* Release old snapshots */
{
/* TODO: Something better */
i64 release_tick = (i64)ctx->world->tick - 5; /* Arbitrary tick offset */
if (release_tick > 0) {
struct sim_snapshot *old = sim_snapshot_from_tick(ctx->snapshot_store, release_tick);
if (old) {
sim_snapshot_release(old);
}
}
}
ctx->world.world_dt_ns = max_i64(0, target_dt_ns * ctx->world.world_timescale);
ctx->world.world_time_ns += ctx->world.world_dt_ns;
f64 sim_time = SECONDS_FROM_NS(ctx->world.world_time_ns);
ctx->world->real_dt_ns = max_i64(0, target_dt_ns);
ctx->world->real_time_ns += ctx->world->real_dt_ns;
ctx->world->world_timescale = 1;
ctx->world->world_dt_ns = max_i64(0, target_dt_ns * ctx->world->world_timescale);
ctx->world->world_time_ns += ctx->world->world_dt_ns;
f64 sim_time = SECONDS_FROM_NS(ctx->world->world_time_ns);
ctx->sprite_frame_scope = sprite_scope_begin();
f64 real_dt = SECONDS_FROM_NS(ctx->world.real_dt_ns);
f64 real_time = SECONDS_FROM_NS(ctx->world.real_time_ns);
f64 world_dt = SECONDS_FROM_NS(ctx->world.world_dt_ns);
f64 world_time = SECONDS_FROM_NS(ctx->world.world_time_ns);
f64 real_dt = SECONDS_FROM_NS(ctx->world->real_dt_ns);
f64 real_time = SECONDS_FROM_NS(ctx->world->real_time_ns);
f64 world_dt = SECONDS_FROM_NS(ctx->world->world_dt_ns);
f64 world_time = SECONDS_FROM_NS(ctx->world->world_time_ns);
(UNUSED)real_dt;
(UNUSED)real_time;
(UNUSED)world_dt;
(UNUSED)world_time;
struct sim_ent *root = sim_ent_from_handle(ctx->world.ent_store, ctx->world.ent_store->root);;
struct sim_ent_store *ent_store = ctx->world.ent_store;
struct sim_ent_store *ent_store = ctx->world->ent_store;
struct space *space = ctx->space;
struct sprite_scope *sprite_frame_scope = ctx->sprite_frame_scope;
struct sim_ent *root = sim_ent_from_handle(ctx->world->ent_store, SIM_ENT_ROOT_HANDLE);
/* ========================== *
* Spawn test entities
* ========================== */
@ -460,8 +481,8 @@ void sim_update(struct sim_ctx *ctx, i64 target_dt_ns)
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_ACTIVE)) {
u64 atick = ent->activation_tick;
if (atick != 0 || ctx->world.tick >= atick) {
sim_ent_activate(ent, ctx->world.tick);
if (atick != 0 || ctx->world->tick >= atick) {
sim_ent_activate(ent, ctx->world->tick);
}
}
}
@ -498,9 +519,9 @@ void sim_update(struct sim_ctx *ctx, i64 target_dt_ns)
for (struct sim_cmd *cmd = sim_cmds.first; cmd; cmd = cmd->next) {
enum sim_cmd_kind kind = cmd->kind;
struct host_channel_id channel_id = cmd->channel_id;
struct sim_client *client = sim_client_from_channel_id(ctx->world.client_store, channel_id);
struct sim_client *client = sim_client_from_channel_id(ctx->world->client_store, channel_id);
if (!client->valid && kind == SIM_CMD_KIND_SIM_CLIENT_CONNECT && !host_channel_id_is_nil(channel_id)) {
client = sim_client_alloc(ctx->world.client_store, channel_id);
client = sim_client_alloc(ctx->world->client_store, channel_id);
/* TODO: Create player ent not here */
/* FIXME: Player ent never released upon disconnect */
struct sim_ent *player_ent = spawn_test_player(ctx);
@ -513,13 +534,13 @@ void sim_update(struct sim_ctx *ctx, i64 target_dt_ns)
}
/* Split cmds by client */
client_cmds = arena_push_array_zero(scratch.arena, struct sim_cmd_list, ctx->world.client_store->num_reserved);
client_cmds = arena_push_array_zero(scratch.arena, struct sim_cmd_list, ctx->world->client_store->num_reserved);
{
struct sim_cmd *cmd = sim_cmds.first;
while (cmd) {
struct sim_cmd *next = cmd->next;
struct host_channel_id channel_id = cmd->channel_id;
struct sim_client *client = sim_client_from_channel_id(ctx->world.client_store, channel_id);
struct sim_client *client = sim_client_from_channel_id(ctx->world->client_store, channel_id);
if (client->valid) {
struct sim_cmd_list *cmd_list = &client_cmds[client->handle.idx];
if (cmd_list->last) {
@ -540,8 +561,8 @@ void sim_update(struct sim_ctx *ctx, i64 target_dt_ns)
* ========================== */
/* Process client cmds */
for (u64 i = 0; i < ctx->world.client_store->num_reserved; ++i) {
struct sim_client *client = &ctx->world.client_store->clients[i];
for (u64 i = 0; i < ctx->world->client_store->num_reserved; ++i) {
struct sim_client *client = &ctx->world->client_store->clients[i];
if (client->valid) {
struct host_channel_id channel_id = client->channel_id;
struct sim_cmd_list cmds = client_cmds[client->handle.idx];
@ -633,7 +654,7 @@ void sim_update(struct sim_ctx *ctx, i64 target_dt_ns)
if (!sim_ent_is_valid_and_active(ent)) continue;
if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) {
struct sim_client *client = sim_client_from_handle(ctx->world.client_store, ent->controlling_client);
struct sim_client *client = sim_client_from_handle(ctx->world->client_store, ent->controlling_client);
if (client->valid) {
ent->control = client->control;
/* TODO: Move this */
@ -1054,8 +1075,8 @@ void sim_update(struct sim_ctx *ctx, i64 target_dt_ns)
* Create mouse joints from client debug drag
* ========================== */
for (u64 i = 0; i < ctx->world.client_store->num_reserved; ++i) {
struct sim_client *client = &ctx->world.client_store->clients[i];
for (u64 i = 0; i < ctx->world->client_store->num_reserved; ++i) {
struct sim_client *client = &ctx->world->client_store->clients[i];
if (client->valid) {
struct v2 cursor = client->cursor_pos;
b32 start_dragging = client->dbg_drag_start;
@ -1126,7 +1147,7 @@ void sim_update(struct sim_ctx *ctx, i64 target_dt_ns)
{
struct phys_ctx phys = ZI;
phys.tick = ctx->world.tick;
phys.tick = ctx->world->tick;
phys.store = ent_store;
phys.space = space;
phys.contact_lookup = &ctx->contact_lookup;
@ -1173,7 +1194,7 @@ void sim_update(struct sim_ctx *ctx, i64 target_dt_ns)
if (!sim_ent_is_valid_and_active(ent)) continue;
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_BULLET)) continue;
if (ent->activation_tick == ctx->world.tick) {
if (ent->activation_tick == ctx->world->tick) {
struct sim_ent *src = sim_ent_from_handle(ent_store, ent->bullet_src);
struct xform src_xf = sim_ent_get_xform(src);
@ -1329,16 +1350,16 @@ void sim_update(struct sim_ctx *ctx, i64 target_dt_ns)
* Publish tick
* ========================== */
for (u64 i = 0; i < ctx->world.client_store->num_reserved; ++i) {
struct sim_client *client = &ctx->world.client_store->clients[i];
for (u64 i = 0; i < ctx->world->client_store->num_reserved; ++i) {
struct sim_client *client = &ctx->world->client_store->clients[i];
if (client->valid) {
struct temp_arena temp = arena_temp_begin(scratch.arena);
/* TODO: Not like this */
struct sim_event snapshot_event = ZI;
snapshot_event.tick = ctx->world.tick;
snapshot_event.tick = ctx->world->tick;
snapshot_event.kind = SIM_EVENT_KIND_SNAPSHOT;
snapshot_event.snapshot_data = sim_snapshot_encode(temp.arena, client, &ctx->world);
snapshot_event.snapshot_data = sim_snapshot_encode(temp.arena, client, ctx->world);
struct sim_event_list l = ZI;
l.first = &snapshot_event;

View File

@ -7,6 +7,9 @@
struct sprite_startup_receipt;
struct phys_startup_receipt;
struct host_startup_receipt;
struct sim_ent_startup_receipt;
struct sim_client_startup_receipt;
struct sim_snapshot_startup_receipt;
/* Absolute layers */
#define SIM_LAYER_FLOOR_DECALS -300
@ -35,14 +38,18 @@ struct sim_ctx {
#endif
struct space *space;
/* Tick */
struct sim_snapshot world;
/* Snapshot */
struct sim_snapshot_store *snapshot_store;
struct sim_snapshot *world;
};
/* TODO: Get rid of startup receipts */
struct sim_ctx *sim_ctx_alloc(struct sprite_startup_receipt *sprite_sr,
struct phys_startup_receipt *phys_sr,
struct host_startup_receipt *host_sr,
struct sim_ent_startup_receipt *sim_ent_sr,
struct sim_client_startup_receipt *sim_client_sr,
struct sim_snapshot_startup_receipt *sim_snapshot_sr,
u16 host_port);
void sim_ctx_release(struct sim_ctx *ctx);

View File

@ -4,47 +4,105 @@
#include "util.h"
#include "arena.h"
/* FIXME: Default client pointers to nil */
#define CHANNEL_LOOKUP_BUCKETS 4096
/* Offset in bytes from start of store struct to start of clients array (assume adjacently allocated) */
#define STORE_CLIENTS_OFFSET (sizeof(struct sim_client_store) + (sizeof(struct sim_client_store) % alignof(struct sim_client)))
/* Accessed via client_nil() */
READONLY struct sim_client _g_sim_client_nil = { .valid = false };
READONLY struct sim_client_store _g_sim_client_store_nil = { .valid = false };
/* Offset in bytes from start of store struct to start of clients array in store arena */
#define STORE_CLIENTS_OFFSET (sizeof(struct sim_client_store) + (sizeof(struct sim_client_channel_lookup_bucket) * CHANNEL_LOOKUP_BUCKETS))
struct sim_client_channel_lookup_bucket {
struct sim_client *first;
struct sim_client *last;
struct sim_client_handle first;
struct sim_client_handle last;
};
/* ========================== *
* Startup
* ========================== */
GLOBAL struct {
struct sim_client *nil_client;
struct sim_client_store *nil_store;
} G = ZI, DEBUG_ALIAS(G, G_sim_client);
/* Accessed via sim_client_nil() */
READONLY struct sim_client **_g_sim_client_nil = &G.nil_client;
/* Accessed via sim_client_store_nil() */
READONLY struct sim_client_store **_g_sim_client_store_nil = &G.nil_store;
struct sim_client_startup_receipt sim_client_startup(void)
{
/* Setup nil store */
G.nil_store = sim_client_store_alloc(GIGABYTE(1));
/* Setup nil client */
G.nil_client = arena_push_zero(&G.nil_store->arena, struct sim_client);
G.nil_client->handle = SIM_CLIENT_NIL_HANDLE;
++G.nil_store->num_allocated;
++G.nil_store->num_reserved;
MEMZERO_STRUCT(G.nil_client);
G.nil_client->valid = false;;
G.nil_store->valid = false;
arena_set_readonly(&G.nil_store->arena);
return (struct sim_client_startup_receipt) { 0 };
}
/* ========================== *
* Store
* ========================== */
struct sim_client_store *sim_client_store_alloc(u64 arena_reserve)
{
struct arena arena = arena_alloc(arena_reserve);
u64 num_channel_lookup_buckets = CHANNEL_LOOKUP_BUCKETS;
struct sim_client_channel_lookup_bucket *channel_lookup_buckets = arena_push_array_zero(&arena, struct sim_client_channel_lookup_bucket, num_channel_lookup_buckets);
struct sim_client_store *store;
{
struct arena arena = arena_alloc(arena_reserve);
store = arena_push_zero(&arena, struct sim_client_store);
store->arena = arena;
}
store->valid = true;
struct sim_client_store *store = arena_push_zero(&arena, struct sim_client_store);
store->clients = arena_dry_push(&arena, struct sim_client);
store->num_channel_lookup_buckets = CHANNEL_LOOKUP_BUCKETS;
store->channel_lookup_buckets = arena_push_array_zero(&store->arena, struct sim_client_channel_lookup_bucket, store->num_channel_lookup_buckets);
store->clients = arena_dry_push(&store->arena, struct sim_client);
ASSERT((u8 *)store->clients - (u8 *)store == STORE_CLIENTS_OFFSET); /* Offset must be correct */
store->arena = arena;
store->num_channel_lookup_buckets = num_channel_lookup_buckets;
store->channel_lookup_buckets = channel_lookup_buckets;
return store;
}
void sim_client_store_copy(struct sim_client_store *dst, struct sim_client_store *src)
{
i64 commit_diff = (i64)src->arena.pos - (i64)dst->arena.pos;
if (commit_diff > 0) {
arena_push_array(&dst->arena, u8, commit_diff);
}
arena_pop_to(&dst->arena, src->arena.pos);
dst->num_channel_lookup_buckets = src->num_channel_lookup_buckets;
dst->channel_lookup_buckets = (struct sim_client_channel_lookup_bucket *)((u8 *)dst + ((u8 *)src->channel_lookup_buckets - (u8 *)src));
dst->first_free_client = src->first_free_client;
dst->clients = (struct sim_client *)((u8 *)dst + ((u8 *)src->clients - (u8 *)src));
dst->num_allocated = src->num_allocated;
dst->num_reserved = src->num_reserved;
MEMCPY(dst->channel_lookup_buckets, src->channel_lookup_buckets, sizeof(*src->channel_lookup_buckets) * src->num_channel_lookup_buckets);
MEMCPY(dst->clients, src->clients, sizeof(*src->clients) * src->num_reserved);
}
void sim_client_store_release(struct sim_client_store *store)
{
arena_release(&store->arena);
}
struct sim_client_store *client_store_from_client(struct sim_client *client)
/* ========================== *
* Client
* ========================== */
struct sim_client_store *sim_client_store_from_client(struct sim_client *client)
{
if (client->valid) {
u64 first_client_addr = (u64)(client - client->handle.idx);
@ -52,14 +110,10 @@ struct sim_client_store *client_store_from_client(struct sim_client *client)
ASSERT(client_store->clients == (struct sim_client *)first_client_addr);
return client_store;
} else {
return client_store_nil();
return sim_client_store_nil();
}
}
/* ========================== *
* Client
* ========================== */
INTERNAL u64 hash_from_channel_id(struct host_channel_id channel_id)
{
return hash_fnv64(HASH_FNV64_BASIS, STRING_FROM_STRUCT(&channel_id));
@ -73,16 +127,16 @@ struct sim_client *sim_client_from_handle(struct sim_client_store *store, struct
return client;
}
}
return client_nil();
return sim_client_nil();
}
struct sim_client *sim_client_from_channel_id(struct sim_client_store *store, struct host_channel_id channel_id)
{
struct sim_client *res = client_nil();
struct sim_client *res = sim_client_nil();
u64 channel_hash = hash_from_channel_id(channel_id);
u64 bucket_index = channel_hash % store->num_channel_lookup_buckets;
struct sim_client_channel_lookup_bucket *bucket = &store->channel_lookup_buckets[bucket_index];
for (struct sim_client *client = bucket->first; client; client = client->next_hash) {
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;
@ -93,10 +147,9 @@ struct sim_client *sim_client_from_channel_id(struct sim_client_store *store, st
struct sim_client *sim_client_alloc(struct sim_client_store *store, struct host_channel_id channel_id)
{
struct sim_client *client = NULL;
struct sim_client_handle handle = ZI;
if (store->first_free_client) {
client = store->first_free_client;
struct sim_client *client = sim_client_from_handle(store, store->first_free_client);
if (client->valid) {
store->first_free_client = client->next_free;
handle = client->handle;
++handle.gen;
@ -107,7 +160,7 @@ struct sim_client *sim_client_alloc(struct sim_client_store *store, struct host_
++store->num_reserved;
}
++store->num_allocated;
*client = _g_sim_client_nil;
*client = *sim_client_nil();
client->valid = true;
client->handle = handle;
@ -118,38 +171,55 @@ struct sim_client *sim_client_alloc(struct sim_client_store *store, struct host_
/* Insert into channel lookup */
u64 bucket_index = channel_hash % store->num_channel_lookup_buckets;
struct sim_client_channel_lookup_bucket *bucket = &store->channel_lookup_buckets[bucket_index];
if (bucket->last) {
bucket->last->next_hash = client;
client->prev_hash = bucket->last;
} else {
bucket->first = client;
{
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;
}
bucket->last = client;
return client;
}
void sim_client_release(struct sim_client *client)
{
struct sim_client_store *store = client_store_from_client(client);
struct sim_client_store *store = sim_client_store_from_client(client);
client->valid = false;
++client->handle.gen;
client->next_free = store->first_free_client;
store->first_free_client = client;
store->first_free_client = client->handle;
--store->num_allocated;
/* Remove from channel lookup */
u64 bucket_index = client->channel_hash % store->num_channel_lookup_buckets;
struct sim_client_channel_lookup_bucket *bucket = &store->channel_lookup_buckets[bucket_index];
struct sim_client *prev = client->prev_hash;
struct sim_client *next = client->next_hash;
if (prev) {
prev->next_hash = next;
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;
bucket->first = next->handle;
}
if (next) {
next->prev_hash = prev;
if (next->valid) {
next->prev_in_bucket = prev->handle;
} else {
bucket->last = prev;
bucket->last = prev->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);
}
}

View File

@ -3,6 +3,8 @@
#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_client {
@ -12,9 +14,9 @@ struct sim_client {
struct host_channel_id channel_id;
u64 channel_hash;
struct sim_client *next_free;
struct sim_client *next_hash;
struct sim_client *prev_hash;
struct sim_client_handle next_free;
struct sim_client_handle next_in_bucket;
struct sim_client_handle prev_in_bucket;
struct v2 cursor_pos;
struct sim_ent_handle camera_ent;
@ -34,30 +36,37 @@ struct sim_client_store {
struct sim_client_channel_lookup_bucket *channel_lookup_buckets;
u64 num_channel_lookup_buckets;
struct sim_client_handle first_free_client;
struct sim_client *clients;
struct sim_client *first_free_client;
u64 num_allocated;
u64 num_reserved;
};
INLINE struct sim_client *client_nil(void)
INLINE struct sim_client *sim_client_nil(void)
{
extern READONLY struct sim_client _g_sim_client_nil;
return &_g_sim_client_nil;
extern READONLY struct sim_client **_g_sim_client_nil;
return *_g_sim_client_nil;
}
INLINE struct sim_client_store *client_store_nil(void)
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;
extern READONLY struct sim_client_store **_g_sim_client_store_nil;
return *_g_sim_client_store_nil;
}
struct sim_client_startup_receipt { i32 _; };
struct sim_client_startup_receipt sim_client_startup(void);
struct sim_client_store *sim_client_store_alloc(u64 arena_reserve);
void sim_client_store_copy(struct sim_client_store *dst, struct sim_client_store *src);
void sim_client_store_release(struct sim_client_store *store);
struct sim_client_store *client_store_from_client(struct sim_client *client);
struct sim_client_store *sim_client_store_from_client(struct sim_client *client);
struct sim_client *sim_client_from_handle(struct sim_client_store *store, struct sim_client_handle handle);
struct sim_client *sim_client_from_channel_id(struct sim_client_store *store, struct host_channel_id channel_id);
struct sim_client *sim_client_alloc(struct sim_client_store *store, struct host_channel_id channel_id);
void sim_client_release(struct sim_client *client);
void sim_client_lerp(struct sim_client *c, struct sim_client *c0, struct sim_client *c1, f64 blend);
#endif

View File

@ -4,22 +4,48 @@
/* Offset in bytes from start of store struct to start of entities array (assume adjacently allocated) */
#define STORE_ENTITIES_OFFSET (sizeof(struct sim_ent_store) + (sizeof(struct sim_ent_store) % alignof(struct sim_ent)))
/* Accessed via sim_ent_store_nil() */
READONLY struct sim_ent_store _g_sim_ent_store_nil = { .valid = false };
/* ========================== *
* Global state
* ========================== */
GLOBAL struct {
struct sim_ent *nil_ent;
struct sim_ent_store *nil_store;
} G = ZI, DEBUG_ALIAS(G, G_sim_ent);
/* Accessed via sim_ent_nil() */
/* TODO: Allocate nil entity in nil store */
READONLY struct sim_ent _g_sim_ent_nil = {
.valid = false,
.local_xform = XFORM_IDENT_NOCAST,
.cached_global_xform = XFORM_IDENT_NOCAST,
.cached_global_xform_dirty = false,
.friction = 0.5f,
.mass_unscaled = 1,
.inertia_unscaled = 1,
.sprite_local_xform = XFORM_IDENT_NOCAST,
.sprite_tint = COLOR_WHITE
};
READONLY struct sim_ent **_g_sim_ent_nil = &G.nil_ent;
/* Accessed via sim_ent_store_nil() */
READONLY struct sim_ent_store **_g_sim_ent_store_nil = &G.nil_store;
struct sim_ent_startup_receipt sim_ent_startup(void)
{
/* Setup nil store */
G.nil_store = sim_ent_store_alloc(GIGABYTE(1), false);
/* Setup nil ent */
G.nil_ent = arena_push_zero(&G.nil_store->arena, struct sim_ent);
++G.nil_store->num_allocated;
++G.nil_store->num_reserved;
MEMZERO_STRUCT(G.nil_ent);
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;
G.nil_ent->valid = false;
G.nil_store->valid = false;
arena_set_readonly(&G.nil_store->arena);
return (struct sim_ent_startup_receipt) { 0 };
}
/* ========================== *
* Store allocation
@ -27,26 +53,49 @@ READONLY struct sim_ent _g_sim_ent_nil = {
INTERNAL struct sim_ent *sim_ent_alloc_internal(struct sim_ent_store *store);
INTERNAL void store_make_root(struct sim_ent_store *store)
struct sim_ent_store *sim_ent_store_alloc(u64 arena_reserve, b32 create_root)
{
struct sim_ent *root = sim_ent_alloc_internal(store);
root->is_root = true;
root->local_xform = XFORM_IDENT;
root->cached_global_xform = XFORM_IDENT;
root->cached_global_xform_dirty = false;
store->root = root->handle;
struct sim_ent_store *store;
{
struct arena arena = arena_alloc(arena_reserve);
store = arena_push_zero(&arena, struct sim_ent_store);
store->arena = arena;
}
store->valid = true;
store->entities = arena_dry_push(&store->arena, struct sim_ent);
ASSERT((u8 *)store->entities - (u8 *)store == STORE_ENTITIES_OFFSET); /* Offset must be correct */
/* Create root entity */
if (create_root) {
struct sim_ent *root = sim_ent_alloc_internal(store);
ASSERT(sim_ent_handle_eq(root->handle, SIM_ENT_ROOT_HANDLE));
root->is_root = true;
}
return store;
}
struct sim_ent_store *sim_ent_store_alloc(u64 arena_reserve)
void sim_ent_store_copy(struct sim_ent_store *dst, struct sim_ent_store *src)
{
struct arena arena = arena_alloc(arena_reserve);
struct sim_ent_store *store = arena_push_zero(&arena, struct sim_ent_store);
store->valid = true;
store->arena = arena;
store->entities = arena_dry_push(&arena, struct sim_ent);
ASSERT((u8 *)store->entities - (u8 *)store == STORE_ENTITIES_OFFSET); /* Offset must be correct */
store_make_root(store);
return store;
i64 commit_diff = (i64)src->arena.pos - (i64)dst->arena.pos;
if (commit_diff > 0) {
arena_push_array(&dst->arena, u8, commit_diff);
}
arena_pop_to(&dst->arena, src->arena.pos);
dst->first_free = src->first_free;
dst->entities = (struct sim_ent *)((u8 *)dst + ((u8 *)src->entities - (u8 *)src));
dst->num_allocated = src->num_allocated;
dst->num_reserved = src->num_reserved;
MEMCPY(dst->entities, src->entities, sizeof(*src->entities) * src->num_reserved);
/* Re-initialize root in case of copy from nil store */
struct sim_ent *root = &dst->entities[0];
root->handle = SIM_ENT_ROOT_HANDLE;
root->valid = true;
root->is_root = true;
}
void sim_ent_store_release(struct sim_ent_store *store)
@ -54,16 +103,6 @@ void sim_ent_store_release(struct sim_ent_store *store)
arena_release(&store->arena);
}
void sim_ent_store_copy_replace(struct sim_ent_store *dest, struct sim_ent_store *src)
{
struct arena dest_arena = dest->arena;
struct sim_ent *dest_entities = dest->entities;
MEMCPY_STRUCT(dest, src);
arena_copy_replace(&dest_arena, &src->arena);
dest->arena = dest_arena;
dest->entities = dest_entities;
}
/* ========================== *
* Entity allocation
* ========================== */
@ -83,7 +122,7 @@ INTERNAL struct sim_ent *sim_ent_alloc_internal(struct sim_ent_store *store)
entity = arena_push(&store->arena, struct sim_ent);
handle = (struct sim_ent_handle) { .gen = 1, .idx = store->num_reserved++ };
}
*entity = _g_sim_ent_nil;
*entity = *sim_ent_nil();
entity->valid = true;
entity->handle = handle;
entity->cached_global_xform_dirty = true;
@ -506,3 +545,43 @@ void sim_ent_activate(struct sim_ent *ent, u64 current_tick)
ent->activation_tick = current_tick;
++ent->continuity_gen;
}
/* ========================== *
* Lerp
* ========================== */
void sim_ent_lerp(struct sim_ent *e, struct sim_ent *e0, struct sim_ent *e1, f64 blend)
{
if (sim_ent_is_valid_and_active(e0) && sim_ent_is_valid_and_active(e1)
&& e0->handle.gen == e1->handle.gen
&& e0->continuity_gen == e1->continuity_gen) {
e->local_xform = xform_lerp(e0->local_xform, e1->local_xform, blend);
if (e->is_top) {
/* TODO: Cache parent & child xforms in sim */
struct xform e0_xf = sim_ent_get_xform(e0);
struct xform e1_xf = sim_ent_get_xform(e1);
sim_ent_set_xform(e, xform_lerp(e0_xf, e1_xf, blend));
}
e->control_force = math_lerp_f32(e0->control_force, e1->control_force, blend);
e->control_torque = math_lerp_f32(e0->control_torque, e1->control_torque, blend);
e->linear_velocity = v2_lerp(e0->linear_velocity, e1->linear_velocity, blend);
e->angular_velocity = math_lerp_angle(e0->angular_velocity, e1->angular_velocity, blend);
e->control.move = v2_lerp(e0->control.move, e1->control.move, blend);
e->control.focus = v2_lerp(e0->control.focus, e1->control.focus, blend);
e->sprite_local_xform = xform_lerp(e0->sprite_local_xform, e1->sprite_local_xform, blend);
e->animation_time_in_frame = math_lerp_f64(e0->animation_time_in_frame, e1->animation_time_in_frame, (f64)blend);
e->animation_frame = (u32)math_round_to_int(math_lerp_f32(e0->animation_frame, e1->animation_frame, blend));
e->camera_quad_xform = xform_lerp(e0->camera_quad_xform, e1->camera_quad_xform, blend);
e->camera_xform_target = xform_lerp(e0->camera_xform_target, e1->camera_xform_target, blend);
e->shake = math_lerp_f32(e0->shake, e1->shake, blend);
e->tracer_gradient_start = v2_lerp(e0->tracer_gradient_start, e1->tracer_gradient_start, blend);
e->tracer_gradient_end = v2_lerp(e0->tracer_gradient_end, e1->tracer_gradient_end, blend);
}
}

View File

@ -5,6 +5,9 @@
#include "mixer.h"
#include "phys.h"
#define SIM_ENT_NIL_HANDLE ((struct sim_ent_handle){ .gen = 0, .idx = 0 })
#define SIM_ENT_ROOT_HANDLE ((struct sim_ent_handle){ .gen = 1, .idx = 0 })
enum sim_ent_prop {
SIM_ENT_PROP_NONE,
@ -47,16 +50,6 @@ enum sim_ent_prop {
SIM_ENT_PROP_COUNT
};
struct sim_ent_store {
b32 valid;
struct arena arena;
u64 num_allocated;
u64 num_reserved;
struct sim_ent_handle first_free;
struct sim_ent_handle root;
struct sim_ent *entities;
};
struct sim_ent_lookup_key {
u64 hash;
};
@ -302,6 +295,16 @@ struct sim_ent {
f32 shake;
};
struct sim_ent_store {
b32 valid;
struct arena arena;
struct sim_ent_handle first_free;
struct sim_ent *entities;
u64 num_allocated;
u64 num_reserved;
};
struct sim_ent_array {
struct sim_ent *entities;
u64 count;
@ -312,6 +315,13 @@ struct sim_ent_prop_array {
u64 count;
};
/* ========================== *
* Startup
* ========================== */
struct sim_ent_startup_receipt { i32 _; };
struct sim_ent_startup_receipt sim_ent_startup(void);
/* ========================== *
* Handle helpers
* ========================== */
@ -327,14 +337,14 @@ INLINE b32 sim_ent_handle_eq(struct sim_ent_handle a, struct sim_ent_handle b)
INLINE struct sim_ent *sim_ent_nil(void)
{
extern READONLY struct sim_ent _g_sim_ent_nil;
return &_g_sim_ent_nil;
extern READONLY struct sim_ent **_g_sim_ent_nil;
return *_g_sim_ent_nil;
}
INLINE struct sim_ent_store *sim_ent_store_nil(void)
{
extern READONLY struct sim_ent_store _g_sim_ent_store_nil;
return &_g_sim_ent_store_nil;
extern READONLY struct sim_ent_store **_g_sim_ent_store_nil;
return *_g_sim_ent_store_nil;
}
/* ========================== *
@ -372,9 +382,9 @@ INLINE b32 sim_ent_is_valid_and_active(struct sim_ent *ent)
* ========================== */
/* Entity store */
struct sim_ent_store *sim_ent_store_alloc(u64 arena_reserve);
struct sim_ent_store *sim_ent_store_alloc(u64 arena_reserve, b32 create_root);
void sim_ent_store_copy(struct sim_ent_store *dst, struct sim_ent_store *src);
void sim_ent_store_release(struct sim_ent_store *store);
void sim_ent_store_copy_replace(struct sim_ent_store *dest, struct sim_ent_store *src);
/* Entity */
struct sim_ent *sim_ent_alloc(struct sim_ent *parent);
@ -416,4 +426,7 @@ struct sim_ent_lookup_key sim_ent_lookup_key_from_two_handles(struct sim_ent_han
/* 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);
#endif

View File

@ -218,7 +218,6 @@ struct string sim_snapshot_encode(struct arena *arena, struct sim_client *client
struct byte_writer bw = bw_from_arena(arena);
bw_write_var_uint(&bw, snapshot->continuity_gen);
bw_write_var_sint(&bw, snapshot->publishtime_ns);
bw_write_var_sint(&bw, snapshot->real_dt_ns);
bw_write_var_sint(&bw, snapshot->real_time_ns);
@ -251,14 +250,12 @@ struct string sim_snapshot_encode(struct arena *arena, struct sim_client *client
return bw_get_written(&bw);
}
void sim_snapshot_decode(struct string str, struct sim_snapshot *snapshot, u64 tick)
void sim_snapshot_decode(struct string str, struct sim_snapshot *snapshot)
{
__prof;
struct byte_reader br = br_from_buffer(str);
snapshot->tick = tick;
snapshot->continuity_gen = br_read_var_uint(&br);
snapshot->publishtime_ns = br_read_var_sint(&br);
snapshot->real_dt_ns = br_read_var_sint(&br);
snapshot->real_time_ns = br_read_var_sint(&br);

View File

@ -108,6 +108,6 @@ void sim_events_from_host_events(struct arena *arena, struct host_event_array ho
* ========================== */
struct string sim_snapshot_encode(struct arena *arena, struct sim_client *client, struct sim_snapshot *snapshot);
void sim_snapshot_decode(struct string str, struct sim_snapshot *snapshot, u64 tick);
void sim_snapshot_decode(struct string str, struct sim_snapshot *snapshot);
#endif

View File

@ -4,15 +4,316 @@
#include "arena.h"
#include "scratch.h"
void sim_snapshot_alloc(struct sim_snapshot *sim_snapshot)
#define TICK_LOOKUP_BUCKETS 31
INTERNAL void sim_snapshot_release_internal(struct sim_snapshot *snapshot);
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;
} G = ZI, DEBUG_ALIAS(G, G_sim_snapshot);
/* Accessed via `sim_snapshot_nil()` */
READONLY struct sim_snapshot **_g_sim_snapshot_nil = &G.nil_snapshot;
/* Accessed via `sim_snapshot_store_nil()` */
READONLY struct sim_snapshot_store **_g_sim_snapshot_store_nil = &G.nil_snapshot_store;
struct sim_snapshot_startup_receipt sim_snapshot_startup(void)
{
MEMZERO_STRUCT(sim_snapshot);
sim_snapshot->client_store = sim_client_store_alloc(GIGABYTE(64));
sim_snapshot->ent_store = sim_ent_store_alloc(GIGABYTE(64));
G.nil_arena = arena_alloc(GIGABYTE(1));
G.nil_snapshot_store = arena_push_zero(&G.nil_arena, struct sim_snapshot_store);
G.nil_snapshot_store->valid = false;
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();
G.nil_snapshot->client_store = sim_client_store_nil();
G.nil_snapshot->ent_store = sim_ent_store_nil();
arena_set_readonly(&G.nil_arena);
return (struct sim_snapshot_startup_receipt ) { 0 };
}
void sim_snapshot_release(struct sim_snapshot *sim_snapshot)
/* ========================== *
* Store
* ========================== */
struct sim_snapshot_store *sim_snapshot_store_alloc(void)
{
sim_ent_store_release(sim_snapshot->ent_store);
sim_client_store_release(sim_snapshot->client_store);
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)
{
/* Release snapshot internal memory */
struct sim_snapshot *ss = sim_snapshot_from_tick(store, store->first_tick);
while ((ss = sim_snapshot_from_tick(store, ss->next_tick))->valid) {
sim_snapshot_release_internal(ss);
}
ss = store->first_free_snapshot;
while ((ss = ss->next_free)) {
sim_snapshot_release_internal(ss);
}
/* Release store */
arena_release(&store->arena);
}
/* ========================== *
* 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_client_store *client_store = NULL;
struct sim_ent_store *ent_store = NULL;
struct sim_snapshot *ss;
{
ss = store->first_free_snapshot;
if (ss) {
store->first_free_snapshot = ss->next_free;
client_store = ss->client_store;
ent_store = ss->ent_store;
} else {
ss = arena_push(&store->arena, struct sim_snapshot);
/* NOTE: These should be released in `sim_snapshot_release_internal` */
ent_store = sim_ent_store_alloc(GIGABYTE(64), true);
client_store = sim_client_store_alloc(GIGABYTE(64));
}
MEMZERO_STRUCT(ss);
}
ss->tick = tick;
ss->valid = true;
ss->store = store;
ss->client_store = client_store;
ss->ent_store = ent_store;
++store->num_ticks;
/* Copy src */
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->received_at_ns = src->received_at_ns;
ss->continuity_gen = src->continuity_gen;
ss->local_client = src->local_client;
sim_client_store_copy(ss->client_store, src->client_store);
sim_ent_store_copy(ss->ent_store, src->ent_store);
/* 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;
}
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;
}
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;
}
/* NOTE: Only releases snapshot into store's free list.
* Use `sim_snapshot_release_internal` to release allocated snapshot memory back to system */
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;
}
INTERNAL void sim_snapshot_release_internal(struct sim_snapshot *snapshot)
{
sim_client_store_release(snapshot->client_store);
sim_ent_store_release(snapshot->ent_store);
}
/* ========================== *
* 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->client_store->num_reserved, ss1->client_store->num_reserved);
for (u64 i = 0; i < num_clients; ++i) {
struct sim_client *c = &ss->client_store->clients[i];
struct sim_client *c0 = &ss0->client_store->clients[i];
struct sim_client *c1 = &ss1->client_store->clients[i];
sim_client_lerp(c, c0, c1, blend);
}
}
/* Blend entities */
{
__profscope(snapshot_lerp_entities);
u64 num_entities = min_u64(ss0->ent_store->num_reserved, ss1->ent_store->num_reserved);
for (u64 i = 0; i < num_entities; ++i) {
struct sim_ent *e = &ss->ent_store->entities[i];
struct sim_ent *e0 = &ss0->ent_store->entities[i];
struct sim_ent *e1 = &ss1->ent_store->entities[i];
sim_ent_lerp(e, e0, e1, blend);
}
}
}
return ss;
}

View File

@ -4,10 +4,32 @@
#include "sim_ent.h"
#include "sim_client.h"
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 {
u64 continuity_gen; /* Starts at 1 */
u64 tick; /* Starts at 1 */
i64 publishtime_ns; /* When was this tick simulated in program time */
/* Managed by store */
b32 valid;
u64 tick;
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;
/* Real time (increases with clock assuming no lag) */
i64 real_dt_ns;
@ -18,13 +40,58 @@ struct sim_snapshot {
i64 world_dt_ns;
i64 world_time_ns;
/* When was this snapshot received */
i64 received_at_ns;
/* If != previous tick's continuity then don't lerp */
u64 continuity_gen;
/* Which client in the client store represents the local host in the original snapshot */
struct sim_client_handle local_client;
struct sim_client_store *client_store;
struct sim_ent_store *ent_store;
};
void sim_snapshot_alloc(struct sim_snapshot *sim_snapshot);
/* ========================== *
* 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);
/* ========================== *
* 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 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);
struct sim_snapshot *sim_snapshot_from_tick(struct sim_snapshot_store *store, u64 tick);
struct sim_snapshot *sim_snapshot_alloc_from_lerp(struct sim_snapshot_store *store, struct sim_snapshot *ss0, struct sim_snapshot *ss1, f64 blend);
#endif

View File

@ -3,6 +3,8 @@
#include "arena.h"
#include "collider.h"
/* FIXME: Default space entry & cell pointers to nil */
/* Offset in bytes from start of space struct to start of entry array (assume adjacently allocated) */
#define SPACE_ENTRIES_OFFSET (sizeof(struct space) + (sizeof(struct space) % alignof(struct space_entry)))

View File

@ -49,6 +49,10 @@ GLOBAL struct {
struct host *host;
struct string connect_address_str;
struct sim_snapshot_store *sim_snapshot_store; /* Contains buffered snapshots from sim */
struct sim_snapshot_store *world_snapshot_store; /* Contains single world snapshot from result of blending sim snapshots */
struct sim_snapshot *world;
/* Usage stats */
i64 last_second_reset_ns;
struct second_stat client_bytes_read;
@ -67,8 +71,6 @@ GLOBAL struct {
struct xform world_to_ui_xf;
struct sim_snapshot world;
struct bind_state bind_states[USER_BIND_KIND_COUNT];
b32 debug_camera;
@ -81,6 +83,13 @@ GLOBAL struct {
struct sys_mutex sys_events_mutex;
struct arena sys_events_arena;
i64 real_dt_ns;
i64 real_time_ns;
/* Calculated from <last snapshot receive time + time since packet receive> */
i64 sim_time_ns;
i64 sim_time_smoothed_ns;
/* Per-frame */
struct v2 screen_size;
struct v2 screen_cursor;
@ -148,6 +157,9 @@ 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_ent_startup_receipt *sim_ent_sr,
struct sim_client_startup_receipt *sim_client_sr,
struct sim_snapshot_startup_receipt *sim_snapshot_sr,
struct string connect_address_str,
struct sys_window *window)
{
@ -161,11 +173,18 @@ struct user_startup_receipt user_startup(struct work_startup_receipt *work_sr,
(UNUSED)mixer_sr;
(UNUSED)phys_sr;
(UNUSED)host_sr;
(UNUSED)sim_ent_sr;
(UNUSED)sim_client_sr;
(UNUSED)sim_snapshot_sr;
G.arena = arena_alloc(GIGABYTE(64));
G.sys_events_mutex = sys_mutex_alloc();
G.sys_events_arena = arena_alloc(GIGABYTE(64));
sim_snapshot_alloc(&G.world);
/* Snapshot store */
G.sim_snapshot_store = sim_snapshot_store_alloc();
G.world_snapshot_store = sim_snapshot_store_alloc();
G.world = sim_snapshot_nil();
//struct sock_address bind_addr = sock_address_from_any_local_interface_with_dynamic_port();
G.host = host_alloc(0);
@ -176,11 +195,13 @@ 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);
if (connect_address_str.len == 0) {
G.sim_ctx = sim_ctx_alloc(sprite_sr, phys_sr, host_sr, 12345);
G.sim_ctx = sim_ctx_alloc(sprite_sr, phys_sr, host_sr, sim_ent_sr, sim_client_sr, sim_snapshot_sr, 12345);
G.connect_address_str = LIT("127.0.0.1:12345");
G.sim_thread = sys_thread_alloc(&user_sim_thread_entry_point, G.sim_ctx, LIT("[P2] Sim thread"));
} else {
@ -237,159 +258,6 @@ INTERNAL SYS_WINDOW_EVENT_CALLBACK_FUNC_DEF(window_event_callback, event)
sys_mutex_unlock(&lock);
}
/* ========================== *
* Sim -> user communication
* ========================== */
#if 0
INTERNAL struct blend_tick *blend_tick_alloc(void)
{
struct blend_tick *bt = NULL;
if (G.head_free_blend_tick) {
bt = G.head_free_blend_tick;
G.head_free_blend_tick = bt->next;
*bt = (struct blend_tick) {
.sim_snapshot = bt->sim_snapshot
};
} else {
bt = arena_push_zero(&G.arena, struct blend_tick);
sim_snapshot_alloc(&bt->sim_snapshot);
}
if (G.head_blend_tick) {
bt->next = G.head_blend_tick;
G.head_blend_tick->prev = bt;
}
G.head_blend_tick = bt;
return bt;
}
INTERNAL void blend_tick_release(struct blend_tick *bt)
{
struct blend_tick *next = bt->next;
struct blend_tick *prev = bt->prev;
/* Remove from list */
if (next) {
next->prev = prev;
}
if (prev) {
prev->next = next;
}
if (bt == G.head_blend_tick) {
G.head_blend_tick = next;
}
/* Add to free list */
bt->next = G.head_free_blend_tick;
bt->prev = NULL;
G.head_free_blend_tick = bt;
}
struct interp_ticks {
struct sim_snapshot *from_tick;
struct sim_snapshot *to_tick;
};
INTERNAL struct interp_ticks pull_ticks(i64 blend_time_ns)
{
__prof;
/* Find newest stored tick */
struct sim_snapshot *newest_tick = NULL;
for (struct blend_tick *bt = G.head_blend_tick; bt; bt = bt->next) {
if (!newest_tick || bt->sim_snapshot.tick > newest_tick->tick) {
newest_tick = &bt->sim_snapshot;
}
}
struct sim_snapshot *from_tick;
struct sim_snapshot *to_tick;
if (newest_tick && sim_get_latest_tick_continuity_gen() == newest_tick->continuity_gen) {
/* Pull new tick from sim thread if necessary */
if (!newest_tick || sim_get_latest_tick() > newest_tick->tick) {
struct blend_tick *latest_bt = blend_tick_alloc();
newest_tick = &latest_bt->sim_snapshot;
sim_get_latest_tick(newest_tick);
}
/* Find oldest tick */
struct sim_snapshot *oldest_tick = NULL;
for (struct blend_tick *bt = G.head_blend_tick; bt; bt = bt->next) {
if (!oldest_tick || bt->sim_snapshot.tick < oldest_tick->tick) {
oldest_tick = &bt->sim_snapshot;
}
}
/* Find closest ticks to blend time */
from_tick = oldest_tick;
to_tick = newest_tick;
for (struct blend_tick *bt = G.head_blend_tick; bt; bt = bt->next) {
i64 bt_time_ns = bt->sim_snapshot.publishtime_ns;
if (bt_time_ns < blend_time_ns && bt_time_ns > from_tick->publishtime_ns) {
from_tick = &bt->sim_snapshot;
}
if (bt_time_ns > blend_time_ns && bt_time_ns < to_tick->publishtime_ns) {
to_tick = &bt->sim_snapshot;
}
}
/* Free any unused old ticks */
{
struct temp_arena scratch = scratch_begin_no_conflict();
struct blend_tick **bts_to_free = arena_dry_push(scratch.arena, struct blend_tick *);
u64 bts_to_free_count = 0;
for (struct blend_tick *bt = G.head_blend_tick; bt; bt = bt->next) {
i64 bt_time_ns = bt->sim_snapshot.publishtime_ns;
if (bt_time_ns < from_tick->publishtime_ns) {
*arena_push(scratch.arena, struct blend_tick *) = bt;
++bts_to_free_count;
}
}
for (u64 i = 0; i < bts_to_free_count; ++i) {
blend_tick_release(bts_to_free[i]);
}
scratch_end(scratch);
}
} else {
/* Latest tick is discontinuous */
/* Free all blend ticks */
{
struct temp_arena scratch = scratch_begin_no_conflict();
struct blend_tick **bts_to_free = arena_dry_push(scratch.arena, struct blend_tick *);
u64 bts_to_free_count = 0;
for (struct blend_tick *bt = G.head_blend_tick; bt; bt = bt->next) {
*arena_push(scratch.arena, struct blend_tick *) = bt;
++bts_to_free_count;
}
for (u64 i = 0; i < bts_to_free_count; ++i) {
blend_tick_release(bts_to_free[i]);
}
scratch_end(scratch);
}
/* Allocate new blend tick */
struct blend_tick *bt = blend_tick_alloc();
struct sim_snapshot *tick = &bt->sim_snapshot;
sim_get_latest_tick(tick);
from_tick = tick;
to_tick = tick;
}
return (struct interp_ticks) {
.from_tick = from_tick,
.to_tick = to_tick
};
}
#endif
/* ========================== *
* Debug draw
* ========================== */
@ -523,101 +391,13 @@ INTERNAL void user_update(void)
* Begin frame
* ========================== */
i64 now_ns = sys_time_ns();
G.real_dt_ns = sys_time_ns() - G.real_time_ns;
G.real_time_ns += G.real_dt_ns;
G.screen_size = sys_window_get_size(G.window);
struct sim_ent_store *store = G.world.ent_store;
struct sprite_scope *sprite_frame_scope = sprite_scope_begin();
struct sim_cmd_list cmd_list = ZI;
#if 0
/* ========================== *
* Interpolate between sim ticks
* ========================== */
{
__profscope(interpolate_ticks);
#if USER_INTERP_ENABLED
/* TODO: Use actual fps of sim thread (will differ from SIM_FPS if sim thread is lagging) to hide lag with slow-motion? */
i64 blend_time_offset_ns = NS_FROM_SECONDS((1.0 / SIM_FPS) * USER_INTERP_OFFSET_TICK_RATIO);
i64 blend_time_ns = G.time_ns > blend_time_offset_ns ? G.time_ns - blend_time_offset_ns : 0;
/* Pull ticks */
struct interp_ticks interp_ticks = pull_ticks(blend_time_ns);
struct sim_snapshot *t0 = interp_ticks.from_tick;
struct sim_snapshot *t1 = interp_ticks.to_tick;
f32 tick_blend = 0;
{
i64 t0_time_ns = t0->publishtime_ns;
i64 t1_time_ns = t1->publishtime_ns;
if (t1_time_ns > t0_time_ns) {
f64 t0_t1_elapsed = SECONDS_FROM_NS(t1_time_ns - t0_time_ns);
f64 t0_blend_elapsed = SECONDS_FROM_NS(blend_time_ns - t0_time_ns);
tick_blend = t0_blend_elapsed / t0_t1_elapsed;
}
tick_blend = clamp_f32(tick_blend, 0.0f, 1.0f);
}
world_copy_replace(&G.sim_snapshot, t0);
/* Blend world globals */
G.sim_snapshot.time_ns = math_lerp_i64(t0->time_ns, t1->time_ns, (f64)tick_blend);
G.sim_snapshot.dt_ns = math_lerp_i64(t0->dt_ns, t1->dt_ns, (f64)tick_blend);
/* Blend entities */
u64 num_entities = min_u64(t0->ent_store->num_reserved, t1->ent_store->num_reserved);
{
__profscope(tick_blending);
for (u64 i = 0; i < num_entities; ++i) {
struct sim_ent *e = &store->entities[i];
struct sim_ent *e0 = &t0->ent_store->entities[i];
struct sim_ent *e1 = &t1->ent_store->entities[i];
if (e0->valid && e1->valid
&& sim_ent_has_prop(e0, SIM_ENT_PROP_ACTIVE) && sim_ent_has_prop(e1, SIM_ENT_PROP_ACTIVE)
&& e0->handle.gen == e1->handle.gen
&& e0->continuity_gen == e1->continuity_gen) {
e->local_xform = xform_lerp(e0->local_xform, e1->local_xform, tick_blend);
if (e->is_top) {
/* TODO: Cache parent & child xforms in sim thread */
struct xform e0_xf = sim_ent_get_xform(e0);
struct xform e1_xf = sim_ent_get_xform(e1);
sim_ent_set_xform(e, xform_lerp(e0_xf, e1_xf, tick_blend));
}
e->control_force = math_lerp_f32(e0->control_force, e1->control_force, tick_blend);
e->control_torque = math_lerp_f32(e0->control_torque, e1->control_torque, tick_blend);
e->linear_velocity = v2_lerp(e0->linear_velocity, e1->linear_velocity, tick_blend);
e->angular_velocity = math_lerp_angle(e0->angular_velocity, e1->angular_velocity, tick_blend);
e->control.move = v2_lerp(e0->control.move, e1->control.move, tick_blend);
e->control.focus = v2_lerp(e0->control.focus, e1->control.focus, tick_blend);
e->sprite_local_xform = xform_lerp(e0->sprite_local_xform, e1->sprite_local_xform, tick_blend);
e->animation_time_in_frame = math_lerp_f64(e0->animation_time_in_frame, e1->animation_time_in_frame, (f64)tick_blend);
e->animation_frame = (u32)math_round_to_int(math_lerp_f32(e0->animation_frame, e1->animation_frame, tick_blend));
e->camera_quad_xform = xform_lerp(e0->camera_quad_xform, e1->camera_quad_xform, tick_blend);
e->camera_xform_target = xform_lerp(e0->camera_xform_target, e1->camera_xform_target, tick_blend);
e->shake = math_lerp_f32(e0->shake, e1->shake, tick_blend);
e->tracer_gradient_start = v2_lerp(e0->tracer_gradient_start, e1->tracer_gradient_start, tick_blend);
e->tracer_gradient_end = v2_lerp(e0->tracer_gradient_end, e1->tracer_gradient_end, tick_blend);
}
}
}
#else
struct interp_ticks interp_ticks = pull_ticks(G.time);
world_copy_replace(&G.sim_snapshot, interp_ticks.to_tick);
tick_is_first_frame = G.sim_snapshot.tick == 0;
#endif
}
#endif
/* ========================== *
* Process host events into sim events
* ========================== */
@ -659,12 +439,22 @@ INTERNAL void user_update(void)
case SIM_EVENT_KIND_SNAPSHOT:
{
#if 0
u64 oldest_tick = snapshot_store_get_oldest_tick(G.snapshot_store);
u64 newest_tick = snapshot_store_get_newest_tick(G.snapshot_store);
#if 1
struct sim_snapshot *existing = sim_snapshot_from_tick(G.sim_snapshot_store, tick);
if (!existing->valid && tick > G.world->tick) {
u64 delta_src_tick = 0;
struct sim_snapshot *delta_src = sim_snapshot_from_tick(G.sim_snapshot_store, delta_src_tick);
ASSERT(delta_src->tick == delta_src_tick); /* User should always have src tick present */
struct string encoded = event->snapshot_data;
struct sim_snapshot *ss = sim_snapshot_alloc(G.sim_snapshot_store, delta_src, tick);
sim_snapshot_decode(encoded, ss);
ss->received_at_ns = G.real_time_ns;
}
#else
struct string encoded = event->snapshot_data;
sim_snapshot_decode(encoded, &G.world, tick);
#endif
struct string snapshot_data = event->snapshot_data;
sim_snapshot_decode(snapshot_data, &G.world, tick);
} break;
default: break;
@ -672,6 +462,82 @@ INTERNAL void user_update(void)
}
}
/* ========================== *
* Create user world from blended snapshots
* ========================== */
{
struct sim_snapshot *newest_snapshot = sim_snapshot_from_tick(G.sim_snapshot_store, G.sim_snapshot_store->last_tick);
/* Calculate sim time based on last received snapshot time,
* then smooth it out to prevent sudden jumps in rendering due to snapshot receive time variance. */
/* TODO: Use a value that indicates desired dt to next frame, rather than real dt from last frame? */
f64 sim_time_smooth_rate_ns = SECONDS_FROM_NS(G.real_dt_ns) / 0.05;
i64 time_since_newest_tick_ns = G.real_time_ns - newest_snapshot->received_at_ns;
G.sim_time_ns = newest_snapshot->real_time_ns + time_since_newest_tick_ns;
G.sim_time_smoothed_ns += (G.sim_time_ns - G.sim_time_smoothed_ns) * sim_time_smooth_rate_ns;
#if USER_INTERP_ENABLED
i64 render_time_ns = G.sim_time_smoothed_ns - (USER_INTERP_RATIO * newest_snapshot->real_dt_ns);
/* Get two snapshots nearest to render time */
struct sim_snapshot *left_snapshot = sim_snapshot_nil();
struct sim_snapshot *right_snapshot = newest_snapshot;
{
struct sim_snapshot *snapshot = sim_snapshot_from_tick(G.sim_snapshot_store, G.sim_snapshot_store->first_tick);
while (snapshot->valid) {
i64 ss_time_ns = snapshot->real_time_ns;
u64 next_tick = snapshot->next_tick;
if (ss_time_ns < render_time_ns && ss_time_ns > left_snapshot->real_time_ns) {
if (left_snapshot->valid) {
/* Snapshot no longer needed since render time has passed, release it */
sim_snapshot_release(left_snapshot);
}
left_snapshot = snapshot;
}
if (ss_time_ns > render_time_ns && ss_time_ns < right_snapshot->real_time_ns) {
right_snapshot = snapshot;
}
snapshot = sim_snapshot_from_tick(G.sim_snapshot_store, next_tick);
}
}
if (G.world->valid) {
sim_snapshot_release(G.world);
}
/* Create world from blended snapshots */
if (left_snapshot->valid && right_snapshot->valid) {
f64 blend = (f64)(render_time_ns - left_snapshot->real_time_ns) / (f64)(right_snapshot->real_time_ns - left_snapshot->real_time_ns);
G.world = sim_snapshot_alloc_from_lerp(G.world_snapshot_store, left_snapshot, right_snapshot, blend);
} else if (left_snapshot->valid) {
G.world = sim_snapshot_alloc(G.world_snapshot_store, left_snapshot, left_snapshot->tick);
} else if (right_snapshot->valid) {
G.world = sim_snapshot_alloc(G.world_snapshot_store, right_snapshot, right_snapshot->tick);
}
#else
/* Release sim snapshots all except for newest tick */
{
struct sim_snapshot *snapshot = sim_snapshot_from_tick(G.sim_snapshot_store, G.sim_snapshot_store->first_tick);
while (snapshot->valid) {
u64 next_tick = snapshot->next_tick;
if (snapshot->tick != newest_snapshot->tick) {
sim_snapshot_release(snapshot);
}
snapshot = sim_snapshot_from_tick(G.sim_snapshot_store, next_tick);
}
}
if (G.world->tick != newest_snapshot->tick) {
if (G.world->valid) {
sim_snapshot_release(G.world);
}
G.world = sim_snapshot_alloc(G.world_snapshot_store, newest_snapshot, newest_snapshot->tick);
}
#endif
}
/* ========================== *
* Process sys events into user bind state
@ -766,16 +632,16 @@ INTERNAL void user_update(void)
* Find local entities
* ========================== */
struct sim_client *local_client = sim_client_from_handle(G.world.client_store, G.world.local_client);
struct sim_ent *local_player = sim_ent_from_handle(G.world.ent_store, local_client->control_ent);
struct sim_ent *local_camera = sim_ent_from_handle(G.world.ent_store, local_client->camera_ent);
struct sim_client *local_client = sim_client_from_handle(G.world->client_store, G.world->local_client);
struct sim_ent *local_player = sim_ent_from_handle(G.world->ent_store, local_client->control_ent);
struct sim_ent *local_camera = sim_ent_from_handle(G.world->ent_store, local_client->camera_ent);
/* ========================== *
* Apply shake
* ========================== */
for (u64 ent_index = 0; ent_index < store->num_reserved; ++ent_index) {
struct sim_ent *ent = &store->entities[ent_index];
for (u64 ent_index = 0; ent_index < G.world->ent_store->num_reserved; ++ent_index) {
struct sim_ent *ent = &G.world->ent_store->entities[ent_index];
if (!sim_ent_is_valid_and_active(ent)) continue;
/* How much time between camera shakes */
@ -783,7 +649,7 @@ INTERNAL void user_update(void)
f32 shake = ent->shake;
if (shake > 0) {
u64 basis = hash_fnv64(HASH_FNV64_BASIS, STRING_FROM_STRUCT(&ent->handle));
u64 angle_seed0 = basis + (u64)(G.world.world_time_ns / frequency_ns);
u64 angle_seed0 = basis + (u64)(G.world->world_time_ns / frequency_ns);
u64 angle_seed1 = angle_seed0 + 1;
f32 angle0 = rng_noise_f32(angle_seed0, 0, TAU);
f32 angle1 = rng_noise_f32(angle_seed1, 0, TAU);
@ -793,7 +659,7 @@ INTERNAL void user_update(void)
struct v2 vec1 = v2_with_len(v2_from_angle(angle1), shake);
/* TODO: Cubic interp? */
f32 blend = (f32)(G.world.world_time_ns % frequency_ns) / (f32)frequency_ns;
f32 blend = (f32)(G.world->world_time_ns % frequency_ns) / (f32)frequency_ns;
struct v2 vec = v2_lerp(vec0, vec1, blend);
struct xform xf = sim_ent_get_xform(ent);
@ -942,8 +808,8 @@ INTERNAL void user_update(void)
/* Copy valid entities */
{
__profscope(copy_sprites_for_sorting);
for (u64 ent_index = 0; ent_index < store->num_reserved; ++ent_index) {
struct sim_ent *ent = &store->entities[ent_index];
for (u64 ent_index = 0; ent_index < G.world->ent_store->num_reserved; ++ent_index) {
struct sim_ent *ent = &G.world->ent_store->entities[ent_index];
if (sim_ent_is_valid_and_active(ent)) {
*arena_push(scratch.arena, struct sim_ent *) = ent;
++sorted_count;
@ -970,7 +836,7 @@ INTERNAL void user_update(void)
struct sprite_tag sprite = ent->sprite;
struct sim_ent *parent = sim_ent_from_handle(store, ent->parent);
struct sim_ent *parent = sim_ent_from_handle(G.world->ent_store, ent->parent);
struct xform xf = sim_ent_get_xform(ent);
struct xform parent_xf = sim_ent_get_xform(parent);
@ -1155,8 +1021,8 @@ INTERNAL void user_update(void)
/* Draw contact constraint */
if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTACT_CONSTRAINT)) {
struct phys_contact_constraint *data = &ent->contact_constraint_data;
struct sim_ent *e0 = sim_ent_from_handle(store, data->e0);
struct sim_ent *e1 = sim_ent_from_handle(store, data->e1);
struct sim_ent *e0 = sim_ent_from_handle(G.world->ent_store, data->e0);
struct sim_ent *e1 = sim_ent_from_handle(G.world->ent_store, data->e1);
(UNUSED)e0;
(UNUSED)e1;
@ -1621,25 +1487,30 @@ INTERNAL void user_update(void)
if (font) {
struct temp_arena temp = arena_temp_begin(scratch.arena);
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("sim tick: %F"), FMT_UINT(G.world.tick)));
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("sim tick: %F"), FMT_UINT(G.world->tick)));
pos.y += spacing;
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("sim real time: %F"), FMT_FLOAT(SECONDS_FROM_NS(G.world.real_time_ns))));
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("sim time: %F"), FMT_FLOAT(SECONDS_FROM_NS(G.sim_time_ns))));
pos.y += spacing;
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("sim world time: %F"), FMT_FLOAT(SECONDS_FROM_NS(G.world.world_time_ns))));
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("sim time (smoothed): %F"), FMT_FLOAT(SECONDS_FROM_NS(G.sim_time_smoothed_ns))));
pos.y += spacing;
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("sim entities: %F/%F"), FMT_UINT(G.world.ent_store->num_allocated), FMT_UINT(G.world.ent_store->num_reserved)));
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("world time: %F"), FMT_FLOAT(SECONDS_FROM_NS(G.world->world_time_ns))));
pos.y += spacing;
pos.y += spacing;
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("sim entities: %F/%F"), FMT_UINT(G.world->ent_store->num_allocated), FMT_UINT(G.world->ent_store->num_reserved)));
pos.y += spacing;
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("Client data read: %F mbit/s"), FMT_FLOAT_P((f64)G.client_bytes_read.last_second * 8 / 1000 / 1000, 2)));
pos.y += spacing;
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("Client data sent: %F mbit/s"), FMT_FLOAT_P((f64)G.client_bytes_sent.last_second * 8 / 1000 / 1000, 2)));
pos.y += spacing;
pos.y += spacing;
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("Memory usage: %F MiB"), FMT_FLOAT_P((f64)atomic_u64_eval(&app_statistics()->memory_committed) / 1024 / 1024, 2)));
pos.y += spacing;
@ -1718,8 +1589,8 @@ INTERNAL void user_update(void)
/* 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 (now_ns - G.last_second_reset_ns > NS_FROM_SECONDS(1)) {
G.last_second_reset_ns = now_ns;
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;
@ -1858,7 +1729,7 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_thread_entry_point, arg)
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_sim_thread_entry_point, arg)
{
struct sim_ctx *ctx = (struct sim_ctx *)arg;
i64 target_dt_ns = NS_FROM_SECONDS(1) / SIM_FPS;;
i64 target_dt_ns = NS_FROM_SECONDS(1) / SIM_TICKS_PER_SECOND;;
while (!atomic_i32_eval(&G.sim_thread_shutdown)) {
sim_update(ctx, target_dt_ns);
}

View File

@ -12,6 +12,9 @@ struct sound_startup_receipt;
struct mixer_startup_receipt;
struct phys_startup_receipt;
struct host_startup_receipt;
struct sim_ent_startup_receipt;
struct sim_client_startup_receipt;
struct sim_snapshot_startup_receipt;
enum user_bind_kind {
USER_BIND_KIND_NONE,
@ -60,6 +63,9 @@ 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_ent_startup_receipt *sim_ent_sr,
struct sim_client_startup_receipt *sim_client_sr,
struct sim_snapshot_startup_receipt *sim_snapshot_sr,
struct string connect_address_str,
struct sys_window *window);