#include "sim.h" #include "sim_ent.h" #include "host.h" #include "arena.h" #include "util.h" #include "arena.h" #include "bitbuff.h" /* Sim hierarchy is as follows: * * Client store -> clients -> snapshots -> ents * * A client store holds clients, which can be retrieved by client handle or by a host channel id (if one is assigned). * * A client holds snapshots, which can be retrieved by tick number. * - The snapshots stored in clients & the contents of those snapshots are determined from data transmitted by the client. * - Different kinds of clients will transmit different subsets of snapshot data (e.g. a master client will transmit most ent state, while slave clients may just transmit 1 or more command ents) * - A client will never hold more than one snapshot for the same tick (e.g. a client will never have 2 snapshots at tick 5) * * A snapshot holds the ent tree for a particular tick, in which ents can be retrieved by ent id. * - A tick is the quantized time step that all clients implicitly conform to. * * An ent is the smallest unit of simulation state. * - It is assigned a 128 bit unique identifer generated at allocation time. * - This id is used to refer to other ents in the tree and to sync ents accross clients. * - This id is usually random, but can be deterministic under certain conditions. * - For example, instead of storing a contact constraint lookup table, contact constraints are * retrieved by searching for the ent with the id resulting from combining the ent ids of the * two contacting entities (plus a unique 'basis' ent id used for all contact constraints). * - The ent also implicitly has a 32 bit index, which is just its offset from the start of the entity array in the local snapshot. * - Since index is based on offset which remains stable regardless of snapshot memory location, it * is used when working in contexts where id is irrelevant. */ #define CLIENT_LOOKUP_BINS 127 #define TICK_LOOKUP_BINS 127 #define ID_LOOKUP_BINS 4096 /* ========================== * * Startup * ========================== */ GLOBAL struct { struct arena *nil_arena; struct sim_client_store *nil_client_store; struct sim_client *nil_client; struct sim_snapshot *nil_snapshot; struct sim_ent *nil_ent; } G = ZI, DEBUG_ALIAS(G, G_sim); /* Accessed via `sim_client_store_nil()` */ READONLY struct sim_client_store **_g_sim_client_store_nil = &G.nil_client_store; /* Accessed via `sim_client_nil()` */ READONLY struct sim_client **_g_sim_client_nil = &G.nil_client; /* Accessed via `sim_snapshot_nil()` */ READONLY struct sim_snapshot **_g_sim_snapshot_nil = &G.nil_snapshot; /* Accessed via `sim_ent_nil()` */ READONLY struct sim_ent **_g_sim_ent_nil = &G.nil_ent; struct sim_startup_receipt sim_startup(void) { G.nil_arena = arena_alloc(GIBI(1)); /* Nil client store */ G.nil_client_store = arena_push(G.nil_arena, struct sim_client_store); G.nil_client_store->valid = 0; /* Nil client */ G.nil_client = arena_push(G.nil_arena, struct sim_client); G.nil_client->valid = 0; G.nil_client->store = sim_client_store_nil(); /* Nil snapshot */ G.nil_snapshot = arena_push(G.nil_arena, struct sim_snapshot); G.nil_snapshot->valid = 0; G.nil_snapshot->client = sim_client_nil(); /* Nil ent */ G.nil_ent = arena_push(G.nil_arena, struct sim_ent); G.nil_ent->ss = sim_snapshot_nil(); G.nil_ent->valid = 0; G.nil_ent->id = SIM_ENT_NIL_ID; G.nil_ent->_local_xform = XFORM_IDENT; G.nil_ent->_xform = XFORM_IDENT; G.nil_ent->_is_xform_dirty = 0; 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; /* Lock nil arena */ arena_set_readonly(G.nil_arena); return (struct sim_startup_receipt) { 0 }; } /* ========================== * * Client store alloc * ========================== */ struct sim_client_store *sim_client_store_alloc(void) { __prof; struct sim_client_store *store; { struct arena *arena = arena_alloc(GIBI(64)); store = arena_push(arena, struct sim_client_store); store->arena = arena; } store->valid = 1; store->num_client_lookup_bins = CLIENT_LOOKUP_BINS; store->client_lookup_bins = arena_push_array(store->arena, struct sim_client_lookup_bin, store->num_client_lookup_bins); store->clients_arena = arena_alloc(GIBI(64)); store->clients = arena_push_dry(store->clients_arena, struct sim_client); return store; } void sim_client_store_release(struct sim_client_store *store) { __prof; for (u64 i = 0; i < store->num_clients_reserved; ++i) { struct sim_client *client = &store->clients[i]; if (client->valid) { sim_client_release(client); } } arena_release(store->clients_arena); arena_release(store->arena); } /* ========================== * * Client alloc * ========================== */ struct sim_client *sim_client_alloc(struct sim_client_store *store) { struct sim_client_handle handle = ZI; 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; } else { client = arena_push_no_zero(store->clients_arena, struct sim_client); handle.gen = 1; handle.idx = store->num_clients_reserved; ++store->num_clients_reserved; } ++store->num_clients_allocated; *client = *sim_client_nil(); client->store = store; client->valid = 1; client->handle = handle; client->snapshots_arena = arena_alloc(GIBI(8)); client->num_snapshot_lookup_bins = TICK_LOOKUP_BINS; client->snapshot_lookup_bins = arena_push_array(client->snapshots_arena, struct sim_snapshot_lookup_bin, client->num_snapshot_lookup_bins); return client; } void sim_client_release(struct sim_client *client) { /* Release internal snapshot memory */ for (u64 i = 0; i < client->num_snapshot_lookup_bins; ++i) { struct sim_snapshot_lookup_bin *bin = &client->snapshot_lookup_bins[i]; struct sim_snapshot *ss = bin->first; while (ss) { struct sim_snapshot *next = ss->next_in_bin; arena_release(ss->ents_arena); arena_release(ss->arena); ss = next; } } /* Remove from channel lookup */ sim_client_set_channel_id(client, HOST_CHANNEL_ID_NIL); /* Release client */ struct sim_client_store *store = client->store; client->valid = 0; client->next_free = store->first_free_client; store->first_free_client = client->handle; --store->num_clients_allocated; ++client->handle.gen; arena_release(client->snapshots_arena); } /* ========================== * * Client lookup * ========================== */ INTERNAL u64 hash_from_channel_id(struct host_channel_id channel_id) { return hash_fnv64(HASH_FNV64_BASIS, STRING_FROM_STRUCT(&channel_id)); } void sim_client_set_channel_id(struct sim_client *client, struct host_channel_id channel_id) { struct sim_client_store *store = client->store; struct host_channel_id old_channel_id = client->channel_id; /* Remove old channel id from channel lookup */ if (!host_channel_id_is_nil(old_channel_id)) { u64 bin_index = client->channel_hash % store->num_client_lookup_bins; struct sim_client_lookup_bin *bin = &store->client_lookup_bins[bin_index]; struct sim_client *prev = sim_client_from_handle(store, client->prev_in_bin); struct sim_client *next = sim_client_from_handle(store, client->next_in_bin); if (prev->valid) { prev->next_in_bin = next->handle; } else { bin->first = next->handle; } if (next->valid) { next->prev_in_bin = prev->handle; } else { bin->last = prev->handle; } } /* Insert into channel lookup */ /* TODO: Enforce no duplicates */ u64 channel_hash = hash_from_channel_id(channel_id); client->channel_id = channel_id; client->channel_hash = channel_hash; if (!host_channel_id_is_nil(channel_id)) { u64 bin_index = channel_hash % store->num_client_lookup_bins; struct sim_client_lookup_bin *bin = &store->client_lookup_bins[bin_index]; { struct sim_client *prev_in_bin = sim_client_from_handle(store, bin->last); if (prev_in_bin->valid) { prev_in_bin->next_in_bin = client->handle; client->prev_in_bin = prev_in_bin->handle; } else { bin->first = client->handle; } bin->last = client->handle; } } } struct sim_client *sim_client_from_channel_id(struct sim_client_store *store, struct host_channel_id channel_id) { struct sim_client *res = sim_client_nil(); u64 channel_hash = hash_from_channel_id(channel_id); u64 bin_index = channel_hash % store->num_client_lookup_bins; struct sim_client_lookup_bin *bin = &store->client_lookup_bins[bin_index]; for (struct sim_client *client = sim_client_from_handle(store, bin->first); client->valid; client = sim_client_from_handle(store, client->next_in_bin)) { if (client->channel_hash == channel_hash) { res = client; break; } } return res; } struct sim_client *sim_client_from_handle(struct sim_client_store *store, struct sim_client_handle handle) { if (handle.gen != 0 && handle.idx < store->num_clients_reserved) { struct sim_client *client = &store->clients[handle.idx]; if (client->handle.gen == handle.gen) { return client; } } return sim_client_nil(); } /* ========================== * * Snapshot alloc * ========================== */ /* Produces a new snapshot at `tick` with data copied from `src` snapshot. */ struct sim_snapshot *sim_snapshot_alloc(struct sim_client *client, struct sim_snapshot *src, u64 tick) { if (tick == 0) { return sim_snapshot_nil(); } struct sim_snapshot *ss; { struct arena *arena = ZI; struct arena *ents_arena = 0; { ss = client->first_free_snapshot; if (ss) { /* Re-use existing snasphot arenas */ client->first_free_snapshot = ss->next_free; ents_arena = ss->ents_arena; arena = ss->arena; } else { /* Arenas allocated here will be released with client */ arena = arena_alloc(GIBI(1)); ents_arena = arena_alloc(GIBI(1)); } } arena_reset(arena); ss = arena_push(arena, struct sim_snapshot); ss->arena = arena; ss->ents_arena = ents_arena; arena_reset(ss->ents_arena); } ss->tick = tick; ss->valid = 1; ss->client = client; ++client->num_ticks; /* Copy src info */ ss->sim_dt_ns = src->sim_dt_ns; ss->sim_time_ns = src->sim_time_ns; ss->continuity_gen = src->continuity_gen; ss->local_player = src->local_player; ss->phys_iteration = src->phys_iteration; /* Copy id lookup bins */ ss->num_id_bins = src->num_id_bins > 0 ? src->num_id_bins : ID_LOOKUP_BINS; ss->id_bins = arena_push_array_no_zero(ss->arena, struct sim_ent_bin, ss->num_id_bins); if (src->num_id_bins > 0) { for (u64 i = 0; i < src->num_id_bins; ++i) { ss->id_bins[i] = src->id_bins[i]; } } else { MEMZERO(ss->id_bins, sizeof(*ss->id_bins) * ss->num_id_bins); } /* 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_no_zero(ss->ents_arena, struct sim_ent, ss->num_ents_reserved); if (ss->num_ents_reserved == 0) { /* Copying from nil snapshot, need to create blank & root entity */ /* Push blank ent at index 0 (because index 0 is never valid anyway since it maps to sim_ent_nil()) */ { arena_push(ss->ents_arena, struct sim_ent); ++ss->num_ents_allocated; ++ss->num_ents_reserved; } /* Push root ent with constant id */ { struct sim_ent *root = arena_push_no_zero(ss->ents_arena, struct sim_ent); *root = *sim_ent_nil(); root->ss = ss; root->valid = 1; root->is_root = 1; root->mass_unscaled = F32_INFINITY; root->inertia_unscaled = F32_INFINITY; sim_ent_set_id(root, SIM_ENT_ROOT_ID); ++ss->num_ents_allocated; ++ss->num_ents_reserved; } } else { for (u64 i = 0; i < ss->num_ents_reserved; ++i) { struct sim_ent *dst_ent = &ss->ents[i]; struct sim_ent *src_ent = &src->ents[i]; *dst_ent = *src_ent; dst_ent->ss = ss; } } /* Release duplicate tick if it exists */ { struct sim_snapshot *existing = sim_snapshot_from_tick(client, tick); if (existing->valid) { sim_snapshot_release(existing); } } /* Linear search to insert snapshot in tick order */ { struct sim_snapshot *prev = sim_snapshot_from_tick(client, client->last_tick); while (prev->valid) { if (prev->tick < tick) { break; } prev = sim_snapshot_from_tick(client, prev->prev_tick); } if (prev->valid) { struct sim_snapshot *next = sim_snapshot_from_tick(client, prev->next_tick); if (next->valid) { next->prev_tick = tick; } else { client->last_tick = tick; } ss->next_tick = prev->next_tick; ss->prev_tick = prev->tick; prev->next_tick = ss->tick; } else { struct sim_snapshot *first = sim_snapshot_from_tick(client, client->first_tick); if (first->valid) { ss->next_tick = first->tick; first->prev_tick = tick; } else { client->last_tick = tick; } ss->next_tick = client->first_tick; client->first_tick = tick; } } /* Insert into lookup */ { u64 bin_index = tick % client->num_snapshot_lookup_bins; struct sim_snapshot_lookup_bin *bin = &client->snapshot_lookup_bins[bin_index]; if (bin->last) { bin->last->next_in_bin = ss; ss->prev_in_bin = bin->last; } else { bin->first = ss; } bin->last = ss; } return ss; } void sim_snapshot_release(struct sim_snapshot *ss) { struct sim_client *client = ss->client; /* Remove from lookup */ { u64 bin_index = ss->tick % client->num_snapshot_lookup_bins; struct sim_snapshot_lookup_bin *bin = &client->snapshot_lookup_bins[bin_index]; struct sim_snapshot *prev = ss->prev_in_bin; struct sim_snapshot *next = ss->next_in_bin; if (prev) { prev->next_in_bin = next; } else { bin->first = next; } if (next) { next->prev_in_bin = prev; } else { bin->last = prev; } } /* Remove from snapshot list */ { struct sim_snapshot *prev = sim_snapshot_from_tick(client, ss->prev_tick); struct sim_snapshot *next = sim_snapshot_from_tick(client, ss->next_tick); if (prev->valid) { prev->next_tick = next->tick; } else { client->first_tick = next->tick; } if (next->valid) { next->prev_tick = prev->tick; } else { client->last_tick = prev->tick; } } ss->valid = 0; ss->next_free = client->first_free_snapshot; client->first_free_snapshot = ss; --client->num_ticks; } /* Release all snapshots for client with tick in range [start, end] */ void sim_snapshot_release_ticks_in_range(struct sim_client *client, u64 start, u64 end) { if (start > end) { u64 swp = start; start = end; end = swp; } struct sim_snapshot *ss = sim_snapshot_from_tick(client, client->first_tick); while (ss->valid) { u64 tick = ss->tick; u64 next_tick = ss->next_tick; if (tick >= start) { if (tick <= end) { sim_snapshot_release(ss); } else { break; } } ss = sim_snapshot_from_tick(client, next_tick); } } /* ========================== * * Snapshot lookup * ========================== */ struct sim_snapshot *sim_snapshot_from_tick(struct sim_client *client, u64 tick) { struct sim_snapshot *ss = sim_snapshot_nil(); if (tick > 0) { u64 bin_index = tick % client->num_snapshot_lookup_bins; struct sim_snapshot_lookup_bin *bin = &client->snapshot_lookup_bins[bin_index]; for (struct sim_snapshot *search = bin->first; search; search = search->next_in_bin) { if (search->tick == tick) { ss = search; break; } } } return ss; } /* Returns the snapshot at nearest valid tick <= supplied tick */ struct sim_snapshot *sim_snapshot_from_closest_tick_lte(struct sim_client *client, u64 tick) { struct sim_snapshot *ss = sim_snapshot_from_tick(client, tick); if (!ss->valid) { /* Degenerate to linear search */ ss = sim_snapshot_from_tick(client, client->last_tick); while (ss->valid) { if (ss->tick <= tick) { break; } ss = sim_snapshot_from_tick(client, ss->prev_tick); } } return ss; } /* Returns the snapshot at nearest valid tick >= supplied tick */ struct sim_snapshot *sim_snapshot_from_closest_tick_gte(struct sim_client *client, u64 tick) { struct sim_snapshot *ss = sim_snapshot_from_tick(client, tick); if (!ss->valid) { /* Degenerate to linear search */ ss = sim_snapshot_from_tick(client, client->first_tick); while (ss->valid) { if (ss->tick >= tick) { break; } ss = sim_snapshot_from_tick(client, ss->next_tick); } } return ss; } /* ========================== * * Tile * ========================== */ struct v2i32 sim_world_tile_index_from_pos(struct v2 pos) { struct v2i32 res = V2I32(pos.x * SIM_TILES_PER_UNIT_SQRT, pos.y * SIM_TILES_PER_UNIT_SQRT); res.x -= pos.x < 0; res.y -= pos.y < 0; return res; } struct v2 sim_pos_from_world_tile_index(struct v2i32 world_tile_index) { struct v2 res = ZI; f32 tile_size = 1.f / SIM_TILES_PER_UNIT_SQRT; res.x = (f32)world_tile_index.x * tile_size; res.y = (f32)world_tile_index.y * tile_size; return res; } struct v2i32 sim_local_tile_index_from_world_tile_index(struct v2i32 world_tile_index) { struct v2i32 res = world_tile_index; res.x += res.x < 0; res.y += res.y < 0; res.x = res.x % SIM_TILES_PER_CHUNK_SQRT; res.y = res.y % SIM_TILES_PER_CHUNK_SQRT; res.x += (world_tile_index.x < 0) * (SIM_TILES_PER_CHUNK_SQRT - 1); res.y += (world_tile_index.y < 0) * (SIM_TILES_PER_CHUNK_SQRT - 1); return res; } struct v2i32 sim_world_tile_index_from_local_tile_index(struct v2i32 tile_chunk_index, struct v2i32 local_tile_index) { struct v2i32 res = ZI; res.x = (tile_chunk_index.x * SIM_TILES_PER_CHUNK_SQRT) + local_tile_index.x; res.y = (tile_chunk_index.y * SIM_TILES_PER_CHUNK_SQRT) + local_tile_index.y; return res; } struct v2i32 sim_tile_chunk_index_from_world_tile_index(struct v2i32 world_tile_index) { struct v2i32 res = world_tile_index; res.x += res.x < 0; res.y += res.y < 0; res.x = res.x / SIM_TILES_PER_CHUNK_SQRT; res.y = res.y / SIM_TILES_PER_CHUNK_SQRT; res.x -= world_tile_index.x < 0; res.y -= world_tile_index.y < 0; return res; } void sim_snapshot_set_tile(struct sim_snapshot *ss, struct v2i32 world_tile_index, enum sim_tile_kind tile_kind) { struct v2i32 chunk_index = sim_tile_chunk_index_from_world_tile_index(world_tile_index); struct sim_ent_id chunk_id = sim_ent_tile_chunk_id_from_tile_chunk_index(chunk_index); struct sim_ent *chunk_ent = sim_ent_from_id(ss, chunk_id); if (!chunk_ent->valid) { struct sim_ent *root = sim_ent_from_id(ss, SIM_ENT_ROOT_ID); chunk_ent = sim_ent_alloc_sync_src_with_id(root, chunk_id); sim_ent_enable_prop(chunk_ent, SEPROP_TILE_CHUNK); chunk_ent->tile_chunk_index = chunk_index; } struct v2i32 local_index = sim_local_tile_index_from_world_tile_index(world_tile_index); chunk_ent->tile_chunk_tiles[local_index.x + (local_index.y * SIM_TILES_PER_CHUNK_SQRT)] = tile_kind; } /* ========================== * * Snapshot lerp * ========================== */ struct sim_snapshot *sim_snapshot_alloc_from_lerp(struct sim_client *client, struct sim_snapshot *ss0, struct sim_snapshot *ss1, f64 blend) { __prof; /* New snapshot will be allocated with same tick as ss0 or ss1, so the result should go into a different client */ ASSERT(ss0->client != client && ss1->client != client); struct sim_snapshot *ss; b32 should_blend = 1; if (ss0->continuity_gen == ss1->continuity_gen && 0 < blend && blend < 1) { ss = sim_snapshot_alloc(client, ss0, ss0->tick); } else if (math_round_to_int64(blend) <= 0) { ss = sim_snapshot_alloc(client, ss0, ss0->tick); should_blend = 0; } else { ss = sim_snapshot_alloc(client, ss1, ss1->tick); should_blend = 0; } if (!ss0->valid || !ss1->valid) { /* New snapshot allocation caused one of the src snapshots original to release. * ss0 & ss1 should be from a separate client than the allocating one. */ ASSERT(0); } if (should_blend) { /* Blend time */ ss->sim_dt_ns = math_lerp_i64(ss0->sim_dt_ns, ss1->sim_dt_ns, blend); ss->sim_time_ns = math_lerp_i64(ss0->sim_time_ns, ss1->sim_time_ns, blend); /* Blend entities */ { __profn("Lerp snapshot entities"); u64 num_entities = min_u64(ss0->num_ents_reserved, ss1->num_ents_reserved); for (u64 i = 0; i < num_entities; ++i) { struct sim_ent *e = &ss->ents[i]; struct sim_ent *e0 = &ss0->ents[i]; struct sim_ent *e1 = &ss1->ents[i]; sim_ent_lerp(e, e0, e1, blend); } } } return ss; } /* ========================== * * Snapshot sync * ========================== */ /* Syncs entity data between snapshots */ void sim_snapshot_sync_ents(struct sim_snapshot *local_ss, struct sim_snapshot *remote_ss, struct sim_ent_id remote_player, u32 sync_flags) { __prof; /* FIXME: Don't trust non-master clients: * - Only sync cmd ents * - Determine new UUIDs for newly created ents */ struct sim_ent *local_root = sim_ent_from_id(local_ss, SIM_ENT_ROOT_ID); struct sim_ent *remote_root = sim_ent_from_id(remote_ss, SIM_ENT_ROOT_ID); /* Create new ents from remote */ for (struct sim_ent *remote_top = sim_ent_from_id(remote_ss, remote_root->first); remote_top->valid; remote_top = sim_ent_from_id(remote_ss, remote_top->next)) { sim_ent_sync_alloc_tree(local_root, remote_top, remote_player); } /* Sync ents with remote, skipping index 0 (nil) & index 1 (root) */ for (u64 i = 2; i < local_ss->num_ents_reserved; ++i) { struct sim_ent *local_ent = &local_ss->ents[i]; if (local_ent->valid && sim_ent_has_prop(local_ent, SEPROP_SYNC_DST)) { b32 should_sync = sim_ent_id_eq(local_ent->owner, remote_player) || sim_ent_id_is_nil(remote_player); if ((sync_flags & SIM_SYNC_FLAG_NOSYNC_PREDICTABLES) && sim_ent_id_eq(local_ent->predictor, local_ss->local_player)) { should_sync = 0; } if (should_sync) { struct sim_ent *remote_ent = sim_ent_from_id(remote_ss, local_ent->id); if (remote_ent->valid) { /* Copy all ent data from remote */ sim_ent_sync(local_ent, remote_ent); } else { /* Remote ent is no longer valid / networked, release it */ sim_ent_enable_prop(local_ent, SEPROP_RELEASE); sim_ent_disable_prop(local_ent, SEPROP_SYNC_DST); } } } } sim_ent_release_all_with_prop(local_ss, SEPROP_RELEASE); } #if 1 /* ========================== * * Snapshot encode * ========================== */ void sim_snapshot_encode(struct bitbuff_writer *bw, struct sim_client *receiver, struct sim_snapshot *ss0, struct sim_snapshot *ss1) { __prof; bw_write_dbg_marker(bw, LIT("SNAPSHOT START")); bw_write_iv(bw, ss1->sim_dt_ns); bw_write_iv(bw, ss1->sim_time_ns); bw_write_uv(bw, ss1->continuity_gen); bw_write_uv(bw, ss1->phys_iteration); bw_write_uid(bw, receiver->player_id.uid); /* Id bins */ /* TODO: Don't encode these */ bw_write_dbg_marker(bw, LIT("SNAPSHOT BINS")); for (u64 i = 0; i < ss1->num_id_bins; ++i) { u32 old_first = 0; u32 old_last = 0; if (i < ss0->num_id_bins) { struct sim_ent_bin *old_bin = &ss0->id_bins[i]; old_first = old_bin->first; old_last = old_bin->last; } struct sim_ent_bin *bin = &ss1->id_bins[i]; b32 first_diff = bin->first != old_first; b32 last_diff = bin->last != old_last; if (first_diff || last_diff) { bw_write_bit(bw, 1); bw_write_uv(bw, i); if (bw_write_bit(bw, first_diff)) { bw_write_uv(bw, bin->first); } if (bw_write_bit(bw, last_diff)) { bw_write_uv(bw, bin->last); } } } bw_write_bit(bw, 0); /* Ents */ bw_write_dbg_marker(bw, LIT("SNAPSHOT NUM ENTS")); if (bw_write_bit(bw, ss1->num_ents_allocated != ss0->num_ents_allocated)) { bw_write_uv(bw, ss1->num_ents_allocated); } if (bw_write_bit(bw, ss1->num_ents_reserved != ss0->num_ents_reserved)) { bw_write_uv(bw, ss1->num_ents_reserved); } bw_write_dbg_marker(bw, LIT("SNAPSHOT ENTS")); bw_write_dbg_marker(bw, STRING_FROM_STRUCT(&ss1->num_ents_reserved)); for (u64 i = 1; i < ss1->num_ents_reserved; ++i) { struct sim_ent *e0 = sim_ent_nil(); if (i < ss0->num_ents_reserved) { e0 = &ss0->ents[i]; } struct sim_ent *e1 = &ss1->ents[i]; sim_ent_encode(bw, e0, e1); } bw_write_dbg_marker(bw, LIT("SNAPSHOT END")); } /* ========================== * * Snapshot decode * ========================== */ void sim_snapshot_decode(struct bitbuff_reader *br, struct sim_snapshot *ss) { __prof; br_read_dbg_marker(br, LIT("SNAPSHOT START")); ss->sim_dt_ns = br_read_iv(br); ss->sim_time_ns = br_read_iv(br); ss->continuity_gen = br_read_uv(br); ss->phys_iteration = br_read_uv(br); ss->local_player = (struct sim_ent_id) { .uid = br_read_uid(br) }; /* Id bins */ /* TODO: Don't decode these, determine them implicitly from decoded ents */ br_read_dbg_marker(br, LIT("SNAPSHOT BINS")); { b32 bin_changed = br_read_bit(br); while (bin_changed) { u32 bin_index = br_read_uv(br); if (bin_index < ss->num_id_bins) { struct sim_ent_bin *bin = &ss->id_bins[bin_index]; if (br_read_bit(br)) { bin->first = br_read_uv(br); } if (br_read_bit(br)) { bin->last = br_read_uv(br); } } else { /* Invalid bin index */ ASSERT(0); } bin_changed = br_read_bit(br); } } /* Ents */ br_read_dbg_marker(br, LIT("SNAPSHOT NUM ENTS")); if (br_read_bit(br)) { ss->num_ents_allocated = br_read_uv(br); } b32 num_ents_reserved_changed = br_read_bit(br); if (num_ents_reserved_changed) { u64 old_num_ents_reserved = ss->num_ents_reserved; ss->num_ents_reserved = br_read_uv(br); i64 reserve_diff = (i64)ss->num_ents_reserved - (i64)old_num_ents_reserved; if (reserve_diff > 0) { arena_push_array_no_zero(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; } } else if (reserve_diff < 0) { /* TODO: Handle this */ /* NOTE: Should be impossible for snasphot reserve count to decrease at the moment */ ASSERT(0); } } br_read_dbg_marker(br, LIT("SNAPSHOT ENTS")); br_read_dbg_marker(br, STRING_FROM_STRUCT(&ss->num_ents_reserved)); for (u64 i = 1; i < ss->num_ents_reserved; ++i) { struct sim_ent *e = &ss->ents[i]; e->ss = ss; sim_ent_decode(br, e); } br_read_dbg_marker(br, LIT("SNAPSHOT END")); } #else /* ========================== * * Snapshot encode * ========================== */ void sim_snapshot_encode(struct bitbuff_writer *bw, struct sim_client *receiver, struct sim_snapshot *ss0, struct sim_snapshot *ss1) { __prof; bw_write_iv(bw, ss1->sim_dt_ns); bw_write_iv(bw, ss1->sim_time_ns); bw_write_uv(bw, ss1->continuity_gen); bw_write_uv(bw, ss1->phys_iteration); bw_write_uid(bw, receiver->player_id.uid); /* Ents */ if (ss1->num_ents_allocated == ss0->num_ents_allocated) { bw_write_bit(bw, 0); } else { bw_write_bit(bw, 1); bw_write_uv(bw, ss1->num_ents_allocated); } if (ss1->num_ents_reserved == ss0->num_ents_reserved) { bw_write_bit(bw, 0); } else { bw_write_bit(bw, 1); bw_write_uv(bw, ss1->num_ents_reserved); } bw_align(bw); 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]; } if (e0->valid != e1->valid) { bw_write_bit(1); bw_write_bit(e1->valid); if (e1->valid) { bw_write_uid(bw, e1->id.uid); } } else { bw_write_bit(0); } if (e1->valid) { struct sim_ent *e1 = &ss1->ents[i]; sim_ent_encode(bw, e0, e1); } } } /* ========================== * * Snapshot decode * ========================== */ struct sim_ent_decode_node { struct string tmp_encoded; }; struct sim_ent_decode_queue { struct sim_ent_decode_node *first; struct sim_ent_decode_node *last; }; void sim_snapshot_decode(struct bitbuff_reader *br, struct sim_snapshot *ss) { __prof; struct arena_temp scratch = scratch_begin_no_conflict(); ss->sim_dt_ns = br_read_iv(br); ss->sim_time_ns = br_read_iv(br); ss->continuity_gen = br_read_uv(br); ss->phys_iteration = br_read_uv(br); ss->local_player = (struct sim_ent_id) { .uid = br_read_uid(br) }; #if 1 if (br_read_bit(br)) { ss->num_ents_allocated = br_read_uv(br); } if (br_read_bit(br)) { u64 old_num_ents_reserved = ss->num_ents_reserved; ss->num_ents_reserved = br_read_uv(br); i64 reserve_diff = (i64)ss->num_ents_reserved - (i64)old_num_ents_reserved; if (reserve_diff > 0) { arena_push_array_no_zero(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; } } } /* Build decode queue */ struct sim_ent_decode_queue queue; b32 should_read_ent = br_read_bit(br); while (should_read_ent) { /* TODO: Delta decode index based on last read index */ u32 index = br_read_uv(br); b32 allocation_changed = br_read_bit(br); b32 released = 0; u32 alloc_parent_index = ZI; struct sim_ent_id alloc_ent_id = ZI; if (allocation_changed) { released = br_read_bit(br); if (released) { struct sim_ent *e = sim_ent_from_index(ss, e); ASSERT(e->valid); /* An entity that we don't have allocated should never have been marked for release */ if (e->valid) { sim_ent_enable_prop(e, SEPROP_RELEASE); } } else { alloc_parent_index = br_read_uv(); alloc_ent_id = sim_ent_id_from_uid(br_read_uid(br)); } } if (!released) { u64 num_ent_bits = br_read_uv(br); struct bitbuff_reader ent_br = br_from_seek_bits(br, num_ent_bits); if (br_num_bits_left(&ent_br) > 0) { struct sim_ent_decode_node *n = arena_push(scratch.arena, struct sim_ent_decode_node); n->is_new = allocation_changed && !released; n->index = index; n->alloc_parent_ndex = alloc_parent_index; n->alloc_ent_id = alloc_ent_id; n->br = ent_br; if (queue.last) { queue.last->next = n; } else { queue.first = n; } queue.last = n; } } should_read_ent = br_read_bit(br); } /* Allocate new ents from decode queue */ for (struct sim_ent_decode_node *n = queue.first; n; n = n->next) { if (n->is_new) { u32 index = n->index; struct sim_ent *parent = sim_ent_from_index(ss, n->alloc_parent_index); ASSERT(!sim_ent_from_index(ss, index)->valid && !sim_ent_from_id(ss, alloc_ent_id)->valid); /* An entity that we have allocated already should never be marked for allocation */ ASSERT(parent->valid); /* Parent for new entity allocation should always be valid */ if (parent->valid && index < ss->num_ents_reserved) { struct sim_ent *ent = &ss->ents[index]; ent->valid = 1; sim_ent_set_id(ent, n->alloc_ent_id); sim_ent_link_parent(parent, ent); } else { /* Received an invalid entity allocation */ ASSERT(0); } } } /* Decode ent data from decode queue */ for (struct sim_ent_decode_node *n = queue.first; n; n = n->next) { struct bitbuff_reader ent_br = n->br; u32 index = n->index; struct sim_ent *e = sim_ent_from_index(ss, index); if (e->valid) { sim_ent_decode(&ent_br, e); } else { /* Received delta for unallocated ent */ ASSERT(0); } } #else /* Ents */ if (br_read_bit(br)) { ss->num_ents_allocated = br_read_uv(br); } if (br_read_bit(br)) { u64 old_num_ents_reserved = ss->num_ents_reserved; ss->num_ents_reserved = br_read_uv(br); i64 reserve_diff = (i64)ss->num_ents_reserved - (i64)old_num_ents_reserved; if (reserve_diff > 0) { arena_push_array_no_zero(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) { b32 allocation_changed = br_read_bit(br); if (allocation_changed) { if (br_read_bit(br)) { struct sim_ent_decode_node *n = arena_push(scratch.arena, struct sim_ent_decode_node) } else { sim_ent_enable_prop(e, SEPROP_RELEASE); } } } for (u64 i = 0; i < ss->num_ents_reserved; ++i) { struct sim_ent *e = &ss->ents[i]; e->ss = ss; b32 valid_changed = br_read_bit(br); b32 allocated = 1; if (valid_changed) { allocated = br_read_bit(br); } if (!allocated) { /* Why is an already released ent being marked as released? */ ASSERT(e->valid); if (e->valid) { sim_ent_enable_prop(e, SEPROP_RELEASE); } } else { sim_ent_decode(br, e); } } sim_ent_release_all_with_prop(ss, SEPROP_RELEASE); #endif scratch_end(scratch); } #endif