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}; use crossbeam_channel::{ bounded as bounded_channel, Receiver as ChannelReceiver, Sender as ChannelSender, TrySendError, }; use raw_window_handle::{DisplayHandle, HandleError, HasDisplayHandle, WindowHandle}; use winit::application::ApplicationHandler; use winit::dpi::PhysicalPosition; use winit::error::EventLoopError; use winit::event::{DeviceEvent, DeviceId, StartCause, WindowEvent}; use winit::event_loop::{ ActiveEventLoop, ControlFlow as EventLoopControlFlow, EventLoop, OwnedDisplayHandle, }; use winit::keyboard::PhysicalKey; use winit::window::{Window as WinitWindow, WindowId as WinitWindowId}; use crate::data_types::dimens::Dimens; use crate::ecs::actions::Actions; use crate::ecs::component::Component; use crate::ecs::entity::obtainer::Obtainer as EntityObtainer; use crate::ecs::event::component::{Added, Changed, EventMatchExt, Removed}; use crate::ecs::pair::{ChildOf, Pair}; use crate::ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; 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::{AtomicTwoF64, MapVec}; use crate::windowing::keyboard::{Key, KeyState, Keyboard, UnknownKeyCodeError}; use crate::windowing::mouse::{ Button as MouseButton, ButtonState as MouseButtonState, Buttons as MouseButtons, Mouse, }; use crate::windowing::window::{ Closed as WindowClosed, CreationAttributes as WindowCreationAttributes, CreationReady as WindowCreationReady, CursorGrabMode, Id as WindowId, Window, }; pub mod keyboard; pub mod mouse; pub mod window; const MESSAGE_FROM_APP_CHANNEL_CAP: usize = 512; const MESSAGE_TO_APP_CHANNEL_CAP: usize = 16; // Increase if more messages are added declare_entity!( pub PHASE, ( Phase, Pair::builder() .relation::() .target_id(*UPDATE_PHASE) .build() ) ); #[derive(Debug, Default)] #[non_exhaustive] pub struct Extension {} impl crate::ecs::extension::Extension for Extension { fn collect(self, mut collector: crate::ecs::extension::Collector<'_>) { collector.add_sole(Context::default()).ok(); collector.add_sole(Keyboard::default()).ok(); collector.add_sole(Mouse::default()).ok(); collector.add_sole(MouseButtons::default()).ok(); collector.add_declared_entity(&PHASE); collector.add_system(*PHASE, update_stuff); collector.add_observer(handle_window_changed); collector.add_observer(handle_window_removed); collector.add_observer(handle_window_creation_ready); } } fn handle_window_creation_ready( observe: Observe>, context: Single, ) { for evt_match in &observe { let Some(ent) = evt_match.try_get_entity() else { unreachable!(); }; if ent.has_component(Window::id()) || ent.has_component(WindowClosed::id()) { continue; } let Some(window_creation_attrs) = ent.get::() else { unreachable!(); }; context.try_send_message_to_app(MessageToApp::CreateWindow( ent.uid(), window_creation_attrs.clone(), )); } } #[tracing::instrument(skip_all)] fn update_stuff( mut context: Single, mut keyboard: Single, mut mouse: Single, mut mouse_buttons: Single, mut actions: Actions, entity_obtainer: EntityObtainer, ) { keyboard.make_key_states_previous(); mouse_buttons.make_states_previous(); 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, ref mut display_handle, ref mut windows, .. } = *context; for message in message_from_app_receiver.try_iter() { tracing::trace!(message=?message, "Received message from app"); match message { MessageFromApp::Init(new_display_handle) => { *display_handle = Some(new_display_handle); } MessageFromApp::WindowCreated( window_ent_id, winit_window, window_creation_attrs, ) => { actions.add_components( window_ent_id, (Window::new(&winit_window, &window_creation_attrs),), ); actions.remove_comps::<(WindowCreationReady,)>(window_ent_id); let window_id = WindowId::from_inner(winit_window.id()); windows.insert(window_id, (winit_window, window_ent_id)); tracing::info!( window_id = ?window_id, window_title = window_creation_attrs.title(), "Window creation completed" ); } MessageFromApp::WindowResized(window_id, new_window_size) => { tracing::debug!( window_id = ?window_id, "Received window resized message" ); let Some(window_ent_id) = windows.get(&window_id).map(|(_, ent_id)| ent_id) else { tracing::error!( wid = ?window_id, "Window does not exist in windowing context" ); continue; }; let Some(window_ent) = entity_obtainer.get_entity(*window_ent_id) else { continue; }; let Some(mut window) = window_ent.get_mut::() else { continue; }; window.set_inner_size(new_window_size); window.set_changed(); } MessageFromApp::WindowCloseRequested(window_id) => { let Some(window_ent_id) = windows.get(&window_id).map(|(_, ent_id)| ent_id) else { tracing::error!( wid = ?window_id, "Window does not exist in windowing context" ); continue; }; actions.remove_comps::<(Window,)>(*window_ent_id); } MessageFromApp::WindowScaleFactorChanged(window_id, scale_factor) => { let Some(window_ent_id) = windows.get(&window_id).map(|(_, ent_id)| ent_id) else { tracing::error!( wid = ?window_id, "Window does not exist in windowing context" ); continue; }; let Some(window_ent) = entity_obtainer.get_entity(*window_ent_id) else { continue; }; let Some(mut window) = window_ent.get_mut::() else { continue; }; window.set_scale_factor(scale_factor); window.set_changed(); } MessageFromApp::KeyboardKeyStateChanged(key, key_state) => { keyboard.set_key_state(key, key_state); } MessageFromApp::MouseButtonStateChanged(mouse_button, mouse_button_state) => { mouse_buttons.set(mouse_button, mouse_button_state); } } } } fn handle_window_changed( observe: Observe<'_, Pair>, context: Single, ) { for evt_match in &observe { let window_ent_id = evt_match.entity_id(); let window = evt_match.get_ent_target_comp(); let Some((winit_window, _)) = context.windows.get(&window.wid()) else { tracing::error!( wid = ?window.wid(), entity_id = %window_ent_id, "Window does not exist in windowing context", ); continue; }; window.apply(winit_window); context.try_send_message_to_app(MessageToApp::SetWindowCursorGrabMode( window.wid(), window.cursor_grab_mode, )); } } fn handle_window_removed( observe: Observe>, window_query: Query<(&Window,)>, mut context: Single, mut actions: Actions, ) { for evt_match in &observe { let window = evt_match.get_ent_target_comp(); context.windows.remove(window.wid()); actions.add_components(evt_match.entity_id(), (WindowClosed,)); } if window_query.iter().count() == 1 { actions.stop(); } } #[derive(Debug, Sole)] pub struct Context { _thread: ThreadJoinHandle<()>, message_from_app_receiver: ChannelReceiver, message_to_app_sender: ChannelSender, shared_state: Arc, display_handle: Option, windows: MapVec, Uid)>, } impl Context { pub fn display_handle(&self) -> Option> { let display_handle = self.display_handle.as_ref()?; display_handle.display_handle().ok() } /// Returns the specified window as a window handle, if it exists. /// /// # Safety /// The Window handle must only be used with thread safe APIs. pub unsafe fn get_window_as_handle( &self, window_id: &WindowId, ) -> Option, HandleError>> { self.windows.get(window_id).map(|(winit_window, _)| { #[cfg(windows)] { use winit::platform::windows::WindowExtWindows; // SAFETY: I don't care unsafe { winit_window.window_handle_any_thread() } } #[cfg(not(windows))] { use raw_window_handle::HasWindowHandle; winit_window.window_handle() } }) } fn try_send_message_to_app(&self, message: MessageToApp) { if let Err(err) = self.message_to_app_sender.try_send(message) { let error = match &err { TrySendError::Full(_) => TrySendError::Full(()), TrySendError::Disconnected(_) => TrySendError::Disconnected(()), }; let message = err.into_inner(); tracing::error!("Failed to send message {error}: {message:?}"); } } } impl Default for Context { fn default() -> Self { let (message_from_app_sender, message_from_app_receiver) = bounded_channel::(MESSAGE_FROM_APP_CHANNEL_CAP); let message_from_app_receiver_b = message_from_app_receiver.clone(); let (message_to_app_sender, message_to_app_receiver) = bounded_channel::(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()) .spawn(move || { let mut app = App { message_from_app_sender, message_from_app_receiver: message_from_app_receiver_b, message_to_app_receiver, shared_state: shared_state_b, windows: MapVec::default(), }; let event_loop = match create_event_loop() { Ok(event_loop) => event_loop, Err(err) => { tracing::error!("Failed to create event loop: {err}"); return; } }; event_loop.set_control_flow(EventLoopControlFlow::Poll); if let Err(err) = event_loop.run_app(&mut app) { tracing::error!("Event loop error occurred: {err}"); } }) .expect("Failed to create windowing thread"), message_from_app_receiver, message_to_app_sender, shared_state, display_handle: None, windows: MapVec::default(), } } } impl Drop for Context { fn drop(&mut self) { self.shared_state.is_dropped.store(true, Ordering::Relaxed); } } fn create_event_loop() -> Result, EventLoopError> { let mut event_loop_builder = EventLoop::builder(); #[cfg(any(x11_platform, wayland_platform))] winit::platform::x11::EventLoopBuilderExtX11::with_any_thread( &mut event_loop_builder, true, ); #[cfg(windows)] winit::platform::windows::EventLoopBuilderExtWindows::with_any_thread( &mut event_loop_builder, true, ); #[cfg(not(any(x11_platform, wayland_platform, windows)))] compile_error!("Unsupported platform"); event_loop_builder.build() } #[derive(Debug)] enum MessageFromApp { Init(OwnedDisplayHandle), WindowCreated(Uid, Arc, WindowCreationAttributes), WindowResized(WindowId, Dimens), WindowCloseRequested(WindowId), WindowScaleFactorChanged(WindowId, f64), KeyboardKeyStateChanged(Key, KeyState), MouseButtonStateChanged(MouseButton, MouseButtonState), } #[derive(Debug)] enum MessageToApp { CreateWindow(Uid, WindowCreationAttributes), SetWindowCursorGrabMode(WindowId, CursorGrabMode), } #[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, message_from_app_receiver: ChannelReceiver, message_to_app_receiver: ChannelReceiver, shared_state: Arc, windows: MapVec, WindowSettings)>, } impl App { #[tracing::instrument(skip_all)] fn handle_received_messages(&mut self, event_loop: &ActiveEventLoop) { for message in self.message_to_app_receiver.try_iter() { match message { MessageToApp::CreateWindow(window_ent_id, window_creation_attrs) => { tracing::info!( "Creating window with title {}", window_creation_attrs.title() ); let winit_window = Arc::new( match event_loop .create_window(window_creation_attrs.clone().into_inner()) { Ok(window) => window, Err(err) => { tracing::error!("Failed to create window: {err}"); continue; } }, ); self.windows.insert( WindowId::from_inner(winit_window.id()), (Arc::downgrade(&winit_window), WindowSettings::default()), ); self.send_message(MessageFromApp::WindowCreated( window_ent_id, winit_window, window_creation_attrs, )); } MessageToApp::SetWindowCursorGrabMode(window_id, cursor_grab_mode) => { let Some((_, window_settings)) = self.windows.get_mut(&window_id) else { tracing::warn!( window_id=?window_id, "Cannot set window cursor grab mode. Window not found" ); continue; }; window_settings.cursor_grab_mode = cursor_grab_mode; } } } } #[tracing::instrument(skip_all)] fn send_message(&self, message: MessageFromApp) { if self.message_from_app_sender.is_full() { tracing::warn!( "Message channel is full! Dropping oldest message from channel" ); self.message_from_app_receiver.try_recv().ok(); } if let Err(err) = self.message_from_app_sender.try_send(message) { let error = match &err { TrySendError::Full(_) => TrySendError::Full(()), TrySendError::Disconnected(_) => TrySendError::Disconnected(()), }; let message = err.into_inner(); tracing::error!("Failed to send message {error}: {message:?}"); } } } impl ApplicationHandler for App { fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) { match cause { StartCause::Init => { self.send_message(MessageFromApp::Init( event_loop.owned_display_handle(), )); } StartCause::Poll => { if self.shared_state.is_dropped.load(Ordering::Relaxed) { event_loop.exit(); return; } self.handle_received_messages(event_loop); } _ => {} } } fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { for (window, _) in self.windows.values() { let Some(window) = window.upgrade() else { continue; }; window.request_redraw(); } } fn resumed(&mut self, _event_loop: &ActiveEventLoop) {} #[tracing::instrument(skip_all)] fn window_event( &mut self, _event_loop: &ActiveEventLoop, window_id: WinitWindowId, event: WindowEvent, ) { match event { WindowEvent::Resized(new_window_size) => { self.send_message(MessageFromApp::WindowResized( WindowId::from_inner(window_id), new_window_size.into(), )); } WindowEvent::CloseRequested => { self.send_message(MessageFromApp::WindowCloseRequested( WindowId::from_inner(window_id), )); } WindowEvent::KeyboardInput { device_id: _, event: keyboard_event, is_synthetic: _, } => { if keyboard_event.repeat { return; } let key_code = match keyboard_event.physical_key { PhysicalKey::Code(key_code) => key_code, PhysicalKey::Unidentified(native_key) => { tracing::warn!("Ignoring unidentified key: {native_key:?}"); return; } }; let key: Key = match key_code.try_into() { Ok(key) => key, Err(UnknownKeyCodeError) => { tracing::warn!("Ignoring key with unknown key code {key_code:?}"); return; } }; self.send_message(MessageFromApp::KeyboardKeyStateChanged( key, keyboard_event.state.into(), )); } WindowEvent::CursorMoved { device_id: _, position } => { 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( button.into(), state.into(), )); } WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: _ } => { self.send_message(MessageFromApp::WindowScaleFactorChanged( WindowId::from_inner(window_id), scale_factor, )); } _ => {} } } #[tracing::instrument(skip_all)] fn device_event( &mut self, _event_loop: &ActiveEventLoop, _device_id: DeviceId, device_event: DeviceEvent, ) { match device_event { DeviceEvent::MouseMotion { delta } => { 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); } } _ => {} } } } #[derive(Debug, Default)] struct WindowSettings { cursor_grab_mode: CursorGrabMode, }