//////////////////////////////////////////////////////////// //~ Acquire PP_Ent *PP_AcquireRawEnt(PP_Snapshot *ss, PP_Ent *parent, PP_EntKey key) { Assert(parent->valid); Assert(ss->valid); Assert(ss == parent->ss); PP_Ent *ent; if (ss->first_free_ent > 0 && ss->first_free_ent < ss->num_ents_reserved) { /* Reuse from free list */ ent = &ss->ents[ss->first_free_ent]; ss->first_free_ent = ent->next_free; } else { /* Make new */ ent = PushStructNoZero(ss->ents_arena, PP_Ent); ++ss->num_ents_reserved; } *ent = *PP_NilEnt(); ent->ss = ss; ent->valid = 1; ent->owner = ss->client->player_id; ent->_is_xform_dirty = 1; ++ss->num_ents_allocated; PP_SetEntKey(ent, key); PP_Link(ent, parent); return ent; } /* Acquires a new entity that will not sync */ PP_Ent *PP_AcquireLocalEnt(PP_Ent *parent) { PP_Snapshot *ss = parent->ss; PP_Ent *e = PP_AcquireRawEnt(ss, parent, PP_RandomKey()); e->owner = ss->local_player; return e; } PP_Ent *PP_AcquireLocalEntWithKey(PP_Ent *parent, PP_EntKey key) { PP_Snapshot *ss = parent->ss; PP_Ent *e = PP_AcquireRawEnt(ss, parent, key); e->owner = ss->local_player; return e; } /* Acquires a new entity to be synced to clients */ PP_Ent *PP_AcquireSyncSrcEnt(PP_Ent *parent) { PP_Snapshot *ss = parent->ss; PP_Ent *e = PP_AcquireRawEnt(ss, parent, PP_RandomKey()); PP_EnableProp(e, PP_Prop_SyncSrc); e->owner = ss->local_player; return e; } PP_Ent *PP_AcquireSyncSrcEntWithKey(PP_Ent *parent, PP_EntKey key) { PP_Snapshot *ss = parent->ss; PP_Ent *e = PP_AcquireRawEnt(ss, parent, key); PP_EnableProp(e, PP_Prop_SyncSrc); e->owner = ss->local_player; return e; } /* Acquires a new entity that will sync with incoming net src ents containing key, and coming from the specified owner */ PP_Ent *PP_AcquireSyncDstEnt(PP_Ent *parent, PP_EntKey ent_id, PP_EntKey owner_id) { PP_Snapshot *ss = parent->ss; PP_Ent *e = PP_AcquireRawEnt(ss, parent, ent_id); PP_EnableProp(e, PP_Prop_SyncDst); e->owner = owner_id; return e; } //////////////////////////////////////////////////////////// //~ Release void PP_ReleaseRawEnt(PP_Ent *ent) { PP_Snapshot *ss = ent->ss; /* Release children */ PP_Ent *child = PP_EntFromKey(ss, ent->first); while (child->valid) { PP_Ent *next = PP_EntFromKey(ss, child->next); PP_ReleaseRawEnt(child); child = next; } /* Release uid */ PP_SetEntKey(ent, PP_NilEntKey); /* Release */ ent->valid = 0; ent->next_free = ss->first_free_ent; ss->first_free_ent = PP_IndexFromEnt(ss, ent); --ss->num_ents_allocated; } void PP_Release(PP_Ent *ent) { PP_Snapshot *ss = ent->ss; PP_Ent *parent = PP_EntFromKey(ss, ent->parent); if (parent->valid) { PP_Unlink(ent); } PP_ReleaseRawEnt(ent); } void PP_ReleaseAllWithProp(PP_Snapshot *ss, PP_Prop prop) { TempArena scratch = BeginScratchNoConflict(); PP_Ent **ents_to_release = PushDry(scratch.arena, PP_Ent *); u64 ents_to_release_count = 0; for (u64 ent_index = 0; ent_index < ss->num_ents_reserved; ++ent_index) { PP_Ent *ent = &ss->ents[ent_index]; if (ent->valid && PP_HasProp(ent, prop)) { *PushStructNoZero(scratch.arena, PP_Ent *) = ent; ++ents_to_release_count; } } /* Release from snapshot */ /* TODO: Breadth first iteration to only release parent entities (since * child entities will be released along with parent anyway) */ for (u64 i = 0; i < ents_to_release_count; ++i) { PP_Ent *ent = ents_to_release[i]; if (ent->valid && !ent->is_root && !PP_HasProp(ent, PP_Prop_Cmd) && !PP_HasProp(ent, PP_Prop_Player)) { PP_Release(ent); } } EndScratch(scratch); } //////////////////////////////////////////////////////////// //~ Activate void PP_ActivateEnt(PP_Ent *ent, u64 current_tick) { PP_EnableProp(ent, PP_Prop_Active); ent->activation_tick = current_tick; ++ent->continuity_gen; } //////////////////////////////////////////////////////////// //~ Ent key u32 PP_IndexFromEnt(PP_Snapshot *ss, PP_Ent *ent) { return ent - ss->ents; } PP_Ent *PP_EntFromIndex(PP_Snapshot *ss, u32 index) { if (index > 0 && index < ss->num_ents_reserved) { return &ss->ents[index]; } else { return PP_NilEnt(); } } PP_EntBin *PP_EntBinFromKey(PP_Snapshot *ss, PP_EntKey key) { return &ss->key_bins[key.uid.lo % ss->num_key_bins]; } /* NOTE: This should only really happen during ent allocation (it doesn't make sense for an allocated ent's key to change) */ void PP_SetEntKey(PP_Ent *ent, PP_EntKey key) { PP_Snapshot *ss = ent->ss; PP_EntKey old_id = ent->key; if (!PP_EqEntKey(old_id, key)) { /* Release old from lookup */ if (!PP_IsNilEntKey(old_id)) { PP_EntBin *bin = PP_EntBinFromKey(ss, old_id); u32 prev_index = 0; u32 next_index = 0; u32 search_index = bin->first; PP_Ent *prev = PP_NilEnt(); PP_Ent *next = PP_NilEnt(); PP_Ent *search = PP_EntFromIndex(ss, search_index); while (search->valid) { next_index = search->next_in_id_bin; next = PP_EntFromIndex(ss, next_index); if (PP_EqEntKey(search->key, old_id)) { break; } prev_index = search_index; prev = search; search_index = next_index; search = next; } /* Old key not in bin, this should be impossible. */ Assert(search->valid); if (prev->valid) { prev->next_in_id_bin = next_index; } else { bin->first = next_index; } if (next->valid) { next->prev_in_id_bin = prev_index; } else { bin->last = prev_index; } } /* Insert new key into lookup */ if (!PP_IsNilEntKey(key)) { #if IsRtcEnabled { PP_Ent *existing = PP_EntFromKey(ss, key); /* Collision should be extremely unlikely under normal circumstances, there's probably a logic error somewhere. */ Assert(!existing->valid); } #endif PP_EntBin *bin = PP_EntBinFromKey(ss, key); u32 ent_index = PP_IndexFromEnt(ss, ent); PP_Ent *last = PP_EntFromIndex(ss, bin->last); if (last->valid) { last->next_in_id_bin = ent_index; ent->prev_in_id_bin = bin->last; } else { bin->first = ent_index; ent->prev_in_id_bin = 0; } bin->last = ent_index; } ent->key = key; } } PP_Ent *PP_EntFromKey(PP_Snapshot *ss, PP_EntKey key) { PP_Ent *result = PP_NilEnt(); if (!PP_IsNilEntKey(key) && ss->valid) { PP_EntBin *bin = PP_EntBinFromKey(ss, key); for (PP_Ent *e = PP_EntFromIndex(ss, bin->first); e->valid; e = PP_EntFromIndex(ss, e->next_in_id_bin)) { if (PP_EqEntKey(e->key, key)) { result = e; break; } } } return result; } PP_EntKey PP_RandomKey(void) { PP_EntKey result = ZI; result.uid = UidFromTrueRand(); return result; } /* Returns the deterministic key of the contact constraint ent key that should be produced from e0 & e1 colliding */ PP_EntKey PP_ContactConstraintKeyFromContactingKeys(PP_EntKey player_id, PP_EntKey id0, PP_EntKey id1) { PP_EntKey result = ZI; result.uid = PP_ContactBasisUid; result.uid = CombineUid(result.uid, player_id.uid); result.uid = CombineUid(result.uid, id0.uid); result.uid = CombineUid(result.uid, id1.uid); return result; } /* Returns the deterministic key of the debug contact constraint ent key that should be produced from e0 & e1 colliding */ PP_EntKey PP_CollisionDebugKeyFromKeys(PP_EntKey player_id, PP_EntKey id0, PP_EntKey id1) { PP_EntKey result = ZI; result.uid = PP_CollisionDebugBasisUid; result.uid = CombineUid(result.uid, player_id.uid); result.uid = CombineUid(result.uid, id0.uid); result.uid = CombineUid(result.uid, id1.uid); return result; } /* Returns the deterministic key of the tile chunk that should be produced at chunk pos */ PP_EntKey PP_TileChunkKeyFromIndex(Vec2I32 chunk_index) { PP_EntKey result = ZI; result.uid = PP_TileChunkBasisUid; result.uid = CombineUid(result.uid, UID(RandU64FromSeed(chunk_index.x), RandU64FromSeed(chunk_index.y))); return result; } //////////////////////////////////////////////////////////// //~ Query PP_Ent *PP_FirstWithProp(PP_Snapshot *ss, PP_Prop prop) { u64 count = ss->num_ents_reserved; PP_Ent *entities = ss->ents; for (u64 ent_index = 0; ent_index < count; ++ent_index) { PP_Ent *ent = &entities[ent_index]; if (ent->valid && PP_HasProp(ent, prop)) { return ent; } } return PP_NilEnt(); } PP_Ent *PP_FirstWithAllProps(PP_Snapshot *ss, PP_PropArray props) { u64 count = ss->num_ents_reserved; PP_Ent *entities = ss->ents; for (u64 ent_index = 0; ent_index < count; ++ent_index) { PP_Ent *ent = &entities[ent_index]; if (ent->valid) { b32 all = 1; for (u64 i = 0; i < props.count; ++i) { if (!PP_HasProp(ent, props.props[i])) { all = 0; break; } } if (all) { return ent; } } } return PP_NilEnt(); } //////////////////////////////////////////////////////////// //~ Tree void PP_Link(PP_Ent *ent, PP_Ent *parent) { PP_Snapshot *ss = ent->ss; PP_Ent *old_parent = PP_EntFromKey(ss, ent->parent); if (old_parent->valid) { /* Unlink from current parent */ PP_Unlink(ent); } PP_EntKey ent_id = ent->key; PP_EntKey last_child_id = parent->last; PP_Ent *last_child = PP_EntFromKey(ss, last_child_id); if (last_child->valid) { ent->prev = last_child_id; last_child->next = ent_id; } else { parent->first = ent_id; } parent->last = ent_id; if (parent->is_root) { ent->is_top = 1; ent->top = ent_id; } else { ent->top = parent->top; } ent->parent = parent->key; } /* NOTE: PP_Ent will be dangling after calling this, should re-link to root ent. */ void PP_Unlink(PP_Ent *ent) { PP_Snapshot *ss = ent->ss; PP_EntKey parent_id = ent->parent; PP_Ent *parent = PP_EntFromKey(ss, parent_id); PP_Ent *prev = PP_EntFromKey(ss, ent->prev); PP_Ent *next = PP_EntFromKey(ss, ent->next); /* Unlink from parent & siblings */ if (prev->valid) { prev->next = next->key; } else { parent->first = next->key; } if (next->valid) { next->prev = prev->key; } else { parent->last = prev->key; } ent->prev = PP_NilEntKey; ent->next = PP_NilEntKey; } //////////////////////////////////////////////////////////// //~ Xform void PP_MarkChildXformsDirty(PP_Snapshot *ss, PP_Ent *ent) { for (PP_Ent *child = PP_EntFromKey(ss, ent->first); child->valid; child = PP_EntFromKey(ss, child->next)) { if (child->_is_xform_dirty) { break; } else { child->_is_xform_dirty = 1; PP_MarkChildXformsDirty(ss, child); } } } Xform PP_XformFromEnt_(PP_Snapshot *ss, PP_Ent *ent) { Xform xf; if (ent->_is_xform_dirty) { if (ent->is_top) { xf = ent->_local_xform; } else { PP_Ent *parent = PP_EntFromKey(ss, ent->parent); xf = PP_XformFromEnt_(ss, parent); xf = MulXform(xf, ent->_local_xform); ent->_xform = xf; ent->_is_xform_dirty = 0; } ent->_xform = xf; ent->_is_xform_dirty = 0; } else { xf = ent->_xform; } return xf; } Xform PP_XformFromEnt(PP_Ent *ent) { Xform xf; if (ent->_is_xform_dirty) { if (ent->is_top) { xf = ent->_local_xform; } else { PP_Snapshot *ss = ent->ss; PP_Ent *parent = PP_EntFromKey(ss, ent->parent); xf = PP_XformFromEnt_(ss, parent); xf = MulXform(xf, ent->_local_xform); ent->_xform = xf; ent->_is_xform_dirty = 0; } ent->_xform = xf; ent->_is_xform_dirty = 0; } else { xf = ent->_xform; } return xf; } Xform PP_LocalXformFromEnt(PP_Ent *ent) { return ent->_local_xform; } void PP_SetXform(PP_Ent *ent, Xform xf) { if (!EqXform(xf, ent->_xform)) { PP_Snapshot *ss = ent->ss; /* Update local xform */ if (ent->is_top) { ent->_local_xform = xf; } else { PP_Ent *parent = PP_EntFromKey(ss, ent->parent); Xform parent_global = PP_XformFromEnt_(ss, parent); ent->_local_xform = MulXform(InvertXform(parent_global), xf); } ent->_xform = xf; ent->_is_xform_dirty = 0; PP_MarkChildXformsDirty(ss, ent); } } void PP_SetLocalXform(PP_Ent *ent, Xform xf) { if (!EqXform(xf, ent->_local_xform)) { ent->_local_xform = xf; ent->_is_xform_dirty = 1; PP_MarkChildXformsDirty(ent->ss, ent); } } //////////////////////////////////////////////////////////// //~ Movement void PP_SetLinearVelocity(PP_Ent *ent, Vec2 velocity) { if (PP_HasProp(ent, PP_Prop_Kinematic) || PP_HasProp(ent, PP_Prop_Dynamic)) { ent->linear_velocity = ClampVec2Len(velocity, SIM_MAX_LINEAR_VELOCITY); } } void PP_SetAngularVelocity(PP_Ent *ent, f32 velocity) { if (PP_HasProp(ent, PP_Prop_Kinematic) || PP_HasProp(ent, PP_Prop_Dynamic)) { ent->angular_velocity = ClampF32(velocity, -SIM_MAX_ANGULAR_VELOCITY, SIM_MAX_ANGULAR_VELOCITY); } } void PP_ApplyLinearImpulse(PP_Ent *ent, Vec2 impulse, Vec2 point) { if (PP_HasProp(ent, PP_Prop_Dynamic)) { Xform xf = PP_XformFromEnt(ent); Vec2 center = xf.og; f32 scale = AbsF32(DeterminantFromXform(xf)); f32 inv_mass = 1.f / (ent->mass_unscaled * scale); f32 inv_inertia = 1.f / (ent->inertia_unscaled * scale); Vec2 vcp = SubVec2(point, center); PP_SetLinearVelocity(ent, AddVec2(ent->linear_velocity, MulVec2(impulse, inv_mass))); PP_SetAngularVelocity(ent, WedgeVec2(vcp, impulse) * inv_inertia); } } void PP_ApplyLinearImpulseToCenter(PP_Ent *ent, Vec2 impulse) { if (PP_HasProp(ent, PP_Prop_Dynamic)) { Xform xf = PP_XformFromEnt(ent); f32 scale = AbsF32(DeterminantFromXform(xf)); f32 inv_mass = 1.f / (ent->mass_unscaled * scale); PP_SetLinearVelocity(ent, AddVec2(ent->linear_velocity, MulVec2(impulse, inv_mass))); } } void PP_ApplyForceToCenter(PP_Ent *ent, Vec2 force) { if (PP_HasProp(ent, PP_Prop_Dynamic)) { ent->force = AddVec2(ent->force, force); } } void PP_ApplyAngularImpulse(PP_Ent *ent, f32 impulse) { if (PP_HasProp(ent, PP_Prop_Dynamic)) { Xform xf = PP_XformFromEnt(ent); f32 scale = AbsF32(DeterminantFromXform(xf)); f32 inv_inertia = 1.f / (ent->inertia_unscaled * scale); PP_SetAngularVelocity(ent, ent->angular_velocity + impulse * inv_inertia); } } void PP_ApplyTorque(PP_Ent *ent, f32 torque) { if (PP_HasProp(ent, PP_Prop_Dynamic)) { ent->torque += torque; } } //////////////////////////////////////////////////////////// //~ Tile PP_Ent *PP_TileChunkFromChunkIndex(PP_Snapshot *ss, Vec2I32 chunk_index) { PP_EntKey chunk_id = PP_TileChunkKeyFromIndex(chunk_index); PP_Ent *chunk_ent = PP_EntFromKey(ss, chunk_id); return chunk_ent; } PP_Ent *PP_TileChunkFromWorldTileIndex(PP_Snapshot *ss, Vec2I32 world_tile_index) { Vec2I32 chunk_index = PP_TileChunkIndexFromWorldTileIndex(world_tile_index); PP_Ent *chunk_ent = PP_TileChunkFromChunkIndex(ss, chunk_index); return chunk_ent; } PP_TileKind PP_TileKindFromChunk(PP_Ent *chunk_ent, Vec2I32 local_tile_index) { PP_TileKind result = chunk_ent->tile_chunk_tiles[local_tile_index.x + (local_tile_index.y * SIM_TILES_PER_CHUNK_SQRT)]; return result; } //////////////////////////////////////////////////////////// //~ Lerp void PP_LerpEnt(PP_Ent *e, PP_Ent *e0, PP_Ent *e1, f64 blend) { if (PP_IsValidAndActive(e0) && PP_IsValidAndActive(e1) && PP_EqEntKey(e0->key, e1->key) && e0->continuity_gen == e1->continuity_gen) { e->_local_xform = LerpXform(e0->_local_xform, e1->_local_xform, blend); if (e->is_top) { /* TODO: Cache parent & child xforms in sim */ Xform e0_xf = PP_XformFromEnt(e0); Xform e1_xf = PP_XformFromEnt(e1); PP_SetXform(e, LerpXform(e0_xf, e1_xf, blend)); } e->control_force = LerpF32(e0->control_force, e1->control_force, blend); e->control_torque = LerpF32(e0->control_torque, e1->control_torque, blend); e->linear_velocity = LerpVec2(e0->linear_velocity, e1->linear_velocity, blend); e->angular_velocity = LerpAngleF32(e0->angular_velocity, e1->angular_velocity, blend); e->control.move = LerpVec2(e0->control.move, e1->control.move, blend); e->control.focus = LerpVec2(e0->control.focus, e1->control.focus, blend); e->sprite_local_xform = LerpXform(e0->sprite_local_xform, e1->sprite_local_xform, blend); e->animation_last_frame_change_time_ns = LerpI64(e0->animation_last_frame_change_time_ns, e1->animation_last_frame_change_time_ns, (f64)blend); e->animation_frame = (u32)RoundF32ToI32(LerpF32(e0->animation_frame, e1->animation_frame, blend)); e->camera_quad_xform = LerpXform(e0->camera_quad_xform, e1->camera_quad_xform, blend); e->camera_xform_target = LerpXform(e0->camera_xform_target, e1->camera_xform_target, blend); e->shake = LerpF32(e0->shake, e1->shake, blend); e->tracer_gradient_start = LerpVec2(e0->tracer_gradient_start, e1->tracer_gradient_start, blend); e->tracer_gradient_end = LerpVec2(e0->tracer_gradient_end, e1->tracer_gradient_end, blend); } } //////////////////////////////////////////////////////////// //~ Sync /* Walks a local & remote ent tree and allocates any missing net dst ents from remote src ents */ void PP_CreateMissingEntsFromSnapshots(PP_Ent *local_parent, PP_Ent *remote, PP_EntKey remote_player) { __prof; if (PP_HasProp(remote, PP_Prop_SyncSrc)) { PP_Snapshot *local_ss = local_parent->ss; PP_Snapshot *remote_ss = remote->ss; PP_EntKey key = remote->key; PP_Ent *local_ent = PP_EntFromKey(local_ss, key); if (!local_ent->valid) { local_ent = PP_AcquireSyncDstEnt(local_parent, key, remote_player); } for (PP_Ent *remote_child = PP_EntFromKey(remote_ss, remote->first); remote_child->valid; remote_child = PP_EntFromKey(remote_ss, remote_child->next)) { PP_CreateMissingEntsFromSnapshots(local_ent, remote_child, remote_player); } } } /* Copies data between two synced entities */ void PP_SyncEnt(PP_Ent *local, PP_Ent *remote) { PP_Ent old = *local; CopyStruct(local, remote); /* Why would 2 ents w/ different uids ever be synced? */ Assert(PP_EqEntKey(local->key, old.key)); local->ss = old.ss; local->key = old.key; /* Keep local tree */ local->parent = old.parent; local->prev = old.prev; local->next = old.next; local->first = old.first; local->last = old.last; local->top = old.top; local->owner = old.owner; /* Keep indices */ local->next_in_id_bin = old.next_in_id_bin; local->prev_in_id_bin = old.prev_in_id_bin; local->next_free = old.next_free; PP_DisableProp(local, PP_Prop_SyncSrc); PP_EnableProp(local, PP_Prop_SyncDst); } #if 1 //////////////////////////////////////////////////////////// //~ Encode void PP_EncodeEnt(BB_Writer *bw, PP_Ent *e0, PP_Ent *e1) { PP_Snapshot *ss = e1->ss; /* FIXME: Things like xforms need to be retreived manually rather than memcopied. */ /* TODO: Granular delta encoding */ u64 pos = 0; e1->ss = e0->ss; while (pos < sizeof(*e1)) { u64 chunk_size = MinU64(pos + 8, sizeof(*e1)) - pos; u8 *chunk0 = (u8 *)e0 + pos; u8 *chunk1 = (u8 *)e1 + pos; if (BB_WriteBit(bw, !EqBytes(chunk0, chunk1, chunk_size))) { u64 bits = 0; CopyBytes(&bits, chunk1, chunk_size); BB_WriteUBits(bw, bits, 64); } pos += 8; } e1->ss = ss; } //////////////////////////////////////////////////////////// //~ Decode void PP_DecodeEnt(BB_Reader *br, PP_Ent *e) { PP_Snapshot *old_ss = e->ss; { u64 pos = 0; while (pos < sizeof(*e)) { u8 *chunk = (u8 *)e + pos; if (BB_ReadBit(br)) { u64 chunk_size = MinU64(pos + 8, sizeof(*e)) - pos; u64 bits = BB_ReadUBits(br, 64); CopyBytes(chunk, &bits, chunk_size); } pos += 8; } } e->ss = old_ss; } #else //////////////////////////////////////////////////////////// //~ Encode void PP_EncodeEnt(BB_Writer *bw, PP_Ent *e0, PP_Ent *e1) { PP_Snapshot *ss = e1->ss; /* FIXME: Things like xforms need to be retreived manually rather than memcopied. * This will also be true for things like ent keys once uids are implemented. */ /* TODO: Granular delta encoding */ u64 pos = 0; e1->ss = e0->ss; while (pos < sizeof(*e1)) { u64 chunk_size = MinU64(pos + 8, sizeof(*e1)) - pos; u8 *chunk0 = (u8 *)e0 + pos; u8 *chunk1 = (u8 *)e1 + pos; if (EqBytes(chunk0, chunk1, chunk_size)) { BB_WriteBit(bw, 0); } else { BB_WriteBit(bw, 1); u64 bits = 0; CopyBytes(&bits, chunk1, chunk_size); BB_WriteUBits(bw, bits, 64); } pos += 8; } e1->ss = ss; } //////////////////////////////////////////////////////////// //~ Decode void PP_DecodeEnt(BB_Reader *br, PP_Ent *e) { PP_Ent decoded = *e; { u64 pos = 0; while (pos < sizeof(decoded)) { u8 *chunk = (u8 *)&decoded + pos; if (BB_ReadBit(br)) { u64 chunk_size = MinU64(pos + 8, sizeof(decoded)) - pos; u64 bits = BB_ReadUBits(br, 64); CopyBytes(chunk, &bits, chunk_size); } pos += 8; } } decoded.ss = e->ss; PP_EntKey old_id = e->key; PP_EntKey new_id = decoded.key; CopyStruct(e, &decoded); e->key = old_id; if (!PP_EqEntKey(old_id, new_id)) { PP_SetEntKey(e, new_id); } } #endif