send tile data via msgs separate from snapshots

This commit is contained in:
jacob 2026-01-13 14:01:54 -06:00
parent 76ce604a45
commit f5a9733525
4 changed files with 202 additions and 356 deletions

View File

@ -192,6 +192,7 @@ Struct(P_World)
P_FrameBin *frame_bins;
P_Frame *first_free_frame;
u64 tiles_hash;
u8 *tiles;
};
@ -219,7 +220,6 @@ Struct(P_DeltaNode)
Struct(P_Snapshot)
{
u64 world_seed;
// i64 src_world_tick;
i64 src_tick;
i64 tick;
i64 time_ns;
@ -233,34 +233,38 @@ Struct(P_Snapshot)
i64 debug_draw_nodes_count;
};
Struct(P_SnapshotNode)
{
P_SnapshotNode *next;
P_Snapshot snapshot;
};
Struct(P_SnapshotList)
{
i64 count;
P_SnapshotNode *first;
P_SnapshotNode *last;
};
////////////////////////////////////////////////////////////
//~ Message types
Enum(P_MsgKind)
{
P_MsgKind_None,
P_MsgKind_Tile,
P_MsgKind_Tiles,
};
Struct(P_Msg)
{
P_MsgKind kind;
u64 tiles_hash;
Rng2I32 tile_range;
u8 *raw_tiles;
};
Struct(P_MsgNode)
{
P_MsgNode *next;
P_MsgNode *prev;
P_Msg msg;
};
Struct(P_MsgList)
{
P_MsgNode *first;
P_MsgNode *last;
i64 count;
};
////////////////////////////////////////////////////////////
//~ Collision types
@ -368,6 +372,7 @@ Enum(P_CmdKind)
Struct(P_Cmd)
{
i64 tick;
b32 predicted;
P_CmdKind kind;
// Tile edit
@ -410,7 +415,8 @@ Struct(P_OutputState)
{
Arena *arena;
P_SnapshotList snapshots;
P_Snapshot snapshot;
P_MsgList msgs;
};
////////////////////////////////////////////////////////////

View File

@ -36,7 +36,7 @@ void S_TickForever(WaveLaneCtx *lane)
// FIXME: Header
Rng user_cmd_tick_range = RNG(0, SIM_TICKS_PER_SECOND * 0.5);
Rng user_cmd_tick_range = RNG(0, SIM_TICKS_PER_SECOND * 1.0);
P_CmdList queued_user_cmds = Zi;
P_CmdNode *first_free_user_cmd_node = 0;
@ -60,62 +60,16 @@ void S_TickForever(WaveLaneCtx *lane)
P_tl.debug_draw_enabled = TweakBool("Simulation debug draw", 1);
ResetArena(frame_arena);
//////////////////////////////
//- Swap
// {
// b32 swapin = IsSwappedIn();
// b32 swapout = shutdown && IsSwappingOut();
// //- Swap in
// if (!world)
// {
// String packed = Zi;
// if (swapin)
// {
// packed = SwappedStateFromName(frame_arena, Lit("pp_sim.swp"));
// }
// P_UnpackedWorld unpacked = P_UnpackWorld(frame_arena, packed);
// ResetArena(world_arena);
// world = PushStruct(world_arena, P_World);
// world->seed = unpacked.seed;
// world_frame->tick = unpacked.tick;
// world_frame->time_ns = unpacked.time_ns;
// if (world->seed == 0)
// {
// TrueRand(StringFromStruct(&world->seed));
// }
// world_frame->ent_bins_count = Kibi(16);
// world_frame->ent_bins = PushStructs(world_arena, P_EntBin, world_frame->ent_bins_count);
// // Copy tiles
// world->tiles = PushStructsNoZero(world_arena, u8, P_TilesCount);
// CopyStructs(world->tiles, unpacked.tiles, P_TilesCount);
// // Copy ents
// P_SpawnEntsFromList(world_arena, world, unpacked.ents);
// }
// //- Swap out
// if (swapout)
// {
// String packed = P_PackWorld(frame_arena, world);
// WriteSwappedState(Lit("pp_sim.swp"), packed);
// }
// }
//////////////////////////////
//- Begin sim frame
P_Frame *prev_world_frame = world->last_frame;
P_Frame *world_frame = P_PushFrame(world, prev_world_frame, prev_world_frame->tick + 1);
b32 tiles_dirty = 0;
// TDOO: Remove this
P_ClearFrames(world, I64Min, prev_world_frame->tick - 1);
i64 frame_begin_ns = TimeNs();
i64 sim_dt_ns = NsFromSeconds(1) / SIM_TICKS_PER_SECOND;
f64 sim_dt = SecondsFromNs(sim_dt_ns);
@ -139,7 +93,7 @@ void S_TickForever(WaveLaneCtx *lane)
{
i64 user_cmds_min_tick = world_frame->tick - user_cmd_tick_range.min;
i64 user_cmds_max_tick = world_frame->tick + user_cmd_tick_range.max;
// Prune user cmds
// Prune queued user cmds
{
for (P_CmdNode *cmd_node = queued_user_cmds.first; cmd_node;)
{
@ -184,10 +138,18 @@ void S_TickForever(WaveLaneCtx *lane)
{
for (P_CmdNode *src_cmd_node = queued_user_cmds.first; src_cmd_node; src_cmd_node = src_cmd_node->next)
{
if (src_cmd_node->cmd.tick == world_frame->tick)
i64 cmd_tick = src_cmd_node->cmd.tick;
if (!src_cmd_node->cmd.predicted)
{
// We can execute unpredicted cmds this frame, since they don't
// need to be buffered to match predicted behavior
cmd_tick = world_frame->tick;
}
if (cmd_tick == world_frame->tick)
{
P_CmdNode *dst_cmd_node = PushStruct(frame_arena, P_CmdNode);
dst_cmd_node->cmd = src_cmd_node->cmd;
dst_cmd_node->cmd.tick = cmd_tick;
DllQueuePush(user_cmds.first, user_cmds.last, dst_cmd_node);
++user_cmds.count;
}
@ -195,6 +157,57 @@ void S_TickForever(WaveLaneCtx *lane)
}
}
//////////////////////////////
//- Swap
{
b32 swapin = IsSwappedIn();
b32 swapout = shutdown && IsSwappingOut();
//- Swap in
if (world->last_frame->tick == 1)
{
String packed = Zi;
if (swapin)
{
packed = SwappedStateFromName(frame_arena, Lit("pp_sim.swp"));
}
P_UnpackedWorld unpacked = P_UnpackWorld(frame_arena, packed);
world_frame = P_PushFrame(world, &P_NilFrame, world->last_frame->tick);
world->seed = unpacked.seed;
world_frame->time_ns = unpacked.time_ns;
if (world->seed == 0)
{
TrueRand(StringFromStruct(&world->seed));
}
// Copy tiles
CopyStructs(world->tiles, unpacked.tiles, P_TilesCount);
// Spawn ents
P_SpawnEntsFromList(world_frame, unpacked.ents);
// Hash tiles
{
u64 old_tiles_hash = world->tiles_hash;
world->tiles_hash = HashString(STRING(P_TilesCount, world->tiles));
if (world->tiles_hash != old_tiles_hash)
{
tiles_dirty = 1;
}
}
}
//- Swap out
if (swapout)
{
String packed = P_PackWorld(frame_arena, world);
WriteSwappedState(Lit("pp_sim.swp"), packed);
}
}
//////////////////////////////
//- Update double-buffered entity data
@ -225,15 +238,13 @@ void S_TickForever(WaveLaneCtx *lane)
// }
//////////////////////////////
//- Process user world delta commands
//- Process user edit commands
// FIXME: Only accept world deltas from users that can edit
// FIXME: Only apply relevant cmds based on tick
{
b32 tiles_dirty = 0;
for (P_CmdNode *cmd_node = user_cmds.first; cmd_node; cmd_node = cmd_node->next)
{
P_Cmd *cmd = &cmd_node->cmd;
@ -266,13 +277,17 @@ void S_TickForever(WaveLaneCtx *lane)
world->tiles[tile_idx] = (u8)tile;
}
}
// Hash tiles
{
u64 old_tiles_hash = world->tiles_hash;
world->tiles_hash = HashString(STRING(P_TilesCount, world->tiles));
if (world->tiles_hash != old_tiles_hash)
{
tiles_dirty = 1;
}
}
}
}
// TODO: Hash tiles here
// if (tiles_dirty)
// {
// }
}
//////////////////////////////
@ -1305,33 +1320,37 @@ void S_TickForever(WaveLaneCtx *lane)
LockTicketMutex(&P.sim_output_back_tm);
{
P_OutputState *output = &P.sim_output_states[P.sim_output_back_idx];
P_SnapshotNode *snapshot_node = PushStruct(output->arena, P_SnapshotNode);
P_Snapshot *snapshot = &snapshot_node->snapshot;
SllQueuePush(output->snapshots.first, output->snapshots.last, snapshot_node);
++output->snapshots.count;
// Build messages
{
// Push tiles
if (tiles_dirty)
{
P_Msg *msg = 0;
{
P_MsgNode *msg_node = PushStruct(output->arena, P_MsgNode);
DllQueuePush(output->msgs.first, output->msgs.last, msg_node);
++output->msgs.count;
msg = &msg_node->msg;
}
msg->kind = P_MsgKind_Tiles;
msg->tiles_hash = world->tiles_hash;
msg->raw_tiles = PushStructsNoZero(output->arena, u8, P_TilesCount);
msg->tile_range = RNG2I32(VEC2I32(0, 0), VEC2I32(P_TilesPitch, P_TilesPitch));
CopyBytes(msg->raw_tiles, world->tiles, P_TilesCount);
has_sent_initial_tick = 1;
}
}
// Build snapshot
{
P_Snapshot *snapshot = &output->snapshot;
snapshot->world_seed = world->seed;
snapshot->tick = world_frame->tick;
snapshot->time_ns = world_frame->time_ns;
// Push full tile delta
// if (!has_sent_initial_tick)
// {
// P_Delta *delta = 0;
// {
// P_DeltaNode *dn = PushStruct(output->arena, P_DeltaNode);
// snapshot->deltas_count += 1;
// SllQueuePush(snapshot->first_delta_node, snapshot->last_delta_node, dn);
// delta = &dn->delta;
// }
// delta->kind = P_DeltaKind_RawTiles;
// delta->raw_tiles = PushStructsNoZero(output->arena, u8, P_TilesCount);
// delta->tile_range = RNG2I32(VEC2I32(0, 0), VEC2I32(P_TilesPitch, P_TilesPitch));
// CopyBytes(delta->raw_tiles, world->tiles, P_TilesCount);
// has_sent_initial_tick = 1;
// }
// Push raw entity deltas
// Push entity deltas
for (P_Ent *ent = P_FirstEnt(world_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent))
{
P_Delta *delta = 0;
@ -1366,6 +1385,7 @@ void S_TickForever(WaveLaneCtx *lane)
P_tl.debug_draw_nodes_count = 0;
}
}
}
UnlockTicketMutex(&P.sim_output_back_tm);
//////////////////////////////

View File

@ -12,7 +12,6 @@ String P_PackWorld(Arena *arena, P_World *src_world)
result.len += StringF(arena, "\n").len;
result.len += StringF(arena, "seed: 0x%F\n", FmtHex(src_world->seed)).len;
result.len += StringF(arena, "tick: %F\n", FmtSint(src_frame->tick)).len;
result.len += StringF(arena, "time: %F\n", FmtSint(src_frame->time_ns)).len;
// Pack entities
@ -100,10 +99,6 @@ P_UnpackedWorld P_UnpackWorld(Arena *arena, String packed)
{
result.seed = CR_IntFromString(top_item->value);
}
if (MatchString(top_item->name, Lit("tick")))
{
result.tick = CR_IntFromString(top_item->value);
}
if (MatchString(top_item->name, Lit("time")))
{
result.time_ns = CR_IntFromString(top_item->value);

View File

@ -639,117 +639,6 @@ void V_TickForever(WaveLaneCtx *lane)
frame->ui_dims.y = MaxF32(frame->ui_dims.y, 64);
frame->draw_dims = frame->ui_dims;
// //////////////////////////////
// //- Pop sim output
// P_OutputState *sim_output = 0;
// LockTicketMutex(&P.sim_output_back_tm);
// {
// sim_output = &P.sim_output_states[P.sim_output_back_idx];
// ++P.sim_output_back_idx;
// if (P.sim_output_back_idx >= countof(P.sim_output_states))
// {
// P.sim_output_back_idx = 0;
// }
// }
// UnlockTicketMutex(&P.sim_output_back_tm);
// //////////////////////////////
// //- Apply sim snapshots
// // FIXME: Only apply latest snapshot
// // FIXME: Real ping
// i64 ping_ns = NsFromSeconds(0.250);
// // TODO: Remove this (testing)
// // b32 received_unseen_tick = 0;
// // b32 tiles_dirty = 0;
// // b32 should_clear_particles = 0;
// // for (P_SnapshotNode *n = sim_output->first_snapshot_node; n; n = n->next)
// // {
// // P_Snapshot *snapshot = &n->snapshot;
// // if (snapshot->tick > world->tick)
// // {
// // world->seed = snapshot->seed;
// // world->tick = snapshot->tick;
// // world->time_ns = snapshot->time_ns;
// // for (P_DeltaNode *dn = snapshot->first_delta_node; dn; dn = dn->next)
// // {
// // P_Delta *delta = &dn->delta;
// // if (delta->kind == P_DeltaKind_Reset)
// // {
// // tiles_dirty = 1;
// // should_clear_particles = 1;
// // }
// // if (delta->kind == P_DeltaKind_RawTiles || delta->kind == P_DeltaKind_Tile)
// // {
// // tiles_dirty = 1;
// // }
// // P_UpdateWorldFromDelta(world, delta);
// // }
// // received_unseen_tick = 1;
// // }
// // }
// {
// P_SnapshotList sim_snapshots = sim_output->snapshots;
// P_UpdateWorldFromSnapshots(sim_world, sim_snapshots);
// }
// //////////////////////////////
// //- Update tiles from sim
// {
// for (P_SnapshotNode *n = sim_output->first_snapshot_node; n; n = n->next)
// {
// P_Snapshot *snapshot = &n->snapshot;
// if (snapshot->tick > world->tick)
// {
// for (u64 placement_idx = 0; placement_idx < snapshot->tile_placements_count; ++placement_idx)
// {
// P_TilePlacement placement = snapshot->tile_placements[placement_idx];
// Rng2I32 dirty_rect = P_UpdateTilesInPlaceFromPlacement(tiles, placement);
// G_CopyCpuToTexture(
// frame->cl,
// gpu_tiles, VEC3I32(dirty_rect.p0.x, dirty_rect.p0.y, 0),
// tiles, VEC3I32(tiles_dims.x, tiles_dims.y, 1),
// RNG3I32(VEC3I32(dirty_rect.p0.x, dirty_rect.p0.y, 0), VEC3I32(dirty_rect.p1.x, dirty_rect.p1.y, 1))
// );
// }
// }
// }
// }
// //////////////////////////////
// //- Update world from sim
// if (sim_output->last_snapshot_node && sim_output->last_snapshot_node->snapshot.tick > world->tick)
// {
// ResetArena(world_arena);
// world = P_WorldFromSnapshot(world_arena, &sim_output->last_snapshot_node->snapshot);
// V.lookup = P_LookupFromWorld(world_arena, world);
// // Copy sim debug info
// sim_debug_draw_descs_count = sim_output->debug_draw_descs_count;
// sim_debug_draw_descs = PushStructsNoZero(world_arena, P_DebugDrawDesc, sim_debug_draw_descs_count);
// CopyStructs(sim_debug_draw_descs, sim_output->debug_draw_descs, sim_debug_draw_descs_count);
// }
//////////////////////////////
//- Process controller events into vis cmds
@ -2631,10 +2520,6 @@ void V_TickForever(WaveLaneCtx *lane)
}
}
//////////////////////////////
//- Pop sim output
@ -2651,80 +2536,58 @@ void V_TickForever(WaveLaneCtx *lane)
UnlockTicketMutex(&P.sim_output_back_tm);
//////////////////////////////
//- Apply sim snapshots
// FIXME: Only apply latest snapshot
// TODO: Remove this (testing)
// b32 received_unseen_tick = 0;
// b32 tiles_dirty = 0;
// b32 should_clear_particles = 0;
// for (P_SnapshotNode *n = sim_output->first_snapshot_node; n; n = n->next)
// {
// P_Snapshot *snapshot = &n->snapshot;
// if (snapshot->tick > world->tick)
// {
// world->seed = snapshot->seed;
// world->tick = snapshot->tick;
// world->time_ns = snapshot->time_ns;
// for (P_DeltaNode *dn = snapshot->first_delta_node; dn; dn = dn->next)
// {
// P_Delta *delta = &dn->delta;
// if (delta->kind == P_DeltaKind_Reset)
// {
// tiles_dirty = 1;
// should_clear_particles = 1;
// }
// if (delta->kind == P_DeltaKind_RawTiles || delta->kind == P_DeltaKind_Tile)
// {
// tiles_dirty = 1;
// }
// P_UpdateWorldFromDelta(world, delta);
// }
// received_unseen_tick = 1;
// }
// }
//- Apply sim msgs
// Apply msgs
{
// P_Msg
for (P_MsgNode *msg_node = sim_output->msgs.first; msg_node; msg_node = msg_node->next)
{
P_Msg *msg = &msg_node->msg;
//- Tiles
if (msg->kind == P_MsgKind_Tiles && sim_world->tiles_hash != msg->tiles_hash)
{
Rng2I32 range = msg->tile_range;
for (i32 tile_y = range.p0.y; tile_y < range.p1.y; ++tile_y)
{
i32 src_tile_y = tile_y - range.p0.y;
for (i32 tile_x = range.p0.x; tile_x < range.p1.x; ++tile_x)
{
i32 src_tile_x = tile_x - range.p0.x;
Vec2 src_tile_pos = VEC2(src_tile_x, src_tile_y);
i32 src_tile_idx = P_TileIdxFromTilePos(src_tile_pos);
u8 src_tile = msg->raw_tiles[src_tile_idx];
Vec2 tile_pos = VEC2(tile_x, tile_y);
i32 tile_idx = P_TileIdxFromTilePos(tile_pos);
sim_world->tiles_hash = msg->tiles_hash;
sim_world->tiles[tile_idx] = src_tile;
}
}
}
}
}
// Apply snapshot
//////////////////////////////
//- Apply sim snapshots
{
if (sim_output->snapshots.last && sim_output->snapshots.last->snapshot.tick > sim_world->last_frame->tick)
P_Snapshot *snapshot = &sim_output->snapshot;
b32 skip_snapshot = 0;
skip_snapshot = snapshot->tick < sim_world->last_frame->tick;
if (!skip_snapshot)
{
P_Snapshot *snapshot = &sim_output->snapshots.last->snapshot;
P_Frame *src_frame = P_FrameFromTick(sim_world, snapshot->src_tick);
P_Frame *dst_frame = P_PushFrame(sim_world, src_frame, snapshot->tick);
sim_world->seed = snapshot->world_seed;
dst_frame->time_ns = snapshot->time_ns;
// Apply deltas
//- Apply deltas
for (P_DeltaNode *dn = snapshot->first_delta_node; dn; dn = dn->next)
{
P_Delta *delta = &dn->delta;
// FIXME: Bounds check tile deltas
if (0)
{
}
//- Reset
// else if (delta->kind == P_DeltaKind_Reset)
// {
// // FIXME: Freelist entities
// dst_frame->ents_count = 0;
// dst_frame->first_ent = 0;
// dst_frame->last_ent = 0;
// ZeroStructs(sim_world->tiles, P_TilesCount);
// ZeroStructs(dst_frame->ent_bins, dst_frame->ent_bins_count);
// tiles_dirty = 1;
// }
//- Raw ent
else if (delta->kind == P_DeltaKind_RawEnt)
if (delta->kind == P_DeltaKind_RawEnt)
{
P_Ent tmp_ent = delta->ent;
P_EntListNode tmp_ent_node = Zi;
@ -2734,46 +2597,9 @@ void V_TickForever(WaveLaneCtx *lane)
ent_list.last = &tmp_ent_node;
P_SpawnEntsFromList(dst_frame, ent_list);
}
//- Raw tiles
// else if (delta->kind == P_DeltaKind_RawTiles)
// {
// Rng2I32 range = delta->tile_range;
// for (i32 tile_y = range.p0.y; tile_y < range.p1.y; ++tile_y)
// {
// i32 src_tile_y = tile_y - range.p0.y;
// for (i32 tile_x = range.p0.x; tile_x < range.p1.x; ++tile_x)
// {
// i32 src_tile_x = tile_x - range.p0.x;
// Vec2 src_tile_pos = VEC2(src_tile_x, src_tile_y);
// i32 src_tile_idx = P_TileIdxFromTilePos(src_tile_pos);
// u8 src_tile = delta->raw_tiles[src_tile_idx];
// Vec2 tile_pos = VEC2(tile_x, tile_y);
// i32 tile_idx = P_TileIdxFromTilePos(tile_pos);
// sim_world->tiles[tile_idx] = src_tile;
// }
// }
// tiles_dirty = 1;
// }
//- Tile range
// else if (delta->kind == P_DeltaKind_Tile)
// {
// P_TileKind tile = delta->tile_kind;
// Rng2I32 range = delta->tiles_range;
// for (i32 tile_y = range.p0.y; tile_y < range.p1.y; ++tile_y)
// {
// for (i32 tile_x = range.p0.x; tile_x < range.p1.x; ++tile_x)
// {
// Vec2 tile_pos = VEC2(tile_x, tile_y);
// i32 tile_idx = P_TileIdxFromTilePos(tile_pos);
// sim_world->tiles[tile_idx] = (u8)tile;
// }
// }
// tiles_dirty = 1;
// }
}
// Update sim debug info
//- Update sim debug info
{
ResetArena(sim_debug_arena);
first_sim_debug_draw_node = 0;
@ -2790,19 +2616,7 @@ void V_TickForever(WaveLaneCtx *lane)
}
}
}
// FIXME: Real prune
// for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent))
// {
// ent->exists = 0;
// }
}
// TODO: Rehash statics
// if (tiles_dirty)
// {
// }
}
// TODO: Remove this
@ -2830,6 +2644,7 @@ void V_TickForever(WaveLaneCtx *lane)
// Push control cmd
{
P_Cmd *cmd = V_PushSimCmd(P_CmdKind_Control);
cmd->predicted = 1;
cmd->target = V.player_key;
cmd->move = frame->move;
cmd->look = frame->look;
@ -2874,6 +2689,12 @@ void V_TickForever(WaveLaneCtx *lane)
// Predict
P_Frame *predict_frame = 0;
{
if (predict_world->tiles_hash != sim_world->tiles_hash)
{
predict_world->tiles_hash = sim_world->tiles_hash;
CopyStructs(predict_world->tiles, sim_world->tiles, P_TilesCount);
}
// i64 step_count = predict_to - sim_world->last_frame->tick;
// // TODO: Preserve constraints?
@ -2912,20 +2733,24 @@ void V_TickForever(WaveLaneCtx *lane)
//- Update blended world
b32 tiles_dirty = 0;
b32 should_clear_particles = 0;
// TODO: Remove this
P_Frame *blend_frame = 0;
{
if (blend_world->tiles_hash != predict_world->tiles_hash)
{
blend_world->tiles_hash = predict_world->tiles_hash;
CopyStructs(blend_world->tiles, predict_world->tiles, P_TilesCount);
tiles_dirty = 1;
}
P_ClearFrames(blend_world, I64Min, I64Max);
blend_frame = P_PushFrame(blend_world, predict_world->last_frame, predict_world->last_frame->tick);
}
// FIXME: Compare tile hashes
b32 tiles_dirty = 0;
b32 should_clear_particles = 0;