diff options
| author | HampusM <hampus@hampusmat.com> | 2026-05-25 19:49:15 +0200 |
|---|---|---|
| committer | HampusM <hampus@hampusmat.com> | 2026-05-25 19:49:15 +0200 |
| commit | f8b7c0cfc73eaeb7ef8dfb3151f93879c97b220a (patch) | |
| tree | 5ff078443f5d64e4f99fd0487b3a2e72ef961540 /engine | |
| parent | 8a41f5aeb3ac143d731928d1c343cd9338190f0a (diff) | |
fix(engine): prevent dropped cursor motion input on Windows
Diffstat (limited to 'engine')
| -rw-r--r-- | engine/Cargo.toml | 6 | ||||
| -rw-r--r-- | engine/src/data_types/vector.rs | 8 | ||||
| -rw-r--r-- | engine/src/util.rs | 96 | ||||
| -rw-r--r-- | engine/src/windowing.rs | 173 |
4 files changed, 207 insertions, 76 deletions
diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 6187bb4..260bb56 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -40,6 +40,12 @@ features = ["derive"] git = "https://github.com/HampusMat/slang-rs" branch = "prebuilt-and-artifacts" +[dependencies.portable-atomic] +version = "=1.13.1" +default-features = false # The "fallback" feature should not be enabled +features = ["require-cas"] + + [build-dependencies] cfg_aliases = "0.2.1" build-rs = "0.3.4" diff --git a/engine/src/data_types/vector.rs b/engine/src/data_types/vector.rs index 933ca51..77173d6 100644 --- a/engine/src/data_types/vector.rs +++ b/engine/src/data_types/vector.rs @@ -113,6 +113,14 @@ impl<Value> From<[Value; 2]> for Vec2<Value> } } +impl<Value> From<(Value, Value)> for Vec2<Value> +{ + fn from((x, y): (Value, Value)) -> Self + { + Self { x, y } + } +} + impl<Value> From<Vec2<Value>> for [Value; 2] { fn from(vec: Vec2<Value>) -> Self diff --git a/engine/src/util.rs b/engine/src/util.rs index 439fe9a..454a776 100644 --- a/engine/src/util.rs +++ b/engine/src/util.rs @@ -1,3 +1,9 @@ +use std::fmt::Debug; +use std::mem::transmute; +use std::sync::atomic::Ordering; + +use portable_atomic::AtomicU128; + use crate::ecs::util::VecExt; #[derive(Debug)] @@ -94,6 +100,96 @@ impl<T> OptionExt<T> for Option<T> } } +pub struct AtomicTwoF64 +{ + inner: AtomicU128, +} + +impl AtomicTwoF64 +{ + pub const fn new((first, second): (f64, f64)) -> Self + { + let mut bytes = [0u8; size_of::<u128>()]; + + bytes.copy_from_slice([first.to_ne_bytes(), second.to_ne_bytes()].as_flattened()); + + Self { + inner: AtomicU128::new(u128::from_ne_bytes(bytes)), + } + } + + pub fn load(&self, order: Ordering) -> (f64, f64) + { + u128_to_two_f64(self.inner.load(order)) + } + + pub fn store(&self, values: (f64, f64), order: Ordering) + { + self.inner.store(two_f64_to_u128(values), order); + } + + pub fn swap(&self, values: (f64, f64), order: Ordering) -> (f64, f64) + { + u128_to_two_f64(self.inner.swap(two_f64_to_u128(values), order)) + } + + pub fn compare_exchange( + &self, + current: (f64, f64), + new: (f64, f64), + success_order: Ordering, + failure_order: Ordering, + ) -> Result<(f64, f64), (f64, f64)> + { + self.inner + .compare_exchange( + two_f64_to_u128(current), + two_f64_to_u128(new), + success_order, + failure_order, + ) + .map(|stored| u128_to_two_f64(stored)) + .map_err(|stored| u128_to_two_f64(stored)) + } +} + +impl Debug for AtomicTwoF64 +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + let (first, second) = self.load(Ordering::Relaxed); + + formatter + .debug_tuple("AtomicTwoF64") + .field(&first) + .field(&second) + .finish() + } +} + +fn two_f64_to_u128((first, second): (f64, f64)) -> u128 +{ + let mut bytes = [0u8; size_of::<u128>()]; + + bytes.copy_from_slice([first.to_ne_bytes(), second.to_ne_bytes()].as_flattened()); + + u128::from_ne_bytes(bytes) +} + +fn u128_to_two_f64(src: u128) -> (f64, f64) +{ + let [first_bytes, second_bytes] = unsafe { + transmute::<[u8; size_of::<u128>()], [[u8; size_of::<f64>()]; 2]>( + src.to_ne_bytes(), + ) + }; + + ( + f64::from_ne_bytes(first_bytes), + f64::from_ne_bytes(second_bytes), + ) +} + macro_rules! try_option { ($expr: expr) => { match $expr { diff --git a/engine/src/windowing.rs b/engine/src/windowing.rs index ea5a692..096a23f 100644 --- a/engine/src/windowing.rs +++ b/engine/src/windowing.rs @@ -1,3 +1,4 @@ +use std::hint::cold_path; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Weak}; use std::thread::{Builder as ThreadBuilder, JoinHandle as ThreadJoinHandle}; @@ -33,8 +34,7 @@ use crate::ecs::sole::Single; use crate::ecs::system::observer::Observe; use crate::ecs::uid::Uid; use crate::ecs::{declare_entity, Query, Sole}; -use crate::util::MapVec; -use crate::vector::Vec2; +use crate::util::{AtomicTwoF64, MapVec}; use crate::windowing::keyboard::{Key, KeyState, Keyboard, UnknownKeyCodeError}; use crate::windowing::mouse::{ Button as MouseButton, @@ -130,7 +130,18 @@ fn update_stuff( { keyboard.make_key_states_previous(); mouse_buttons.make_states_previous(); - mouse.curr_tick_position_delta = Vec2::default(); + + mouse.curr_tick_position_delta = context + .shared_state + .relative_mouse_pos_delta + .swap((0.0, 0.0), Ordering::Relaxed) + .into(); + + mouse.position = context + .shared_state + .absolute_mouse_pos + .load(Ordering::Relaxed) + .into(); let Context { ref message_from_app_receiver, @@ -235,12 +246,6 @@ fn update_stuff( MessageFromApp::KeyboardKeyStateChanged(key, key_state) => { keyboard.set_key_state(key, key_state); } - MessageFromApp::MouseMovedTo { position } => { - mouse.position = position; - } - MessageFromApp::MouseMoved { position_delta } => { - mouse.curr_tick_position_delta += position_delta; - } MessageFromApp::MouseButtonStateChanged(mouse_button, mouse_button_state) => { mouse_buttons.set(mouse_button, mouse_button_state); } @@ -300,9 +305,9 @@ fn handle_window_removed( pub struct Context { _thread: ThreadJoinHandle<()>, - is_dropped: Arc<AtomicBool>, message_from_app_receiver: ChannelReceiver<MessageFromApp>, message_to_app_sender: ChannelSender<MessageToApp>, + shared_state: Arc<SharedState>, display_handle: Option<OwnedDisplayHandle>, windows: MapVec<WindowId, (Arc<winit::window::Window>, Uid)>, } @@ -362,10 +367,6 @@ impl Default for Context { fn default() -> Self { - let is_dropped = Arc::new(AtomicBool::new(false)); - - let is_dropped_b = is_dropped.clone(); - let (message_from_app_sender, message_from_app_receiver) = bounded_channel::<MessageFromApp>(MESSAGE_FROM_APP_CHANNEL_CAP); @@ -374,6 +375,9 @@ impl Default for Context let (message_to_app_sender, message_to_app_receiver) = bounded_channel::<MessageToApp>(MESSAGE_TO_APP_CHANNEL_CAP); + let shared_state = Arc::new(SharedState::default()); + let shared_state_b = shared_state.clone(); + Self { _thread: ThreadBuilder::new() .name("windowing".to_string()) @@ -382,9 +386,8 @@ impl Default for Context message_from_app_sender, message_from_app_receiver: message_from_app_receiver_b, message_to_app_receiver, - is_dropped: is_dropped_b, + shared_state: shared_state_b, windows: MapVec::default(), - focused_window_id: None, }; let event_loop = match create_event_loop() { @@ -402,9 +405,9 @@ impl Default for Context } }) .expect("Failed to create windowing thread"), - is_dropped, message_from_app_receiver, message_to_app_sender, + shared_state, display_handle: None, windows: MapVec::default(), } @@ -415,7 +418,7 @@ impl Drop for Context { fn drop(&mut self) { - self.is_dropped.store(true, Ordering::Relaxed); + self.shared_state.is_dropped.store(true, Ordering::Relaxed); } } @@ -450,14 +453,6 @@ enum MessageFromApp WindowCloseRequested(WindowId), WindowScaleFactorChanged(WindowId, f64), KeyboardKeyStateChanged(Key, KeyState), - MouseMovedTo - { - position: Vec2<f64>, - }, - MouseMoved - { - position_delta: Vec2<f64>, - }, MouseButtonStateChanged(MouseButton, MouseButtonState), } @@ -469,14 +464,33 @@ enum MessageToApp } #[derive(Debug)] +struct SharedState +{ + relative_mouse_pos_delta: AtomicTwoF64, + absolute_mouse_pos: AtomicTwoF64, + is_dropped: AtomicBool, +} + +impl Default for SharedState +{ + fn default() -> Self + { + Self { + relative_mouse_pos_delta: AtomicTwoF64::new((0.0, 0.0)), + absolute_mouse_pos: AtomicTwoF64::new((0.0, 0.0)), + is_dropped: AtomicBool::new(false), + } + } +} + +#[derive(Debug)] struct App { message_from_app_sender: ChannelSender<MessageFromApp>, message_from_app_receiver: ChannelReceiver<MessageFromApp>, message_to_app_receiver: ChannelReceiver<MessageToApp>, - is_dropped: Arc<AtomicBool>, + shared_state: Arc<SharedState>, windows: MapVec<WindowId, (Weak<WinitWindow>, WindowSettings)>, - focused_window_id: Option<WindowId>, } impl App @@ -567,7 +581,7 @@ impl ApplicationHandler for App )); } StartCause::Poll => { - if self.is_dropped.load(Ordering::Relaxed) { + if self.shared_state.is_dropped.load(Ordering::Relaxed) { event_loop.exit(); return; } @@ -642,9 +656,38 @@ impl ApplicationHandler for App )); } WindowEvent::CursorMoved { device_id: _, position } => { - self.send_message(MessageFromApp::MouseMovedTo { - position: Vec2 { x: position.x, y: position.y }, - }); + self.shared_state + .absolute_mouse_pos + .store((position.x, position.y), Ordering::Relaxed); + + let Some((window, window_settings)) = + self.windows.get(&WindowId::from_inner(window_id)) + else { + cold_path(); + return; + }; + + if window_settings.cursor_grab_mode != CursorGrabMode::Locked { + return; + } + + let Some(window) = window.upgrade() else { + cold_path(); + return; + }; + + let window_size = window.inner_size(); + + if let Err(err) = window.set_cursor_position(PhysicalPosition { + x: window_size.width / 2, + y: window_size.height / 2, + }) { + cold_path(); + tracing::error!( + window_id=?window_id, + "Failed to lock cursor position: {err}" + ); + }; } WindowEvent::MouseInput { device_id: _, state, button } => { self.send_message(MessageFromApp::MouseButtonStateChanged( @@ -652,13 +695,6 @@ impl ApplicationHandler for App state.into(), )); } - WindowEvent::Focused(is_focused) => { - if is_focused { - self.focused_window_id = Some(WindowId::from_inner(window_id)); - } else { - self.focused_window_id = None; - } - } WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: _ } => { self.send_message(MessageFromApp::WindowScaleFactorChanged( WindowId::from_inner(window_id), @@ -679,44 +715,29 @@ impl ApplicationHandler for App { match device_event { DeviceEvent::MouseMotion { delta } => { - self.send_message(MessageFromApp::MouseMoved { - position_delta: Vec2 { x: delta.0, y: delta.1 }, - }); - - let Some(focused_window_id) = self.focused_window_id else { - return; - }; - - let Some((focused_window, focused_window_settings)) = - self.windows.get(&focused_window_id) - else { - tracing::error!( - window_id=?focused_window_id, - "Focused window not found" - ); - return; - }; - - if focused_window_settings.cursor_grab_mode != CursorGrabMode::Locked { - return; + let curr_mouse_pos_delta = self + .shared_state + .relative_mouse_pos_delta + .load(Ordering::Relaxed); + + if self + .shared_state + .relative_mouse_pos_delta + .compare_exchange( + curr_mouse_pos_delta, + ( + curr_mouse_pos_delta.0 + delta.0, + curr_mouse_pos_delta.1 + delta.1, + ), + Ordering::Relaxed, + Ordering::Relaxed, + ) + .is_err() + { + self.shared_state + .relative_mouse_pos_delta + .store(delta, Ordering::Relaxed); } - - // TODO: This might need to be optimized - let Some(focused_window) = focused_window.upgrade() else { - return; - }; - - let focused_window_size = focused_window.inner_size(); - - if let Err(err) = focused_window.set_cursor_position(PhysicalPosition { - x: focused_window_size.width / 2, - y: focused_window_size.height / 2, - }) { - tracing::error!( - window_id=?focused_window_id, - "Failed to set cursor position in focused window: {err}" - ); - }; } _ => {} } |
