power_play/src/sim/sim_core.c
2025-07-30 17:54:31 -05:00

1105 lines
34 KiB
C

/* 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 {
Arena *nil_arena;
ClientStore *nil_client_store;
Client *nil_client;
Snapshot *nil_snapshot;
Ent *nil_ent;
} G = ZI, DebugAlias(G, G_sim);
/* Accessed via `sim_client_store_nil()` */
Readonly ClientStore **_g_sim_client_store_nil = &G.nil_client_store;
/* Accessed via `sim_client_nil()` */
Readonly Client **_g_sim_client_nil = &G.nil_client;
/* Accessed via `sim_snapshot_nil()` */
Readonly Snapshot **_g_sim_snapshot_nil = &G.nil_snapshot;
/* Accessed via `sim_ent_nil()` */
Readonly Ent **_g_sim_ent_nil = &G.nil_ent;
SimStartupReceipt sim_startup(void)
{
__prof;
G.nil_arena = AllocArena(Gibi(1));
/* Nil client store */
G.nil_client_store = PushStruct(G.nil_arena, ClientStore);
G.nil_client_store->valid = 0;
/* Nil client */
G.nil_client = PushStruct(G.nil_arena, Client);
G.nil_client->valid = 0;
G.nil_client->store = sim_client_store_nil();
/* Nil snapshot */
G.nil_snapshot = PushStruct(G.nil_arena, Snapshot);
G.nil_snapshot->valid = 0;
G.nil_snapshot->client = sim_client_nil();
/* Nil ent */
G.nil_ent = PushStruct(G.nil_arena, 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 = XformIdentity;
G.nil_ent->_xform = XformIdentity;
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 = XformIdentity;
G.nil_ent->sprite_tint = ColorWhite;
/* Lock nil arena */
SetArenaReadonly(G.nil_arena);
return (SimStartupReceipt) { 0 };
}
/* ========================== *
* Client store alloc
* ========================== */
ClientStore *sim_client_store_alloc(void)
{
__prof;
ClientStore *store;
{
Arena *arena = AllocArena(Gibi(64));
store = PushStruct(arena, ClientStore);
store->arena = arena;
}
store->valid = 1;
store->num_client_lookup_bins = CLIENT_LOOKUP_BINS;
store->client_lookup_bins = PushStructs(store->arena, ClientLookupBin, store->num_client_lookup_bins);
store->clients_arena = AllocArena(Gibi(64));
store->clients = PushDry(store->clients_arena, Client);
return store;
}
void sim_client_store_release(ClientStore *store)
{
__prof;
for (u64 i = 0; i < store->num_clients_reserved; ++i) {
Client *client = &store->clients[i];
if (client->valid) {
sim_client_release(client);
}
}
ReleaseArena(store->clients_arena);
ReleaseArena(store->arena);
}
/* ========================== *
* Client alloc
* ========================== */
Client *sim_client_alloc(ClientStore *store)
{
ClientHandle handle = ZI;
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 = PushStructNoZero(store->clients_arena, 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 = AllocArena(Gibi(8));
client->num_snapshot_lookup_bins = TICK_LOOKUP_BINS;
client->snapshot_lookup_bins = PushStructs(client->snapshots_arena, SnapshotLookupBin, client->num_snapshot_lookup_bins);
return client;
}
void sim_client_release(Client *client)
{
/* Release internal snapshot memory */
for (u64 i = 0; i < client->num_snapshot_lookup_bins; ++i) {
SnapshotLookupBin *bin = &client->snapshot_lookup_bins[i];
Snapshot *ss = bin->first;
while (ss) {
Snapshot *next = ss->next_in_bin;
ReleaseArena(ss->ents_arena);
ReleaseArena(ss->arena);
ss = next;
}
}
/* Remove from channel lookup */
sim_client_set_channel_id(client, HOST_CHANNEL_ID_NIL);
/* Release client */
ClientStore *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;
ReleaseArena(client->snapshots_arena);
}
/* ========================== *
* Client lookup
* ========================== */
internal u64 hash_from_channel_id(N_ChannelId channel_id)
{
return HashFnv64(Fnv64Basis, StringFromStruct(&channel_id));
}
void sim_client_set_channel_id(Client *client, N_ChannelId channel_id)
{
ClientStore *store = client->store;
N_ChannelId 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;
ClientLookupBin *bin = &store->client_lookup_bins[bin_index];
Client *prev = sim_client_from_handle(store, client->prev_in_bin);
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;
ClientLookupBin *bin = &store->client_lookup_bins[bin_index];
{
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;
}
}
}
Client *sim_client_from_channel_id(ClientStore *store, N_ChannelId channel_id)
{
Client *result = sim_client_nil();
u64 channel_hash = hash_from_channel_id(channel_id);
u64 bin_index = channel_hash % store->num_client_lookup_bins;
ClientLookupBin *bin = &store->client_lookup_bins[bin_index];
for (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) {
result = client;
break;
}
}
return result;
}
Client *sim_client_from_handle(ClientStore *store, ClientHandle handle)
{
if (handle.gen != 0 && handle.idx < store->num_clients_reserved) {
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. */
Snapshot *sim_snapshot_alloc(Client *client, Snapshot *src, u64 tick)
{
if (tick == 0) {
return sim_snapshot_nil();
}
Snapshot *ss;
{
Arena *arena = ZI;
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 = AllocArena(Gibi(1));
ents_arena = AllocArena(Gibi(1));
}
}
ResetArena(arena);
ss = PushStruct(arena, Snapshot);
ss->arena = arena;
ss->ents_arena = ents_arena;
ResetArena(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 = PushStructsNoZero(ss->arena, EntBin, 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 {
ZeroBytes(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 = PushStructsNoZero(ss->ents_arena, Ent, ss->num_ents_reserved);
if (ss->num_ents_reserved == 0) {
/* Copying from nil snapshot, need to create blank & root entity */
/* PushStruct blank ent at index 0 (because index 0 is never valid anyway since it maps to sim_ent_nil()) */
{
PushStruct(ss->ents_arena, Ent);
++ss->num_ents_allocated;
++ss->num_ents_reserved;
}
/* PushStruct root ent with constant id */
{
Ent *root = PushStructNoZero(ss->ents_arena, Ent);
*root = *sim_ent_nil();
root->ss = ss;
root->valid = 1;
root->is_root = 1;
root->mass_unscaled = F32Infinity;
root->inertia_unscaled = F32Infinity;
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) {
Ent *dst_ent = &ss->ents[i];
Ent *src_ent = &src->ents[i];
*dst_ent = *src_ent;
dst_ent->ss = ss;
}
}
/* Release duplicate tick if it exists */
{
Snapshot *existing = sim_snapshot_from_tick(client, tick);
if (existing->valid) {
sim_snapshot_release(existing);
}
}
/* Linear search to insert snapshot in tick order */
{
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) {
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 {
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;
SnapshotLookupBin *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(Snapshot *ss)
{
Client *client = ss->client;
/* Remove from lookup */
{
u64 bin_index = ss->tick % client->num_snapshot_lookup_bins;
SnapshotLookupBin *bin = &client->snapshot_lookup_bins[bin_index];
Snapshot *prev = ss->prev_in_bin;
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 */
{
Snapshot *prev = sim_snapshot_from_tick(client, ss->prev_tick);
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(Client *client, u64 start, u64 end)
{
if (start > end) {
u64 swp = start;
start = end;
end = swp;
}
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
* ========================== */
Snapshot *sim_snapshot_from_tick(Client *client, u64 tick)
{
Snapshot *ss = sim_snapshot_nil();
if (tick > 0) {
u64 bin_index = tick % client->num_snapshot_lookup_bins;
SnapshotLookupBin *bin = &client->snapshot_lookup_bins[bin_index];
for (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 */
Snapshot *sim_snapshot_from_closest_tick_lte(Client *client, u64 tick)
{
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 */
Snapshot *sim_snapshot_from_closest_tick_gte(Client *client, u64 tick)
{
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
* ========================== */
Vec2I32 sim_world_tile_index_from_pos(Vec2 pos)
{
Vec2I32 result = VEC2I32(pos.x * SIM_TILES_PER_UNIT_SQRT, pos.y * SIM_TILES_PER_UNIT_SQRT);
result.x -= pos.x < 0;
result.y -= pos.y < 0;
return result;
}
Vec2 sim_pos_from_world_tile_index(Vec2I32 world_tile_index)
{
Vec2 result = ZI;
f32 tile_size = 1.f / SIM_TILES_PER_UNIT_SQRT;
result.x = (f32)world_tile_index.x * tile_size;
result.y = (f32)world_tile_index.y * tile_size;
return result;
}
Vec2I32 sim_local_tile_index_from_world_tile_index(Vec2I32 world_tile_index)
{
Vec2I32 result = world_tile_index;
result.x += result.x < 0;
result.y += result.y < 0;
result.x = result.x % SIM_TILES_PER_CHUNK_SQRT;
result.y = result.y % SIM_TILES_PER_CHUNK_SQRT;
result.x += (world_tile_index.x < 0) * (SIM_TILES_PER_CHUNK_SQRT - 1);
result.y += (world_tile_index.y < 0) * (SIM_TILES_PER_CHUNK_SQRT - 1);
return result;
}
Vec2I32 sim_world_tile_index_from_local_tile_index(Vec2I32 tile_chunk_index, Vec2I32 local_tile_index)
{
Vec2I32 result = ZI;
result.x = (tile_chunk_index.x * SIM_TILES_PER_CHUNK_SQRT) + local_tile_index.x;
result.y = (tile_chunk_index.y * SIM_TILES_PER_CHUNK_SQRT) + local_tile_index.y;
return result;
}
Vec2I32 sim_tile_chunk_index_from_world_tile_index(Vec2I32 world_tile_index)
{
Vec2I32 result = world_tile_index;
result.x += result.x < 0;
result.y += result.y < 0;
result.x = result.x / SIM_TILES_PER_CHUNK_SQRT;
result.y = result.y / SIM_TILES_PER_CHUNK_SQRT;
result.x -= world_tile_index.x < 0;
result.y -= world_tile_index.y < 0;
return result;
}
void sim_snapshot_set_tile(Snapshot *ss, Vec2I32 world_tile_index, TileKind tile_kind)
{
Vec2I32 chunk_index = sim_tile_chunk_index_from_world_tile_index(world_tile_index);
EntId chunk_id = sim_ent_tile_chunk_id_from_tile_chunk_index(chunk_index);
Ent *chunk_ent = sim_ent_from_id(ss, chunk_id);
if (!chunk_ent->valid) {
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;
}
Vec2I32 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
* ========================== */
Snapshot *sim_snapshot_alloc_from_lerp(Client *client, Snapshot *ss0, 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);
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 (RoundF64ToI64(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 = LerpI64(ss0->sim_dt_ns, ss1->sim_dt_ns, blend);
ss->sim_time_ns = LerpI64(ss0->sim_time_ns, ss1->sim_time_ns, blend);
/* Blend entities */
{
__profn("Lerp snapshot entities");
u64 num_entities = MinU64(ss0->num_ents_reserved, ss1->num_ents_reserved);
for (u64 i = 0; i < num_entities; ++i) {
Ent *e = &ss->ents[i];
Ent *e0 = &ss0->ents[i];
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(Snapshot *local_ss, Snapshot *remote_ss, EntId remote_player, u32 sync_flags)
{
__prof;
/* FIXME: Don't trust non-master clients:
* - Only sync cmd ents
* - Determine new UUids for newly created ents
*/
Ent *local_root = sim_ent_from_id(local_ss, SIM_ENT_ROOT_ID);
Ent *remote_root = sim_ent_from_id(remote_ss, SIM_ENT_ROOT_ID);
/* Create new ents from remote */
for (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) {
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) {
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(BB_Writer *bw, Client *receiver, Snapshot *ss0, Snapshot *ss1)
{
__prof;
BB_WriteDebugMarker(bw, Lit("SNAPSHOT START"));
BB_WriteIV(bw, ss1->sim_dt_ns);
BB_WriteIV(bw, ss1->sim_time_ns);
BB_WriteUV(bw, ss1->continuity_gen);
BB_WriteUV(bw, ss1->phys_iteration);
BB_WriteUid(bw, receiver->player_id.uid);
/* Id bins */
/* TODO: Don't encode these */
BB_WriteDebugMarker(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) {
EntBin *old_bin = &ss0->id_bins[i];
old_first = old_bin->first;
old_last = old_bin->last;
}
EntBin *bin = &ss1->id_bins[i];
b32 first_diff = bin->first != old_first;
b32 last_diff = bin->last != old_last;
if (first_diff || last_diff) {
BB_WriteBit(bw, 1);
BB_WriteUV(bw, i);
if (BB_WriteBit(bw, first_diff)) {
BB_WriteUV(bw, bin->first);
}
if (BB_WriteBit(bw, last_diff)) {
BB_WriteUV(bw, bin->last);
}
}
}
BB_WriteBit(bw, 0);
/* Ents */
BB_WriteDebugMarker(bw, Lit("SNAPSHOT NUM ENTS"));
if (BB_WriteBit(bw, ss1->num_ents_allocated != ss0->num_ents_allocated)) {
BB_WriteUV(bw, ss1->num_ents_allocated);
}
if (BB_WriteBit(bw, ss1->num_ents_reserved != ss0->num_ents_reserved)) {
BB_WriteUV(bw, ss1->num_ents_reserved);
}
BB_WriteDebugMarker(bw, Lit("SNAPSHOT ENTS"));
BB_WriteDebugMarker(bw, StringFromStruct(&ss1->num_ents_reserved));
for (u64 i = 1; i < ss1->num_ents_reserved; ++i) {
Ent *e0 = sim_ent_nil();
if (i < ss0->num_ents_reserved) {
e0 = &ss0->ents[i];
}
Ent *e1 = &ss1->ents[i];
sim_ent_encode(bw, e0, e1);
}
BB_WriteDebugMarker(bw, Lit("SNAPSHOT END"));
}
/* ========================== *
* Snapshot decode
* ========================== */
void sim_snapshot_decode(BB_Reader *br, Snapshot *ss)
{
__prof;
BB_ReadDebugMarker(br, Lit("SNAPSHOT START"));
ss->sim_dt_ns = BB_ReadIV(br);
ss->sim_time_ns = BB_ReadIV(br);
ss->continuity_gen = BB_ReadUV(br);
ss->phys_iteration = BB_ReadUV(br);
ss->local_player = (EntId) { .uid = BB_ReadUid(br) };
/* Id bins */
/* TODO: Don't decode these, determine them implicitly from decoded ents */
BB_ReadDebugMarker(br, Lit("SNAPSHOT BINS"));
{
b32 bin_changed = BB_ReadBit(br);
while (bin_changed) {
u32 bin_index = BB_ReadUV(br);
if (bin_index < ss->num_id_bins) {
EntBin *bin = &ss->id_bins[bin_index];
if (BB_ReadBit(br)) {
bin->first = BB_ReadUV(br);
}
if (BB_ReadBit(br)) {
bin->last = BB_ReadUV(br);
}
} else {
/* Invalid bin index */
Assert(0);
}
bin_changed = BB_ReadBit(br);
}
}
/* Ents */
BB_ReadDebugMarker(br, Lit("SNAPSHOT NUM ENTS"));
if (BB_ReadBit(br)) {
ss->num_ents_allocated = BB_ReadUV(br);
}
b32 num_ents_reserved_changed = BB_ReadBit(br);
if (num_ents_reserved_changed) {
u64 old_num_ents_reserved = ss->num_ents_reserved;
ss->num_ents_reserved = BB_ReadUV(br);
i64 reserve_diff = (i64)ss->num_ents_reserved - (i64)old_num_ents_reserved;
if (reserve_diff > 0) {
PushStructsNoZero(ss->ents_arena, Ent, reserve_diff);
for (u64 i = old_num_ents_reserved; i < ss->num_ents_reserved; ++i) {
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);
}
}
BB_ReadDebugMarker(br, Lit("SNAPSHOT ENTS"));
BB_ReadDebugMarker(br, StringFromStruct(&ss->num_ents_reserved));
for (u64 i = 1; i < ss->num_ents_reserved; ++i) {
Ent *e = &ss->ents[i];
e->ss = ss;
sim_ent_decode(br, e);
}
BB_ReadDebugMarker(br, Lit("SNAPSHOT END"));
}
#else
/* ========================== *
* Snapshot encode
* ========================== */
void sim_snapshot_encode(BB_Writer *bw, Client *receiver, Snapshot *ss0, Snapshot *ss1)
{
__prof;
BB_WriteIV(bw, ss1->sim_dt_ns);
BB_WriteIV(bw, ss1->sim_time_ns);
BB_WriteUV(bw, ss1->continuity_gen);
BB_WriteUV(bw, ss1->phys_iteration);
BB_WriteUid(bw, receiver->player_id.uid);
/* Ents */
if (ss1->num_ents_allocated == ss0->num_ents_allocated) {
BB_WriteBit(bw, 0);
} else {
BB_WriteBit(bw, 1);
BB_WriteUV(bw, ss1->num_ents_allocated);
}
if (ss1->num_ents_reserved == ss0->num_ents_reserved) {
BB_WriteBit(bw, 0);
} else {
BB_WriteBit(bw, 1);
BB_WriteUV(bw, ss1->num_ents_reserved);
}
BB_AlignWriter(bw);
for (u64 i = 0; i < ss1->num_ents_reserved; ++i) {
Ent *e0 = sim_ent_nil();
if (i < ss0->num_ents_reserved) {
e0 = &ss0->ents[i];
}
if (e0->valid != e1->valid) {
BB_WriteBit(1);
BB_WriteBit(e1->valid);
if (e1->valid) {
BB_WriteUid(bw, e1->id.uid);
}
} else {
BB_WriteBit(0);
}
if (e1->valid) {
Ent *e1 = &ss1->ents[i];
sim_ent_encode(bw, e0, e1);
}
}
}
/* ========================== *
* Snapshot decode
* ========================== */
struct sim_ent_decode_node {
String tmp_encoded;
};
struct sim_ent_decode_queue {
struct sim_ent_decode_node *first;
struct sim_ent_decode_node *last;
};
void sim_snapshot_decode(BB_Reader *br, Snapshot *ss)
{
__prof;
TempArena scratch = BeginScratchNoConflict();
ss->sim_dt_ns = BB_ReadIV(br);
ss->sim_time_ns = BB_ReadIV(br);
ss->continuity_gen = BB_ReadUV(br);
ss->phys_iteration = BB_ReadUV(br);
ss->local_player = (EntId) { .uid = BB_ReadUid(br) };
#if 1
if (BB_ReadBit(br)) {
ss->num_ents_allocated = BB_ReadUV(br);
}
if (BB_ReadBit(br)) {
u64 old_num_ents_reserved = ss->num_ents_reserved;
ss->num_ents_reserved = BB_ReadUV(br);
i64 reserve_diff = (i64)ss->num_ents_reserved - (i64)old_num_ents_reserved;
if (reserve_diff > 0) {
PushStructsNoZero(ss->ents_arena, Ent, reserve_diff);
for (u64 i = old_num_ents_reserved; i < ss->num_ents_reserved; ++i) {
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 = BB_ReadBit(br);
while (should_read_ent) {
/* TODO: Delta decode index based on last read index */
u32 index = BB_ReadUV(br);
b32 allocation_changed = BB_ReadBit(br);
b32 released = 0;
u32 alloc_parent_index = ZI;
EntId alloc_ent_id = ZI;
if (allocation_changed) {
released = BB_ReadBit(br);
if (released) {
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 = BB_ReadUV();
alloc_ent_id = sim_ent_id_from_uid(BB_ReadUid(br));
}
}
if (!released) {
u64 num_ent_bits = BB_ReadUV(br);
BB_Reader ent_br = br_from_seek_bits(br, num_ent_bits);
if (BB_NumBitsRemaining(&ent_br) > 0) {
struct sim_ent_decode_node *n = PushStruct(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 = BB_ReadBit(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;
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) {
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) {
BB_Reader ent_br = n->br;
u32 index = n->index;
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 (BB_ReadBit(br)) {
ss->num_ents_allocated = BB_ReadUV(br);
}
if (BB_ReadBit(br)) {
u64 old_num_ents_reserved = ss->num_ents_reserved;
ss->num_ents_reserved = BB_ReadUV(br);
i64 reserve_diff = (i64)ss->num_ents_reserved - (i64)old_num_ents_reserved;
if (reserve_diff > 0) {
PushStructsNoZero(ss->ents_arena, Ent, reserve_diff);
for (u64 i = old_num_ents_reserved; i < ss->num_ents_reserved; ++i) {
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 = BB_ReadBit(br);
if (allocation_changed) {
if (BB_ReadBit(br)) {
struct sim_ent_decode_node *n = PushStruct(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) {
Ent *e = &ss->ents[i];
e->ss = ss;
b32 valid_changed = BB_ReadBit(br);
b32 allocated = 1;
if (valid_changed) {
allocated = BB_ReadBit(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
EndScratch(scratch);
}
#endif