constraints lookup

This commit is contained in:
jacob 2026-02-09 14:11:08 -06:00
parent 003bdb2d55
commit b7f2c3d185
7 changed files with 423 additions and 103 deletions

View File

@ -7,6 +7,10 @@ Readonly P_Ent P_NilEnt = {
.health = 1,
};
Readonly P_Constraint P_NilConstraint = {
0
};
Readonly P_Frame P_NilFrame = {
.first_ent = &P_NilEnt,
.last_ent = &P_NilEnt,
@ -23,7 +27,12 @@ void P_Bootstrap(void)
////////////////////////////////////////////////////////////
//~ Nil helpers
b32 P_IsKeyNil(P_Key key)
b32 P_IsEntKeyNil(P_EntKey key)
{
return key.v == 0;
}
b32 P_IsConstraintKeyNil(P_ConstraintKey key)
{
return key.v == 0;
}
@ -33,6 +42,11 @@ b32 P_IsEntNil(P_Ent *ent)
return ent == 0 || ent == &P_NilEnt;
}
b32 P_IsConstraintNil(P_Constraint *constraint)
{
return constraint == 0 || constraint == &P_NilConstraint;
}
b32 P_IsFrameNil(P_Frame *frame)
{
return frame == 0 || frame == &P_NilFrame;
@ -41,15 +55,28 @@ b32 P_IsFrameNil(P_Frame *frame)
////////////////////////////////////////////////////////////
//~ Key helpers
b32 P_MatchKey(P_Key a, P_Key b)
b32 P_MatchEntKey(P_EntKey a, P_EntKey b)
{
return a.v == b.v;
}
P_Key P_RandKey(void)
b32 P_MatchConstraintKey(P_ConstraintKey a, P_ConstraintKey b)
{
return a.v == b.v;
}
P_ConstraintKey P_ConstraintKeyFromU64s(u64 a, u64 b)
{
return (P_ConstraintKey) { .v = MixU64s(a, b) };
}
////////////////////////////////////////////////////////////
//~ Rand helpers
P_EntKey P_RandEntKey(void)
{
// TODO: Don't use true randomness for entity keys. It's overkill & non-deterministic.
P_Key result = Zi;
P_EntKey result = Zi;
TrueRand(StringFromStruct(&result));
return result;
}
@ -1083,11 +1110,11 @@ Vec2 P_EdgePointFromShape(P_Shape shape, Vec2 dir)
////////////////////////////////////////////////////////////
//~ Lookup helpers
P_Ent *P_EntFromKey(P_Frame *frame, P_Key key)
P_Ent *P_EntFromKey(P_Frame *frame, P_EntKey key)
{
P_Ent *result = &P_NilEnt;
P_World *world = frame->world;
if (!P_IsKeyNil(key) && frame->tick > 0 && frame->ents_count > 0 && frame->ent_bins_count > 0)
if (!P_IsEntKeyNil(key) && frame->tick > 0 && frame->ents_count > 0 && frame->ent_bins_count > 0)
{
i64 tick = frame->tick;
P_EntBin *bin = &frame->ent_bins[key.v % frame->ent_bins_count];
@ -1103,6 +1130,26 @@ P_Ent *P_EntFromKey(P_Frame *frame, P_Key key)
return result;
}
P_Constraint *P_ConstraintFromKey(P_Frame *frame, P_ConstraintKey key)
{
P_Constraint *result = &P_NilConstraint;
P_World *world = frame->world;
if (!P_IsConstraintKeyNil(key) && frame->tick > 0 && frame->constraints_count > 0 && frame->constraint_bins_count > 0)
{
i64 tick = frame->tick;
P_ConstraintBin *bin = &frame->constraint_bins[key.v % frame->constraint_bins_count];
for (P_Constraint *c = bin->first; c; c = c->next_in_bin)
{
if (c->key.v == key.v)
{
result = c;
break;
}
}
}
return result;
}
////////////////////////////////////////////////////////////
//~ Iteration helpers
@ -1126,6 +1173,26 @@ P_Ent *P_NextEnt(P_Ent *e)
return result;
}
P_Constraint *P_FirstConstraint(P_Frame *frame)
{
P_Constraint *result = &P_NilConstraint;
if (!P_IsConstraintNil(frame->first_constraint))
{
result = frame->first_constraint;
}
return result;
}
P_Constraint *P_NextConstraint(P_Constraint *c)
{
P_Constraint *result = &P_NilConstraint;
if (!P_IsConstraintNil(c) && !P_IsConstraintNil(c->next))
{
result = c->next;
}
return result;
}
////////////////////////////////////////////////////////////
//~ List helpers
@ -1294,8 +1361,8 @@ void P_SpawnEntsFromList(P_Frame *frame, P_EntList ents)
for (P_EntListNode *n = ents.first; n; n = n->next)
{
P_Ent *src = &n->ent;
P_Key key = src->key;
if (!P_IsKeyNil(src->key))
P_EntKey key = src->key;
if (!P_IsEntKeyNil(src->key))
{
P_EntBin *bin = &frame->ent_bins[key.v % frame->ent_bins_count];
P_Ent *dst = bin->first;
@ -1338,6 +1405,27 @@ void P_SpawnEntsFromList(P_Frame *frame, P_EntList ents)
}
}
P_Constraint *P_PushConstraint(P_Frame *frame, P_ConstraintKey key)
{
P_World *world = frame->world;
P_Constraint *constraint = world->first_free_constraint;
if (constraint)
{
SllStackPop(world->first_free_constraint);
}
else
{
constraint = PushStructNoZero(world->frames_arena, P_Constraint);
}
*constraint = P_NilConstraint;
constraint->key = key;
P_ConstraintBin *bin = &frame->constraint_bins[key.v % frame->constraint_bins_count];
DllQueuePushNPZ(&P_NilConstraint, frame->first_constraint, frame->last_constraint, constraint, next, prev);
DllQueuePushNP(bin->first, bin->last, constraint, next_in_bin, prev_in_bin);
++frame->constraints_count;
return constraint;
}
P_Frame *P_FrameFromTick(P_World *world, i64 tick)
{
P_Frame *result = &P_NilFrame;
@ -1367,17 +1455,25 @@ void P_ClearFrames(P_World *world, i64 tick_min, i64 tick_max)
P_Frame *next_frame = frame->next;
if (frame->tick >= tick_min && frame->tick <= tick_max)
{
// Free ents
if (!P_IsEntNil(frame->first_ent))
{
frame->last_ent->next = world->first_free_ent;
world->first_free_ent = frame->first_ent;
}
// Free constraints
if (!P_IsConstraintNil(frame->first_constraint))
{
frame->last_constraint->next = world->first_free_constraint;
world->first_free_constraint = frame->first_constraint;
}
// Free frame
u64 hash = MixU64(frame->tick);
P_FrameBin *bin = &world->frame_bins[hash % world->frame_bins_count];
DllQueueRemoveNPZ(&P_NilFrame, world->first_frame, world->last_frame, frame, next, prev);
DllQueueRemoveNPZ(0, bin->first, bin->last, frame, next_in_bin, prev_in_bin);
SllStackPush(world->first_free_frame, frame);
}
else
@ -1396,16 +1492,17 @@ P_Frame *P_PushFrame(P_World *world, P_Frame *src_frame, i64 tick)
SllStackPop(world->first_free_frame);
i64 old_ent_bins_count = frame->ent_bins_count;
P_EntBin *old_ent_bins = frame->ent_bins;
i64 old_constraints_cap = frame->constraints_cap;
P_Constraint *old_constraints = frame->constraints;
i64 old_constraint_bins_count = frame->constraint_bins_count;
P_ConstraintBin *old_constraint_bins = frame->constraint_bins;
{
ZeroStruct(frame);
}
frame->ent_bins_count = old_ent_bins_count;
frame->ent_bins = old_ent_bins;
frame->constraints_cap = old_constraints_cap;
frame->constraints = old_constraints;
ZeroStructs(frame->ent_bins, frame->ent_bins_count);
frame->constraint_bins_count = old_constraint_bins_count;
frame->constraint_bins = old_constraint_bins;
ZeroStructs(frame->constraint_bins, frame->constraint_bins_count);
}
else
{
@ -1425,11 +1522,12 @@ P_Frame *P_PushFrame(P_World *world, P_Frame *src_frame, i64 tick)
frame->ent_bins = PushStructs(world->frames_arena, P_EntBin, frame->ent_bins_count);
}
if (!frame->constraints)
frame->first_constraint = &P_NilConstraint;
frame->last_constraint = &P_NilConstraint;
if (!frame->constraint_bins)
{
frame->constraints_cap = Kibi(4);
frame->constraints_count = 0;
frame->constraints = PushStructsNoZero(world->frames_arena, P_Constraint, frame->constraints_cap);
frame->constraint_bins_count = Kibi(1);
frame->constraint_bins = PushStructs(world->frames_arena, P_ConstraintBin, frame->constraint_bins_count);
}
}
@ -1452,6 +1550,26 @@ P_Frame *P_PushFrame(P_World *world, P_Frame *src_frame, i64 tick)
++frame->ents_count;
}
// Copy constraints
for (P_Constraint *src = P_FirstConstraint(src_frame); !P_IsConstraintNil(src); src = P_NextConstraint(src))
{
P_Constraint *dst = world->first_free_constraint;
if (dst)
{
SllStackPop(world->first_free_constraint);
}
else
{
dst = PushStructNoZero(world->frames_arena, P_Constraint);
LogDebugF("RAAAAAAH");
}
*dst = *src;
P_ConstraintBin *bin = &frame->constraint_bins[src->key.v % frame->constraint_bins_count];
DllQueuePushNPZ(&P_NilConstraint, frame->first_constraint, frame->last_constraint, dst, next, prev);
DllQueuePushNP(bin->first, bin->last, dst, next_in_bin, prev_in_bin);
++frame->constraints_count;
}
// Clear frames
P_ClearFrames(world, tick, I64Max);
@ -1522,9 +1640,9 @@ void P_StepFrame(P_Frame *frame)
{
if (player->is_player)
{
if (P_IsKeyNil(player->guy))
if (P_IsEntKeyNil(player->guy))
{
player->guy = P_RandKey();
player->guy = P_RandEntKey();
}
P_Ent *guy = P_EntFromKey(frame, player->guy);
if (P_IsEntNil(guy))
@ -1576,7 +1694,10 @@ void P_StepFrame(P_Frame *frame)
{
// TODO: Something better than linear distance for scoring
f32 score = Vec2Len(SubVec2(ent->xf.t, spawn->ent->xf.t));
spawn->score = MinF32(spawn->score, score);
if (score < 10)
{
spawn->score = MinF32(spawn->score, score);
}
}
}
}
@ -1585,7 +1706,7 @@ void P_StepFrame(P_Frame *frame)
i64 highest_score = -Inf;
for (SpawnNode *spawn = first_spawn; spawn; spawn = spawn->next)
{
b32 ignore_spawn = spawns_count > 1 && P_MatchKey(spawn->ent->key, player->spawn);
b32 ignore_spawn = spawns_count > 1 && P_MatchEntKey(spawn->ent->key, player->spawn);
if (!ignore_spawn)
{
f32 rand_score_spread = 5;
@ -1686,10 +1807,7 @@ void P_StepFrame(P_Frame *frame)
}
\
//////////////////////////////
//- Copy previous frame's constraints
frame->constraints_count = MinI64(prev_frame->constraints_count, frame->constraints_cap);
CopyStructs(frame->constraints, prev_frame->constraints, frame->constraints_count);
//- Setup constraints store
i32 solver_steps_count = 4;
f32 solver_dt = sim_dt / solver_steps_count;
@ -1717,6 +1835,8 @@ void P_StepFrame(P_Frame *frame)
// TODO: Not like this
if (!is_predicting)
{
for (P_Ent *ent0 = P_FirstEnt(frame); !P_IsEntNil(ent0); ent0 = P_NextEnt(ent0))
@ -1734,31 +1854,15 @@ void P_StepFrame(P_Frame *frame)
P_CollisionResult collision = P_CollisionResultFromShapes(shape0, shape1);
if (collision.collision_points_count > 0)
{
// FIXME: Key lookup
P_Constraint *constraint = 0;
P_ConstraintKey constraint_key = P_ConstraintKeyFromU64s(ent0->key.v, ent1->key.v);
P_Constraint *constraint = P_ConstraintFromKey(frame, constraint_key);
if (constraint->last_touched_tick < frame->tick)
{
b32 match = 0;
for (i64 constraint_idx = 0; constraint_idx < frame->constraints_count; ++constraint_idx)
if (P_IsConstraintNil(constraint))
{
constraint = &frame->constraints[constraint_idx];
if (P_MatchKey(constraint->ent0, ent0->key) && P_MatchKey(constraint->ent1, ent1->key))
{
match = 1;
break;
}
constraint = P_PushConstraint(frame, constraint_key);
}
if (!match)
{
if (frame->constraints_count < frame->constraints_cap)
{
constraint = &frame->constraints[frame->constraints_count];
frame->constraints_count += 1;
ZeroStruct(constraint);
}
}
}
if (constraint)
{
constraint->flags = P_ConstraintFlag_Gentle | P_ConstraintFlag_NoWarmStart;
constraint->last_touched_tick = frame->tick;
constraint->normal = collision.collision_normal;
@ -1881,6 +1985,161 @@ void P_StepFrame(P_Frame *frame)
//////////////////////////////
//- Generate solid constraints
// // TODO: Not like this
// for (P_Ent *ent0 = P_FirstEnt(frame); !P_IsEntNil(ent0); ent0 = P_NextEnt(ent0))
// {
// if (ent0->is_guy)
// {
// P_Shape shape0 = P_WorldShapeFromEnt(ent0);
// Rng2 aabb = P_BoundingBoxFromShape(shape0);
// P_Query collisions = P_QueryFromRect(scratch.arena, aabb, P_QueryFlag_IncludeWalls);
// for (P_QueryItem *item = collisions.first; item; item = item->next)
// {
// P_Shape shape1 = item->shape;
// // TODO: World query
// P_CollisionResult collision = P_CollisionResultFromShapes(shape0, shape1);
// if (collision.collision_points_count > 0)
// {
// P_Constraint *constraint = P_ConstraintFromKey(world_frame, constraint_key);
// if (P_IsConstraintNil(constraint))
// {
// constraint = P_PushConstraint(world_frame, constraint_key);
// }
// if (constraint->last_touched_tick < frame->tick)
// {
// constraint->last_touched_Tick = frame->tick;
// constraint->flags = P_ConstraintFlag_Solid;
// constraint->last_touched_tick = frame->tick;
// constraint->normal = collision.collision_normal;
// // constraint->friction = SqrtF32(ent0->friction * ent1->friction);
// constraint->friction = 0;
// // TODO: Real masses
// f32 inv_m0 = 10;
// f32 inv_m1 = 10;
// f32 inv_i0 = 0;
// f32 inv_i1 = 0;
// // Treat non-predicted guys as infinite-mass
// if (is_predicting && ent0 != local_guy)
// {
// inv_m0 = 0;
// }
// if (is_predicting && ent1 != local_guy)
// {
// inv_m1 = 0;
// }
// constraint->ent0 = ent0->key;
// constraint->ent1 = ent1->key;
// // constraint->static_center1 = shape1.center_of_mass;
// constraint->inv_m0 = inv_m0;
// constraint->inv_m1 = inv_m1;
// constraint->inv_i0 = inv_i0;
// constraint->inv_i1 = inv_i1;
// // Delete old contacts that are no longer present
// for (i32 contact_point_idx = 0; contact_point_idx < constraint->points_count; ++contact_point_idx)
// {
// P_ContactPoint *contact = &constraint->points[contact_point_idx];
// u32 id = contact->id;
// b32 match = 0;
// for (i32 collision_point_idx = 0; collision_point_idx < collision.collision_points_count; ++collision_point_idx)
// {
// if (collision.collision_points[collision_point_idx].id == id)
// {
// match = 1;
// break;
// }
// }
// if (!match)
// {
// // Delete contact by replacing with last in array
// *contact = constraint->points[constraint->points_count - 1];
// constraint->points_count -= 1;
// contact_point_idx -= 1;
// }
// }
// // Create / update contacts from collision
// for (i32 collision_point_idx = 0; collision_point_idx < collision.collision_points_count; ++collision_point_idx)
// {
// P_CollisionPoint collision_point = collision.collision_points[collision_point_idx];
// u32 id = collision_point.id;
// P_ContactPoint *contact = 0;
// {
// for (i32 contact_point_idx = 0; contact_point_idx < constraint->points_count; ++contact_point_idx)
// {
// P_ContactPoint *tmp = &constraint->points[contact_point_idx];
// if (tmp->id == id)
// {
// contact = tmp;
// break;
// }
// }
// if (!contact)
// {
// contact = &constraint->points[constraint->points_count];
// constraint->points_count += 1;
// ZeroStruct(contact);
// }
// }
// contact->id = id;
// Vec2 vcp0 = SubVec2(collision_point.p, shape0.center_of_mass);
// Vec2 vcp1 = SubVec2(collision_point.p, shape1.center_of_mass);
// contact->vcp0 = vcp0;
// contact->vcp1 = vcp1;
// contact->starting_separation = collision_point.separation;
// // Debug draw
// // {
// // // P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0);
// // // P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1);
// // Vec2 normal = constraint->normal;
// // Vec2 center0 = Zi;
// // Vec2 center1 = Zi;
// // if (!P_IsEntNil(ent0)) center0 = P_WorldShapeFromEnt(ent0).center_of_mass;
// // if (!P_IsEntNil(ent1)) center1 = P_WorldShapeFromEnt(ent1).center_of_mass;
// // Vec2 p0 = AddVec2(center0, vcp0);
// // Vec2 p1 = AddVec2(center1, vcp1);
// // P_DebugDrawPoint(p0, Color_Cyan);
// // P_DebugDrawLine(p0, AddVec2(p0, normal), Color_White);
// // }
// }
// }
// }
// }
// }
// }
// //////////////////////////////
// //- Generate solid constraints
@ -1893,6 +2152,7 @@ void P_StepFrame(P_Frame *frame)
// if (ent0->is_guy)
// {
// P_Shape shape0 = P_WorldShapeFromEnt(ent0);
// for (P_Ent *ent1 = P_FirstEnt(frame); !P_IsEntNil(ent1); ent1 = P_NextEnt(ent1))
// {
// if (ent1->is_guy && ent1->key.v > ent0->key.v)
@ -1910,7 +2170,7 @@ void P_StepFrame(P_Frame *frame)
// for (i64 constraint_idx = 0; constraint_idx < frame->constraints_count; ++constraint_idx)
// {
// constraint = &frame->constraints[constraint_idx];
// if (P_MatchKey(constraint->ent0, ent0->key) && P_MatchKey(constraint->ent1, ent1->key))
// if (P_MatchEntKey(constraint->ent0, ent0->key) && P_MatchEntKey(constraint->ent1, ent1->key))
// {
// match = 1;
// break;
@ -2045,10 +2305,10 @@ void P_StepFrame(P_Frame *frame)
//- Prune constraints
{
i64 constraint_idx = 0;
while (constraint_idx < frame->constraints_count)
i64 prune_constraints_count = 0;
P_Constraint **prune_constraints = PushStructsNoZero(scratch.arena, P_Constraint *, frame->constraints_count);
for (P_Constraint *constraint = P_FirstConstraint(frame); !P_IsConstraintNil(constraint); constraint = P_NextConstraint(constraint))
{
P_Constraint *constraint = &frame->constraints[constraint_idx];
b32 prune = 1;
if (constraint->last_touched_tick == frame->tick)
{
@ -2061,17 +2321,20 @@ void P_StepFrame(P_Frame *frame)
}
if (prune)
{
// Prune by replacing with last constraint
// TODO: Investigate whether the reordering here can degrade stability
P_Constraint *last_constraint = &frame->constraints[frame->constraints_count - 1];
*constraint = *last_constraint;
frame->constraints_count -= 1;
}
else
{
constraint_idx += 1;
prune_constraints[prune_constraints_count] = constraint;
prune_constraints_count += 1;
}
}
for (i64 prune_idx = 0; prune_idx < prune_constraints_count; ++prune_idx)
{
P_Constraint *constraint = prune_constraints[prune_idx];
P_ConstraintBin *bin = &frame->constraint_bins[constraint->key.v % frame->constraint_bins_count];
DllQueueRemoveNP(bin->first, bin->last, constraint, next_in_bin, prev_in_bin);
DllQueueRemoveNPZ(&P_NilConstraint, frame->first_constraint, frame->last_constraint, constraint, next, prev);
frame->constraints_count -= 1;
SllStackPush(world->first_free_constraint, constraint);
}
}
//////////////////////////////
@ -2082,9 +2345,8 @@ void P_StepFrame(P_Frame *frame)
//////////////////////////////
//- Prepare constraints
for (i64 constraint_idx = 0; constraint_idx < frame->constraints_count; ++constraint_idx)
for (P_Constraint *constraint = P_FirstConstraint(frame); !P_IsConstraintNil(constraint); constraint = P_NextConstraint(constraint))
{
P_Constraint *constraint = &frame->constraints[constraint_idx];
Vec2 normal = constraint->normal;
Vec2 tangent = PerpVec2(normal);
f32 inv_m0 = constraint->inv_m0;
@ -2119,9 +2381,8 @@ void P_StepFrame(P_Frame *frame)
//////////////////////////////
//- Warm start constraints
for (i64 constraint_idx = 0; constraint_idx < frame->constraints_count; ++constraint_idx)
for (P_Constraint *constraint = P_FirstConstraint(frame); !P_IsConstraintNil(constraint); constraint = P_NextConstraint(constraint))
{
P_Constraint *constraint = &frame->constraints[constraint_idx];
if (!(constraint->flags & P_ConstraintFlag_NoWarmStart))
{
P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0);
@ -2166,10 +2427,8 @@ void P_StepFrame(P_Frame *frame)
//////////////////////////////
//- Solve constraints
for (i64 constraint_idx = 0; constraint_idx < frame->constraints_count; ++constraint_idx)
for (P_Constraint *constraint = P_FirstConstraint(frame); !P_IsConstraintNil(constraint); constraint = P_NextConstraint(constraint))
{
P_Constraint *constraint = &frame->constraints[constraint_idx];
P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0);
P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1);
@ -2455,7 +2714,7 @@ void P_StepFrame(P_Frame *frame)
{
P_Ent *bullet = P_PushTempEnt(scratch.arena, &bullets_to_spawn);
bullet->is_bullet = 1;
bullet->key = P_RandKey();
bullet->key = P_RandEntKey();
f32 rand_speed = Norm24(P_RandU64FromEnt(firer)) - 0.5;
f32 rand_angle = Norm24(P_RandU64FromEnt(firer)) - 0.5;
@ -2503,7 +2762,7 @@ void P_StepFrame(P_Frame *frame)
f32 closest_len_sq = Inf;
for (P_Ent *potential_victim = P_FirstEnt(frame); !P_IsEntNil(potential_victim); potential_victim = P_NextEnt(potential_victim))
{
if (potential_victim->is_guy && !P_MatchKey(potential_victim->key, bullet->bullet_firer))
if (potential_victim->is_guy && !P_MatchEntKey(potential_victim->key, bullet->bullet_firer))
{
P_Shape victim_world_shape = P_WorldShapeFromEnt(potential_victim);

View File

@ -3,9 +3,15 @@
////////////////////////////////////////////////////////////
//~ Key types
#define P_NilKey ((P_Key) { 0 })
#define P_NilEntKey ((P_EntKey) { 0 })
#define P_NilConstraintKey ((P_EntKey) { 0 })
Struct(P_Key)
Struct(P_EntKey)
{
u64 v;
};
Struct(P_ConstraintKey)
{
u64 v;
};
@ -99,7 +105,7 @@ Struct(P_Ent)
//- Persistent data
P_Key key;
P_EntKey key;
u64 rand_seq;
i64 created_at_ns;
@ -117,7 +123,7 @@ Struct(P_Ent)
i64 last_fire_ns;
b32 has_weapon;
P_Key bullet_firer;
P_EntKey bullet_firer;
b32 is_bullet;
Vec2 bullet_start;
Vec2 bullet_end;
@ -132,10 +138,10 @@ Struct(P_Ent)
//- Player
P_Key spawn;
P_EntKey spawn;
b32 is_player;
P_Key guy;
P_EntKey guy;
f32 ping;
u8 string_len;
@ -207,11 +213,18 @@ Struct(P_ContactPoint)
Struct(P_Constraint)
{
P_Constraint *next;
P_Constraint *prev;
P_Constraint *next_in_bin;
P_Constraint *prev_in_bin;
P_ConstraintKey key;
P_ConstraintFlag flags;
i64 last_touched_tick;
P_Key ent0;
P_Key ent1;
P_EntKey ent0;
P_EntKey ent1;
Vec2 static_center0;
Vec2 static_center1;
@ -228,6 +241,12 @@ Struct(P_Constraint)
P_ContactPoint points[2];
};
Struct(P_ConstraintBin)
{
P_Constraint *first;
P_Constraint *last;
};
////////////////////////////////////////////////////////////
//~ World types
@ -250,6 +269,9 @@ Struct(P_Frame)
i64 tick;
i64 time_ns;
//////////////////////////////
//- Ents
i64 ents_count;
P_Ent *first_ent;
P_Ent *last_ent;
@ -257,9 +279,15 @@ Struct(P_Frame)
i64 ent_bins_count;
P_EntBin *ent_bins;
i64 constraints_cap;
//////////////////////////////
//- Constraints
i64 constraints_count;
P_Constraint *constraints;
P_Constraint *first_constraint;
P_Constraint *last_constraint;
i64 constraint_bins_count;
P_ConstraintBin *constraint_bins;
//////////////////////////////
//- Snapshot-assembly state
@ -285,6 +313,7 @@ Struct(P_World)
RandState rand;
P_Ent *first_free_ent;
P_Constraint *first_free_constraint;
P_Frame *first_frame;
P_Frame *last_frame;
@ -328,7 +357,7 @@ Struct(P_Msg)
b32 affect_bots;
P_PrefabKind prefab;
P_Key key;
P_EntKey key;
Xform xf;
P_TileKind tile_kind;
@ -408,6 +437,14 @@ Struct(P_RaycastResult)
////////////////////////////////////////////////////////////
//~ State types
Struct(P_SimStatistics)
{
NET_PipeStatistics pipe;
i64 tick;
i64 ents_count;
i64 constraints_count;
};
Struct(P_Ctx)
{
// Sim -> Vis state
@ -420,14 +457,14 @@ Struct(P_Ctx)
P_DebugDrawNode *first_debug_draw_node;
P_DebugDrawNode *last_debug_draw_node;
NET_PipeStatistics pipe_stats;
P_SimStatistics stats;
} s2v;
};
Struct(P_ThreadLocalCtx)
{
b32 is_predicting;
P_Key local_player;
P_EntKey local_player;
//- Per-thread debug info
Arena *debug_arena;
@ -456,19 +493,28 @@ void P_Bootstrap(void);
////////////////////////////////////////////////////////////
//~ Nil helpers
b32 P_IsKeyNil(P_Key key);
b32 P_IsEntKeyNil(P_EntKey key);
b32 P_IsConstraintKeyNil(P_ConstraintKey key);
b32 P_IsEntNil(P_Ent *ent);
b32 P_IsConstraintNil(P_Constraint *constraint);
b32 P_IsFrameNil(P_Frame *frame);
////////////////////////////////////////////////////////////
//~ Key helpers
b32 P_MatchKey(P_Key a, P_Key b);
P_Key P_RandKey(void);
u64 P_RandU64FromEnt(P_Ent *ent);
b32 P_MatchEntKey(P_EntKey a, P_EntKey b);
b32 P_MatchConstraintKey(P_ConstraintKey a, P_ConstraintKey b);
P_ConstraintKey P_ConstraintKeyFromU64s(u64 a, u64 b);
#define P_FmtKey(key) FmtHandle((key).v)
////////////////////////////////////////////////////////////
//~ Rand helpers
P_EntKey P_RandEntKey(void);
u64 P_RandU64FromEnt(P_Ent *ent);
////////////////////////////////////////////////////////////
//~ String helpers
@ -519,7 +565,8 @@ Vec2 P_EdgePointFromShape(P_Shape shape, Vec2 dir);
////////////////////////////////////////////////////////////
//~ Lookup helpers
P_Ent *P_EntFromKey(P_Frame *frame, P_Key key);
P_Ent *P_EntFromKey(P_Frame *frame, P_EntKey key);
P_Constraint *P_ConstraintFromKey(P_Frame *frame, P_ConstraintKey key);
////////////////////////////////////////////////////////////
//~ Iteration helpers
@ -527,6 +574,9 @@ P_Ent *P_EntFromKey(P_Frame *frame, P_Key key);
P_Ent *P_FirstEnt(P_Frame *frame);
P_Ent *P_NextEnt(P_Ent *e);
P_Constraint *P_FirstConstraint(P_Frame *frame);
P_Constraint *P_NextConstraint(P_Constraint *c);
////////////////////////////////////////////////////////////
//~ List helpers
@ -551,6 +601,7 @@ P_Msg *P_PushMsg(P_MsgKind kind, String data);
P_World *P_AcquireWorld(void);
void P_SpawnEntsFromList(P_Frame *frame, P_EntList ents);
P_Constraint *P_PushConstraint(P_Frame *frame, P_ConstraintKey key);
P_Frame *P_FrameFromTick(P_World *world, i64 tick);
void P_ClearFrames(P_World *world, i64 tick_min, i64 tick_max);

View File

@ -272,9 +272,9 @@ void S_TickForever(WaveLaneCtx *lane)
P_EntList new_players = Zi;
for (S_Client *client = S.first_client; !S_IsClientNil(client); client = client->next)
{
if (P_IsKeyNil(client->player))
if (P_IsEntKeyNil(client->player))
{
client->player = P_RandKey();
client->player = P_RandEntKey();
}
P_Ent *player = P_EntFromKey(world_frame, client->player);
if (P_IsEntNil(player))
@ -587,7 +587,7 @@ void S_TickForever(WaveLaneCtx *lane)
if (!guy->is_guy)
{
guy = P_PushTempEnt(frame_arena, &ents);
guy->key = P_RandKey();
guy->key = P_RandEntKey();
guy->is_guy = 1;
guy->has_weapon = 1;
bot->guy = guy->key;
@ -928,7 +928,10 @@ void S_TickForever(WaveLaneCtx *lane)
}
ResetArena(P.s2v.arena);
P.s2v.gen += 1;
P.s2v.pipe_stats = NET_StatsFromPipe(net_pipe);
P.s2v.stats.pipe = NET_StatsFromPipe(net_pipe);
P.s2v.stats.tick = world_frame->tick;
P.s2v.stats.ents_count = world_frame->ents_count;
P.s2v.stats.constraints_count = world_frame->constraints_count;
{
i64 dst_idx = 0;

View File

@ -12,7 +12,7 @@ Struct(S_Client)
NET_Key net_key;
P_MsgList out_msgs;
P_Key player;
P_EntKey player;
i32 name_len;
u8 name_text[P_MaxPlayerNameLen];

View File

@ -135,7 +135,7 @@ P_UnpackedWorld P_UnpackWorld(Arena *arena, String packed)
for (CR_Item *ent_item = top_item->first; ent_item; ent_item = ent_item->next)
{
P_Ent *ent = P_PushTempEnt(arena, &result.ents);
ent->key = (P_Key) { .v = CR_IntFromString(ent_item->name) };
ent->key = (P_EntKey) { .v = CR_IntFromString(ent_item->name) };
for (CR_Item *attr = ent_item->first; attr; attr = attr->next)
{
if (MatchString(attr->name, Lit("props")))

View File

@ -357,7 +357,7 @@ void V_TickForever(WaveLaneCtx *lane)
Arena *sim_debug_arena = AcquireArena(Gibi(64));
P_DebugDrawNode *first_sim_debug_draw_node = 0;
P_DebugDrawNode *last_sim_debug_draw_node = 0;
NET_PipeStatistics sim_pipe_stats = Zi;
P_SimStatistics sim_stats = Zi;
P_World *sim_world = P_AcquireWorld();
P_World *predict_world = P_AcquireWorld();
@ -576,7 +576,7 @@ void V_TickForever(WaveLaneCtx *lane)
frame->dt = SecondsFromNs(frame->dt_ns);
frame->rand = prev_frame->rand;
if (P_IsKeyNil(V.player_key))
if (P_IsEntKeyNil(V.player_key))
{
TrueRand(StringFromStruct(&V.player_key));
}
@ -714,7 +714,7 @@ void V_TickForever(WaveLaneCtx *lane)
if (P.s2v.gen > s2v_gen)
{
s2v_gen = P.s2v.gen;
sim_pipe_stats = P.s2v.pipe_stats;
sim_stats = P.s2v.stats;
{
ResetArena(sim_debug_arena);
@ -1204,7 +1204,7 @@ void V_TickForever(WaveLaneCtx *lane)
i64 dst_tick = BB_ReadIV(&bbr);
i64 time_ns = BB_ReadIV(&bbr);
u64 world_seed = BB_ReadUBits(&bbr, 64);
P_Key player_key = { .v = BB_ReadUBits(&bbr, 64) };
P_EntKey player_key = { .v = BB_ReadUBits(&bbr, 64) };
i64 tmp_remote_ack = BB_ReadIV(&bbr);
i64 tmp_ack_mirror = BB_ReadIV(&bbr);
@ -1881,7 +1881,7 @@ void V_TickForever(WaveLaneCtx *lane)
SPR_Sprite wep = SPR_SpriteFromSheet(anim.wep_sheet, anim.span, anim.frame_seq);
Vec2 look = ent->control.look;
if (P_MatchKey(ent->key, local_guy->key))
if (P_MatchEntKey(ent->key, local_guy->key))
{
// Late latch local player aim direction for lower-latency
look = frame->look;
@ -2046,7 +2046,7 @@ void V_TickForever(WaveLaneCtx *lane)
{
P_Msg *msg = P_PushMsg(P_MsgKind_Prefab, Zstr);
msg->prefab = frame->equipped_prefab;
msg->key = P_RandKey();
msg->key = P_RandEntKey();
msg->xf.t = frame->world_cursor;
}
}
@ -4074,17 +4074,24 @@ void V_TickForever(WaveLaneCtx *lane)
{
UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y);
{
UI_BuildLabelF("Local server world tick: %F", FmtSint(sim_stats.tick));
UI_BuildLabelF("Local server world entities: %F", FmtSint(sim_stats.ents_count));
UI_BuildLabelF("Local server world constraints: %F", FmtSint(sim_stats.constraints_count));
UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y);
UI_BuildLabelF("Sim world seed: 0x%F", FmtHex(sim_world->seed));
UI_BuildLabelF("Sim world tick: %F", FmtSint(sim_world->last_frame->tick));
UI_BuildLabelF("Sim world entities: %F", FmtSint(sim_world->last_frame->ents_count));
UI_BuildLabelF("Sim world constraints: %F", FmtSint(sim_world->last_frame->constraints_count));
UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y);
UI_BuildLabelF("Predicted world seed: 0x%F", FmtHex(predict_world->seed));
UI_BuildLabelF("Predicted world tick: %F", FmtSint(predict_world->last_frame->tick));
UI_BuildLabelF("Predicted world entities: %F", FmtSint(predict_world->last_frame->ents_count));
UI_BuildLabelF("Predicted world constraints: %F", FmtSint(predict_world->last_frame->constraints_count));
UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y);
UI_BuildLabelF("Local world seed: 0x%F", FmtHex(local_world->seed));
UI_BuildLabelF("Local world tick: %F", FmtSint(local_world->last_frame->tick));
UI_BuildLabelF("Local world entities: %F", FmtSint(local_world->last_frame->ents_count));
UI_BuildLabelF("Local world constraints: %F", FmtSint(local_world->last_frame->constraints_count));
}
UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y);
{
@ -4113,8 +4120,8 @@ void V_TickForever(WaveLaneCtx *lane)
UI_BuildLabelF("RTT: %Fms", FmtFloat(smoothed_rtt * 1000, .p = 3));
UI_BuildLabelF("Client send: %F MiB", FmtFloat(CeilF64((f64)vis_pipe_stats.total_bytes_sent / 1024) / 1024, .p = 3));
UI_BuildLabelF("Client recv: %F MiB", FmtFloat(CeilF64((f64)vis_pipe_stats.total_bytes_received / 1024) / 1024, .p = 3));
UI_BuildLabelF("Server send: %F MiB", FmtFloat(CeilF64((f64)sim_pipe_stats.total_bytes_sent / 1024) / 1024, .p = 3));
UI_BuildLabelF("Server recv: %F MiB", FmtFloat(CeilF64((f64)sim_pipe_stats.total_bytes_received / 1024) / 1024, .p = 3));
UI_BuildLabelF("Server send: %F MiB", FmtFloat(CeilF64((f64)sim_stats.pipe.total_bytes_sent / 1024) / 1024, .p = 3));
UI_BuildLabelF("Server recv: %F MiB", FmtFloat(CeilF64((f64)sim_stats.pipe.total_bytes_received / 1024) / 1024, .p = 3));
}
UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y);
{
@ -4533,7 +4540,7 @@ void V_TickForever(WaveLaneCtx *lane)
{
P_Msg *msg = P_PushMsg(P_MsgKind_Prefab, Zstr);
msg->prefab = P_PrefabKind_Bot;
msg->key = P_RandKey();
msg->key = P_RandEntKey();
msg->xf.t = frame->world_cursor;
if (kind == V_CmdKind_spawn_tp_bot)
{

View File

@ -251,7 +251,7 @@ Struct(V_Frame)
Struct(V_Ctx)
{
P_Key player_key;
P_EntKey player_key;
i64 panels_count;
i64 windows_count;