summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2026-05-25 19:49:15 +0200
committerHampusM <hampus@hampusmat.com>2026-05-25 19:49:15 +0200
commitf8b7c0cfc73eaeb7ef8dfb3151f93879c97b220a (patch)
tree5ff078443f5d64e4f99fd0487b3a2e72ef961540
parent8a41f5aeb3ac143d731928d1c343cd9338190f0a (diff)
fix(engine): prevent dropped cursor motion input on Windows
-rw-r--r--.cargo/config.toml2
-rw-r--r--Cargo.lock7
-rw-r--r--engine/Cargo.toml6
-rw-r--r--engine/src/data_types/vector.rs8
-rw-r--r--engine/src/util.rs96
-rw-r--r--engine/src/windowing.rs173
6 files changed, 216 insertions, 76 deletions
diff --git a/.cargo/config.toml b/.cargo/config.toml
index c82825c..af130b7 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -7,3 +7,5 @@ SLANG_USE_PREBUILT = "1"
[target.'cfg(unix)']
rustflags = ["-Clink-arg=-Wl,-rpath,$ORIGIN"]
+[target.'cfg(target_arch = "x86_64")']
+rustflags = ["-C", "target-cpu=x86-64-v2"]
diff --git a/Cargo.lock b/Cargo.lock
index 16cb0e3..b79a049 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -574,6 +574,7 @@ dependencies = [
"opengl-bindings",
"parking_lot",
"paste",
+ "portable-atomic",
"raw-window-handle",
"safer-ffi",
"seq-macro",
@@ -1677,6 +1678,12 @@ dependencies = [
]
[[package]]
+name = "portable-atomic"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
+
+[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
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}"
- );
- };
}
_ => {}
}