Add GamePhase handling and DeltaBoard #4

Merged
hodasemi merged 6 commits from dev into master 2023-01-19 12:46:53 +00:00
11 changed files with 661 additions and 275 deletions

View file

@ -0,0 +1,6 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypes="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="2560" reference_height="1440">
<!-- max 5 entries, each entry gets 39 pixel (at 1440p) -->
<grid id="main_grid" x_dim="1" y_dim="5" x_offset="-410" y_offset="10" width="400" height="195"
vert_align="top" hori_align="right" margin="0" padding="0"> </grid>
</root>

View file

@ -0,0 +1,211 @@
use std::sync::Arc;
use anyhow::Result;
use ui::prelude::*;
use utilities::prelude::Color;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum BehindLeader {
Time(f64),
Laps(i32),
}
pub struct LeaderBoardEntry {
id: i32,
name: String,
place: u8,
behind: BehindLeader,
time_behind_next: f64,
best_lap: f64,
snippet: Arc<GuiSnippet>,
name_label: Arc<Label>,
place_label: Arc<Label>,
time_label: Arc<Label>,
place_updated: bool,
}
impl LeaderBoardEntry {
const ENTRY: &str = include_str!("leaderboard_entry.xml");
pub fn empty(gui_handler: &Arc<GuiHandler>) -> Result<Self> {
Self::new(
gui_handler,
-1,
"".to_string(),
0,
BehindLeader::Laps(0),
0.0,
0.0,
)
}
pub fn new(
gui_handler: &Arc<GuiHandler>,
id: i32,
name: String,
place: u8,
behind: BehindLeader,
time_behind_next: f64,
best_lap: f64,
) -> Result<Self> {
let snippet = GuiSnippet::from_str(gui_handler, Self::ENTRY)?;
let name_label: Arc<Label> = snippet.element("name")?;
let place_label: Arc<Label> = snippet.element("place")?;
let time_label: Arc<Label> = snippet.element("time")?;
name_label.set_text(&name)?;
place_label.set_text(place)?;
time_label.set_text("---")?;
Ok(Self {
id,
name,
place,
behind,
time_behind_next,
best_lap,
snippet,
name_label,
place_label,
time_label,
place_updated: true,
})
}
pub fn change_id(&mut self, id: i32) {
self.id = id;
}
pub fn id(&self) -> i32 {
self.id
}
pub fn place(&self) -> u8 {
self.place
}
pub fn name(&self) -> &str {
&self.name
}
pub fn change_name(&mut self, name: String) -> Result<()> {
self.name = name;
self.name_label.set_text(&self.name)
}
pub fn snippet(&self) -> Arc<GuiSnippet> {
self.snippet.clone()
}
pub fn change_background_color(&self, color: Color) -> Result<()> {
self.time_label.set_background(color)?;
self.name_label.set_background(color)?;
self.place_label.set_background(color)?;
Ok(())
}
pub fn update_place(&mut self, place: u8) -> Result<()> {
if self.place != place {
self.place_updated = true;
}
self.place = place;
self.place_label.set_text(self.place)
}
pub fn update_time_behind_leader(&mut self, behind: BehindLeader) -> Result<()> {
if self.behind != behind {
self.behind = behind;
match self.behind {
BehindLeader::Time(time_behind) => {
// check if we are leader
if time_behind == 0.0 {
self.time_label.set_text("---")?;
} else {
let text = if time_behind > 60.0 {
let full_minutes = (self.best_lap / 60.0).floor();
let remainder = self.best_lap - (full_minutes * 60.0);
format!("+{:.0}:{:.0}", full_minutes, remainder)
} else {
format!("+{:.3}", time_behind)
};
self.time_label.set_text(text)?;
}
}
BehindLeader::Laps(laps_behind) => {
let text = if laps_behind == 1 {
format!("+{} Lap", laps_behind)
} else {
format!("+{} Laps", laps_behind)
};
self.time_label.set_text(text)?;
}
}
}
Ok(())
}
pub fn update_best_lap(&mut self, time: f64) -> Result<()> {
if self.best_lap != time {
self.best_lap = time;
if self.best_lap < 0.0 {
self.time_label.set_text("---")?;
} else {
let text = if self.best_lap > 60.0 {
let full_minutes = (self.best_lap / 60.0).floor();
let remainder = self.best_lap - (full_minutes * 60.0);
format!("{:.0}:{:.3}", full_minutes, remainder)
} else {
format!("{:.3}", self.best_lap)
};
self.time_label.set_text(text)?;
}
}
Ok(())
}
pub fn update_time_behind_next(&mut self, time: f64) -> Result<()> {
self.time_behind_next = time;
let text = if self.time_behind_next > 60.0 {
let full_minutes = (self.time_behind_next / 60.0).floor();
let remainder = self.time_behind_next - (full_minutes * 60.0);
format!("+{:.0}:{:.3}", full_minutes, remainder)
} else {
format!("+{:.3}", self.time_behind_next)
};
self.time_label.set_text(text)
}
pub fn needs_resorting(&self) -> bool {
self.place_updated
}
pub fn resorting_finished(&mut self) {
self.place_updated = false;
}
}

