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
129
130
use crate::{ffi, flat, rlbot::RLBot};
use std::{
    error::Error,
    mem,
    time::{Duration, Instant},
};

/// An iterator-like object that yields physics ticks from the game as they
/// occur.
pub struct Physicist<'a> {
    rlbot: &'a RLBot,
    ratelimiter: ratelimit::Limiter,
    prev_ball_frame: i32,
}

impl<'a> Physicist<'a> {
    const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);

    pub(crate) fn new(rlbot: &'a RLBot) -> Self {
        // Physics ticks happen at 120Hz. The goal is never to miss any. But if we poll
        // too often, the game crashes, so space out the checks.
        let ratelimiter = ratelimit::Builder::new()
            .interval(Duration::from_millis(1))
            .build();

        Self {
            rlbot,
            ratelimiter,
            prev_ball_frame: 0,
        }
    }

    /// Block until the next physics tick occurs, and then return it.
    ///
    /// # Errors
    ///
    /// This function returns an error if ten seconds pass without a new tick
    /// being received. The assumption is that the game froze or crashed, and
    /// waiting longer will not help.
    #[allow(clippy::should_implement_trait)]
    #[deprecated(
        note = "the struct-based methods are deprecated; use the flatbuffer equivalents instead"
    )]
    #[allow(deprecated)]
    pub fn next(&mut self) -> Result<ffi::RigidBodyTick, Box<dyn Error>> {
        self.spin(|this| Ok(this.try_next()?), Self::DEFAULT_TIMEOUT)
    }

    /// Polls for a new physics tick.
    ///
    /// If there is a tick that is newer than the previous tick, it is
    /// returned. Otherwise, `None` is returned.
    #[deprecated(
        note = "the struct-based methods are deprecated; use the flatbuffer equivalents instead"
    )]
    #[allow(deprecated)]
    pub fn try_next(&mut self) -> Result<Option<ffi::RigidBodyTick>, Box<dyn Error>> {
        let mut result = unsafe { mem::uninitialized() };
        self.rlbot.interface().update_rigid_body_tick(&mut result)?;
        if result.Ball.State.Frame != self.prev_ball_frame {
            self.prev_ball_frame = result.Ball.State.Frame;
            Ok(Some(result))
        } else {
            Ok(None)
        }
    }

    /// Block until the next physics tick occurs, and then return it.
    ///
    /// # Errors
    ///
    /// This function returns an error if ten seconds pass without a new tick
    /// being received. The assumption is that the game froze or crashed, and
    /// waiting longer will not help.
    pub fn next_flat<'fb>(&mut self) -> Result<flat::RigidBodyTick<'fb>, Box<dyn Error>> {
        self.spin(|this| Ok(this.try_next_flat()), Self::DEFAULT_TIMEOUT)
    }

    /// Block until the next physics tick occurs, and then return it.
    ///
    /// This works the same as `next_flat`, but lets the caller choose the
    /// timeout.
    pub fn next_flat_with_timeout<'fb>(
        &mut self,
        timeout: Duration,
    ) -> Result<flat::RigidBodyTick<'fb>, Box<dyn Error>> {
        self.spin(|this| Ok(this.try_next_flat()), timeout)
    }

    /// Polls for a new physics tick.
    ///
    /// If there is a tick that is newer than the previous tick, it is
    /// returned. Otherwise, `None` is returned.
    #[allow(clippy::redundant_closure)]
    pub fn try_next_flat<'fb>(&mut self) -> Option<flat::RigidBodyTick<'fb>> {
        if let Some(tick) = self.rlbot.interface().update_rigid_body_tick_flatbuffer() {
            let ball = tick.ball();
            match ball.as_ref().and_then(|b| b.state()).map(|s| s.frame()) {
                Some(ball_frame) if ball_frame != self.prev_ball_frame => {
                    self.prev_ball_frame = ball_frame;
                    return Some(tick);
                }
                _ => {}
            }
        }
        None
    }

    /// Keep trying `f` until the timeout elapses.
    fn spin<R>(
        &mut self,
        f: impl Fn(&mut Self) -> Result<Option<R>, Box<dyn Error>>,
        timeout: Duration,
    ) -> Result<R, Box<dyn Error>> {
        let start = Instant::now();

        loop {
            self.ratelimiter.wait();

            if let Some(tick) = f(self)? {
                return Ok(tick);
            }

            let elapsed = Instant::now() - start;
            if elapsed > timeout {
                return Err(From::from("no physics tick received within the timeout"));
            }
        }
    }
}