diff options
Diffstat (limited to 'engine/src/windowing.rs')
| -rw-r--r-- | engine/src/windowing.rs | 669 | 
1 files changed, 669 insertions, 0 deletions
diff --git a/engine/src/windowing.rs b/engine/src/windowing.rs new file mode 100644 index 0000000..69adae9 --- /dev/null +++ b/engine/src/windowing.rs @@ -0,0 +1,669 @@ +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::<ChildOf>() +            .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<Pair<Added, WindowCreationReady>>, +    context: Single<Context>, +) +{ +    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::<WindowCreationAttributes>() 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<Context>, +    mut keyboard: Single<Keyboard>, +    mut mouse_motion: Single<MouseMotion>, +    mut mouse_buttons: Single<MouseButtons>, +    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::<Window>() 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<Changed, Window>>, +    context: Single<Context>, +) +{ +    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<Pair<Removed, Window>>, +    window_query: Query<(&Window,)>, +    mut context: Single<Context>, +    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<AtomicBool>, +    message_from_app_receiver: ChannelReceiver<MessageFromApp>, +    message_to_app_sender: ChannelSender<MessageToApp>, +    display_handle: Option<OwnedDisplayHandle>, +    windows: MapVec<WindowId, (Arc<winit::window::Window>, Uid)>, +} + +impl Context +{ +    pub fn display_handle(&self) -> Option<DisplayHandle<'_>> +    { +        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<Result<WindowHandle<'_>, 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::<MessageFromApp>(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::<MessageToApp>(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<EventLoop<()>, 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<WinitWindow>, WindowCreationAttributes), +    WindowResized(WindowId, Dimens<u32>), +    WindowCloseRequested(WindowId), +    KeyboardKeyStateChanged(Key, KeyState), +    MouseMoved +    { +        position_delta: Vec2<f64>, +    }, +    MouseButtonStateChanged(MouseButton, MouseButtonState), +} + +#[derive(Debug)] +enum MessageToApp +{ +    CreateWindow(Uid, WindowCreationAttributes), +    SetWindowCursorGrabMode(WindowId, CursorGrabMode), +} + +#[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>, +    windows: MapVec<WindowId, (Weak<WinitWindow>, WindowSettings)>, +    focused_window_id: Option<WindowId>, +} + +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, +}  | 
