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
use crate::game_input::{FrameNum, GameInput};
use log::{error, info};
use std::cmp::min;

const FRAME_WINDOW_SIZE: usize = 40;
const MIN_UNIQUE_FRAMES: usize = 10;
const MIN_FRAME_ADVANTAGE: usize = 3;
const MAX_FRAME_ADVANTAGE: usize = 9;

pub struct TimeSync {
    local: [i32; FRAME_WINDOW_SIZE],
    remote: [i32; FRAME_WINDOW_SIZE],
    last_inputs: [GameInput; MIN_UNIQUE_FRAMES],
    _next_prediction: usize,
}

impl Default for TimeSync {
    fn default() -> Self {
        TimeSync::new()
    }
}

impl TimeSync {
    pub const fn new() -> Self {
        TimeSync {
            local: [0; FRAME_WINDOW_SIZE],
            remote: [0; FRAME_WINDOW_SIZE],
            _next_prediction: FRAME_WINDOW_SIZE * 3,
            last_inputs: [GameInput::new(); MIN_UNIQUE_FRAMES],
        }
    }
    pub fn advance_frame(&mut self, input: &GameInput, advantage: i32, r_advantage: i32) {
        let _sleep_time: i32 = 0;
        // Remember the last frame and frame advantage
        match input.frame {
            Some(frame) => {
                self.last_inputs[frame as usize % MIN_UNIQUE_FRAMES] = input.clone();
                self.local[frame as usize % FRAME_WINDOW_SIZE] = advantage;
                self.remote[frame as usize % FRAME_WINDOW_SIZE] = r_advantage;
            }
            None => error!("game input frame is null"),
        }
    }

    pub fn recommend_frame_wait_duration(&mut self, require_idle_input: bool) -> FrameNum {
        let mut count = 0;
        // Average our local and remote frame advantages
        let mut sum = 0;
        let (advantage, r_advantage): (f32, f32);
        for i in 0..FRAME_WINDOW_SIZE {
            sum += self.local[i];
        }
        advantage = sum as f32 / FRAME_WINDOW_SIZE as f32;
        sum = 0;
        for i in 0..FRAME_WINDOW_SIZE {
            sum += self.remote[i];
        }
        r_advantage = sum as f32 / FRAME_WINDOW_SIZE as f32;

        count += 1;

        // See if someone should take action.  The person furthest ahead
        // needs to slow down so the other user can catch up.
        // Only do this if both clients agree on who's ahead!!
        if advantage >= r_advantage {
            return 0;
        }

        // Both clients agree that we're the one ahead.  Split
        // the difference between the two to figure out how long to
        // sleep for.
        let sleep_frames: FrameNum = (((r_advantage - advantage) / 2.) + 0.5) as FrameNum;

        info!("iteration {}: sleep frames is {}\n", count, sleep_frames);

        // Some things just aren't worth correcting for.  Make sure
        // the difference is relevant before proceeding.
        if sleep_frames < MIN_FRAME_ADVANTAGE as u32 {
            return 0;
        }

        // Make sure our input had been "idle enough" before recommending
        // a sleep.  This tries to make the emulator sleep while the
        // user's input isn't sweeping in arcs (e.g. fireball motions in
        // Street Fighter), which could cause the player to miss moves.

        if require_idle_input {
            for i in 1..MIN_UNIQUE_FRAMES {
                if !self.last_inputs[i].equal(&self.last_inputs[0], true) {
                    info!(
                        "iteration {}: rejecting due to input stuff at position {}...!!!\n",
                        count, i
                    );
                    return 0;
                }
            }
        }

        // Success!!! Recommend the number of frames to sleep and adjust
        return min(sleep_frames, MAX_FRAME_ADVANTAGE as FrameNum);
    }
}