dynamic time dilation for server->client prediction rate

This commit is contained in:
jacob 2026-01-20 01:59:56 -06:00
parent 4d7e06541e
commit 0c9dd684af
13 changed files with 233 additions and 125 deletions

View File

@ -162,7 +162,7 @@ u64 AlignU64(u64 x, u64 align)
return result;
}
u64 AlignU64ToNextPow2(u64 x)
u64 NextPow2U64(u64 x)
{
u64 result = 0;
if (x > 0)
@ -213,22 +213,55 @@ f32 LerpAngleF32(f32 a, f32 b, f32 t)
i32 LerpI32(i32 val0, i32 val1, f32 t)
{
return val0 + RoundF32(((f32)val1 - (f32)val0) * t);
return val0 + RoundF32((f32)(val1 - val0) * t);
}
i64 LerpI64(i64 val0, i64 val1, f64 t)
{
return val0 + RoundF64(((f64)val1 - (f64)val0) * t);
return val0 + RoundF64((f64)(val1 - val0) * t);
}
i32 LerpU32(u32 val0, u32 val1, f32 t)
{
return val0 + RoundF32(((f32)val1 - (f32)val0) * t);
return val0 + RoundF32((f32)(val1 - val0) * t);
}
i64 LerpU64(u64 val0, u64 val1, f64 t)
{
return val0 + RoundF64(((f64)val1 - (f64)val0) * t);
return val0 + RoundF64((f64)(val1 - val0) * t);
}
////////////////////////////////////////////////////////////
//~ Smoothstep
f32 SmoothstepF32(f32 a, f32 b, f32 t)
{
f32 result = 0;
if (a == b)
{
result = (t < a) ? 0 : 1;
}
else
{
t = ClampF32((t - a) / (b - a), 0, 1);
result = t * t * (3.0f - 2.0f * t);
}
return result;
}
f64 SmoothstepF64(f64 a, f64 b, f64 t)
{
f64 result = 0;
if (a == b)
{
result = (t < a) ? 0 : 1;
}
else
{
t = ClampF64((t - a) / (b - a), 0, 1);
result = t * t * (3.0f - 2.0f * t);
}
return result;
}
////////////////////////////////////////////////////////////

View File

@ -275,7 +275,7 @@ u64 PowU64(u64 v, u8 exp);
//~ Align
u64 AlignU64(u64 x, u64 align);
u64 AlignU64ToNextPow2(u64 x);
u64 NextPow2U64(u64 x);
////////////////////////////////////////////////////////////
//~ Trig
@ -307,6 +307,12 @@ i64 LerpI64(i64 val0, i64 val1, f64 t);
i32 LerpU32(u32 val0, u32 val1, f32 t);
i64 LerpU64(u64 val0, u64 val1, f64 t);
////////////////////////////////////////////////////////////
//~ Smoothstep
f32 SmoothstepF32(f32 a, f32 b, f32 t);
f64 SmoothstepF64(f64 a, f64 b, f64 t);
////////////////////////////////////////////////////////////
//~ Color

View File

@ -46,68 +46,70 @@ u32 countof(T arr[N])
}
////////////////////////////////////////////////////////////
//~ Min / max
//~ C -> HLSL interoperability stubs
//- Min
#define MinU8(...) min(__VA_ARGS__)
#define MinI8(...) min(__VA_ARGS__)
#define MinU32(...) min(__VA_ARGS__)
#define MinI32(...) min(__VA_ARGS__)
#define MinF32(...) min(__VA_ARGS__)
#define MinU64(...) min(__VA_ARGS__)
#define MinI64(...) min(__VA_ARGS__)
#define MinF64(...) min(__VA_ARGS__)
#define MinU8 min
#define MinI8 min
#define MinU32 min
#define MinI32 min
#define MinF32 min
#define MinU64 min
#define MinI64 min
#define MinF64 min
//- Max
#define MaxU8(...) max(__VA_ARGS__)
#define MaxI8(...) max(__VA_ARGS__)
#define MaxU32(...) max(__VA_ARGS__)
#define MaxI32(...) max(__VA_ARGS__)
#define MaxF32(...) max(__VA_ARGS__)
#define MaxU64(...) max(__VA_ARGS__)
#define MaxI64(...) max(__VA_ARGS__)
#define MaxF64(...) max(__VA_ARGS__)
#define MaxU8 max
#define MaxI8 max
#define MaxU32 max
#define MaxI32 max
#define MaxF32 max
#define MaxU64 max
#define MaxI64 max
#define MaxF64 max
//- Clamp
#define ClampU32(...) clamp(__VA_ARGS__)
#define ClampI32(...) clamp(__VA_ARGS__)
#define ClampF32(...) clamp(__VA_ARGS__)
#define ClampU64(...) clamp(__VA_ARGS__)
#define ClampI64(...) clamp(__VA_ARGS__)
#define ClampF64(...) clamp(__VA_ARGS__)
#define MatchFloor(a, b) all(floor(a) == floor(b))
////////////////////////////////////////////////////////////
//~ Float ops
#define ClampU32 clamp
#define ClampI32 clamp
#define ClampF32 clamp
#define ClampU64 clamp
#define ClampI64 clamp
#define ClampF64 clamp
//- Round
#define RoundF32(v) round(v)
#define RoundF64(v) round(v)
#define RoundF32 round
#define RoundF64 round
//- Floor
#define FloorF32(v) floor(v)
#define FloorF64(v) floor(v)
#define FloorF32 floor
#define FloorF64 floor
//- Ceil
#define CeilF32(v) ceil(v)
#define CeilF64(v) ceil(v)
#define CeilF32 ceil
#define CeilF64 ceil
//- Trunc
#define TruncF32(v) trunc(v)
#define TruncF64(v) trunc(v)
#define TruncF32 trunc
#define TruncF64 trunc
//- Mod
#define ModF32(v, m) fmod((v), (m))
#define ModF64(v, m) fmod((v), (m))
#define ModF32 fmod
#define ModF64 fmod
//- Abs
#define AbsF32(v) abs(v)
#define AbsF64(v) abs(v)
#define AbsF32 abs
#define AbsF64 abs
//- Sign
#define SignF32(v) sign(v)
#define SignF64(v) sign(v)
#define SignF32 sign
#define SignF64 sign
//- Smoothstep
#define SmoothstepF32 smoothstep
#define SmoothstepF64 smoothstep
//- Matchfloor
#define MatchFloor(a, b) all(floor(a) == floor(b))
////////////////////////////////////////////////////////////
//~ Derivative helpers

