rlbot 0.5.0

RLBot bindings for Rust
Documentation
#![allow(clippy::float_cmp)]

use crate::{
    ffi, flat, game::GameTickPacket, game_deserialize::deserialize_game_tick_packet, rlbot::RLBot,
};
use std::{
    error::Error,
    mem,
    time::{Duration, Instant},
};

/// An iterator-like object that yields packets from the game as they occur.
pub struct Packeteer<'a> {
    rlbot: &'a RLBot,
    ratelimiter: ratelimit::Limiter,
    prev_game_time: f32,
}

impl<'a> Packeteer<'a> {
    pub(crate) fn new(rlbot: &'a RLBot) -> Self {
        // The goal is never to miss any packets. But if we poll too often, the
        // game crashes, so it's a fine line. With an interval of 3ms we can
        // catch 333 updates per second. That should be plenty.
        let ratelimiter = ratelimit::Builder::new()
            .interval(Duration::from_millis(3))
            .build();

        Self {
            rlbot,
            ratelimiter,
            prev_game_time: 0.0,
        }
    }

    /// Blocks until we receive the next unique [`GameTickPacket`], and then
    /// returns it.
    ///
    /// # Errors
    ///
    /// This function returns an error if ten seconds pass without a new
    /// packet being received. The assumption is that the game froze or
    /// crashed, and waiting longer will not help.
    #[allow(clippy::should_implement_trait)]
    pub fn next(&mut self) -> Result<GameTickPacket, Box<dyn Error>> {
        self.next_flatbuffer().map(deserialize_game_tick_packet)
    }

    /// Polls for the next unique [`GameTickPacket`].
    ///
    /// If there is a packet that is newer than the previous packet, it is
    /// returned. Otherwise, `None` is returned.
    pub fn try_next(&mut self) -> Option<GameTickPacket> {
        self.try_next_flat().map(deserialize_game_tick_packet)
    }

    /// Blocks until we receive the next unique [`ffi::LiveDataPacket`], and
    /// then returns it.
    ///
    /// # Errors
    ///
    /// This function returns an error if ten seconds pass without a new
    /// packet being received. The assumption is that the game froze or
    /// crashed, and waiting longer will not help.
    #[deprecated(
        note = "the struct-based methods are deprecated; use the flatbuffer equivalents instead"
    )]
    #[allow(deprecated)]
    pub fn next_ffi(&mut self) -> Result<ffi::LiveDataPacket, Box<dyn Error>> {
        self.spin(Self::try_next_ffi)
    }

    /// Polls for the next unique [`ffi::LiveDataPacket`].
    ///
    /// If there is a packet that is newer than the previous packet, 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_ffi(&mut self) -> Result<Option<ffi::LiveDataPacket>, Box<dyn Error>> {
        let mut packet = unsafe { mem::uninitialized() };
        self.rlbot
            .interface()
            .update_live_data_packet(&mut packet)?;

        let game_time = packet.GameInfo.TimeSeconds;
        if game_time != self.prev_game_time {
            self.prev_game_time = game_time;
            Ok(Some(packet))
        } else {
            Ok(None)
        }
    }

    /// Blocks until we receive the next unique [`flat::GameTickPacket`], and
    /// then returns it.
    ///
    /// # Errors
    ///
    /// This function returns an error if ten seconds pass without a new
    /// packet being received. The assumption is that the game froze or
    /// crashed, and waiting longer will not help.
    pub fn next_flatbuffer<'fb>(&mut self) -> Result<flat::GameTickPacket<'fb>, Box<dyn Error>> {
        self.spin(|this| Ok(this.try_next_flat()))
    }

    /// Polls for the next unique [`flat::GameTickPacket`].
    ///
    /// If there is a packet that is newer than the previous packet, it is
    /// returned. Otherwise, `None` is returned.
    pub fn try_next_flat<'fb>(&mut self) -> Option<flat::GameTickPacket<'fb>> {
        if let Some(packet) = self.rlbot.interface().update_live_data_packet_flatbuffer() {
            let game_time = packet.gameInfo().map(|gi| gi.secondsElapsed());
            if let Some(game_time) = game_time {
                if game_time != self.prev_game_time {
                    self.prev_game_time = game_time;
                    return Some(packet);
                }
            }
        }
        None
    }

    /// Keep trying `f` until the timeout elapses.
    fn spin<R>(
        &mut self,
        f: impl Fn(&mut Self) -> Result<Option<R>, Box<dyn Error>>,
    ) -> 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 > Duration::from_secs(10) {
                return Err(From::from("no physics tick received after ten seconds"));
            }
        }
    }
}