From 0c9dd684af6cbacdbc9ca6eba7d3b27d47dff2f0 Mon Sep 17 00:00:00 2001 From: jacob Date: Tue, 20 Jan 2026 01:59:56 -0600 Subject: [PATCH] dynamic time dilation for server->client prediction rate --- src/base/base_math.c | 43 ++++++++- src/base/base_math.h | 8 +- src/base/base_shader.gh | 86 +++++++++-------- src/config.h | 1 + src/gpu/gpu_dx12/gpu_dx12_core.c | 4 +- src/pp/pp.c | 16 ++-- src/pp/pp.h | 7 +- src/pp/pp_sim/pp_sim_core.c | 16 ++-- src/pp/pp_sim/pp_sim_core.h | 2 +- src/pp/pp_vis/pp_vis_core.c | 159 +++++++++++++++++++++---------- src/pp/pp_vis/pp_vis_core.h | 2 +- src/pp/pp_vis/pp_vis_gpu.g | 6 +- src/pp/pp_vis/pp_vis_shared.cgh | 8 +- 13 files changed, 233 insertions(+), 125 deletions(-) diff --git a/src/base/base_math.c b/src/base/base_math.c index 722ad4ec..6c42bca2 100644 --- a/src/base/base_math.c +++ b/src/base/base_math.c @@ -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; } //////////////////////////////////////////////////////////// diff --git a/src/base/base_math.h b/src/base/base_math.h index fe0102a5..d57ea56b 100644 --- a/src/base/base_math.h +++ b/src/base/base_math.h @@ -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 diff --git a/src/base/base_shader.gh b/src/base/base_shader.gh index ffccf45e..f9b73f18 100644 --- a/src/base/base_shader.gh +++ b/src/base/base_shader.gh @@ -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 diff --git a/src/config.h b/src/config.h index c6e950f5..06e5648f 100644 --- a/src/config.h +++ b/src/config.h @@ -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 diff --git a/src/gpu/gpu_dx12/gpu_dx12_core.c b/src/gpu/gpu_dx12/gpu_dx12_core.c index f4e93d96..76992c26 100644 --- a/src/gpu/gpu_dx12/gpu_dx12_core.c +++ b/src/gpu/gpu_dx12/gpu_dx12_core.c @@ -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); diff --git a/src/pp/pp.c b/src/pp/pp.c index 6907adf1..afbd8cec 100644 --- a/src/pp/pp.c +++ b/src/pp/pp.c @@ -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; diff --git a/src/pp/pp.h b/src/pp/pp.h index 5819e2e6..58337b86 100644 --- a/src/pp/pp.h +++ b/src/pp/pp.h @@ -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; diff --git a/src/pp/pp_sim/pp_sim_core.c b/src/pp/pp_sim/pp_sim_core.c index a405bf56..05cc9528 100644 --- a/src/pp/pp_sim/pp_sim_core.c +++ b/src/pp/pp_sim/pp_sim_core.c @@ -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); } } diff --git a/src/pp/pp_sim/pp_sim_core.h b/src/pp/pp_sim/pp_sim_core.h index 3efa0959..e38c4582 100644 --- a/src/pp/pp_sim/pp_sim_core.h +++ b/src/pp/pp_sim/pp_sim_core.h @@ -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; diff --git a/src/pp/pp_vis/pp_vis_core.c b/src/pp/pp_vis/pp_vis_core.c index deb9bab1..82f4a26d 100644 --- a/src/pp/pp_vis/pp_vis_core.c +++ b/src/pp/pp_vis/pp_vis_core.c @@ -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) - { - i64 remote_buffered_cmds = dst_tick - tmp_remote_ack; + P_Control *acked_control = &local_controls[remote_ack % local_controls_cap]; + if (acked_control->tick == 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_SetNext(FontSize, UI_Top(FontSize) * theme.h2); - UI_BuildLabelF("Player: \"%F\"", FmtString(name)); + 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(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); diff --git a/src/pp/pp_vis/pp_vis_core.h b/src/pp/pp_vis/pp_vis_core.h index 8ecaa2d1..003435c6 100644 --- a/src/pp/pp_vis/pp_vis_core.h +++ b/src/pp/pp_vis/pp_vis_core.h @@ -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; diff --git a/src/pp/pp_vis/pp_vis_gpu.g b/src/pp/pp_vis/pp_vis_gpu.g index 42288187..f539c8d8 100644 --- a/src/pp/pp_vis/pp_vis_gpu.g +++ b/src/pp/pp_vis/pp_vis_gpu.g @@ -56,7 +56,7 @@ ComputeShader(V_ClearParticlesCS, 64) V_GpuParams params = G_Dereference(V_ShaderConst_Params)[0]; RWStructuredBuffer particles = G_Dereference(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 drynesses = G_Dereference(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) diff --git a/src/pp/pp_vis/pp_vis_shared.cgh b/src/pp/pp_vis/pp_vis_shared.cgh index 5c079bfd..b492b562 100644 --- a/src/pp/pp_vis/pp_vis_shared.cgh +++ b/src/pp/pp_vis/pp_vis_shared.cgh @@ -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