2022-05-22 17:19:13 +00:00
|
|
|
use std::{
|
|
|
|
collections::HashMap,
|
|
|
|
time::{Duration, Instant},
|
|
|
|
};
|
2022-05-21 18:12:10 +00:00
|
|
|
|
|
|
|
use anyhow::Result;
|
|
|
|
use egui_extras::RetainedImage;
|
2022-05-22 17:19:13 +00:00
|
|
|
use image::RgbImage;
|
2022-05-21 18:12:10 +00:00
|
|
|
use scrap::{Capturer, Display};
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
capture,
|
|
|
|
image_processing::{self, Region},
|
|
|
|
ocr,
|
2022-05-22 17:19:13 +00:00
|
|
|
state::{AppState, DebugOcrFrame, ParsedFrame, RaceState, SharedAppState},
|
2022-05-21 18:12:10 +00:00
|
|
|
};
|
|
|
|
|
2022-05-22 17:19:13 +00:00
|
|
|
fn is_finished_lap(state: &AppState, frame: &ParsedFrame) -> bool {
|
|
|
|
if let Some(race) = &state.current_race {
|
|
|
|
if let Some(last_finish) = &race.last_lap_record_time {
|
|
|
|
let diff = Instant::now().duration_since(*last_finish);
|
|
|
|
if diff < Duration::from_secs(5) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(prev_frame) = state.buffered_frames.back() {
|
|
|
|
prev_frame.lap_time.is_some()
|
|
|
|
&& prev_frame.lap_time == frame.lap_time
|
|
|
|
&& prev_frame.lap_time.unwrap() > Duration::from_secs(5)
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn merge_with_max<T: Ord + Clone>(a: &Option<T>, b: &Option<T>) -> Option<T> {
|
|
|
|
match (a, b) {
|
|
|
|
(Some(a), None) => Some(a.clone()),
|
|
|
|
(None, Some(b)) => Some(b.clone()),
|
|
|
|
(None, None) => None,
|
|
|
|
(Some(a), Some(b)) => {
|
|
|
|
if a > b {
|
|
|
|
Some(a.clone())
|
|
|
|
} else {
|
|
|
|
Some(b.clone())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn merge_frames(prev: &ParsedFrame, next: &ParsedFrame) -> ParsedFrame {
|
|
|
|
ParsedFrame {
|
|
|
|
lap: merge_with_max(&prev.lap, &next.lap),
|
|
|
|
health: merge_with_max(&prev.health, &next.health),
|
|
|
|
gas: merge_with_max(&prev.gas, &next.gas),
|
|
|
|
tyres: merge_with_max(&prev.tyres, &next.tyres),
|
|
|
|
lap_time: merge_with_max(&prev.lap_time, &next.lap_time),
|
|
|
|
best_time: merge_with_max(&prev.best_time, &next.best_time),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_new_frame(state: &mut AppState, frame: ParsedFrame, image: &RgbImage) {
|
|
|
|
if frame.lap_time.is_some() {
|
|
|
|
state.last_frame = Some(frame.clone());
|
|
|
|
state.frames_without_lap = 0;
|
|
|
|
|
|
|
|
if state.current_race.is_none() {
|
|
|
|
let mut race = RaceState::default();
|
|
|
|
race.screencap = Some(
|
|
|
|
RetainedImage::from_image_bytes(
|
|
|
|
"screencap",
|
|
|
|
&image_processing::to_png_bytes(image),
|
|
|
|
)
|
|
|
|
.expect("failed to save screenshot"),
|
|
|
|
);
|
|
|
|
state.current_race = Some(race);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
state.frames_without_lap += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if state.frames_without_lap >= 10 {
|
|
|
|
if let Some(race) = state.current_race.take() {
|
|
|
|
state.past_races.push_front(race);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if is_finished_lap(state, &frame) {
|
|
|
|
let mut merged = merge_frames(state.buffered_frames.back().unwrap(), &frame);
|
|
|
|
if let Some(lap) = &merged.lap {
|
|
|
|
merged.lap = Some(lap - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(race) = state.current_race.as_mut() {
|
|
|
|
race.laps.push(merged);
|
|
|
|
race.last_lap_record_time = Some(Instant::now());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
state.buffered_frames.push_back(frame);
|
|
|
|
if state.buffered_frames.len() >= 20 {
|
|
|
|
state.buffered_frames.pop_front();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn run_loop_once(state: &SharedAppState) -> Result<()> {
|
|
|
|
let config = state.lock().unwrap().config.clone();
|
2022-05-21 18:12:10 +00:00
|
|
|
let frame = capture::get_frame()?;
|
2022-05-22 17:19:13 +00:00
|
|
|
let ocr_results = ocr::ocr_all_regions(&frame, &config.ocr_regions).await;
|
2022-05-21 18:12:10 +00:00
|
|
|
|
|
|
|
let mut saved_frames = HashMap::new();
|
|
|
|
if state.lock().unwrap().debug_frames {
|
2022-05-22 17:19:13 +00:00
|
|
|
let hasher = img_hash::HasherConfig::new().to_hasher();
|
|
|
|
for region in &config.ocr_regions {
|
2022-05-21 18:12:10 +00:00
|
|
|
let mut extracted = image_processing::extract_region(&frame, region);
|
2022-05-22 17:19:13 +00:00
|
|
|
image_processing::filter_to_white(&mut extracted);
|
2022-05-21 18:12:10 +00:00
|
|
|
let retained = RetainedImage::from_image_bytes(
|
|
|
|
®ion.name,
|
|
|
|
&image_processing::to_png_bytes(&extracted),
|
|
|
|
)
|
|
|
|
.unwrap();
|
2022-05-22 17:19:13 +00:00
|
|
|
let have_to_use_other_image_library_version = img_hash::image::RgbImage::from_raw(
|
|
|
|
extracted.width(),
|
|
|
|
extracted.height(),
|
|
|
|
extracted.as_raw().to_vec(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
let hash = hasher.hash_image(&have_to_use_other_image_library_version);
|
|
|
|
saved_frames.insert(
|
|
|
|
region.name.clone(),
|
|
|
|
DebugOcrFrame {
|
|
|
|
image: retained,
|
|
|
|
rgb_image: extracted,
|
|
|
|
img_hash: hash,
|
|
|
|
},
|
|
|
|
);
|
2022-05-21 18:12:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
{
|
|
|
|
let mut state = state.lock().unwrap();
|
|
|
|
let parsed = ParsedFrame::parse(&ocr_results);
|
2022-05-22 17:19:13 +00:00
|
|
|
handle_new_frame(&mut state, parsed, &frame);
|
2022-05-21 18:12:10 +00:00
|
|
|
state.raw_data = ocr_results;
|
|
|
|
state.saved_frames = saved_frames;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn run_control_loop(state: SharedAppState) -> Result<()> {
|
|
|
|
loop {
|
2022-05-22 17:19:13 +00:00
|
|
|
if let Err(e) = run_loop_once(&state).await {
|
2022-05-21 18:12:10 +00:00
|
|
|
eprintln!("Error in control loop: {:?}", e)
|
|
|
|
}
|
|
|
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
|
|
}
|
|
|
|
}
|