Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/config/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ pub struct GraphSettings {
pub trail_brake_threshold: f32,
#[serde(default)]
pub phase_plot_open: bool,
#[serde(default = "default_true")]
pub show_track_strip: bool,
#[serde(default)]
pub lap_comparison_open: bool,
pub show_tc: bool,
pub show_speed: bool,
}

fn default_true() -> bool {
Expand All @@ -71,6 +77,10 @@ pub struct ColorScheme {
pub trail_brake: String,
#[serde(default = "default_abs_cornering_color")]
pub abs_cornering: String,
#[serde(default = "default_tc_active_color")]
pub tc_active: String,
#[serde(default = "default_speed_color")]
pub speed: String,
}

fn default_clutch_color() -> String {
Expand All @@ -85,6 +95,14 @@ fn default_abs_cornering_color() -> String {
"#FF44AA".to_string()
}

fn default_tc_active_color() -> String {
"#FFCC00".to_string()
}

fn default_speed_color() -> String {
"#E8C800".to_string()
}

/// Pre-parsed version of [`ColorScheme`] holding `Color32` values ready for rendering.
///
/// Derive once per settings change via [`ParsedColors::from_scheme`] instead of
Expand All @@ -100,6 +118,8 @@ pub struct ParsedColors {
pub text: egui::Color32,
pub trail_brake: egui::Color32,
pub abs_cornering: egui::Color32,
pub tc_active: egui::Color32,
pub speed: egui::Color32,
}

impl ParsedColors {
Expand All @@ -114,6 +134,8 @@ impl ParsedColors {
text: AppSettings::parse_color(&scheme.text),
trail_brake: AppSettings::parse_color(&scheme.trail_brake),
abs_cornering: AppSettings::parse_color(&scheme.abs_cornering),
tc_active: AppSettings::parse_color(&scheme.tc_active),
speed: AppSettings::parse_color(&scheme.speed),
}
}
}
Expand Down Expand Up @@ -150,6 +172,10 @@ impl Default for AppSettings {
show_abs_cornering: true,
trail_brake_threshold: 5.0,
phase_plot_open: false,
show_track_strip: true,
lap_comparison_open: false,
show_tc: true,
show_speed: true,
},
colors: ColorScheme {
throttle: "#00FF00".to_string(),
Expand All @@ -161,6 +187,8 @@ impl Default for AppSettings {
text: "#FFFFFF".to_string(),
trail_brake: "#00BBFF".to_string(),
abs_cornering: "#FF44AA".to_string(),
tc_active: "#FFCC00".to_string(),
speed: "#E8C800".to_string(),
},
overlay: OverlaySettings {
width: 600.0,
Expand Down Expand Up @@ -267,6 +295,19 @@ mod tests {
show_grid = true
show_legend = true
line_width = 2.0
speed_mph = false
show_throttle = true
show_brake = true
show_abs = true
show_clutch = false
show_trail_brake = true
show_abs_cornering = true
trail_brake_threshold = 5.0
phase_plot_open = true
show_track_strip = false
lap_comparison_open = false
show_tc = true
show_speed = false

[colors]
throttle = "#00FF00"
Expand All @@ -275,6 +316,10 @@ mod tests {
background = "#1A1A1A"
grid = "#333333"
text = "#FFFFFF"
trail_brake = "#00BBFF"
abs_cornering = "#FF44AA"
tc_active = "#FFCC00"
speed = "#E8C800"

[overlay]
width = 600.0
Expand Down
117 changes: 117 additions & 0 deletions src/core/lap_store.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//! Lap boundary detection and per-lap telemetry storage.

use crate::core::TelemetryPoint;
use std::time::Instant;

/// A single telemetry sample stored within a lap, keyed by track position.
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub struct LapPoint {
/// Track position: 0.0 = start/finish line, 1.0 = just before it.
pub track_position: f32,
pub throttle: f32,
pub brake: f32,
pub speed: f32,
pub abs_active: bool,
/// Milliseconds elapsed since the first point of this lap.
pub elapsed_ms: f32,
}

/// Detects lap crossings and maintains a reference lap for comparison.
#[derive(Clone, Default)]
pub struct LapStore {
/// The most recently completed full lap, sorted by `track_position`.
pub reference_lap: Option<Vec<LapPoint>>,
current_lap: Vec<LapPoint>,
lap_start_at: Option<Instant>,
last_track_pos: Option<f32>,
/// Deduplication: skip if this point was already processed.
last_captured_at: Option<Instant>,
}

impl LapStore {
pub fn new() -> Self {
Self::default()
}

/// Push the latest telemetry point.
///
/// When `track_position` crosses the start/finish line (wraps from near
/// 1.0 back to near 0.0), the completed lap is saved as the reference.
pub fn push(&mut self, pt: &TelemetryPoint) {
// `buffer.latest()` returns the same point every frame until a new one
// arrives, so we skip duplicates by comparing captured_at instants.
if self.last_captured_at == Some(pt.captured_at) {
return;
}
self.last_captured_at = Some(pt.captured_at);

let pos = pt.telemetry.track_position;

let crossed = self
.last_track_pos
.map(|last| last > 0.85 && pos < 0.15)
.unwrap_or(false);

if crossed && self.current_lap.len() > 20 {
// Completed a valid lap — promote to reference.
let mut completed = std::mem::take(&mut self.current_lap);
// Sort by track_position so the comparison panel can interpolate.
completed.sort_by(|a, b| {
a.track_position
.partial_cmp(&b.track_position)
.unwrap_or(std::cmp::Ordering::Equal)
});
self.reference_lap = Some(completed);
self.lap_start_at = None;
}

// Begin timing the new lap on its first point.
if self.current_lap.is_empty() {
self.lap_start_at = Some(pt.captured_at);
}

if let Some(start) = self.lap_start_at {
self.current_lap.push(LapPoint {
track_position: pos,
throttle: pt.telemetry.throttle,
brake: pt.telemetry.brake,
speed: pt.telemetry.speed,
abs_active: pt.abs_active,
elapsed_ms: pt.captured_at.duration_since(start).as_secs_f32() * 1000.0,
});
}

self.last_track_pos = Some(pos);
}

/// The telemetry points for the lap currently in progress, in push order.
pub fn current_lap(&self) -> &[LapPoint] {
&self.current_lap
}

/// Promote the current partial lap to the reference immediately.
pub fn set_current_as_reference(&mut self) {
if !self.current_lap.is_empty() {
let mut snap = self.current_lap.clone();
snap.sort_by(|a, b| {
a.track_position
.partial_cmp(&b.track_position)
.unwrap_or(std::cmp::Ordering::Equal)
});
self.reference_lap = Some(snap);
}
}

pub fn clear_reference(&mut self) {
self.reference_lap = None;
}

/// Reset all accumulated data (call after a plugin change).
pub fn clear(&mut self) {
self.current_lap.clear();
self.lap_start_at = None;
self.last_track_pos = None;
self.last_captured_at = None;
}
}
2 changes: 2 additions & 0 deletions src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

pub mod buffer;
pub mod collector;
pub mod lap_store;
pub mod model;

pub use buffer::TelemetryBuffer;
pub use collector::DataCollector;
pub use lap_store::LapStore;
pub use model::{TelemetryData, TelemetryPoint, VehicleTelemetry};
Loading