~starkingdoms/starkingdoms

ref: 8013bd0d83684d62a4d0bdeb707bd92e262dbecb starkingdoms/crates/unified/src/client/interpolation.rs -rw-r--r-- 4.2 KiB
8013bd0d — core feat(netcode-rewrite): vastly improve client simulation accuracy 28 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use std::f64::consts::PI;
use bevy::math::DVec2;
use bevy_replicon::client::confirm_history::ConfirmHistory;
use bevy_replicon::prelude::Replicated;
use bevy_replicon::shared::replicon_tick::RepliconTick;
use crate::prelude::*;
use crate::shared::config::planet::PlanetSpring;

const CORRECTION_FACTOR: f64 = 0.1;
const SNAP_THRESHOLD: f64 = 500.0;

#[derive(Component, Default)]
struct LocalState {
    position: DVec2,
    linvel: DVec2,
    angvel: f64,
    rotation_cos: f64,
    rotation_sin: f64,
}

#[derive(Component, Default)]
struct CorrectionTarget {
    position: DVec2,
    linvel: DVec2,
    angvel: f64,
    rotation_cos: f64,
    rotation_sin: f64,
    last_tick: Option<RepliconTick>,
}

pub fn prediction_plugin(app: &mut App) {
    app
        .add_observer(init_prediction)
        .add_systems(PreUpdate, (
            save_local_state.before(bevy_replicon::client::ClientSystems::Receive),
            record_server_correction.after(bevy_replicon::client::ClientSystems::Receive),
        ))
        .add_systems(FixedUpdate, apply_correction
            .after(PhysicsSystems::Prepare)
            .before(PhysicsSystems::StepSimulation));
}

fn init_prediction(
    trigger: On<Add, Replicated>,
    query: Query<(&Position, &Rotation, &LinearVelocity, Option<&AngularVelocity>), Without<PlanetSpring>>,
    mut commands: Commands,
) {
    let entity = trigger.event_target();
    let Ok((pos, rot, linvel, angvel)) = query.get(entity) else { return };
    commands.entity(entity).insert((
        LocalState {
            position: pos.0,
            linvel: linvel.0,
            angvel: angvel.map(|a| a.0).unwrap_or(0.0),
            rotation_cos: rot.cos,
            rotation_sin: rot.sin,
        },
        CorrectionTarget::default(),
    ));
}

fn save_local_state(
    mut query: Query<(&Position, &Rotation, &LinearVelocity, &AngularVelocity, &mut LocalState)>,
) {
    for (pos, rot, linvel, angvel, mut local) in &mut query {
        if pos.0.is_nan() { continue; }
        local.position = pos.0;
        local.linvel = linvel.0;
        local.angvel = angvel.0;
        local.rotation_cos = rot.cos;
        local.rotation_sin = rot.sin;
    }
}

fn record_server_correction(
    mut query: Query<(
        &mut Position, &mut Rotation, &mut LinearVelocity, &mut AngularVelocity,
        &ConfirmHistory,
        &mut CorrectionTarget, &LocalState,
    )>,
) {
    for (mut pos, mut rot, mut linvel, mut angvel, history, mut target, local) in &mut query {
        let tick = history.last_tick();
        if target.last_tick != Some(tick) {
            target.last_tick = Some(tick);
            target.position = pos.0;
            target.linvel = linvel.0;
            target.angvel = angvel.0;
            target.rotation_cos = rot.cos;
            target.rotation_sin = rot.sin;
        }
        pos.0 = local.position;
        rot.cos = local.rotation_cos;
        rot.sin = local.rotation_sin;
        linvel.0 = local.linvel;
        angvel.0 = local.angvel;
    }
}

fn apply_correction(
    mut query: Query<(&mut Position, &mut Rotation, &mut LinearVelocity, &mut AngularVelocity, &CorrectionTarget)>,
) {
    for (mut pos, mut rot, mut linvel, mut angvel, target) in &mut query {
        let Some(_) = target.last_tick else { continue };

        let pos_err = target.position - pos.0;
        if pos_err.length() > SNAP_THRESHOLD {
            pos.0 = target.position;
            linvel.0 = target.linvel;
            angvel.0 = target.angvel;
            rot.cos = target.rotation_cos;
            rot.sin = target.rotation_sin;
        } else {
            pos.0 += pos_err * CORRECTION_FACTOR;
            let cur_linvel = linvel.0;
            linvel.0 += (target.linvel - cur_linvel) * CORRECTION_FACTOR;
            let cur_angvel = angvel.0;
            angvel.0 += (target.angvel - cur_angvel) * CORRECTION_FACTOR;

            let angle_local = rot.sin.atan2(rot.cos);
            let angle_target = target.rotation_sin.atan2(target.rotation_cos);
            let diff = ((angle_target - angle_local + PI).rem_euclid(2.0 * PI)) - PI;
            let new_angle = angle_local + diff * CORRECTION_FACTOR;
            rot.cos = new_angle.cos();
            rot.sin = new_angle.sin();
        }
    }
}