From 62c60723a77e9d3b984f94924194b1a0ba753916 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Sun, 5 Dec 2021 21:31:26 -0700 Subject: recognize connection errors too --- index.html | 2 +- src/history.rs | 1 - src/lib.rs | 190 ++++++++++++++++++++++++++++++++++++++------------------- src/ocr.rs | 7 +-- 4 files changed, 131 insertions(+), 69 deletions(-) diff --git a/index.html b/index.html index cb5aa8c..bc7a760 100644 --- a/index.html +++ b/index.html @@ -27,7 +27,7 @@
-

queue go brrr

+

queue go brrr

For tracking your Login Queue (Extreme) raid progression.

Share your FFXIV window, and then this tool will try to calculate how much longer you probably have to wait. diff --git a/src/history.rs b/src/history.rs index 6031499..a579a8c 100644 --- a/src/history.rs +++ b/src/history.rs @@ -1,7 +1,6 @@ // god is dead and we have killed him and consequently std::time::Instant doesn't work on wasm use chrono::prelude::*; -use chrono::Duration; use wasm_bindgen::prelude::*; struct Reading { diff --git a/src/lib.rs b/src/lib.rs index b0c7353..572b533 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use image::{DynamicImage, ImageFormat}; +use image::{DynamicImage, GrayImage, ImageFormat}; use std::cell::{Cell, RefCell}; use std::rc::Rc; @@ -40,6 +40,14 @@ fn get_queue_size(image: DynamicImage, corner: &(u32, u32)) -> DynamicImage { useful_region } +fn is_connection_error(image: &GrayImage) -> bool { + // prior experiments show that the 80th percentile is like 20 and the 100th percentile is 51 for the error state + let pct_80 = imageproc::stats::percentile(image, 80); + let pct_100 = imageproc::stats::percentile(image, 100); + + pct_80 < 25 && pct_100 < 60 +} + fn update_target_marker( target_marker: &HtmlElement, width: &Rc>, @@ -96,6 +104,120 @@ impl QueuePosition { } } +enum Status { + ETAPending, + HasETA(js_sys::Date), + ETAFucked, + ConnectionError, +} + +impl Status { + fn render_title(&self) -> String { + match self { + Status::ETAPending => format!("queue go brrr - ETA pending"), + Status::ETAFucked => format!("queue go brrr - ETA fucked (try reloading i guess?)"), + Status::HasETA(date) => { + let empty_array = js_sys::Array::new(); + let options = { + let kv_pair = js_sys::Array::new(); + kv_pair.push(&JsValue::from("timeStyle")); + kv_pair.push(&JsValue::from("short")); + let kv_pairs = js_sys::Array::new(); + kv_pairs.push(&kv_pair); + js_sys::Object::from_entries(&kv_pairs).unwrap() + }; + let format = js_sys::Intl::DateTimeFormat::new(&empty_array, &options); + let format = format.format(); + let locale_string = format.call1(&JsValue::UNDEFINED, &date).unwrap(); + locale_string.as_string().unwrap() + } + Status::ConnectionError => format!("queue go brrr - connection error?"), + } + } +} + +#[derive(Clone)] +struct State { + history: Rc>, + status: Rc>, + queue_position: QueuePosition, + preview_image: Element, + current_queue_position: HtmlElement, + title_h1: HtmlElement, + title_element: HtmlElement, +} + +impl State { + fn new(queue_position: &QueuePosition) -> Self { + let document = gloo::utils::document(); + let preview_image = document.get_element_by_id("photo").unwrap(); + let current_queue_position = document + .get_element_by_id("current-queue-position") + .unwrap() + .dyn_into() + .unwrap(); + let title_h1 = document + .get_element_by_id("title") + .unwrap() + .dyn_into() + .unwrap(); + let title_element = document + .query_selector("title") + .unwrap() + .unwrap() + .dyn_into() + .unwrap(); + Self { + history: Rc::new(RefCell::new(History::default())), + status: Rc::new(RefCell::new(Status::ETAPending)), + queue_position: queue_position.clone(), + preview_image, + current_queue_position, + title_h1, + title_element, + } + } + + async fn update(&self, data: Blob) { + let image = blob_to_image(data).await; + let useful_region = get_queue_size(image, &self.queue_position.get()); + let data_uri = image_to_data_uri(&useful_region); + self.preview_image.set_attribute("src", &data_uri).unwrap(); + let useful_region = image::imageops::grayscale(&useful_region); + let new_status = if is_connection_error(&useful_region) { + Status::ConnectionError + } else { + let queue_size = ocr::ocr(&useful_region); + match queue_size { + Some(queue_size) => { + self.current_queue_position + .set_inner_text(&format!("{}", queue_size)); + match self.history.try_borrow_mut() { + Ok(mut history) => { + history.record(queue_size); + let estimated_finish = history.completion_time(); + match estimated_finish { + Ok(finish) => Status::HasETA(finish), + Err(NoETA::Pending) => Status::ETAPending, + Err(NoETA::Fuck) => Status::ETAFucked, + } + } + Err(_) => return, + } + } + None => { + self.current_queue_position + .set_inner_text("something i can't read"); + return; + } + } + }; + let title = new_status.render_title(); + self.title_h1.set_inner_text(&title); + self.title_element.set_inner_text(&title); + } +} + async fn do_boot() { let width = Rc::new(Cell::new(0)); let height = Rc::new(Cell::new(0)); @@ -106,7 +228,7 @@ async fn do_boot() { let document = gloo::utils::document(); - let history = Rc::new(RefCell::new(History::default())); + let state = State::new(&queue_position); let (manual_update_tx, manual_update_rx) = mpsc::channel::<()>(5); @@ -198,8 +320,7 @@ async fn do_boot() { manual_update_rx, ); let update_future = update_stream.for_each(move |_| { - let history = history.clone(); - let queue_position = queue_position.clone(); + let state = state.clone(); if !streaming.get() { return futures::future::ready(()); } @@ -220,66 +341,9 @@ async fn do_boot() { canvas .to_blob( Closure::once_into_js(move |data: Blob| { - let history = history.clone(); - let queue_position = queue_position.clone(); - // lifetime bullshit here - let document = gloo::utils::document(); - let photo = document.get_element_by_id("photo").unwrap(); - let current_queue_position: HtmlElement = document - .get_element_by_id("current-queue-position") - .unwrap() - .dyn_into() - .unwrap(); - let eta: HtmlElement = document - .get_element_by_id("eta") - .unwrap() - .dyn_into() - .unwrap(); + let state = state.clone(); spawn_local(async move { - let image = blob_to_image(data).await; - let useful_region = get_queue_size(image, &queue_position.get()); - let data_uri = image_to_data_uri(&useful_region); - photo.set_attribute("src", &data_uri).unwrap(); - let queue_size = ocr::ocr(useful_region); - match queue_size { - Some(queue_size) => { - current_queue_position.set_inner_text(&format!("{}", queue_size)); - if let Ok(mut history) = history.try_borrow_mut() { - history.record(queue_size); - let estimated_finish = history.completion_time(); - let eta_text = match estimated_finish { - Ok(finish) => { - let empty_array = js_sys::Array::new(); - let options = { - let kv_pair = js_sys::Array::new(); - kv_pair.push(&JsValue::from("timeStyle")); - kv_pair.push(&JsValue::from("short")); - let kv_pairs = js_sys::Array::new(); - kv_pairs.push(&kv_pair); - js_sys::Object::from_entries(&kv_pairs).unwrap() - }; - let format = js_sys::Intl::DateTimeFormat::new( - &empty_array, - &options, - ); - let format = format.format(); - let locale_string = - format.call1(&JsValue::UNDEFINED, &finish).unwrap(); - locale_string.as_string().unwrap() - } - Err(NoETA::Pending) => format!("pending"), - Err(NoETA::Fuck) => { - format!("fucked (try reloading i guess?)") - } - }; - let label = format!(" - ETA {}", eta_text); - eta.set_inner_text(&label); - } - } - None => { - current_queue_position.set_inner_text("something i can't read"); - } - } + state.update(data).await; }); }) .dyn_ref() diff --git a/src/ocr.rs b/src/ocr.rs index 14021cc..52e0020 100644 --- a/src/ocr.rs +++ b/src/ocr.rs @@ -1,4 +1,4 @@ -use image::{DynamicImage, GrayImage, ImageFormat}; +use image::{GrayImage, ImageFormat}; use imageproc::template_matching::MatchTemplateMethod; use lazy_static::lazy_static; @@ -36,11 +36,10 @@ fn x_matches(image: &GrayImage, template: &GrayImage) -> Vec { .collect() } -pub fn ocr(image: DynamicImage) -> Option { - let grayscale_image = image::imageops::grayscale(&image); +pub fn ocr(image: &GrayImage) -> Option { let mut digit_x_positions: Vec<(u8, u32)> = (0..10) .flat_map(|i| { - x_matches(&grayscale_image, &REFERENCES[i as usize]) + x_matches(image, &REFERENCES[i as usize]) .into_iter() .map(move |x| (i, x)) }) -- cgit v1.2.3