View file

@ -1,12 +1,12 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<?xml-model href="../gui.xsd" type="application/xml" schematypes="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid id="grid" x_dim="9" y_dim="1">
<grid x_dim="11" y_dim="1" padding="2" margin="2">
<label id="place" x_slot="0" y_slot="0" text_color="black" text_alignment="right"></label>
<label
id="name"
x_slot="1" y_slot="0" x_size="6" text_color="black" text_alignment="left"></label>
x_slot="1" y_slot="0" x_size="7" text_color="black"></label>
<label
id="time_behind"
x_slot="7" y_slot="0" x_size="2" text_color="black" text_alignment="right"></label>
id="time"
x_slot="8" y_slot="0" x_size="3" text_color="black" text_alignment="right"></label>
</grid>
</root>

View file

@ -1,5 +1,6 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<?xml-model href="../gui.xsd" type="application/xml" schematypes="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="2560" reference_height="1440">
<grid id="main_grid" x_dim="1" y_dim="25" x_offset="10" y_offset="10" width="350" height="875"
<!-- max 25 entries, each entry gets 39 pixel (at 1440p) -->
<grid id="main_grid" x_dim="1" y_dim="25" x_offset="10" y_offset="10" width="400" height="975"
vert_align="top" hori_align="left" margin="0" padding="0"> </grid>
</root>

View file

