use crate::{ffi, flat, rlbot::RLBot};
use std::{
error::Error,
mem,
time::{Duration, Instant},
};
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 {
let ratelimiter = ratelimit::Builder::new()
.interval(Duration::from_millis(1))
.build();
Self {
rlbot,
ratelimiter,
prev_ball_frame: 0,
}
}
#[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)
}
#[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)
}
}
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)
}
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)
}
#[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
}
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"));
}
}
}
}