power_play/src/pp/pp_ent.c
2025-08-05 17:00:01 -05:00

872 lines
22 KiB
C

////////////////////////////////
//~ Acquire
Entity *AcquireRaw(Snapshot *ss, Entity *parent, EntityId id)
{
Assert(parent->valid);
Assert(ss->valid);
Assert(ss == parent->ss);
Entity *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, Entity);
++ss->num_ents_reserved;
}
*ent = *NilEntity();
ent->ss = ss;
ent->valid = 1;
ent->owner = ss->client->player_id;
ent->_is_xform_dirty = 1;
++ss->num_ents_allocated;
SetEntityId(ent, id);
Link(ent, parent);
return ent;
}
/* Acquires a new entity that will not sync */
Entity *AcquireLocal(Entity *parent)
{
Snapshot *ss = parent->ss;
Entity *e = AcquireRaw(ss, parent, RandomId());
e->owner = ss->local_player;
return e;
}
Entity *AcquireLocalWithId(Entity *parent, EntityId id)
{
Snapshot *ss = parent->ss;
Entity *e = AcquireRaw(ss, parent, id);
e->owner = ss->local_player;
return e;
}
/* Acquires a new entity to be synced to clients */
Entity *AcquireSyncSrc(Entity *parent)
{
Snapshot *ss = parent->ss;
Entity *e = AcquireRaw(ss, parent, RandomId());
EnableProp(e, Prop_SyncSrc);
e->owner = ss->local_player;
return e;
}
Entity *AcquireSyncSrcWithId(Entity *parent, EntityId id)
{
Snapshot *ss = parent->ss;
Entity *e = AcquireRaw(ss, parent, id);
EnableProp(e, Prop_SyncSrc);
e->owner = ss->local_player;
return e;
}
/* Acquires a new entity that will sync with incoming net src ents containing id, and coming from the specified owner */
Entity *AcquireSyncDst(Entity *parent, EntityId ent_id, EntityId owner_id)
{
Snapshot *ss = parent->ss;
Entity *e = AcquireRaw(ss, parent, ent_id);
EnableProp(e, Prop_SyncDst);
e->owner = owner_id;
return e;
}
////////////////////////////////
//~ Release
void ReleaseRaw(Entity *ent)
{
Snapshot *ss = ent->ss;
/* Release children */
Entity *child = EntityFromId(ss, ent->first);
while (child->valid)
{
Entity *next = EntityFromId(ss, child->next);
ReleaseRaw(child);
child = next;
}
/* Release uid */
SetEntityId(ent, NilEntityId);
/* Release */
ent->valid = 0;
ent->next_free = ss->first_free_ent;
ss->first_free_ent = IndexFromEntity(ss, ent);
--ss->num_ents_allocated;
}
void Release(Entity *ent)
{
Snapshot *ss = ent->ss;
Entity *parent = EntityFromId(ss, ent->parent);
if (parent->valid)
{
Unlink(ent);
}
ReleaseRaw(ent);
}
void ReleaseAllWithProp(Snapshot *ss, Prop prop)
{
TempArena scratch = BeginScratchNoConflict();
Entity **ents_to_release = PushDry(scratch.arena, Entity *);
u64 ents_to_release_count = 0;
for (u64 ent_index = 0; ent_index < ss->num_ents_reserved; ++ent_index)
{
Entity *ent = &ss->ents[ent_index];
if (ent->valid && HasProp(ent, prop))
{
*PushStructNoZero(scratch.arena, Entity *) = 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)
{
Entity *ent = ents_to_release[i];
if (ent->valid && !ent->is_root && !HasProp(ent, Prop_Cmd) && !HasProp(ent, Prop_Player))
{
Release(ent);
}
}
EndScratch(scratch);
}
////////////////////////////////
//~ Activate
void Activate(Entity *ent, u64 current_tick)
{
EnableProp(ent, Prop_Active);
ent->activation_tick = current_tick;
++ent->continuity_gen;
}
////////////////////////////////
//~ Entity id
u32 IndexFromEntity(Snapshot *ss, Entity *ent)
{
return ent - ss->ents;
}
Entity *EntityFromIndex(Snapshot *ss, u32 index)
{
if (index > 0 && index < ss->num_ents_reserved)
{
return &ss->ents[index];
}
else
{
return NilEntity();
}
}
EntBin *BinFromId(Snapshot *ss, EntityId id)
{
return &ss->id_bins[id.uid.lo % ss->num_id_bins];
}
/* NOTE: This should only really happen during ent allocation (it doesn't make sense for an allocated ent's id to change) */
void SetEntityId(Entity *ent, EntityId id)
{
Snapshot *ss = ent->ss;
EntityId old_id = ent->id;
if (!EqId(old_id, id))
{
/* Release old from lookup */
if (!IsNilId(old_id))
{
EntBin *bin = BinFromId(ss, old_id);
u32 prev_index = 0;
u32 next_index = 0;
u32 search_index = bin->first;
Entity *prev = NilEntity();
Entity *next = NilEntity();
Entity *search = EntityFromIndex(ss, search_index);
while (search->valid)
{
next_index = search->next_in_id_bin;
next = EntityFromIndex(ss, next_index);
if (EqId(search->id, old_id))
{
break;
}
prev_index = search_index;
prev = search;
search_index = next_index;
search = next;
}
/* Old id 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 id into lookup */
if (!IsNilId(id))
{
#if RtcIsEnabled
{
Entity *existing = EntityFromId(ss, id);
/* Collision should be extremely unlikely under normal circumstances, there's probably a logic error somewhere. */
Assert(!existing->valid);
}
#endif
EntBin *bin = BinFromId(ss, id);
u32 ent_index = IndexFromEntity(ss, ent);
Entity *last = EntityFromIndex(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->id = id;
}
}
Entity *EntityFromId(Snapshot *ss, EntityId id)
{
Entity *result = NilEntity();
if (!IsNilId(id) && ss->valid)
{
EntBin *bin = BinFromId(ss, id);
for (Entity *e = EntityFromIndex(ss, bin->first); e->valid; e = EntityFromIndex(ss, e->next_in_id_bin))
{
if (EqId(e->id, id))
{
result = e;
break;
}
}
}
return result;
}
EntityId RandomId(void)
{
EntityId result = ZI;
result.uid = UidFromTrueRand();
return result;
}
/* Returns the deterministic id of the contact constraint ent id that should be produced from e0 & e1 colliding */
EntityId ContactConstraintIdFromContactingIds(EntityId player_id, EntityId id0, EntityId id1)
{
EntityId result = ZI;
result.uid = 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 id of the debug contact constraint ent id that should be produced from e0 & e1 colliding */
EntityId CollisionDebugIdFromIds(EntityId player_id, EntityId id0, EntityId id1)
{
EntityId result = ZI;
result.uid = 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 id of the tile chunk that should be produced at chunk pos */
EntityId TileChunkIdFromIndex(Vec2I32 chunk_index)
{
EntityId result = ZI;
result.uid = TileChunkBasisUid;
result.uid = CombineUid(result.uid, UID(RandU64FromSeed(chunk_index.x), RandU64FromSeed(chunk_index.y)));
return result;
}
////////////////////////////////
//~ Query
Entity *FirstWithProp(Snapshot *ss, Prop prop)
{
u64 count = ss->num_ents_reserved;
Entity *entities = ss->ents;
for (u64 ent_index = 0; ent_index < count; ++ent_index)
{
Entity *ent = &entities[ent_index];
if (ent->valid && HasProp(ent, prop))
{
return ent;
}
}
return NilEntity();
}
Entity *FirstWithAllProps(Snapshot *ss, PropArray props)
{
u64 count = ss->num_ents_reserved;
Entity *entities = ss->ents;
for (u64 ent_index = 0; ent_index < count; ++ent_index)
{
Entity *ent = &entities[ent_index];
if (ent->valid)
{
b32 all = 1;
for (u64 i = 0; i < props.count; ++i)
{
if (!HasProp(ent, props.props[i]))
{
all = 0;
break;
}
}
if (all)
{
return ent;
}
}
}
return NilEntity();
}
////////////////////////////////
//~ Tree
void Link(Entity *ent, Entity *parent)
{
Snapshot *ss = ent->ss;
Entity *old_parent = EntityFromId(ss, ent->parent);
if (old_parent->valid)
{
/* Unlink from current parent */
Unlink(ent);
}
EntityId ent_id = ent->id;
EntityId last_child_id = parent->last;
Entity *last_child = EntityFromId(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->id;
}
/* NOTE: Entity will be dangling after calling this, should re-link to root ent. */
void Unlink(Entity *ent)
{
Snapshot *ss = ent->ss;
EntityId parent_id = ent->parent;
Entity *parent = EntityFromId(ss, parent_id);
Entity *prev = EntityFromId(ss, ent->prev);
Entity *next = EntityFromId(ss, ent->next);
/* Unlink from parent & siblings */
if (prev->valid)
{
prev->next = next->id;
}
else
{
parent->first = next->id;
}
if (next->valid)
{
next->prev = prev->id;
}
else
{
parent->last = prev->id;
}
ent->prev = NilEntityId;
ent->next = NilEntityId;
}
////////////////////////////////
//~ Xform
void MarkChildXformsDirty(Snapshot *ss, Entity *ent)
{
for (Entity *child = EntityFromId(ss, ent->first); child->valid; child = EntityFromId(ss, child->next))
{
if (child->_is_xform_dirty)
{
break;
}
else
{
child->_is_xform_dirty = 1;
MarkChildXformsDirty(ss, child);
}
}
}
Xform XformFromEntity_(Snapshot *ss, Entity *ent)
{
Xform xf;
if (ent->_is_xform_dirty)
{
if (ent->is_top)
{
xf = ent->_local_xform;
}
else
{
Entity *parent = EntityFromId(ss, ent->parent);
xf = XformFromEntity_(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 XformFromEntity(Entity *ent)
{
Xform xf;
if (ent->_is_xform_dirty)
{
if (ent->is_top)
{
xf = ent->_local_xform;
}
else
{
Snapshot *ss = ent->ss;
Entity *parent = EntityFromId(ss, ent->parent);
xf = XformFromEntity_(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 LocalXformFromEntity(Entity *ent)
{
return ent->_local_xform;
}
void SetXform(Entity *ent, Xform xf)
{
if (!EqXform(xf, ent->_xform))
{
Snapshot *ss = ent->ss;
/* Update local xform */
if (ent->is_top)
{
ent->_local_xform = xf;
}
else
{
Entity *parent = EntityFromId(ss, ent->parent);
Xform parent_global = XformFromEntity_(ss, parent);
ent->_local_xform = MulXform(InvertXform(parent_global), xf);
}
ent->_xform = xf;
ent->_is_xform_dirty = 0;
MarkChildXformsDirty(ss, ent);
}
}
void SetLocalXform(Entity *ent, Xform xf)
{
if (!EqXform(xf, ent->_local_xform))
{
ent->_local_xform = xf;
ent->_is_xform_dirty = 1;
MarkChildXformsDirty(ent->ss, ent);
}
}
////////////////////////////////
//~ Movement
void SetLinearVelocity(Entity *ent, Vec2 velocity)
{
if (HasProp(ent, Prop_Kinematic) || HasProp(ent, Prop_Dynamic))
{
ent->linear_velocity = ClampVec2Len(velocity, SIM_MAX_LINEAR_VELOCITY);
}
}
void SetAngularVelocity(Entity *ent, f32 velocity)
{
if (HasProp(ent, Prop_Kinematic) || HasProp(ent, Prop_Dynamic))
{
ent->angular_velocity = ClampF32(velocity, -SIM_MAX_ANGULAR_VELOCITY, SIM_MAX_ANGULAR_VELOCITY);
}
}
void ApplyLinearImpulse(Entity *ent, Vec2 impulse, Vec2 point)
{
if (HasProp(ent, Prop_Dynamic))
{
Xform xf = XformFromEntity(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);
SetLinearVelocity(ent, AddVec2(ent->linear_velocity, MulVec2(impulse, inv_mass)));
SetAngularVelocity(ent, WedgeVec2(vcp, impulse) * inv_inertia);
}
}
void ApplyLinearImpulseToCenter(Entity *ent, Vec2 impulse)
{
if (HasProp(ent, Prop_Dynamic))
{
Xform xf = XformFromEntity(ent);
f32 scale = AbsF32(DeterminantFromXform(xf));
f32 inv_mass = 1.f / (ent->mass_unscaled * scale);
SetLinearVelocity(ent, AddVec2(ent->linear_velocity, MulVec2(impulse, inv_mass)));
}
}
void ApplyForceToCenter(Entity *ent, Vec2 force)
{
if (HasProp(ent, Prop_Dynamic))
{
ent->force = AddVec2(ent->force, force);
}
}
void ApplyAngularImpulse(Entity *ent, f32 impulse)
{
if (HasProp(ent, Prop_Dynamic))
{
Xform xf = XformFromEntity(ent);
f32 scale = AbsF32(DeterminantFromXform(xf));
f32 inv_inertia = 1.f / (ent->inertia_unscaled * scale);
SetAngularVelocity(ent, ent->angular_velocity + impulse * inv_inertia);
}
}
void ApplyTorque(Entity *ent, f32 torque)
{
if (HasProp(ent, Prop_Dynamic))
{
ent->torque += torque;
}
}
////////////////////////////////
//~ Tile
Entity *TileChunkFromChunkIndex(Snapshot *ss, Vec2I32 chunk_index)
{
EntityId chunk_id = TileChunkIdFromIndex(chunk_index);
Entity *chunk_ent = EntityFromId(ss, chunk_id);
return chunk_ent;
}
Entity *TileChunkFromWorldTileIndex(Snapshot *ss, Vec2I32 world_tile_index)
{
Vec2I32 chunk_index = TileChunkIndexFromWorldTileIndex(world_tile_index);
Entity *chunk_ent = TileChunkFromChunkIndex(ss, chunk_index);
return chunk_ent;
}
TileKind TileKindFromChunk(Entity *chunk_ent, Vec2I32 local_tile_index)
{
TileKind result = chunk_ent->tile_chunk_tiles[local_tile_index.x + (local_tile_index.y * SIM_TILES_PER_CHUNK_SQRT)];
return result;
}
////////////////////////////////
//~ Lerp
void LerpEntity(Entity *e, Entity *e0, Entity *e1, f64 blend)
{
if (IsValidAndActive(e0) && IsValidAndActive(e1)
&& EqId(e0->id, e1->id)
&& 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 = XformFromEntity(e0);
Xform e1_xf = XformFromEntity(e1);
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 CreateMissingEntitiesFromSnapshots(Entity *local_parent, Entity *remote, EntityId remote_player)
{
__prof;
if (HasProp(remote, Prop_SyncSrc))
{
Snapshot *local_ss = local_parent->ss;
Snapshot *remote_ss = remote->ss;
EntityId id = remote->id;
Entity *local_ent = EntityFromId(local_ss, id);
if (!local_ent->valid)
{
local_ent = AcquireSyncDst(local_parent, id, remote_player);
}
for (Entity *remote_child = EntityFromId(remote_ss, remote->first); remote_child->valid; remote_child = EntityFromId(remote_ss, remote_child->next))
{
CreateMissingEntitiesFromSnapshots(local_ent, remote_child, remote_player);
}
}
}
/* Copies data between two synced entities */
void SyncEntity(Entity *local, Entity *remote)
{
Entity old = *local;
CopyStruct(local, remote);
/* Why would 2 ents w/ different uids ever be synced? */
Assert(EqId(local->id, old.id));
local->ss = old.ss;
local->id = old.id;
/* 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;
DisableProp(local, Prop_SyncSrc);
EnableProp(local, Prop_SyncDst);
}
////////////////////////////////
//~ Encode / decode
#if 1
////////////////////////////////
//~ Encode
void EncodeEntity(BB_Writer *bw, Entity *e0, Entity *e1)
{
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 DecodeEntity(BB_Reader *br, Entity *e)
{
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 EncodeEntity(BB_Writer *bw, Entity *e0, Entity *e1)
{
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 handles 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 DecodeEntity(BB_Reader *br, Entity *e)
{
Entity 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;
EntityId old_id = e->id;
EntityId new_id = decoded.id;
CopyStruct(e, &decoded);
e->id = old_id;
if (!EqId(old_id, new_id))
{
SetEntityId(e, new_id);
}
}
#endif