diff --git a/res/graphics/box.ase b/res/graphics/box.ase index 1613cbe1..44dfa89f 100644 --- a/res/graphics/box.ase +++ b/res/graphics/box.ase @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc6935736f09575dfcbcdaaa55a8fd025777d5427eb15d8ed5a76f755e0c952c -size 623 +oid sha256:4511b3d43b961486e8a9464be00675c5d488f9b4168f1e97ef679859c4128937 +size 3471 diff --git a/src/app.c b/src/app.c index b9c1315c..d9b7ef3f 100644 --- a/src/app.c +++ b/src/app.c @@ -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; diff --git a/src/config.h b/src/config.h index 3c43da26..4a1c2322 100644 --- a/src/config.h +++ b/src/config.h @@ -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? - * = * + * = * * 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 diff --git a/src/phys.c b/src/phys.c index c2f48110..b519c9c5 100644 --- a/src/phys.c +++ b/src/phys.c @@ -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]; diff --git a/src/sim.c b/src/sim.c index 290d164c..5708d108 100644 --- a/src/sim.c +++ b/src/sim.c @@ -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; diff --git a/src/sim.h b/src/sim.h index 1594cb79..d489eb4c 100644 --- a/src/sim.h +++ b/src/sim.h @@ -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); diff --git a/src/sim_client.c b/src/sim_client.c index b5164f7d..a1597805 100644 --- a/src/sim_client.c +++ b/src/sim_client.c @@ -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); } } diff --git a/src/sim_client.h b/src/sim_client.h index 0fd7ede4..f9792743 100644 --- a/src/sim_client.h +++ b/src/sim_client.h @@ -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 diff --git a/src/sim_ent.c b/src/sim_ent.c index 0b495bca..0b59994b 100644 --- a/src/sim_ent.c +++ b/src/sim_ent.c @@ -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); + } +} diff --git a/src/sim_ent.h b/src/sim_ent.h index 92b704e0..8d25a093 100644 --- a/src/sim_ent.h +++ b/src/sim_ent.h @@ -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 diff --git a/src/sim_msg.c b/src/sim_msg.c index 99b20bf5..6df32201 100644 --- a/src/sim_msg.c +++ b/src/sim_msg.c @@ -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); diff --git a/src/sim_msg.h b/src/sim_msg.h index 9c9fff16..a262a7d7 100644 --- a/src/sim_msg.h +++ b/src/sim_msg.h @@ -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 diff --git a/src/sim_snapshot.c b/src/sim_snapshot.c index 4baa7b40..7499f75a 100644 --- a/src/sim_snapshot.c +++ b/src/sim_snapshot.c @@ -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; } diff --git a/src/sim_snapshot.h b/src/sim_snapshot.h index 6e601ad9..30382aaf 100644 --- a/src/sim_snapshot.h +++ b/src/sim_snapshot.h @@ -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 diff --git a/src/space.c b/src/space.c index 363790a6..ecba51cb 100644 --- a/src/space.c +++ b/src/space.c @@ -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))) diff --git a/src/user.c b/src/user.c index e2098e63..48fcec3e 100644 --- a/src/user.c +++ b/src/user.c @@ -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 */ + 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); } diff --git a/src/user.h b/src/user.h index 69240f22..a0bc26db 100644 --- a/src/user.h +++ b/src/user.h @@ -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);