refragment simulation snapshots to ensure client frames advance atomically

This commit is contained in:
jacob 2026-01-19 03:17:26 -06:00
parent 64aff1893d
commit 327a0e4af4
3 changed files with 116 additions and 63 deletions

View File

@ -1,3 +1,5 @@
#define P_MaxFrameSnapshotFragments Kibi(4)
////////////////////////////////////////////////////////////
//~ Key types
@ -240,6 +242,13 @@ Struct(P_Frame)
i64 max_constraints;
i64 constraints_count;
P_Constraint *constraints;
//////////////////////////////
//- Snapshot-assembly state
u64 fragments_count;
u64 received_fragments_count;
u64 received_fragment_bits[(P_MaxFrameSnapshotFragments + 63) / 64];
};
Struct(P_FrameBin)

View File

@ -861,7 +861,7 @@ void S_TickForever(WaveLaneCtx *lane)
PackedDeltaNode *next;
String packed;
};
i64 total_delta_bytes = 0;
i64 total_delta_accum = 0;
PackedDeltaNode *first_delta_node = 0;
PackedDeltaNode *last_delta_node = 0;
@ -887,30 +887,49 @@ void S_TickForever(WaveLaneCtx *lane)
PackedDeltaNode *delta_node = PushStruct(frame_arena, PackedDeltaNode);
delta_node->packed = PushString(frame_arena, packed);
SllQueuePush(first_delta_node, last_delta_node, delta_node);
total_delta_bytes += packed.len;
total_delta_accum += packed.len;
}
}
}
//- Collect & send snapshots
//- Count fragments
i64 fragment_delta_accum_cutoff_threshold = NET_PacketSize / 2;
i64 fragments_count = 0;
{
i64 cur_fragment_delta_accum = 0;
for (PackedDeltaNode *delta_node = first_delta_node; delta_node; delta_node = delta_node->next)
{
cur_fragment_delta_accum += delta_node->packed.len;
if (!delta_node->next || (i64)(cur_fragment_delta_accum + delta_node->next->packed.len) >= fragment_delta_accum_cutoff_threshold)
{
fragments_count += 1;
cur_fragment_delta_accum = 0;
}
}
}
//- Collect & send fragments
{
u64 snapshot_cutoff_threshold = NET_PacketSize / 2;
PackedDeltaNode *delta_node = first_delta_node;
u64 snapshot_start = 0;
b32 new_snapshot = 1;
u64 fragment_start = 0;
i64 fragment_idx = 0;
i64 cur_fragment_delta_accum = 0;
b32 new_fragment = 1;
b32 done = 0;
while (!done)
{
PackedDeltaNode *next_delta_node = delta_node ? delta_node->next : 0;
{
//- Init snapshot
if (new_snapshot)
//- Init fragment
if (new_fragment)
{
new_snapshot = 0;
new_fragment = 0;
BB_ResetWriter(&packer_bbw);
snapshot_start = BB_GetNumBytesWritten(&packer_bbw);
fragment_start = BB_GetNumBytesWritten(&packer_bbw);
BB_WriteBit(&packer_bbw, 1); // Raw
BB_WriteIV(&packer_bbw, fragment_idx);
BB_WriteIV(&packer_bbw, fragments_count);
BB_WriteIV(&packer_bbw, src_frame->tick);
BB_WriteIV(&packer_bbw, world_frame->tick);
BB_WriteIV(&packer_bbw, world_frame->time_ns);
@ -924,43 +943,45 @@ void S_TickForever(WaveLaneCtx *lane)
if (delta_node)
{
BB_WriteBytes(&packer_bbw, delta_node->packed);
cur_fragment_delta_accum += delta_node->packed.len;
}
//- Submit snapshot
//- Submit fragment
{
if (!delta_node || !next_delta_node)
{
new_snapshot = 1;
new_fragment = 1;
done = 1;
}
u64 next_len = 0;
i64 next_delta_size = 0;
if (next_delta_node)
{
next_len = next_delta_node->packed.len;
next_delta_size = next_delta_node->packed.len;
}
u64 cur_snapshot_len = BB_GetNumBytesWritten(&packer_bbw);
if ((cur_snapshot_len - snapshot_start) + next_len >= snapshot_cutoff_threshold)
if (cur_fragment_delta_accum + next_delta_size >= fragment_delta_accum_cutoff_threshold)
{
new_snapshot = 1;
new_fragment = 1;
}
}
if (new_snapshot)
if (new_fragment)
{
u64 snapshot_end = BB_GetNumBytesWritten(&packer_bbw);
String snapshot = Zi;
snapshot.text = BB_GetWrittenRaw(&packer_bbw) + snapshot_start;
snapshot.len = snapshot_end - snapshot_start;
NET_Send(net_pipe, client->net_key, snapshot, NET_SendFlag_Raw);
u64 fragment_end = BB_GetNumBytesWritten(&packer_bbw);
String fragment = Zi;
fragment.text = BB_GetWrittenRaw(&packer_bbw) + fragment_start;
fragment.len = fragment_end - fragment_start;
NET_Send(net_pipe, client->net_key, fragment, NET_SendFlag_Raw);
// Sanity check to catch whenever we end up packing too much information
// into a single raw snapshot, causing it to drop. This isn't necessarily
// into a single raw fragment, causing it to drop. This isn't necessarily
// an error, but signals we may have botched the packing code or need tighter
// compression
Assert(snapshot.len <= NET_PacketSize);
Assert(fragment.len <= NET_PacketSize);
fragment_idx += 1;
}
}
delta_node = next_delta_node;
}
Assert(fragment_idx == fragments_count);
}
}

