use crate::windows_util::SharedMemory;
use crate::{Moment, RacingFlags, Simetry};
use anyhow::{bail, Result};
use std::borrow::Cow;
use std::time::Duration;
use uom::si::angular_velocity::radian_per_second;
use uom::si::f64::{AngularVelocity, Velocity};
use uom::si::velocity::meter_per_second;
pub mod bindings;
pub struct Client {
shared_memory: SharedMemory,
last_ticks: i32,
}
impl Client {
pub async fn connect(retry_delay: Duration) -> Self {
loop {
if let Ok(v) = Self::try_connect().await {
return v;
}
tokio::time::sleep(retry_delay).await
}
}
pub async fn try_connect() -> Result<Self> {
let poll_delay = Duration::from_millis(250);
let shared_memory =
SharedMemory::connect(bindings::R3E_SHARED_MEMORY_NAME, poll_delay).await;
Ok(Self {
shared_memory,
last_ticks: 0,
})
}
pub async fn next_sim_state(&mut self) -> Result<SimState> {
loop {
let r3e_shared = unsafe { self.shared_memory.copy_as::<bindings::r3e_shared>() };
let r3e_shared_retry = unsafe { self.shared_memory.copy_as::<bindings::r3e_shared>() };
if r3e_shared != r3e_shared_retry {
continue;
}
if r3e_shared.version_major != bindings::R3E_VERSION_MAJOR
|| r3e_shared.version_minor < bindings::R3E_VERSION_MINOR
{
let major = r3e_shared.version_major;
let minor = r3e_shared.version_minor;
bail!(
"API version {}.{} is incompatible with {}.{} version from the game",
bindings::R3E_VERSION_MAJOR,
bindings::R3E_VERSION_MINOR,
major,
minor,
);
}
if self.last_ticks == r3e_shared.player.game_simulation_ticks {
continue;
}
self.last_ticks = r3e_shared.player.game_simulation_ticks;
return Ok(SimState { r3e_shared });
}
}
}
#[derive(Debug)]
pub struct SimState {
pub r3e_shared: bindings::r3e_shared,
}
impl SimState {
pub fn current_driver_data(&self) -> Option<&bindings::r3e_driver_data> {
let slot_id = self.r3e_shared.vehicle_info.slot_id;
if slot_id < 0 {
return None;
}
self.r3e_shared
.all_drivers_data_1
.iter()
.find(|v| v.driver_info.slot_id == slot_id)
}
}
#[async_trait::async_trait]
impl Simetry for Client {
fn name(&self) -> &str {
"RaceRoomRacingExperience"
}
async fn next_moment(&mut self) -> Option<Box<dyn Moment + Send + Sync + 'static>> {
Some(Box::new(self.next_sim_state().await.ok()?))
}
}
impl Moment for SimState {
fn vehicle_gear(&self) -> Option<i8> {
let gear = self.r3e_shared.gear as i8;
if gear == -2 {
return None;
}
Some(gear)
}
fn vehicle_velocity(&self) -> Option<Velocity> {
Some(Velocity::new::<meter_per_second>(
self.r3e_shared.car_speed as f64,
))
}
fn vehicle_engine_rotation_speed(&self) -> Option<AngularVelocity> {
Some(AngularVelocity::new::<radian_per_second>(
self.r3e_shared.engine_rps as f64,
))
}
fn vehicle_max_engine_rotation_speed(&self) -> Option<AngularVelocity> {
let value = self.r3e_shared.max_engine_rps;
if value < 0.0 {
return None;
}
Some(AngularVelocity::new::<radian_per_second>(value as f64))
}
fn is_pit_limiter_engaged(&self) -> Option<bool> {
let pit_limiter = self.r3e_shared.pit_limiter;
if pit_limiter == -1 {
return None;
}
Some(pit_limiter != 0)
}
fn is_vehicle_in_pit_lane(&self) -> Option<bool> {
let in_pitlane = self.r3e_shared.in_pitlane;
if in_pitlane == -1 {
return None;
}
Some(in_pitlane != 0)
}
fn shift_point(&self) -> Option<AngularVelocity> {
let value = self.r3e_shared.upshift_rps;
if value < 0.0 {
return None;
}
Some(AngularVelocity::new::<radian_per_second>(value as f64))
}
fn flags(&self) -> Option<RacingFlags> {
let flags = self.r3e_shared.flags;
Some(RacingFlags {
green: flags.green > 0,
yellow: flags.yellow > 0,
blue: flags.blue > 0,
white: flags.white > 0,
red: false,
black: flags.black > 0,
checkered: flags.checkered > 0,
meatball: false,
black_and_white: flags.black_and_white > 0,
start_ready: false,
start_set: false,
start_go: false,
})
}
fn vehicle_brand_id(&self) -> Option<Cow<str>> {
let value = self.r3e_shared.vehicle_info.manufacturer_id;
Some(value.to_string().into())
}
fn vehicle_model_id(&self) -> Option<Cow<str>> {
let value = self.r3e_shared.vehicle_info.model_id;
Some(value.to_string().into())
}
fn is_ignition_on(&self) -> Option<bool> {
Some(self.current_driver_data()?.engineState > 0)
}
}