power_play/src/sim_snapshot.c
2025-02-11 19:31:06 -06:00

543 lines
18 KiB
C

#include "sim_snapshot.h"
#include "sim.h"
#include "sim_ent.h"
#include "sim_client.h"
#include "arena.h"
#include "scratch.h"
#define TICK_LOOKUP_BUCKETS 127
#define CLIENT_LOOKUP_BUCKETS 127
struct sim_snapshot_lookup_bucket {
struct sim_snapshot *first;
struct sim_snapshot *last;
};
/* ========================== *
* Startup
* ========================== */
GLOBAL struct {
struct arena nil_arena;
struct sim_snapshot_store *nil_snapshot_store;
struct sim_snapshot *nil_snapshot;
struct sim_ent *nil_ent;
struct sim_client *nil_client;
} G = ZI, DEBUG_ALIAS(G, G_sim_snapshot);
/* Accessed via `sim_snapshot_store_nil()` */
READONLY struct sim_snapshot_store **_g_sim_snapshot_store_nil = &G.nil_snapshot_store;
/* Accessed via `sim_snapshot_nil()` */
READONLY struct sim_snapshot **_g_sim_snapshot_nil = &G.nil_snapshot;
/* Accessed via `sim_ent_nil()` */
READONLY struct sim_ent **_g_sim_ent_nil = &G.nil_ent;
/* Accessed via `sim_client_nil()` */
READONLY struct sim_client **_g_sim_client_nil = &G.nil_client;
struct sim_snapshot_startup_receipt sim_snapshot_startup(void)
{
G.nil_arena = arena_alloc(GIGABYTE(1));
/* Nil snapshot store */
G.nil_snapshot_store = arena_push_zero(&G.nil_arena, struct sim_snapshot_store);
G.nil_snapshot_store->valid = false;
/* Nil snapshot */
G.nil_snapshot = arena_push_zero(&G.nil_arena, struct sim_snapshot);
G.nil_snapshot->valid = false;
G.nil_snapshot->store = sim_snapshot_store_nil();
/* Nil ent */
G.nil_ent = arena_push_zero(&G.nil_arena, struct sim_ent);
G.nil_ent->ss = sim_snapshot_nil();
G.nil_ent->valid = false;
G.nil_ent->handle = SIM_ENT_NIL_HANDLE;
G.nil_ent->local_xform = XFORM_IDENT;
G.nil_ent->cached_global_xform = XFORM_IDENT;
G.nil_ent->cached_global_xform_dirty = false;
G.nil_ent->friction = 0.5f;
G.nil_ent->mass_unscaled = 1;
G.nil_ent->inertia_unscaled = 1;
G.nil_ent->sprite_local_xform = XFORM_IDENT;
G.nil_ent->sprite_tint = COLOR_WHITE;
/* Nil client */
G.nil_client = arena_push_zero(&G.nil_arena, struct sim_client);
G.nil_client->ss = sim_snapshot_nil();
G.nil_client->valid = false;
/* Lock nil arena */
arena_set_readonly(&G.nil_arena);
return (struct sim_snapshot_startup_receipt ) { 0 };
}
/* ========================== *
* Store
* ========================== */
struct sim_snapshot_store *sim_snapshot_store_alloc(void)
{
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 */
for (u64 i = 0; i < store->num_lookup_buckets; ++i) {
struct sim_snapshot_lookup_bucket *bucket = &store->lookup_buckets[i];
struct sim_snapshot *ss = bucket->first;
while (ss) {
struct sim_snapshot *next = ss->next_in_bucket;
arena_release(&ss->clients_arena);
arena_release(&ss->ents_arena);
arena_release(&ss->arena);
ss = next;
}
}
{
struct sim_snapshot *ss = store->first_free_snapshot;
while (ss) {
struct sim_snapshot *next = ss->next_free;
arena_release(&ss->clients_arena);
arena_release(&ss->ents_arena);
arena_release(&ss->arena);
ss = next;
}
}
/* Release store */
arena_release(&store->arena);
}
/* ========================== *
* Alloc
* ========================== */
/* Produces a new snapshot at `tick` with data copied from `src` snapshot. */
struct sim_snapshot *sim_snapshot_alloc(struct sim_snapshot_store *store, struct sim_snapshot *src, u64 tick)
{
if (tick == 0) {
return sim_snapshot_nil();
}
struct sim_snapshot *ss;
{
struct arena arena = ZI;
struct arena clients_arena = ZI;
struct arena ents_arena = ZI;
{
ss = store->first_free_snapshot;
if (ss) {
/* Re-use existing snasphot arenas */
store->first_free_snapshot = ss->next_free;
ents_arena = ss->ents_arena;
clients_arena = ss->clients_arena;
arena = ss->arena;
} else {
/* Arenas allocated here will be released along with the entire snasphot store */
arena = arena_alloc(GIGABYTE(8));
clients_arena = arena_alloc(GIGABYTE(8));
ents_arena = arena_alloc(GIGABYTE(8));
}
}
arena_reset(&arena);
ss = arena_push_zero(&arena, struct sim_snapshot);
ss->arena = arena;
ss->clients_arena = clients_arena;
arena_reset(&ss->clients_arena);
ss->ents_arena = ents_arena;
arena_reset(&ss->ents_arena);
}
ss->tick = tick;
ss->valid = true;
ss->store = store;
++store->num_ticks;
/* Copy src info */
ss->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;
/* Copy client lookup buckets */
if (src->num_client_lookup_buckets > 0) {
ss->num_client_lookup_buckets = src->num_client_lookup_buckets;
ss->client_lookup_buckets = arena_push_array(&ss->arena, struct sim_client_lookup_bucket, ss->num_client_lookup_buckets);
MEMCPY(ss->client_lookup_buckets, src->client_lookup_buckets, sizeof(*ss->client_lookup_buckets) * ss->num_client_lookup_buckets);
} else {
ss->num_client_lookup_buckets = CLIENT_LOOKUP_BUCKETS;
ss->client_lookup_buckets = arena_push_array_zero(&ss->arena, struct sim_client_lookup_bucket, ss->num_client_lookup_buckets);
}
/* Copy clients */
ss->first_free_client = src->first_free_client;
ss->num_clients_allocated = src->num_clients_allocated;
ss->num_clients_reserved = src->num_clients_reserved;
ss->clients = arena_push_array(&ss->clients_arena, struct sim_client, ss->num_clients_reserved);
for (u64 i = 0; i < ss->num_clients_reserved; ++i) {
struct sim_client *dst_client = &ss->clients[i];
struct sim_client *src_client = &src->clients[i];
*dst_client = *src_client;
dst_client->ss = ss;
}
/* Copy entities */
ss->first_free_ent = src->first_free_ent;
ss->num_ents_allocated = src->num_ents_allocated;
ss->num_ents_reserved = src->num_ents_reserved;
ss->ents = arena_push_array(&ss->ents_arena, struct sim_ent, ss->num_ents_reserved);
if (ss->num_ents_reserved == 0) {
/* Create root ent if copying from nil store */
struct sim_ent *root = arena_push(&ss->ents_arena, struct sim_ent);
*root = *sim_ent_nil();
root->ss = ss;
root->handle = SIM_ENT_ROOT_HANDLE;
root->valid = true;
root->is_root = true;
++ss->num_ents_allocated;
++ss->num_ents_reserved;
} else {
for (u64 i = 0; i < ss->num_ents_reserved; ++i) {
struct sim_ent *dst_ent = &ss->ents[i];
struct sim_ent *src_ent = &src->ents[i];
*dst_ent = *src_ent;
dst_ent->ss = ss;
}
}
/* Release duplicate tick if it exists */
{
struct sim_snapshot *existing = sim_snapshot_from_tick(store, tick);
if (existing->valid) {
sim_snapshot_release(existing);
}
}
/* Linear search to insert snapshot in tick order */
{
struct sim_snapshot *prev = sim_snapshot_from_tick(store, store->last_tick);
while (prev->valid) {
if (prev->tick < tick) {
break;
}
prev = sim_snapshot_from_tick(store, prev->prev_tick);
}
if (prev->valid) {
struct sim_snapshot *next = sim_snapshot_from_tick(store, prev->next_tick);
if (next->valid) {
next->prev_tick = tick;
} else {
store->last_tick = tick;
}
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;
}
/* ========================== *
* Lookup
* ========================== */
struct sim_snapshot *sim_snapshot_from_tick(struct sim_snapshot_store *store, u64 tick)
{
struct sim_snapshot *ss = sim_snapshot_nil();
if (tick > 0) {
u64 bucket_index = tick % store->num_lookup_buckets;
struct sim_snapshot_lookup_bucket *bucket = &store->lookup_buckets[bucket_index];
for (struct sim_snapshot *search = bucket->first; search; search = search->next_in_bucket) {
if (search->tick == tick) {
ss = search;
break;
}
}
}
return ss;
}
/* ========================== *
* Lerp
* ========================== */
struct sim_snapshot *sim_snapshot_alloc_from_lerp(struct sim_snapshot_store *store, struct sim_snapshot *ss0, struct sim_snapshot *ss1, f64 blend)
{
__prof;
/* New snapshot will be allocated with same tick as ss0 or ss1, so the result should go into a different store */
ASSERT(ss0->store != store && ss1->store != store);
struct sim_snapshot *ss;
b32 should_blend = true;
if (ss0->continuity_gen == ss1->continuity_gen && 0 < blend && blend < 1) {
ss = sim_snapshot_alloc(store, ss0, ss0->tick);
} else if (math_round_to_int64(blend) <= 0) {
ss = sim_snapshot_alloc(store, ss0, ss0->tick);
should_blend = false;
} else {
ss = sim_snapshot_alloc(store, ss1, ss1->tick);
should_blend = false;
}
if (!ss0->valid || !ss1->valid) {
/* New snapshot allocation caused one of the src snapshots original to release.
* ss0 & ss1 should be from a separate store than the allocating one. */
ASSERT(false);
}
if (should_blend) {
/* Blend time */
ss->real_dt_ns = math_lerp_i64(ss0->real_dt_ns, ss1->real_dt_ns, blend);
ss->real_time_ns = math_lerp_i64(ss0->real_time_ns, ss1->real_time_ns, blend);
ss->world_timescale = math_lerp_f64(ss0->world_timescale, ss1->world_timescale, blend);
ss->world_dt_ns = math_lerp_i64(ss0->world_dt_ns, ss1->world_dt_ns, blend);
ss->world_time_ns = math_lerp_i64(ss0->world_time_ns, ss1->world_time_ns, blend);
/* Blend clients */
{
__profscope(snapshot_lerp_clients);
u64 num_clients = min_u64(ss0->num_clients_reserved, ss1->num_clients_reserved);
for (u64 i = 0; i < num_clients; ++i) {
struct sim_client *c = &ss->clients[i];
struct sim_client *c0 = &ss0->clients[i];
struct sim_client *c1 = &ss1->clients[i];
sim_client_lerp(c, c0, c1, blend);
}
}
/* Blend entities */
{
__profscope(snapshot_lerp_entities);
u64 num_entities = min_u64(ss0->num_ents_reserved, ss1->num_ents_reserved);
for (u64 i = 0; i < num_entities; ++i) {
struct sim_ent *e = &ss->ents[i];
struct sim_ent *e0 = &ss0->ents[i];
struct sim_ent *e1 = &ss1->ents[i];
sim_ent_lerp(e, e0, e1, blend);
}
}
}
return ss;
}
/* ========================== *
* Encode
* ========================== */
void sim_snapshot_encode(struct sim_encoder *enc, struct sim_snapshot *ss0, struct sim_snapshot *ss1)
{
__prof;
struct byte_writer *bw = &enc->bw;
struct sim_client *client = enc->client;
bw_write_var_uint(bw, ss1->continuity_gen);
bw_write_var_sint(bw, ss1->real_dt_ns);
bw_write_var_sint(bw, ss1->real_time_ns);
bw_write_f64(bw, ss1->world_timescale);
bw_write_var_sint(bw, ss1->world_dt_ns);
bw_write_var_sint(bw, ss1->world_time_ns);
bw_write_var_uint(bw, client->handle.gen);
bw_write_var_uint(bw, client->handle.idx);
/* Clients */
if (ss1->num_clients_allocated == ss0->num_clients_allocated) {
bw_write_u8(bw, 0);
} else {
bw_write_u8(bw, 1);
bw_write_var_uint(bw, ss1->num_clients_allocated);
}
if (ss1->num_clients_reserved == ss0->num_clients_reserved) {
bw_write_u8(bw, 0);
} else {
bw_write_u8(bw, 1);
bw_write_var_uint(bw, ss1->num_clients_reserved);
}
for (u64 i = 0; i < ss1->num_clients_reserved; ++i) {
struct sim_client *c0 = sim_client_nil();
if (i < ss0->num_clients_reserved) {
c0 = &ss0->clients[i];
}
struct sim_client *c1 = &ss1->clients[i];
sim_client_encode(enc, c0, c1);
}
/* Ents */
if (ss1->num_ents_allocated == ss0->num_ents_allocated) {
bw_write_u8(bw, 0);
} else {
bw_write_u8(bw, 1);
bw_write_var_uint(bw, ss1->num_ents_allocated);
}
if (ss1->num_ents_reserved == ss0->num_ents_reserved) {
bw_write_u8(bw, 0);
} else {
bw_write_u8(bw, 1);
bw_write_var_uint(bw, ss1->num_ents_reserved);
}
for (u64 i = 0; i < ss1->num_ents_reserved; ++i) {
struct sim_ent *e0 = sim_ent_nil();
if (i < ss0->num_ents_reserved) {
e0 = &ss0->ents[i];
}
struct sim_ent *e1 = &ss1->ents[i];
sim_ent_encode(enc, e0, e1);
}
}
/* ========================== *
* Decode
* ========================== */
void sim_snapshot_decode(struct sim_decoder *dec, struct sim_snapshot *ss)
{
__prof;
struct byte_reader *br = &dec->br;
ss->continuity_gen = br_read_var_uint(br);
ss->real_dt_ns = br_read_var_sint(br);
ss->real_time_ns = br_read_var_sint(br);
ss->world_timescale = br_read_f64(br);
ss->world_dt_ns = br_read_var_sint(br);
ss->world_time_ns = br_read_var_sint(br);
ss->local_client.gen = br_read_var_uint(br);
ss->local_client.idx = br_read_var_uint(br);
/* Clients */
if (br_read_u8(br)) {
ss->num_clients_allocated = br_read_var_uint(br);
}
if (br_read_u8(br)) {
u64 old_num_clients_reserved = ss->num_clients_reserved;
ss->num_clients_reserved = br_read_var_uint(br);
i64 reserve_diff = (i64)ss->num_clients_reserved - (i64)old_num_clients_reserved;
if (reserve_diff > 0) {
arena_push_array(&ss->clients_arena, struct sim_client, reserve_diff);
for (u64 i = old_num_clients_reserved; i < ss->num_clients_reserved; ++i) {
struct sim_client *c = &ss->clients[i];
*c = *sim_client_nil();
c->ss = ss;
}
}
}
for (u64 i = 0; i < ss->num_clients_reserved; ++i) {
struct sim_client *c = &ss->clients[i];
sim_client_decode(dec, c);
}
/* Ents */
if (br_read_u8(br)) {
ss->num_ents_allocated = br_read_var_uint(br);
}
if (br_read_u8(br)) {
u64 old_num_ents_reserved = ss->num_ents_reserved;
ss->num_ents_reserved = br_read_var_uint(br);
i64 reserve_diff = (i64)ss->num_ents_reserved - (i64)old_num_ents_reserved;
if (reserve_diff > 0) {
arena_push_array(&ss->ents_arena, struct sim_ent, reserve_diff);
for (u64 i = old_num_ents_reserved; i < ss->num_ents_reserved; ++i) {
struct sim_ent *e = &ss->ents[i];
*e = *sim_ent_nil();
e->ss = ss;
}
}
}
for (u64 i = 0; i < ss->num_ents_reserved; ++i) {
struct sim_ent *e = &ss->ents[i];
sim_ent_decode(dec, e);
}
}