1026 lines
33 KiB
C
1026 lines
33 KiB
C
#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(GIGABYTE(1));
|
|
|
|
/* Nil client store */
|
|
G.nil_client_store = arena_push_zero(&G.nil_arena, struct sim_client_store);
|
|
G.nil_client_store->valid = false;
|
|
|
|
/* Nil client */
|
|
G.nil_client = arena_push_zero(&G.nil_arena, struct sim_client);
|
|
G.nil_client->valid = false;
|
|
G.nil_client->store = sim_client_store_nil();
|
|
|
|
/* Nil snapshot */
|
|
G.nil_snapshot = arena_push_zero(&G.nil_arena, struct sim_snapshot);
|
|
G.nil_snapshot->valid = false;
|
|
G.nil_snapshot->client = sim_client_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->id = SIM_ENT_NIL_ID;
|
|
G.nil_ent->_local_xform = XFORM_IDENT;
|
|
G.nil_ent->_xform = XFORM_IDENT;
|
|
G.nil_ent->_is_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;
|
|
|
|
/* 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(GIGABYTE(64));
|
|
store = arena_push_zero(&arena, struct sim_client_store);
|
|
store->arena = arena;
|
|
}
|
|
store->valid = true;
|
|
store->num_client_lookup_bins = CLIENT_LOOKUP_BINS;
|
|
store->client_lookup_bins = arena_push_array_zero(&store->arena, struct sim_client_lookup_bin, store->num_client_lookup_bins);
|
|
store->clients_arena = arena_alloc(GIGABYTE(64));
|
|
store->clients = arena_dry_push(&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(&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 = true;
|
|
client->handle = handle;
|
|
|
|
client->snapshots_arena = arena_alloc(GIGABYTE(8));
|
|
client->num_snapshot_lookup_bins = TICK_LOOKUP_BINS;
|
|
client->snapshot_lookup_bins = arena_push_array_zero(&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 = false;
|
|
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 = ZI;
|
|
{
|
|
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(GIGABYTE(1));
|
|
ents_arena = arena_alloc(GIGABYTE(1));
|
|
}
|
|
}
|
|
arena_reset(&arena);
|
|
ss = arena_push_zero(&arena, struct sim_snapshot);
|
|
ss->arena = arena;
|
|
|
|
ss->ents_arena = ents_arena;
|
|
arena_reset(&ss->ents_arena);
|
|
}
|
|
|
|
ss->tick = tick;
|
|
ss->valid = true;
|
|
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_client_ent = src->local_client_ent;
|
|
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(&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(&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_zero(&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(&ss->ents_arena, struct sim_ent);
|
|
*root = *sim_ent_nil();
|
|
root->ss = ss;
|
|
root->valid = true;
|
|
root->is_root = true;
|
|
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 = false;
|
|
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;
|
|
}
|
|
|
|
/* ========================== *
|
|
* 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 = true;
|
|
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 = false;
|
|
} else {
|
|
ss = sim_snapshot_alloc(client, 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 client than the allocating one. */
|
|
ASSERT(false);
|
|
}
|
|
|
|
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 */
|
|
{
|
|
__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;
|
|
}
|
|
|
|
/* ========================== *
|
|
* 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_client_ent)
|
|
{
|
|
__prof;
|
|
|
|
/* FIXME: Only sync cmds from non-master remote */
|
|
/* FIXME: Only sync ents from correct owner */
|
|
|
|
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_client_ent);
|
|
}
|
|
|
|
/* Sync ents with remote */
|
|
for (u64 i = 0; 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, SIM_ENT_PROP_SYNC_DST)) {
|
|
b32 should_sync = sim_ent_id_eq(local_ent->owner, remote_client_ent) || sim_ent_id_eq(remote_client_ent, SIM_ENT_NIL_ID);
|
|
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, SIM_ENT_PROP_RELEASE);
|
|
sim_ent_disable_prop(local_ent, SIM_ENT_PROP_SYNC_DST);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sim_ent_release_all_with_prop(local_ss, SIM_ENT_PROP_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_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->ent_id.uid);
|
|
|
|
/* Id bins */
|
|
/* TODO: Don't encode these */
|
|
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];
|
|
u32 new_first = bin->first;
|
|
u32 new_last = bin->last;
|
|
if (new_first != old_first || new_last != old_last) {
|
|
bw_write_bit(bw, 1);
|
|
bw_write_uv(bw, i);
|
|
if (old_first == bin->first) {
|
|
bw_write_bit(bw, 0);
|
|
} else {
|
|
bw_write_bit(bw, 1);
|
|
bw_write_uv(bw, bin->first);
|
|
}
|
|
if (old_last == bin->last) {
|
|
bw_write_bit(bw, 0);
|
|
} else {
|
|
bw_write_bit(bw, 1);
|
|
bw_write_uv(bw, bin->last);
|
|
}
|
|
}
|
|
}
|
|
bw_write_bit(bw, 0);
|
|
|
|
/* 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);
|
|
}
|
|
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(bw, e0, e1);
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Snapshot decode
|
|
* ========================== */
|
|
|
|
void sim_snapshot_decode(struct bitbuff_reader *br, struct sim_snapshot *ss)
|
|
{
|
|
__prof;
|
|
|
|
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_client_ent = (struct sim_ent_id) { .uid = br_read_uid(br) };
|
|
|
|
/* Id bins */
|
|
/* TODO: Don't decode these, determine them implicitly from decoded ents */
|
|
{
|
|
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(false);
|
|
}
|
|
|
|
bin_changed = br_read_bit(br);
|
|
}
|
|
}
|
|
|
|
/* 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(&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];
|
|
e->ss = ss;
|
|
sim_ent_decode(br, e);
|
|
}
|
|
}
|
|
|
|
|
|
#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->ent_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 temp_arena 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_client_ent = (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(&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 = false;
|
|
|
|
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, SIM_ENT_PROP_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_zero(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 = true;
|
|
sim_ent_set_id(ent, n->alloc_ent_id);
|
|
sim_ent_link_parent(parent, ent);
|
|
} else {
|
|
/* Received an invalid entity allocation */
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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(false);
|
|
}
|
|
}
|
|
#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(&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_zero(scratch.arena, struct sim_ent_decode_node)
|
|
} else {
|
|
sim_ent_enable_prop(e, SIM_ENT_PROP_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 = true;
|
|
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, SIM_ENT_PROP_RELEASE);
|
|
}
|
|
} else {
|
|
sim_ent_decode(br, e);
|
|
}
|
|
}
|
|
sim_ent_release_all_with_prop(ss, SIM_ENT_PROP_RELEASE);
|
|
#endif
|
|
|
|
scratch_end(scratch);
|
|
}
|
|
#endif
|