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
use std::time::Duration;

use bevy::prelude::*;
use de_core::{
    gamestate::GameState,
    gconfig::is_multiplayer,
    objects::{Local, MovableSolid},
    schedule::{Movement, PreMovement},
    state::AppState,
};
use de_messages::ToPlayers;
use de_multiplayer::{NetEntities, NetRecvTransformEvent, ToPlayersEvent};

use crate::movement::MovementSet;

const MIN_SYNC_PERIOD: Duration = Duration::from_millis(800);
const SYNC_RANDOMIZATION_MS: u64 = 250;

pub(crate) struct SyncingPlugin;

impl Plugin for SyncingPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(
            PreMovement,
            setup_entities
                .run_if(is_multiplayer)
                .run_if(in_state(AppState::InGame)),
        )
        .add_systems(
            Movement,
            (
                receive_transforms
                    .run_if(on_event::<NetRecvTransformEvent>())
                    .after(MovementSet::UpdateTransform),
                send_transforms
                    .run_if(is_multiplayer)
                    .after(MovementSet::UpdateTransform),
            )
                .run_if(in_state(GameState::Playing)),
        );
    }
}

#[derive(Component)]
struct SyncTimer(Duration);

impl SyncTimer {
    fn schedule(time: Duration) -> Duration {
        let jitter = Duration::from_millis(fastrand::u64(0..SYNC_RANDOMIZATION_MS));
        time + MIN_SYNC_PERIOD + jitter
    }

    fn new(time: Duration) -> Self {
        Self(Self::schedule(time))
    }

    /// Sets sync expiration to the future relative to the current time.
    fn refresh(&mut self, time: Duration) {
        self.0 = Self::schedule(time);
    }

    /// Returns true if transform sync is already due.
    fn outdated(&self, time: Duration) -> bool {
        time >= self.0
    }
}

type NotSetUp = (With<MovableSolid>, With<Local>, Without<SyncTimer>);

fn setup_entities(mut commands: Commands, time: Res<Time>, entities: Query<Entity, NotSetUp>) {
    let time = time.elapsed();
    for entity in entities.iter() {
        commands.entity(entity).insert(SyncTimer::new(time));
    }
}

fn receive_transforms(
    mut entities: Query<&mut Transform>,
    mut events: EventReader<NetRecvTransformEvent>,
) {
    for event in events.read() {
        if let Ok(mut transform) = entities.get_mut(event.entity()) {
            *transform = event.transform();
        }
    }
}

fn send_transforms(
    time: Res<Time>,
    net_entities: NetEntities,
    mut entities: Query<(Entity, &mut SyncTimer, &Transform)>,
    mut net_events: EventWriter<ToPlayersEvent>,
) {
    let time = time.elapsed();
    for (entity, mut sync, transform) in entities.iter_mut() {
        if sync.outdated(time) {
            sync.refresh(time);

            net_events.send(ToPlayersEvent::new(ToPlayers::Transform {
                entity: net_entities.local_net_id(entity),
                transform: transform.into(),
            }));
        }
    }
}