View File

@ -41,6 +41,7 @@
#define SIM_MAX_PING 5.0
#define SIM_TICKS_PER_SECOND 64
#define SIM_TICK_INTERVAL_NS (NsFromSeconds(1) / SIM_TICKS_PER_SECOND)
// Like USER_INTERP_RATIO, but applies to snapshots received by the local sim from the
// master sim (how far back in time should the client render the server's state)
#define SIM_CLIENT_INTERP_RATIO 2.0

View File

@ -967,7 +967,7 @@ G_ResourceHandle G_PushResource(G_ArenaHandle arena_handle, G_CommandListHandle
d3d_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
d3d_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
d3d_desc.Format = DXGI_FORMAT_UNKNOWN;
d3d_desc.Width = AlignU64ToNextPow2(MaxU64(desc.buffer.size, min_buffer_size));
d3d_desc.Width = NextPow2U64(MaxU64(desc.buffer.size, min_buffer_size));
d3d_desc.Height = 1;
d3d_desc.DepthOrArraySize = 1;
d3d_desc.MipLevels = 1;
@ -1593,7 +1593,7 @@ G_D12_StagingRegionNode *G_D12_PushStagingRegion(G_D12_CmdList *cl, u64 size)
// Queue old ring for deletion
old_ring = ring;
ring = 0;
u64 new_ring_size = MaxU64(AlignU64ToNextPow2(size), Mebi(8));
u64 new_ring_size = MaxU64(NextPow2U64(size), Mebi(8));
if (old_ring)
{
new_ring_size = MaxU64(new_ring_size, old_ring->size * 2);

View File

@ -1357,14 +1357,14 @@ 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_max_constraints = frame->max_constraints;
i64 old_constraints_cap = frame->constraints_cap;
P_Constraint *old_constraints = frame->constraints;
{
ZeroStruct(frame);
}
frame->ent_bins_count = old_ent_bins_count;
frame->ent_bins = old_ent_bins;
frame->max_constraints = old_max_constraints;
frame->constraints_cap = old_constraints_cap;
frame->constraints = old_constraints;
ZeroStructs(frame->ent_bins, frame->ent_bins_count);
}
@ -1388,9 +1388,9 @@ P_Frame *P_PushFrame(P_World *world, P_Frame *src_frame, i64 tick)
if (!frame->constraints)
{
frame->max_constraints = Kibi(4);
frame->constraints_cap = Kibi(4);
frame->constraints_count = 0;
frame->constraints = PushStructsNoZero(world->frames_arena, P_Constraint, frame->max_constraints);
frame->constraints = PushStructsNoZero(world->frames_arena, P_Constraint, frame->constraints_cap);
}
}
@ -1436,7 +1436,7 @@ void P_StepFrame(P_Frame *frame)
P_World *world = frame->world;
P_Frame *prev_frame = frame->prev;
i64 sim_dt_ns = NsFromSeconds(1) / SIM_TICKS_PER_SECOND;
i64 sim_dt_ns = SIM_TICK_INTERVAL_NS;
f64 sim_dt = SecondsFromNs(sim_dt_ns);
//////////////////////////////
@ -1582,7 +1582,7 @@ void P_StepFrame(P_Frame *frame)
//////////////////////////////
//- Copy previous frame's constraints
frame->constraints_count = MinI64(prev_frame->constraints_count, frame->max_constraints);
frame->constraints_count = MinI64(prev_frame->constraints_count, frame->constraints_cap);
CopyStructs(frame->constraints, prev_frame->constraints, frame->constraints_count);
i32 solver_steps_count = 4;
@ -1641,7 +1641,7 @@ void P_StepFrame(P_Frame *frame)
}
if (!match)
{
if (frame->constraints_count < frame->max_constraints)
if (frame->constraints_count < frame->constraints_cap)
{
constraint = &frame->constraints[frame->constraints_count];
frame->constraints_count += 1;
@ -1809,7 +1809,7 @@ void P_StepFrame(P_Frame *frame)
// }
// if (!match)
// {
// if (frame->constraints_count < frame->max_constraints)
// if (frame->constraints_count < frame->constraints_cap)
// {
// constraint = &frame->constraints[frame->constraints_count];
// frame->constraints_count += 1;

View File

@ -79,6 +79,9 @@ Struct(P_Control)
i64 tick;
i64 orig_tick; // Will differ from tick if this control was propagated from the control of another tick
// TODO: Move this to client-only code
i64 produced_at_ns;
Vec2 move;
Vec2 look;
f32 fire_held;
@ -132,9 +135,9 @@ Struct(P_Ent)
//- Player
b32 is_player;
P_Key guy;
f32 ping;
u8 string_len;
u8 string_text[P_MaxPlayerNameLen + 8];
@ -239,7 +242,7 @@ Struct(P_Frame)
i64 ent_bins_count;
P_EntBin *ent_bins;
i64 max_constraints;
i64 constraints_cap;
i64 constraints_count;
P_Constraint *constraints;

View File

@ -225,8 +225,8 @@ void S_TickForever(WaveLaneCtx *lane)
client->name_len = MinI64(countof(client->name_text), src_name.len);
CopyBytes(client->name_text, src_name.text, client->name_len);
client->max_controls = SIM_MAX_PING * SIM_TICKS_PER_SECOND;
client->controls = PushStructs(perm, P_Control, client->max_controls);
client->controls_cap = NextPow2U64(SIM_MAX_PING * SIM_TICKS_PER_SECOND);
client->controls = PushStructs(perm, P_Control, client->controls_cap);
DllQueuePushNPZ(&S_NilClient, S.first_client, S.last_client, client, next, prev);
DllQueuePushNP(bin->first, bin->last, client, next_in_bin, prev_in_bin);
@ -344,7 +344,7 @@ void S_TickForever(WaveLaneCtx *lane)
if (raw_control)
{
i64 control_tick = raw_control->tick;
P_Control *control = &client->controls[control_tick % client->max_controls];
P_Control *control = &client->controls[control_tick % client->controls_cap];
{
*control = *raw_control;
control->move = ClampVec2Len(control->move, 1);
@ -355,12 +355,12 @@ void S_TickForever(WaveLaneCtx *lane)
// In case previous snapshots weren't received, backfill with newest snapshot
//
// TODO: Not like this
i64 max_propagate_count = SIM_TICKS_PER_SECOND / 4;
i64 max_propagate_count = SIM_TICKS_PER_SECOND;
{
// Propagate backwards
for (i64 prop_tick = control_tick - 1; prop_tick >= MaxI64(prop_tick - max_propagate_count, world_frame->tick); --prop_tick)
{
P_Control *prop_control = &client->controls[prop_tick % client->max_controls];
P_Control *prop_control = &client->controls[prop_tick % client->controls_cap];
if (prop_control->orig_tick != prop_control->tick && control_tick < prop_control->orig_tick)
{
*prop_control = *control;
@ -396,7 +396,7 @@ void S_TickForever(WaveLaneCtx *lane)
i64 ack_tick = client->ack;
for (i64 check_ack_tick = world_frame->tick; check_ack_tick < world_frame->tick + max_ack_count; ++check_ack_tick)
{
P_Control *ack_control = &client->controls[check_ack_tick % client->max_controls];
P_Control *ack_control = &client->controls[check_ack_tick % client->controls_cap];
if (ack_control->orig_tick == check_ack_tick)
{
ack_tick = check_ack_tick;
@ -423,7 +423,7 @@ void S_TickForever(WaveLaneCtx *lane)
for (S_Client *client = S.first_client; !S_IsClientNil(client); client = client->next)
{
P_Control *control = &client->controls[world_frame->tick % client->max_controls];
P_Control *control = &client->controls[world_frame->tick % client->controls_cap];
if (control->tick == world_frame->tick)
{
P_Ent *player = P_EntFromKey(world_frame, client->player);
@ -989,7 +989,7 @@ void S_TickForever(WaveLaneCtx *lane)
if (!shutdown)
{
i64 step_dt_ns = NsFromSeconds(1) / SIM_TICKS_PER_SECOND;
i64 step_dt_ns = SIM_TICK_INTERVAL_NS;
PLT_SleepFrame(time_ns, step_dt_ns);
}
}

View File

@ -17,7 +17,7 @@ Struct(S_Client)
i32 name_len;
u8 name_text[P_MaxPlayerNameLen];
i64 max_controls;
i64 controls_cap;
P_Control *controls;
i64 remote_ack;

View File

@ -66,7 +66,7 @@ void V_PushParticles(V_Emitter src)
{
V_Frame *frame = V_CurrentFrame();
i64 split_threshold = 256; // Arbitrary threshold. Lower counts cause less shader work but more cpu -> gpu upload bandwidth.
src.count = MinU64(src.count, V_MaxParticles);
src.count = MinU64(src.count, V_ParticlesCap);
if (src.seed == 0)
{
src.seed = RandU64FromState(&frame->rand);
@ -364,20 +364,25 @@ void V_TickForever(WaveLaneCtx *lane)
P_World *predict_world = P_AcquireWorld();
P_World *local_world = P_AcquireWorld();
i64 max_local_controls = SIM_MAX_PING * SIM_TICKS_PER_SECOND;
P_Control *local_controls = PushStructs(perm, P_Control, max_local_controls);
i64 local_controls_cap = NextPow2U64(SIM_MAX_PING * SIM_TICKS_PER_SECOND);
P_Control *local_controls = PushStructs(perm, P_Control, local_controls_cap);
// Remote ack represents player control snapshots that the sim has received
i64 remote_ack_received_at_ns = 0;
i64 remote_ack = 0;
i64 remote_ack_received_at_ns = 0;
i64 prev_snapshot_sent_at_ns = 0;
i64 known_sim_tick = 0;
i64 remote_buffered_controls_count = 0;
// Which tick snapshots we have received and finished assembling from the server
i64 ack = 0;
i64 ack_mirror = 0;
// Basically same as ack, except it includes unassembled snapshots.
i64 known_sim_tick = 0;
f64 most_recent_rtt = 0;
// i64 smoothed_rtt_ns = 0;
// f64 smoothed_rtt = 0.300;
f64 smoothed_rtt = 1.00;
// f64 smoothed_rtt = 2;
f32 smooth_remote_buffered_ticks = 0;
f32 smooth_remote_buffered_ticks_target = 2;
@ -427,7 +432,7 @@ void V_TickForever(WaveLaneCtx *lane)
gpu_particles = G_PushBuffer(
gpu_perm, cl,
V_Particle,
V_MaxParticles,
V_ParticlesCap,
.flags = G_ResourceFlag_ZeroMemory | G_ResourceFlag_AllowShaderReadWrite
);
gpu_particles_ref = G_PushRWStructuredBufferRef(gpu_perm, gpu_particles, V_Particle);
@ -568,6 +573,7 @@ void V_TickForever(WaveLaneCtx *lane)
frame->edit_camera_zoom = prev_frame->edit_camera_zoom;
frame->sim_key = prev_frame->sim_key;
frame->desired_sim_key = prev_frame->desired_sim_key;
frame->predict_tick_accum = prev_frame->predict_tick_accum;
frame->tick = V.current_frame_tick;
frame->time_ns = TimeNs();
@ -1048,13 +1054,6 @@ void V_TickForever(WaveLaneCtx *lane)
//////////////////////////////
//- Pop messages from sim
// FIXME: Reject messages if not from currently connected sim
P_MsgList in_msgs = Zi;
{
NET_MsgList net_msgs = NET_Swap(frame->arena, net_pipe);
@ -1124,11 +1123,13 @@ void V_TickForever(WaveLaneCtx *lane)
remote_ack_received_at_ns = frame->time_ns;
remote_ack = tmp_remote_ack;
ack_mirror = tmp_ack_mirror;
}
remote_buffered_controls_count = tmp_remote_ack - dst_tick;
if (dst_tick >= known_sim_tick)
P_Control *acked_control = &local_controls[remote_ack % local_controls_cap];
if (acked_control->tick == remote_ack)
{
i64 remote_buffered_cmds = dst_tick - tmp_remote_ack;
most_recent_rtt = SecondsFromNs(frame->time_ns - acked_control->produced_at_ns);
}
}
if (dst_tick > ack && fragments_count < P_MaxFrameSnapshotFragments)
@ -1196,6 +1197,14 @@ void V_TickForever(WaveLaneCtx *lane)
}
}
//////////////////////////////
//- Compute smoothed rtt
{
f64 change_rate = 1.0 * frame->dt;
smoothed_rtt = LerpF64(smoothed_rtt, most_recent_rtt, change_rate);
}
//////////////////////////////
//- Apply sim msgs
@ -1242,7 +1251,6 @@ void V_TickForever(WaveLaneCtx *lane)
//////////////////////////////
//- Compute movement & look
if (frame->predict_to != prev_frame->predict_to)
{
Vec2 move = Zi;
{
@ -1253,8 +1261,8 @@ void V_TickForever(WaveLaneCtx *lane)
}
move = ClampVec2Len(move, 1);
f32 fire_held = frame->held_buttons[Button_M1];
f32 fire_presses = fire_held && !prev_frame->held_buttons[Button_M1];
Vec2 look = Zi;
f32 fire_presses = fire_held && !prev_frame->held_buttons[Button_M1];
{
Vec2 center = P_WorldShapeFromEnt(local_guy).centroid;
look = SubVec2(frame->world_cursor, center);
@ -1285,23 +1293,56 @@ void V_TickForever(WaveLaneCtx *lane)
//////////////////////////////
//- Determine local simulation bounds
//
// Prediction rate will increase based on how many commands of ours we know
// that the server has buffered up or is missing.
//
// If the server is missing commands, we increase the rate of prediction
// until the server reports it has enough of our commands buffered up.
//
// If the server reports it has too many of our commands buffered up, we
// slow the rate of prediction.
// FIXME: Real ping
// f64 ping = 0.250;
f64 ping = 0;
i64 ping_ns = NsFromSeconds(ping);
{
// How many buffered commands of ours we'd like the server to have
i64 target_buffered_controls_count = 1;
// frame->predict_to = sim_world->last_frame->tick + MaxF64(CeilF64(ping * SIM_TICKS_PER_SECOND), 1.0);
f64 rtt_bias_factor = 5.0;
f64 dilation_factor = SmoothstepF64(
-(SIM_TICKS_PER_SECOND * smoothed_rtt * rtt_bias_factor),
(SIM_TICKS_PER_SECOND * smoothed_rtt * rtt_bias_factor),
target_buffered_controls_count - remote_buffered_controls_count
) * 2 - 1;
// frame->predict_to = ack + 10;
frame->predict_to = MaxI64(prev_frame->predict_to, ack + 10);
f64 predict_dt = frame->dt + frame->dt * dilation_factor;
frame->predict_tick_accum += predict_dt * SIM_TICKS_PER_SECOND;
if (ack == 0 || AbsF64(frame->predict_tick_accum - ack) > SIM_TICKS_PER_SECOND)
{
frame->predict_tick_accum = ack + (SIM_TICKS_PER_SECOND * smoothed_rtt * 0.5);
LogDebugF("RESET");
}
// LogDebugF(
// "Buffered count: %F. Ack: %F, Predict tick accum: %F, Predict dt: %F, Dilation factor: %F",
// FmtSint(remote_buffered_controls_count),
// FmtFloat(ack),
// FmtFloat(frame->predict_tick_accum),
// FmtFloat(predict_dt),
// FmtFloat(dilation_factor)
// );
frame->predict_tick_accum = MaxF64(prev_frame->predict_tick_accum, frame->predict_tick_accum);
}
i64 prev_frame_predict_to = FloorF64(prev_frame->predict_tick_accum);
i64 predict_to = FloorF64(frame->predict_tick_accum);
//////////////////////////////
//- Create player control
if (frame->predict_to > prev_frame->predict_to)
if (predict_to > prev_frame_predict_to)
{
i64 control_tick = frame->predict_to;
i64 control_tick = predict_to;
P_Control control = Zi;
control.tick = control_tick;
@ -1309,18 +1350,20 @@ void V_TickForever(WaveLaneCtx *lane)
control.move = frame->move;
control.look = frame->look;
control.fire_held = frame->fire_held;
// FIXME: Don't propagate fire presses over multiple sim frames
control.fire_presses = frame->fire_presses;
//- Fill controls buffer backwards
i64 max_fill_count = SIM_TICKS_PER_SECOND / 4;
for (i64 dst_tick = control_tick; dst_tick > MaxI64(prev_frame->predict_to, (dst_tick - max_local_controls)); --dst_tick)
for (i64 dst_tick = control_tick; dst_tick > MaxI64(prev_frame_predict_to, (dst_tick - local_controls_cap)); --dst_tick)
{
P_Control *dst_control = &local_controls[dst_tick % max_local_controls];
P_Control *dst_control = &local_controls[dst_tick % local_controls_cap];
if (dst_tick > dst_control->orig_tick)
{
*dst_control = control;
dst_control->tick = dst_tick;
dst_control->orig_tick = control_tick;
dst_control->produced_at_ns = frame->time_ns;
}
else
{
@ -1348,9 +1391,9 @@ void V_TickForever(WaveLaneCtx *lane)
i64 total_delta_bytes = 0;
PackedDeltaNode *first_delta_node = 0;
PackedDeltaNode *last_delta_node = 0;
if (frame->predict_to >= ack)
if (predict_to >= ack)
{
i64 last_send_tick = frame->predict_to;
i64 last_send_tick = predict_to;
i64 first_send_tick = MaxI64(ack, 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)
@ -1361,7 +1404,7 @@ void V_TickForever(WaveLaneCtx *lane)
{
// TODO: Delta compress
should_transmit = 1;
P_Control *control = &local_controls[send_tick % max_local_controls];
P_Control *control = &local_controls[send_tick % local_controls_cap];
BB_WriteBytes(&packer_bbw, StringFromStruct(control));
}
u64 delta_end = BB_GetNumBytesWritten(&packer_bbw);
@ -1487,8 +1530,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 = ack - 2;
last_predict_tick = predict_to;
first_predict_tick = ack;
first_predict_tick = MaxI64(first_predict_tick, last_predict_tick - max_predict_ticks);
}
@ -1517,7 +1559,6 @@ void V_TickForever(WaveLaneCtx *lane)
// Predict
P_Frame *predict_frame = predict_world->last_frame;
if (frame->predict_to != prev_frame->predict_to)
{
if (predict_world->tiles_hash != sim_world->tiles_hash)
{
@ -1535,7 +1576,7 @@ void V_TickForever(WaveLaneCtx *lane)
P_Ent *predict_player = P_EntFromKey(predict_frame, V.player_key);
if (!P_IsEntNil(predict_player))
{
P_Control *predict_control = &local_controls[predict_tick % max_local_controls];
P_Control *predict_control = &local_controls[predict_tick % local_controls_cap];
if (predict_control->tick == predict_tick)
{
predict_player->control = *predict_control;
@ -1608,7 +1649,7 @@ void V_TickForever(WaveLaneCtx *lane)
// P_Ent *predict_player = P_EntFromKey(predict_frame, local_player->key);
// if (!P_IsEntNil(predict_player))
// {
// P_Control *predict_control = &local_controls[predict_tick % max_local_controls];
// P_Control *predict_control = &local_controls[predict_tick % local_controls_cap];
// if (predict_control->tick == predict_tick)
// {
// predict_player->control = *predict_control;
@ -3574,6 +3615,7 @@ void V_TickForever(WaveLaneCtx *lane)
}
UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y);
{
UI_BuildLabelF("Ping: %F", 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));
@ -3617,10 +3659,15 @@ void V_TickForever(WaveLaneCtx *lane)
if (frame->held_buttons[Button_Tab])
{
UI_Size name_sz = UI_GROW(0.75, 0);
UI_Size ping_sz = UI_GROW(0.25, 0);
UI_Size spacing_sz = UI_FNT(1, 0);
Struct(BoardRow)
{
BoardRow *next;
String name;
f32 ping;
};
i64 players_count = 0;
i64 board_rows_count = 0;
@ -3638,7 +3685,8 @@ void V_TickForever(WaveLaneCtx *lane)
board_rows_count += 1;
}
String name = P_StringFromEnt(player);
row->name= name;
row->name = name;
row->ping = player->ping;
}
}
@ -3658,18 +3706,32 @@ void V_TickForever(WaveLaneCtx *lane)
UI_SetNext(Flags, UI_BoxFlag_Floating);
UI_PushCP(UI_BuildColumnEx(board_key));
{
UI_BuildSpacer(spacing_sz, Axis_Y);
for (BoardRow *board_row = first_board_row; board_row; board_row = board_row->next)
{
String name = board_row->name;
UI_SetNext(Width, UI_GROW(1, 0));
UI_SetNext(Width, UI_GROW(1, 1));
UI_SetNext(Height, UI_FNT(2.5, 0));
UI_SetNext(Tint, 0);
UI_SetNext(ChildAlignment, UI_Region_Left);
UI_PushCP(UI_BuildRow());
UI_PushCP(UI_BuildRow()); // Scoreboard row
{
UI_BuildSpacer(spacing_sz, Axis_X);
UI_SetNext(Width, name_sz);
UI_PushCP(UI_BuildColumn()); // Player name column
{
UI_SetNext(FontSize, UI_Top(FontSize) * theme.h2);
UI_BuildLabelF("Player: \"%F\"", FmtString(name));
UI_BuildLabelF("Player: \"%F\"", FmtString(board_row->name));
}
UI_PopCP(UI_TopCP());
UI_BuildSpacer(spacing_sz, Axis_X);
UI_SetNext(Width, ping_sz);
UI_PushCP(UI_BuildColumn()); // Ping column
{
UI_SetNext(FontSize, UI_Top(FontSize) * theme.h2);
UI_BuildLabelF("Ping: %F ", FmtFloat(board_row->ping, .p = 2));
}
UI_PopCP(UI_TopCP());
}
UI_PopCP(UI_TopCP());
@ -3678,6 +3740,7 @@ void V_TickForever(WaveLaneCtx *lane)
UI_BuildDivider(UI_PIX(1, 1), theme.col.divider, Axis_Y);
}
}
UI_BuildSpacer(spacing_sz, Axis_Y);
}
UI_PopCP(UI_TopCP());
}
@ -4140,7 +4203,7 @@ void V_TickForever(WaveLaneCtx *lane)
// Clear particles
if (should_clear_particles)
{
G_Compute(frame->cl, V_ClearParticlesCS, V_ThreadGroupSizeFromBufferSize(V_MaxParticles));
G_Compute(frame->cl, V_ClearParticlesCS, V_ThreadGroupSizeFromBufferSize(V_ParticlesCap));
}
// Discard render target
@ -4160,7 +4223,7 @@ void V_TickForever(WaveLaneCtx *lane)
G_DumbMemorySync(frame->cl, gpu_particles);
// Simulate particles
G_Compute(frame->cl, V_SimParticlesCS, V_ThreadGroupSizeFromBufferSize(V_MaxParticles));
G_Compute(frame->cl, V_SimParticlesCS, V_ThreadGroupSizeFromBufferSize(V_ParticlesCap));
// Barrier since stains were written
G_DumbGlobalMemorySync(frame->cl);

View File

@ -285,7 +285,7 @@ Struct(V_Frame)
V_CmdNode *first_cmd_node;
V_CmdNode *last_cmd_node;
i64 predict_to;
f64 predict_tick_accum;
// Control
Vec2 move;

View File

@ -56,7 +56,7 @@ ComputeShader(V_ClearParticlesCS, 64)
V_GpuParams params = G_Dereference<V_GpuParams>(V_ShaderConst_Params)[0];
RWStructuredBuffer<V_Particle> particles = G_Dereference<V_Particle>(params.particles);
u32 particle_idx = SV_DispatchThreadID;
if (particle_idx < V_MaxParticles)
if (particle_idx < V_ParticlesCap)
{
particles[particle_idx].exists = 0;
}
@ -332,7 +332,7 @@ ComputeShader(V_EmitParticlesCS, 64)
for (u32 i = 0; i < emitter.count; ++i)
{
u32 particle_seq = emitter.first_particle_seq + i;
u32 particle_idx = particle_seq % (u32)V_MaxParticles;
u32 particle_idx = particle_seq % (u32)V_ParticlesCap;
particles[particle_idx].exists = 1;
particles[particle_idx].emitter_init_num = emitter_idx + 1;
particles[particle_idx].seq = particle_seq;
@ -352,7 +352,7 @@ ComputeShader(V_SimParticlesCS, 64)
RWTexture2D<f32> drynesses = G_Dereference<f32>(params.drynesses);
u32 particle_idx = SV_DispatchThreadID;
if (particle_idx < V_MaxParticles)
if (particle_idx < V_ParticlesCap)
{
V_Particle particle = particles[particle_idx];
if (particle.exists > 0)

View File

@ -1,10 +1,10 @@
#define V_CellsPerMeter 32.0
#define V_CellsPerSqMeter (V_CellsPerMeter * V_CellsPerMeter)
// #define V_MaxParticles Kibi(128)
// #define V_MaxParticles Mebi(1)
#define V_MaxParticles Mebi(2)
// #define V_MaxParticles Mebi(16)
// #define V_ParticlesCap Kibi(128)
// #define V_ParticlesCap Mebi(1)
#define V_ParticlesCap Mebi(2)
// #define V_ParticlesCap Mebi(16)
////////////////////////////////////////////////////////////
//~ Constant types