View File

@ -371,7 +371,7 @@ void V_TickForever(WaveLaneCtx *lane)
i64 remote_ack = 0;
i64 prev_snapshot_sent_at_ns = 0;
i64 prev_known_sim_tick = 0;
i64 ready_sim_tick = 0;
i64 known_sim_tick = 0;
Vec2I32 tiles_dims = VEC2I32(P_TilesPitch, P_TilesPitch);
@ -2872,6 +2872,8 @@ void V_TickForever(WaveLaneCtx *lane)
//- Read header
BB_ReadBit(&bbr); // Raw
i64 fragment_idx = BB_ReadIV(&bbr);
i64 fragments_count = BB_ReadIV(&bbr);
i64 src_tick = BB_ReadIV(&bbr);
i64 dst_tick = BB_ReadIV(&bbr);
i64 time_ns = BB_ReadIV(&bbr);
@ -2886,6 +2888,8 @@ void V_TickForever(WaveLaneCtx *lane)
remote_ack = tmp_remote_ack;
}
if (dst_tick > ready_sim_tick && fragments_count < P_MaxFrameSnapshotFragments)
{
P_Frame *src_frame = P_FrameFromTick(sim_world, src_tick);
if (src_frame->tick == src_tick)
{
@ -2897,9 +2901,20 @@ void V_TickForever(WaveLaneCtx *lane)
{
dst_frame = P_PushFrame(sim_world, src_frame, dst_tick);
}
dst_frame->fragments_count = fragments_count;
dst_frame->time_ns = time_ns;
sim_world->seed = world_seed;
if (fragment_idx < P_MaxFrameSnapshotFragments)
{
u64 *frag_bit_chunk = &dst_frame->received_fragment_bits[fragment_idx / 64];
u64 frag_bit = (u64)1 << fragment_idx;
b32 is_new = !(*frag_bit_chunk & frag_bit);
if (is_new)
{
*frag_bit_chunk |= frag_bit;
dst_frame->received_fragments_count += 1;
//- Read deltas
BB_ReadAlignToNextByte(&bbr);
@ -2923,10 +2938,20 @@ void V_TickForever(WaveLaneCtx *lane)
done = 1;
}
}
// Mark snapshot as ready if assembled
if (dst_frame->received_fragments_count >= dst_frame->fragments_count)
{
ready_sim_tick = dst_tick;
}
}
}
}
}
}
}
}
//////////////////////////////
//- Apply sim msgs
@ -2973,7 +2998,7 @@ void V_TickForever(WaveLaneCtx *lane)
// TODO: Remove this
// TODO: Delete all frames except for prediction base & remote ack
P_ClearFrames(sim_world, I64Min, known_sim_tick - SIM_TICKS_PER_SECOND);
P_ClearFrames(sim_world, I64Min, ready_sim_tick - SIM_TICKS_PER_SECOND);
// P_ClearFrames(sim_world, I64Min, sim_world->last_frame->tick - 1);
P_Frame *sim_frame = sim_world->last_frame;
@ -3031,9 +3056,7 @@ void V_TickForever(WaveLaneCtx *lane)
// frame->predict_to = sim_world->last_frame->tick + MaxF64(CeilF64(ping * SIM_TICKS_PER_SECOND), 1.0);
// frame->predict_to = known_sim_tick + 10;
frame->predict_to = known_sim_tick + 20;
frame->predict_to = ready_sim_tick + 10;
//////////////////////////////
//- Create player control
@ -3087,10 +3110,10 @@ void V_TickForever(WaveLaneCtx *lane)
i64 total_delta_bytes = 0;
PackedDeltaNode *first_delta_node = 0;
PackedDeltaNode *last_delta_node = 0;
if (frame->predict_to >= known_sim_tick)
if (frame->predict_to >= ready_sim_tick)
{
i64 last_send_tick = frame->predict_to;
i64 first_send_tick = MaxI64(known_sim_tick, remote_ack + 1);
i64 first_send_tick = MaxI64(ready_sim_tick, remote_ack + 1);
first_send_tick = MaxI64(first_send_tick, last_send_tick - max_control_sends);
for (i64 send_tick = first_send_tick; send_tick <= last_send_tick; ++send_tick)
{
@ -3138,7 +3161,7 @@ void V_TickForever(WaveLaneCtx *lane)
BB_ResetWriter(&packer_bbw);
snapshot_start = BB_GetNumBytesWritten(&packer_bbw);
BB_WriteBit(&packer_bbw, 1); // Raw
BB_WriteIV(&packer_bbw, known_sim_tick);
BB_WriteIV(&packer_bbw, ready_sim_tick);
}
//- Append packed delta
@ -3233,7 +3256,7 @@ void V_TickForever(WaveLaneCtx *lane)
// FIXME: Not like this
i64 max_predict_ticks = SIM_TICKS_PER_SECOND;
last_predict_tick = frame->predict_to;
first_predict_tick = known_sim_tick - 2;
first_predict_tick = ready_sim_tick - 2;
first_predict_tick = MaxI64(first_predict_tick, last_predict_tick - max_predict_ticks);
}