diff --git a/src/backend/common.rs b/src/backend/common.rs index 8a6bdf6..b2d3a36 100644 --- a/src/backend/common.rs +++ b/src/backend/common.rs @@ -1,9 +1,4 @@ -use std::{ - collections::{BinaryHeap, VecDeque}, - f32::consts::PI, - sync::Arc, - time::Instant, -}; +use std::{f32::consts::PI, sync::Arc}; use once_cell::sync::Lazy; #[cfg(feature = "openxr")] @@ -25,7 +20,7 @@ use crate::{ state::AppState, }; -use super::overlay::{OverlayBackend, OverlayData, OverlayState}; +use super::overlay::OverlayData; #[derive(Error, Debug)] pub enum BackendError { @@ -351,90 +346,6 @@ pub enum OverlaySelector { Name(Arc), } -struct AppTask { - pub not_before: Instant, - pub task: TaskType, -} - -impl PartialEq for AppTask { - fn eq(&self, other: &Self) -> bool { - self.not_before == other.not_before - } -} -impl PartialOrd for AppTask { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} -impl Eq for AppTask {} -impl Ord for AppTask { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.not_before.cmp(&other.not_before).reverse() - } -} - -pub enum SystemTask { - ColorGain(ColorChannel, f32), - ResetPlayspace, - FixFloor, -} - -pub type OverlayTask = dyn FnOnce(&mut AppState, &mut OverlayState) + Send; -pub type CreateOverlayTask = - dyn FnOnce(&mut AppState) -> Option<(OverlayState, Box)> + Send; - -pub enum TaskType { - Global(Box), - Overlay(OverlaySelector, Box), - CreateOverlay(OverlaySelector, Box), - DropOverlay(OverlaySelector), - System(SystemTask), -} - -#[derive(Deserialize, Clone, Copy)] -pub enum ColorChannel { - R, - G, - B, - All, -} - -pub struct TaskContainer { - tasks: BinaryHeap, -} - -impl TaskContainer { - pub fn new() -> Self { - Self { - tasks: BinaryHeap::new(), - } - } - - pub fn enqueue(&mut self, task: TaskType) { - self.tasks.push(AppTask { - not_before: Instant::now(), - task, - }); - } - - pub fn enqueue_at(&mut self, task: TaskType, not_before: Instant) { - self.tasks.push(AppTask { not_before, task }); - } - - pub fn retrieve_due(&mut self, dest_buf: &mut VecDeque) { - let now = Instant::now(); - - while let Some(task) = self.tasks.peek() { - if task.not_before > now { - break; - } - - // Safe unwrap because we peeked. - dest_buf.push_back(self.tasks.pop().unwrap().task); - } - } -} - pub fn raycast_plane( source: &Affine3A, source_fwd: Vec3A, diff --git a/src/backend/input.rs b/src/backend/input.rs index 9b76db3..35dc970 100644 --- a/src/backend/input.rs +++ b/src/backend/input.rs @@ -4,12 +4,12 @@ use glam::{Affine3A, Vec2, Vec3, Vec3A}; use smallvec::{smallvec, SmallVec}; -use crate::backend::common::{snap_upright, OverlaySelector, TaskType}; +use crate::backend::common::{snap_upright, OverlaySelector}; use crate::config::{save_state, AStrMapExt, GeneralConfig}; use crate::overlays::anchor::ANCHOR_NAME; use crate::state::AppState; -use super::common::TaskContainer; +use super::task::{TaskContainer, TaskType}; use super::{ common::{raycast_cylinder, raycast_plane, OverlayContainer}, overlay::OverlayData, diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 9d6a682..3c2e86a 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -15,3 +15,5 @@ pub mod uidev; pub mod osc; pub mod overlay; + +pub mod task; diff --git a/src/backend/openvr/helpers.rs b/src/backend/openvr/helpers.rs index 0425c5c..cb30698 100644 --- a/src/backend/openvr/helpers.rs +++ b/src/backend/openvr/helpers.rs @@ -4,7 +4,7 @@ use glam::Affine3A; use ovr_overlay::{pose::Matrix3x4, settings::SettingsManager, sys::HmdMatrix34_t}; use thiserror::Error; -use crate::backend::common::{BackendError, ColorChannel}; +use crate::backend::{common::BackendError, task::ColorChannel}; pub trait Affine3AConvert { fn from_affine(affine: &Affine3A) -> Self; diff --git a/src/backend/openvr/mod.rs b/src/backend/openvr/mod.rs index 8628956..dc1e1e8 100644 --- a/src/backend/openvr/mod.rs +++ b/src/backend/openvr/mod.rs @@ -20,7 +20,7 @@ use vulkano::{ use crate::{ backend::{ - common::SystemTask, + common::{BackendError, OverlayContainer}, input::interact, notifications::NotificationManager, openvr::{ @@ -31,6 +31,7 @@ use crate::{ overlay::OpenVrOverlayData, }, overlay::OverlayData, + task::{SystemTask, TaskType}, }, graphics::WlxGraphics, overlays::{ @@ -40,8 +41,6 @@ use crate::{ state::AppState, }; -use super::common::{BackendError, OverlayContainer, TaskType}; - pub mod helpers; pub mod input; pub mod lines; diff --git a/src/backend/openxr/mod.rs b/src/backend/openxr/mod.rs index c3bb05e..c409564 100644 --- a/src/backend/openxr/mod.rs +++ b/src/backend/openxr/mod.rs @@ -13,11 +13,12 @@ use vulkano::{command_buffer::CommandBufferUsage, Handle, VulkanObject}; use crate::{ backend::{ - common::{OverlayContainer, TaskType}, + common::{BackendError, OverlayContainer}, input::interact, notifications::NotificationManager, openxr::{input::DoubleClickCounter, lines::LinePool, overlay::OpenXrOverlayData}, overlay::OverlayData, + task::TaskType, }, graphics::WlxGraphics, overlays::{ @@ -27,8 +28,6 @@ use crate::{ state::AppState, }; -use super::common::BackendError; - mod helpers; mod input; mod lines; diff --git a/src/backend/overlay.rs b/src/backend/overlay.rs index 4dfe0cd..b99bdf3 100644 --- a/src/backend/overlay.rs +++ b/src/backend/overlay.rs @@ -124,7 +124,7 @@ impl OverlayState { self.saved_transform = None; } - self.transform = app.anchor * self.get_transform(); + self.transform = self.parent_transform(app).unwrap_or(app.anchor) * self.get_transform(); if self.grabbable && hard_reset { self.realign(&app.input_state.hmd); diff --git a/src/backend/task.rs b/src/backend/task.rs new file mode 100644 index 0000000..2b15251 --- /dev/null +++ b/src/backend/task.rs @@ -0,0 +1,113 @@ +use std::{ + cmp, + collections::{BinaryHeap, VecDeque}, + sync::atomic::{self, AtomicUsize}, + time::Instant, +}; + +use serde::Deserialize; + +use crate::state::AppState; + +use super::{ + common::OverlaySelector, + overlay::{OverlayBackend, OverlayState}, +}; + +static TASK_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0); + +struct AppTask { + pub not_before: Instant, + pub id: usize, + pub task: TaskType, +} + +impl PartialEq for AppTask { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == cmp::Ordering::Equal + } +} +impl PartialOrd for AppTask { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Eq for AppTask {} +impl Ord for AppTask { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.not_before + .cmp(&other.not_before) + .then(self.id.cmp(&other.id)) + .reverse() + } +} + +pub enum SystemTask { + ColorGain(ColorChannel, f32), + ResetPlayspace, + FixFloor, +} + +pub type OverlayTask = dyn FnOnce(&mut AppState, &mut OverlayState) + Send; +pub type CreateOverlayTask = + dyn FnOnce(&mut AppState) -> Option<(OverlayState, Box)> + Send; + +pub enum TaskType { + Global(Box), + Overlay(OverlaySelector, Box), + CreateOverlay(OverlaySelector, Box), + DropOverlay(OverlaySelector), + System(SystemTask), +} + +#[derive(Deserialize, Clone, Copy)] +pub enum ColorChannel { + R, + G, + B, + All, +} + +pub struct TaskContainer { + tasks: BinaryHeap, +} + +impl TaskContainer { + pub fn new() -> Self { + Self { + tasks: BinaryHeap::new(), + } + } + + pub fn enqueue(&mut self, task: TaskType) { + self.tasks.push(AppTask { + not_before: Instant::now(), + id: TASK_AUTO_INCREMENT.fetch_add(1, atomic::Ordering::Relaxed), + task, + }); + } + + /// Enqueue a task to be executed at a specific time. + /// If the time is in the past, the task will be executed immediately. + /// Multiple tasks enqueued for the same instant will be executed in order of submission. + pub fn enqueue_at(&mut self, task: TaskType, not_before: Instant) { + self.tasks.push(AppTask { + not_before, + id: TASK_AUTO_INCREMENT.fetch_add(1, atomic::Ordering::Relaxed), + task, + }); + } + + pub fn retrieve_due(&mut self, dest_buf: &mut VecDeque) { + let now = Instant::now(); + + while let Some(task) = self.tasks.peek() { + if task.not_before > now { + break; + } + + // Safe unwrap because we peeked. + dest_buf.push_back(self.tasks.pop().unwrap().task); + } + } +} diff --git a/src/gui/modular/button.rs b/src/gui/modular/button.rs index 9f0ff68..b8978ce 100644 --- a/src/gui/modular/button.rs +++ b/src/gui/modular/button.rs @@ -11,9 +11,10 @@ use serde::Deserialize; use crate::{ backend::{ - common::{ColorChannel, OverlaySelector, SystemTask, TaskType}, + common::OverlaySelector, input::PointerMode, overlay::RelativeTo, + task::{ColorChannel, SystemTask, TaskType}, }, config::{save_settings, save_state, AStrSetExt}, overlays::{ diff --git a/src/overlays/mirror.rs b/src/overlays/mirror.rs index d26fbf3..dc5407f 100644 --- a/src/overlays/mirror.rs +++ b/src/overlays/mirror.rs @@ -9,10 +9,11 @@ use wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture, PipewireSel use crate::{ backend::{ - common::{OverlaySelector, TaskType}, + common::OverlaySelector, overlay::{ ui_transform, OverlayBackend, OverlayRenderer, OverlayState, SplitOverlayBackend, }, + task::TaskType, }, state::{AppSession, AppState}, }; diff --git a/src/overlays/toast.rs b/src/overlays/toast.rs index 6c9e323..1784595 100644 --- a/src/overlays/toast.rs +++ b/src/overlays/toast.rs @@ -1,18 +1,15 @@ -use std::{ - f32::consts::PI, - ops::Add, - sync::{atomic::AtomicUsize, Arc}, - time::Instant, -}; +use std::{f32::consts::PI, ops::Add, sync::Arc, time::Instant}; use glam::{vec3a, Quat, Vec3A}; use idmap_derive::IntegerId; +use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use crate::{ backend::{ - common::{OverlaySelector, TaskType}, + common::OverlaySelector, overlay::{OverlayBackend, OverlayState, RelativeTo}, + task::TaskType, }, gui::{color_parse, CanvasBuilder}, state::{AppState, LeftRight}, @@ -22,8 +19,7 @@ const FONT_SIZE: isize = 16; const PADDING: (f32, f32) = (25., 7.); const PIXELS_TO_METERS: f32 = 1. / 2000.; const TOAST_AUDIO_WAV: &[u8] = include_bytes!("../res/557297.wav"); - -static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0); +static TOAST_NAME: Lazy> = Lazy::new(|| "toast".into()); #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum DisplayMethod { @@ -77,36 +73,49 @@ impl Toast { self.submit_at(app, Instant::now()); } pub fn submit_at(self, app: &mut AppState, instant: Instant) { - let auto_increment = AUTO_INCREMENT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - let name: Arc = format!("toast-{}", auto_increment).into(); - let selector = OverlaySelector::Name(name.clone()); + let selector = OverlaySelector::Name(TOAST_NAME.clone()); let destroy_at = instant.add(std::time::Duration::from_secs_f32(self.timeout)); let has_sound = self.sound && app.session.config.notifications_sound_enabled; + // drop any toast that was created before us. + // (DropOverlay only drops overlays that were + // created before current frame) + app.tasks + .enqueue_at(TaskType::DropOverlay(selector.clone()), instant); + + // CreateOverlay only creates the overlay if + // the selector doesn't exist yet, so in case + // multiple toasts are submitted for the same + // frame, only the first one gets created app.tasks.enqueue_at( TaskType::CreateOverlay( selector.clone(), - Box::new(move |app| new_toast(self, name, app)), + Box::new(move |app| { + let mut maybe_toast = new_toast(self, app); + if let Some((state, _)) = maybe_toast.as_mut() { + state.auto_movement(app); + app.tasks.enqueue_at( + // at timeout, drop the overlay by ID instead + // in order to avoid dropping any newer toasts + TaskType::DropOverlay(OverlaySelector::Id(state.id)), + destroy_at, + ); + } + maybe_toast + }), ), instant, ); - app.tasks - .enqueue_at(TaskType::DropOverlay(selector), destroy_at); - if has_sound { app.audio.play(TOAST_AUDIO_WAV); } } } -fn new_toast( - toast: Toast, - name: Arc, - app: &mut AppState, -) -> Option<(OverlayState, Box)> { +fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box)> { let current_method = app .session .toast_topics @@ -186,7 +195,7 @@ fn new_toast( } let state = OverlayState { - name, + name: TOAST_NAME.clone(), want_visible: true, spawn_scale: size.0 * PIXELS_TO_METERS, spawn_rotation, diff --git a/src/state.rs b/src/state.rs index 4d882ba..2c3159e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use smallvec::{smallvec, SmallVec}; use crate::{ - backend::{common::TaskContainer, input::InputState}, + backend::{input::InputState, task::TaskContainer}, config::GeneralConfig, config_io, graphics::WlxGraphics,