@ -1,3 +1,7 @@
mod leaderboard_entry;
use leaderboard_entry::*;
use std::{
ffi::{c_char, CStr},
sync::Arc,
@ -16,30 +20,51 @@ use crate::write_log;
pub struct LeaderBoard {
gui_handler: Arc<GuiHandler>,
gui: Arc<GuiBuilder>,
main_grid: Arc<Grid>,
leaderboard: Arc<GuiBuilder>,
deltaboard: Arc<GuiBuilder>,
entries: Vec<LeaderBoardEntry>,
leaderboard_grid: Arc<Grid>,
deltaboard_grid: Arc<Grid>,
leaderboard_entries: Vec<LeaderBoardEntry>,
deltaboard_entries: [LeaderBoardEntry; 5],
leaderboard_redraw: bool,
last_player_id: i32,
entry_backgrounds: [Color; 2],
player_background: Color,
}
impl LeaderBoard {
const GRID: &str = include_str!("leaderboard_grid.xml");
const ENTRY: &str = include_str!("leaderboard_entry.xml");
const LEADERBOARD: &str = include_str!("leaderboard_grid.xml");
const DELTABOARD: &str = include_str!("deltaboard_grid.xml");
pub fn new(gui_handler: &Arc<GuiHandler>) -> Result<Self> {
let gui = GuiBuilder::from_str(gui_handler, Self::GRID)?;
let leaderboard = GuiBuilder::from_str(gui_handler, Self::LEADERBOARD)?;
let deltaboard = GuiBuilder::from_str(gui_handler, Self::DELTABOARD)?;
let main_grid = gui.element("main_grid")?;
let leaderboard_grid = leaderboard.element("main_grid")?;
let deltaboard_grid = deltaboard.element("main_grid")?;
Ok(Self {
gui_handler: gui_handler.clone(),
gui,
main_grid,
leaderboard,
deltaboard,
leaderboard_grid,
deltaboard_grid,
entries: Vec::new(),
leaderboard_entries: Vec::new(),
deltaboard_entries: [
LeaderBoardEntry::empty(gui_handler)?,
LeaderBoardEntry::empty(gui_handler)?,
LeaderBoardEntry::empty(gui_handler)?,
LeaderBoardEntry::empty(gui_handler)?,
LeaderBoardEntry::empty(gui_handler)?,
],
leaderboard_redraw: false,
last_player_id: -1,
entry_backgrounds: [Color::try_from("#838383")?, Color::try_from("#545454")?],
player_background: Color::try_from("#b4bf26")?,
@ -53,13 +78,20 @@ impl LeaderBoard {
.to_string()
}
fn race_leaderboard(&mut self, vehicle_scorings: &[VehicleScoringInfoV01]) -> Result<()> {
fn update_leaderboard<F>(
&mut self,
vehicle_scorings: &[VehicleScoringInfoV01],
f: F,
) -> Result<()>
where
F: Fn(&mut LeaderBoardEntry, &VehicleScoringInfoV01) -> Result<()>,
{
for vehicle_scoring in vehicle_scorings {
let driver_name = Self::c_char_to_string(vehicle_scoring.mDriverName);
// check driver list
match self
.entries
.leaderboard_entries
.iter_mut()
.find(|entry| vehicle_scoring.mID == entry.id())
{
@ -69,8 +101,8 @@ impl LeaderBoard {
}
entry.update_place(vehicle_scoring.mPlace)?;
entry.update_time_behind_leader(vehicle_scoring.mTimeBehindLeader)?;
entry.update_time_behind_next(vehicle_scoring.mTimeBehindNext)?;
f(entry, vehicle_scoring)?;
}
None => {
let entry = LeaderBoardEntry::new(
@ -78,26 +110,39 @@ impl LeaderBoard {
vehicle_scoring.mID,
driver_name,
vehicle_scoring.mPlace,
vehicle_scoring.mTimeBehindLeader,
{
let laps_behind = vehicle_scoring.mLapsBehindLeader;
if laps_behind != 0 {
BehindLeader::Laps(laps_behind)
} else {
BehindLeader::Time(vehicle_scoring.mTimeBehindLeader)
}
},
vehicle_scoring.mTimeBehindNext,
vehicle_scoring.mBestLapTime,
)?;
self.entries.push(entry);
self.leaderboard_entries.push(entry);
}
}
}
// TODO: when people disconnect there needs to be a remove function
// vehicle_scorings.len() != self.leaderboard_entries.len()
write_log!("create entries");
// check if entry count in grid is the same as the gathered entries
let force_update = if !self.entries.is_empty()
let force_update = if !self.leaderboard_entries.is_empty()
&& self
.main_grid
.child_at(0, self.entries.len() - 1)?
.leaderboard_grid
.child_at(0, self.leaderboard_entries.len() - 1)?
.is_none()
{
for i in 0..self.entries.len() {
self.main_grid.detach(0, i)?;
for (i, entry) in self.leaderboard_entries.iter().enumerate() {
self.leaderboard_grid.detach(0, i)?;
entry.snippet().set_visibility(false)?;
}
true
@ -106,33 +151,154 @@ impl LeaderBoard {
};
// check if any entry needs resorting
if force_update || self.entries.iter().any(|entry| entry.needs_resorting()) {
if force_update
|| self
.leaderboard_entries
.iter()
.any(|entry| entry.needs_resorting())
{
write_log!("leader board update required");
self.entries
self.leaderboard_entries
.sort_by(|lhs, rhs| lhs.place().cmp(&rhs.place()));
for (i, entry) in self.entries.iter_mut().enumerate() {
for (i, entry) in self.leaderboard_entries.iter_mut().enumerate() {
entry.resorting_finished();
entry.change_background_color(self.entry_backgrounds[i % 2])?;
self.main_grid.attach(entry.snippet(), 0, i, 1, 1)?;
// don't break here, just skip adding to grid
// because resorting_finished should be called for every entry
if i < self.leaderboard_grid.dimensions().1 {
entry.change_background_color(self.entry_backgrounds[i % 2])?;
self.leaderboard_grid.attach(entry.snippet(), 0, i, 1, 1)?;
}
}
self.leaderboard_redraw = true;
}
// update delta board
if self.last_player_id != -1 {
write_log!("update delta board");
if let Some((index, _player_entry)) = self
.leaderboard_entries
.iter()
.enumerate()
.find(|(_index, entry)| entry.id() == self.last_player_id)
{
let mut start_index = if index >= 2 { index - 2 } else { 0 };
let max = self.leaderboard_entries.len().min(5);
write_log!(format!(
"Delta Board: start {} - count {}",
start_index, max
));
// clear old entries
for i in 0..5 {
if let Some(child) = self.deltaboard_grid.detach(0, i)? {
if let Some(visiblity) = child.visibility() {
visiblity.set_visibility(false)?;
}
}
}
// add new entries
for i in 0..max {
if let Some(leaderboard_entry) = self.leaderboard_entries.get(start_index) {
if let Some(entry) = self.deltaboard_entries.get_mut(i) {
entry.change_id(leaderboard_entry.id());
entry.update_place(leaderboard_entry.place())?;
if entry.id() == self.last_player_id {
entry.change_background_color(self.player_background)?;
} else {
entry.change_background_color(self.entry_backgrounds[i % 2])?;
}
if entry.name() != leaderboard_entry.name() {
entry.change_name(leaderboard_entry.name().to_string())?;
}
if let Some(vehicle_scoring) = vehicle_scorings
.iter()
.find(|scoring| scoring.mID == entry.id())
{
entry.update_time_behind_next(vehicle_scoring.mTimeBehindNext)?;
}
self.deltaboard_grid.attach(entry.snippet(), 0, i, 1, 1)?;
}
start_index += 1;
}
}
}
}
if self.entries.is_empty() {
self.gui.disable()?;
} else {
self.gui.enable()?;
}
Ok(())
}
fn race_leaderboard(&mut self, vehicle_scorings: &[VehicleScoringInfoV01]) -> Result<()> {
self.update_leaderboard(vehicle_scorings, |entry, scoring| {
let laps_behind = scoring.mLapsBehindLeader;
if laps_behind != 0 {
entry.update_time_behind_leader(BehindLeader::Laps(laps_behind))
} else {
entry.update_time_behind_leader(BehindLeader::Time(scoring.mTimeBehindLeader))
}
})
}
fn quali_leaderboard(&mut self, vehicle_scorings: &[VehicleScoringInfoV01]) -> Result<()> {
self.update_leaderboard(vehicle_scorings, |entry, scoring| {
entry.update_best_lap(scoring.mBestLapTime)
})
}
}
impl UiOverlay for LeaderBoard {}
impl DataReceiver for LeaderBoard {
fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> {
match phase {
GamePhase::Practice | GamePhase::Qualifying => {
for entry in self.leaderboard_entries.iter_mut() {
entry.update_best_lap(-1.0)?;
}
self.leaderboard.enable()?;
self.deltaboard.enable()?;
}
GamePhase::Race => {
for entry in self.leaderboard_entries.iter_mut() {
entry.update_time_behind_leader(BehindLeader::Time(0.0))?;
}
self.leaderboard.enable()?;
self.deltaboard.enable()?;
}
_ => {
self.last_player_id = -1;
self.leaderboard.disable()?;
self.deltaboard.disable()?;
}
}
Ok(())
}
fn update_for_phase(&self, phase: GamePhase) -> bool {
match phase {
GamePhase::Practice | GamePhase::Qualifying | GamePhase::Race => true,
_ => false,
}
}
fn scoring_update(
&mut self,
phase: GamePhase,
@ -141,13 +307,15 @@ impl DataReceiver for LeaderBoard {
write_log!("=================== leader board: scoring update ===================");
match phase {
GamePhase::TestDay => self.gui.disable()?,
GamePhase::Practice => self.gui.disable()?,
GamePhase::Qualifying => self.gui.disable()?,
GamePhase::Warmup => self.gui.disable()?,
GamePhase::Practice | GamePhase::Qualifying => {
self.quali_leaderboard(vehicle_scorings)?;
}
GamePhase::Race => {
self.race_leaderboard(vehicle_scorings)?;
}
_ => (),
}
write_log!("leader board update finished");
@ -160,132 +328,28 @@ impl DataReceiver for LeaderBoard {
player_id: Option<i32>,
_telemetries: &[rF2VehicleTelemetry],
) -> Result<()> {
if let Some(player_id) = player_id {
if let Some(entry) = self.entries.iter().find(|entry| entry.id() == player_id) {
entry.change_background_color(self.player_background)?;
if self.leaderboard_redraw {
self.leaderboard_redraw = false;
if let Some(player_id) = player_id {
if let Some(entry) = self
.leaderboard_entries
.iter()
.find(|entry| entry.id() == player_id)
{
write_log!(format!(
"Update player entry background color: {:?}",
self.player_background
));
entry.change_background_color(self.player_background)?;
}
}
}
if let Some(player_id) = player_id {
self.last_player_id = player_id;
}
Ok(())
}
}
struct LeaderBoardEntry {
id: i32,
name: String,
place: u8,
time_behind_leader: f64,
time_behind_next: f64,
snippet: Arc<GuiSnippet>,
grid: Arc<Grid>,
name_label: Arc<Label>,
place_label: Arc<Label>,
time_behind_label: Arc<Label>,
place_updated: bool,
}
impl LeaderBoardEntry {
pub fn new(
gui_handler: &Arc<GuiHandler>,
id: i32,
name: String,
place: u8,
time_behind_leader: f64,
time_behind_next: f64,
) -> Result<Self> {
let snippet = GuiSnippet::from_str(gui_handler, LeaderBoard::ENTRY)?;
let background = snippet.element("grid")?;
let name_label: Arc<Label> = snippet.element("name")?;
let place_label: Arc<Label> = snippet.element("place")?;
let time_behind_label: Arc<Label> = snippet.element("time_behind")?;
name_label.set_text(&name)?;
place_label.set_text(place)?;
time_behind_label.set_text(format!("{:.3}", time_behind_leader))?;
Ok(Self {
id,
name,
place,
time_behind_leader,
time_behind_next,
snippet,
grid: background,
name_label,
place_label,
time_behind_label,
place_updated: true,
})
}
pub fn id(&self) -> i32 {
self.id
}
pub fn place(&self) -> u8 {
self.place
}
pub fn name(&self) -> &str {
&self.name
}
pub fn change_name(&mut self, name: String) -> Result<()> {
self.name = name;
self.name_label.set_text(&self.name)
}
pub fn snippet(&self) -> Arc<GuiSnippet> {
self.snippet.clone()
}
pub fn change_background_color(&self, color: Color) -> Result<()> {
self.grid.set_background(color)
}
pub fn update_place(&mut self, place: u8) -> Result<()> {
if self.place != place {
self.place_updated = true;
}
self.place = place;
self.place_label.set_text(self.place)
}
pub fn update_time_behind_leader(&mut self, time: f64) -> Result<()> {
self.time_behind_leader = time;
// check if we are leader
if self.time_behind_leader == 0.0 {
self.time_behind_label.set_text("---")
} else {
self.time_behind_label
.set_text(format!("+{:.3}", self.time_behind_leader))
}
}
pub fn update_time_behind_next(&mut self, time: f64) -> Result<()> {
self.time_behind_next = time;
Ok(())
}
pub fn needs_resorting(&self) -> bool {
self.place_updated
}
pub fn resorting_finished(&mut self) {
self.place_updated = false;
}
}

View file

@ -26,6 +26,8 @@ pub struct Pedals {
throttle: Arc<ProgressBar>,
_history: Arc<Icon>,
enable: bool,
throttle_samples: HeapRb<f32>,
brake_samples: HeapRb<f32>,
@ -138,6 +140,8 @@ impl Pedals {
throttle,
_history: history,
enable: false,
throttle_samples,
brake_samples,
@ -202,7 +206,7 @@ impl Pedals {
let command_buffer =
CommandBuffer::new_primary().build(self.device.clone(), self.queue.clone())?;
{
if self.enable {
let mut recorder = command_buffer.begin(VkCommandBufferBeginInfo::new(
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
| VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
@ -231,6 +235,30 @@ impl Pedals {
impl UiOverlay for Pedals {}
impl DataReceiver for Pedals {
fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> {
match phase {
GamePhase::None | GamePhase::TestDay => {
self.enable = false;
self.gui.disable()?;
}
_ => {
self.enable = true;
self.gui.enable()?;
}
}
Ok(())
}
fn update_for_phase(&self, phase: GamePhase) -> bool {
match phase {
GamePhase::Practice | GamePhase::Qualifying | GamePhase::Race | GamePhase::Warmup => {
true
}
_ => false,
}
}
fn scoring_update(
&mut self,
_phase: GamePhase,
@ -244,25 +272,18 @@ impl DataReceiver for Pedals {
player_id: Option<i32>,
telemetries: &[rF2VehicleTelemetry],
) -> Result<()> {
match player_id {
Some(id) => {
self.gui.enable()?;
if let Some(id) = player_id {
if let Some(telemetry) = telemetries.iter().find(|telemetry| telemetry.id == id) {
let brake = 1.0 - telemetry.unfiltered_brake as f32;
let throttle = 1.0 - telemetry.unfiltered_throttle as f32;
if let Some(telemetry) = telemetries.iter().find(|telemetry| telemetry.id == id) {
let brake = 1.0 - telemetry.unfiltered_brake as f32;
let throttle = 1.0 - telemetry.unfiltered_throttle as f32;
self.throttle.set_progress(throttle)?;
self.brake.set_progress(brake)?;
self.throttle.set_progress(throttle)?;
self.brake.set_progress(brake)?;
self.throttle_samples.push_overwrite(throttle);
self.brake_samples.push_overwrite(brake);
self.throttle_samples.push_overwrite(throttle);
self.brake_samples.push_overwrite(brake);
self.update_vertex_buffers()?;
}
}
None => {
self.gui.disable()?;
self.update_vertex_buffers()?;
}
}

View file

@ -1,4 +1,4 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<?xml-model href="../gui.xsd" type="application/xml" schematypes="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="2560" reference_height="1440">
<grid x_dim="10" y_dim="2" x_offset="-850" y_offset="-190" width="200" height="160"
vert_align="bottom" hori_align="right" margin="3" padding="3" background="#686868">

View file

@ -73,6 +73,8 @@ pub struct Radar {
car_width: f32,
car_height: f32,
enable: bool,
device: Arc<Device>,
queue: Arc<Mutex<Queue>>,
@ -171,6 +173,8 @@ impl Radar {
car_width,
car_height,
enable: false,
device,
queue,
@ -216,7 +220,7 @@ impl Radar {
let command_buffer =
CommandBuffer::new_primary().build(self.device.clone(), self.queue.clone())?;
{
if self.enable {
let mut recorder = command_buffer.begin(VkCommandBufferBeginInfo::new(
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
| VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
@ -268,6 +272,24 @@ impl Radar {
impl UiOverlay for Radar {}
impl DataReceiver for Radar {
fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> {
match phase {
GamePhase::None | GamePhase::TestDay => self.enable = false,
_ => self.enable = true,
}
Ok(())
}
fn update_for_phase(&self, phase: GamePhase) -> bool {
match phase {
GamePhase::Practice | GamePhase::Qualifying | GamePhase::Race | GamePhase::Warmup => {
true
}
_ => false,
}
}
fn scoring_update(
&mut self,
_phase: GamePhase,
@ -286,68 +308,71 @@ impl DataReceiver for Radar {
self.cars.clear();
if let Some(player_id) = player_id {
// make sure there are enough cars in buffer
if self.car_handles.len() < telemetries.len() {
let size_diff = telemetries.len() - self.car_handles.len();
if self.enable {
if let Some(player_id) = player_id {
// make sure there are enough cars in buffer
if self.car_handles.len() < telemetries.len() {
let size_diff = telemetries.len() - self.car_handles.len();
for _ in 0..size_diff {
self.car_handles
.push(self.create_car_object(vec2(0.0, 0.0), [0.0, 0.0, 0.0, 0.0])?);
for _ in 0..size_diff {
self.car_handles
.push(self.create_car_object(vec2(0.0, 0.0), [0.0, 0.0, 0.0, 0.0])?);
}
}
let mut player_position = CarPosition::default();
let mut other_positions = Vec::new();
for telemetry in telemetries {
let car = CarPosition::new(
convert_vec(telemetry.position),
[
convert_vec(telemetry.orientation[0]),
convert_vec(telemetry.orientation[1]),
convert_vec(telemetry.orientation[2]),
],
);
if telemetry.id == player_id {
player_position = car
} else {
other_positions.push(car);
}
}
// update radar objects
let mut buffer_car_index = 0;
for other_position in other_positions {
let diff = player_position.position - other_position.position;
let distance = diff.magnitude();
// check if car is close enough to the players car
if distance < self.config.radar_car_distance {
let offset =
diff.xz() * (self.radar_extent / self.config.radar_car_distance);
let buffered_car = self.car_handles[buffer_car_index].clone();
buffer_car_index += 1;
buffered_car.update(
self.ortho,
offset,
player_position.rotation,
other_position.rotation,
self.radar_center,
self.car_width,
self.car_height,
[0.9, 0.9, 0.0, 0.9],
)?;
self.cars.push(buffered_car);
}
}
}
let mut player_position = CarPosition::default();
let mut other_positions = Vec::new();
for telemetry in telemetries {
let car = CarPosition::new(
convert_vec(telemetry.position),
[
convert_vec(telemetry.orientation[0]),
convert_vec(telemetry.orientation[1]),
convert_vec(telemetry.orientation[2]),
],
);
if telemetry.id == player_id {
player_position = car
} else {
other_positions.push(car);
}
}
// update radar objects
let mut buffer_car_index = 0;
for other_position in other_positions {
let diff = player_position.position - other_position.position;
let distance = diff.magnitude();
// check if car is close enough to the players car
if distance < self.config.radar_car_distance {
let offset = diff.xz() * (self.radar_extent / self.config.radar_car_distance);
let buffered_car = self.car_handles[buffer_car_index].clone();
buffer_car_index += 1;
buffered_car.update(
self.ortho,
offset,
player_position.rotation,
other_position.rotation,
self.radar_center,
self.car_width,
self.car_height,
[0.9, 0.9, 0.0, 0.9],
)?;
self.cars.push(buffered_car);
}
}
write_log!(format!("other cars: {:?}", self.cars.len()));
}
write_log!(format!("other cars: {:?}", self.cars.len()));
Ok(())
}
}

View file

@ -26,11 +26,7 @@ impl Watermark {
impl UiOverlay for Watermark {}
impl DataReceiver for Watermark {
fn scoring_update(
&mut self,
phase: GamePhase,
_vehicle_scoring: &[VehicleScoringInfoV01],
) -> Result<()> {
fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> {
match phase {
GamePhase::TestDay => self.gui.enable()?,
_ => self.gui.disable()?,
@ -39,6 +35,20 @@ impl DataReceiver for Watermark {
Ok(())
}
fn update_for_phase(&self, phase: GamePhase) -> bool {
match phase {
_ => false,
}
}
fn scoring_update(
&mut self,
_phase: GamePhase,
_vehicle_scoring: &[VehicleScoringInfoV01],
) -> Result<()> {
Ok(())
}
fn telemetry_update(
&mut self,
_player_id: Option<i32>,

View file

@ -1,4 +1,4 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<?xml-model href="../gui.xsd" type="application/xml" schematypes="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="2560" reference_height="1440">
<grid x_dim="1" y_dim="1" x_offset="10" y_offset="10" width="300" height="50"
vert_align="top" hori_align="left" margin="2" padding="2" background="#c9c9c9">

View file

@ -1,13 +1,21 @@
use anyhow::Result;
use rfactor_sm_reader::*;
use std::{cell::RefCell, rc::Rc, time::Instant};
use std::{
cell::RefCell,
rc::Rc,
time::{Duration, Instant},
};
use crate::write_log;
use super::UiOverlay;
pub trait DataReceiver {
fn game_phase_change(&mut self, phase: GamePhase) -> Result<()>;
fn update_for_phase(&self, phase: GamePhase) -> bool;
fn scoring_update(
&mut self,
phase: GamePhase,
@ -21,13 +29,16 @@ pub trait DataReceiver {
) -> Result<()>;
}
#[derive(Clone, Copy, Debug)]
const GAME_PHASE_TIME_OUT: Duration = Duration::from_secs(2);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum GamePhase {
TestDay,
Practice,
Qualifying,
Warmup,
Race,
None,
}
impl TryFrom<i32> for GamePhase {
@ -51,8 +62,10 @@ pub struct RFactorData {
telemetry_reader: TelemetryReader,
scoring_reader: ScoringReader,
last_scoring_read: Duration,
start_time: Instant,
player_id: Option<i32>,
previous_game_phase: GamePhase,
receivers: Vec<Rc<RefCell<dyn UiOverlay>>>,
}
@ -67,8 +80,10 @@ impl RFactorData {
telemetry_reader: TelemetryReader::new(start_time.elapsed().as_secs_f32())?,
scoring_reader: ScoringReader::new(start_time.elapsed().as_secs_f32())?,
last_scoring_read: start_time.elapsed(),
start_time,
player_id: None,
previous_game_phase: GamePhase::None,
receivers: Vec::new(),
})
@ -86,35 +101,66 @@ impl RFactorData {
write_log!(" =================== update RFactorData ===================");
// get scoring info
if let Some((scoring_info, vehicle_scorings)) =
self.scoring_reader.vehicle_scoring(self.now())
{
write_log!(format!(
"new scoring info: vehicles: {}",
scoring_info.mNumVehicles
));
match self.scoring_reader.vehicle_scoring(self.now()) {
Some((scoring_info, vehicle_scorings)) => {
self.last_scoring_read = self.start_time.elapsed();
// check for player id
if scoring_info.mNumVehicles == 0 {
self.player_id = None;
} else if self.player_id.is_none() {
for vehicle_scoring in vehicle_scorings.iter() {
if vehicle_scoring.mIsPlayer != 0 {
write_log!(format!("player found: {}", vehicle_scoring.mID));
self.player_id = Some(vehicle_scoring.mID);
break;
write_log!(format!(
"new scoring info: vehicles: {}",
scoring_info.mNumVehicles
));
// check for player id
if scoring_info.mNumVehicles == 0 {
self.player_id = None;
} else if self.player_id.is_none() {
for vehicle_scoring in vehicle_scorings.iter() {
if vehicle_scoring.mIsPlayer != 0 {
write_log!(format!("player found: {}", vehicle_scoring.mID));
self.player_id = Some(vehicle_scoring.mID);
break;
}
}
}
{
let phase = GamePhase::try_from(scoring_info.mSession)?;
if self.previous_game_phase != phase {
self.previous_game_phase = phase;
for receiver in self.receivers.iter() {
receiver
.borrow_mut()
.game_phase_change(self.previous_game_phase)?;
}
}
write_log!(format!("GamePhase: {:?}", self.previous_game_phase));
}
for receiver in self.receivers.iter() {
let mut rec_mut = receiver.borrow_mut();
if rec_mut.update_for_phase(self.previous_game_phase) {
rec_mut.scoring_update(self.previous_game_phase, &vehicle_scorings)?;
}
}
}
None => {
let now = self.start_time.elapsed();
let phase = GamePhase::try_from(scoring_info.mSession)?;
if now > (self.last_scoring_read + GAME_PHASE_TIME_OUT) {
if self.previous_game_phase != GamePhase::None {
self.previous_game_phase = GamePhase::None;
write_log!(format!("GamePhase: {:?}", phase));
for receiver in self.receivers.iter() {
receiver
.borrow_mut()
.scoring_update(phase, &vehicle_scorings)?;
for receiver in self.receivers.iter() {
receiver
.borrow_mut()
.game_phase_change(self.previous_game_phase)?;
}
}
}
}
}
@ -124,9 +170,11 @@ impl RFactorData {
write_log!("new telemetry update");
for receiver in self.receivers.iter() {
receiver
.borrow_mut()
.telemetry_update(self.player_id, &telemetries)?;
let mut rec_mut = receiver.borrow_mut();
if rec_mut.update_for_phase(self.previous_game_phase) {
rec_mut.telemetry_update(self.player_id, &telemetries)?;
}
}
}