use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Weak}; use std::thread::{spawn, JoinHandle as ThreadJoinHandle}; use crossbeam_channel::{ bounded as bounded_channel, Receiver as ChannelReceiver, Sender as ChannelSender, TrySendError, }; use ecs::actions::Actions; use ecs::component::Component; use ecs::entity::obtainer::Obtainer as EntityObtainer; use ecs::event::component::{Added, Changed, Removed}; use ecs::pair::{ChildOf, Pair}; use ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; use ecs::sole::Single; use ecs::system::observer::Observe; use ecs::uid::Uid; use ecs::{declare_entity, Query, Sole}; 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::util::MapVec; use crate::vector::Vec2; use crate::windowing::keyboard::{Key, KeyState, Keyboard, UnknownKeyCodeError}; use crate::windowing::mouse::{ Button as MouseButton, ButtonState as MouseButtonState, Buttons as MouseButtons, Motion as MouseMotion, }; 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 = 128; 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 ecs::extension::Extension for Extension { fn collect(self, mut collector: ecs::extension::Collector<'_>) { collector.add_sole(Context::default()).ok(); collector.add_sole(Keyboard::default()).ok(); collector.add_sole(MouseMotion::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.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_motion: Single, mut mouse_buttons: Single, mut actions: Actions, entity_obtainer: EntityObtainer, ) { keyboard.make_key_states_previous(); mouse_buttons.make_states_previous(); mouse_motion.position_delta = Vec2::default(); let Context { ref message_from_app_receiver, ref mut display_handle, ref mut windows, .. } = *context; for message in message_from_app_receiver.try_iter() { 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); tracing::debug!("Added window component to window entity"); windows.insert( WindowId::from_inner(winit_window.id()), (winit_window, window_ent_id), ); } MessageFromApp::WindowResized(window_id, new_window_size) => { let Some(window_ent_id) = windows.get(&window_id).map(|(_, ent_id)| ent_id) else { 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::KeyboardKeyStateChanged(key, key_state) => { keyboard.set_key_state(key, key_state); } MessageFromApp::MouseMoved { position_delta } => { mouse_motion.position_delta += position_delta; } 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.id(); let window = evt_match.get_changed_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_removed_comp(); context.windows.remove(window.wid()); actions.add_components(evt_match.id(), (WindowClosed,)); } if window_query.iter().count() == 1 { actions.stop(); } } #[derive(Debug, Sole)] pub struct Context { _thread: ThreadJoinHandle<()>, is_dropped: Arc, message_from_app_receiver: ChannelReceiver, message_to_app_sender: ChannelSender, 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 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::(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); Self { _thread: spawn(move || { let mut app = App { message_from_app_sender, message_from_app_receiver: message_from_app_receiver_b, message_to_app_receiver, is_dropped: is_dropped_b, windows: MapVec::default(), focused_window_id: None, }; 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}"); } }), is_dropped, message_from_app_receiver, message_to_app_sender, display_handle: None, windows: MapVec::default(), } } } impl Drop for Context { fn drop(&mut self) { self.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), KeyboardKeyStateChanged(Key, KeyState), MouseMoved { position_delta: Vec2, }, MouseButtonStateChanged(MouseButton, MouseButtonState), } #[derive(Debug)] enum MessageToApp { CreateWindow(Uid, WindowCreationAttributes), SetWindowCursorGrabMode(WindowId, CursorGrabMode), } #[derive(Debug)] struct App { message_from_app_sender: ChannelSender, message_from_app_receiver: ChannelReceiver, message_to_app_receiver: ChannelReceiver, is_dropped: Arc, windows: MapVec, WindowSettings)>, focused_window_id: Option, } impl App { 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; } }, ); tracing::info!("Created window has title {}", winit_window.title()); 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; } } } } 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.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) {} 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::MouseInput { device_id: _, state, button } => { self.send_message(MessageFromApp::MouseButtonStateChanged( button.into(), 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; } } _ => {} } } fn device_event( &mut self, _event_loop: &ActiveEventLoop, _device_id: DeviceId, device_event: DeviceEvent, ) { 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; } // 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}" ); }; } _ => {} } } } #[derive(Debug, Default)] struct WindowSettings { cursor_grab_mode: CursorGrabMode, }