796 lines
20 KiB
C
796 lines
20 KiB
C
////////////////////////////////////////////////////////////
|
|
//~ 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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ 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;
|
|
|
|
}
|