diff --git a/Cargo.toml b/Cargo.toml index ce8b35d1095..faaf51acba2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,7 @@ simple_logger = "2.1.0" [target.'cfg(target_os = "android")'.dependencies] # Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995 ndk = { git = "https://github.com/rust-windowing/android-ndk-rs", rev = "7e33384" } -ndk-glue = { git = "https://github.com/rust-windowing/android-ndk-rs", rev = "7e33384" } +android-activity = { git = "https://github.com/rib/android-activity" } [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] objc = "0.2.7" diff --git a/src/event_loop.rs b/src/event_loop.rs index 890e4e560ea..78bed692aa7 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -96,7 +96,10 @@ impl EventLoopBuilder { /// `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`. /// If it is not set, winit will try to connect to a Wayland connection, and if that fails, /// will fall back on X11. If this variable is set with any other value, winit will panic. + /// - **Android:** Must be configured with an `AndroidApp` from `android_main()` by calling + /// [`.with_android_app(app)`] before calling `.build()`. /// + /// [`.with_android_app(app)`]: crate::platform::android::EventLoopBuilderExtAndroid::with_android_app /// [`platform`]: crate::platform #[inline] pub fn build(&mut self) -> EventLoop { diff --git a/src/platform/android.rs b/src/platform/android.rs index af15f3efd98..0f39e028c34 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -1,11 +1,13 @@ #![cfg(any(target_os = "android"))] use crate::{ - event_loop::{EventLoop, EventLoopWindowTarget}, + event_loop::{EventLoop, EventLoopBuilder, EventLoopWindowTarget}, window::{Window, WindowBuilder}, }; use ndk::configuration::Configuration; -use ndk_glue::Rect; + +use android_activity as ndk_glue; +use ndk_glue::{Rect, AndroidApp}; /// Additional methods on [`EventLoop`] that are specific to Android. pub trait EventLoopExtAndroid {} @@ -38,3 +40,17 @@ impl EventLoopWindowTargetExtAndroid for EventLoopWindowTarget {} pub trait WindowBuilderExtAndroid {} impl WindowBuilderExtAndroid for WindowBuilder {} + +pub trait EventLoopBuilderExtAndroid { + /// Associates the `AndroidApp` that was passed to `android_main()` with the event loop + /// + /// This must be called on Android since the `AndroidApp` is not global state. + fn with_android_app(&mut self, app: AndroidApp) -> &mut Self; +} + +impl EventLoopBuilderExtAndroid for EventLoopBuilder { + fn with_android_app(&mut self, app: AndroidApp) -> &mut Self { + self.platform_specific.android_app = Some(app); + self + } +} \ No newline at end of file diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index e37bd1f5fe9..017450ea06e 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -2,52 +2,32 @@ use std::{ collections::VecDeque, - sync::{Arc, Mutex, RwLock}, - time::{Duration, Instant}, + sync::{Arc, RwLock, mpsc::{Sender, TryRecvError}, mpsc::Receiver, atomic::{AtomicBool, Ordering}}, + time::{Duration, Instant}, hash::Hash, }; use ndk::{ configuration::Configuration, - event::{InputEvent, KeyAction, Keycode, MotionAction}, - looper::{ForeignLooper, Poll, ThreadLooper}, }; -use ndk_glue::{Event, Rect}; use once_cell::sync::Lazy; use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; +use android_activity as ndk_glue; +use ndk_glue::{Rect, MainEvent, AndroidApp, AndroidAppWaker}; +use ndk_glue::input::{InputEvent, KeyAction, Keycode, MotionAction}; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error, - event::{self, VirtualKeyCode}, - event_loop::{self, ControlFlow}, + event::{self, VirtualKeyCode, StartCause}, + event_loop::{self, ControlFlow, EventLoopWindowTarget as RootELW}, monitor, window::{self, CursorGrabMode}, }; + static CONFIG: Lazy> = Lazy::new(|| { - RwLock::new(Configuration::from_asset_manager( - #[allow(deprecated)] // TODO: rust-windowing/winit#2196 - &ndk_glue::native_activity().asset_manager(), - )) + RwLock::new(Configuration::new()) }); -// If this is `Some()` a `Poll::Wake` is considered an `EventSource::Internal` with the event -// contained in the `Option`. The event is moved outside of the `Option` replacing it with a -// `None`. -// -// This allows us to inject event into the event loop without going through `ndk-glue` and -// calling unsafe function that should only be called by Android. -static INTERNAL_EVENT: Lazy>> = Lazy::new(|| RwLock::new(None)); - -enum InternalEvent { - RedrawRequested, -} - -enum EventSource { - Callback, - InputQueue, - User, - Internal(InternalEvent), -} fn ndk_keycode_to_virtualkeycode(keycode: Keycode) -> Option { match keycode { @@ -213,357 +193,590 @@ fn ndk_keycode_to_virtualkeycode(keycode: Keycode) -> Option Option { - match poll { - Poll::Event { ident, .. } => match ident { - ndk_glue::NDK_GLUE_LOOPER_EVENT_PIPE_IDENT => Some(EventSource::Callback), - ndk_glue::NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT => Some(EventSource::InputQueue), - _ => unreachable!(), - }, - Poll::Timeout => None, - Poll::Wake => Some( - INTERNAL_EVENT - .write() - .unwrap() - .take() - .map_or(EventSource::User, EventSource::Internal), - ), - Poll::Callback => unreachable!(), +struct PeekableReceiver { + recv: Receiver, + first: Option, +} + +impl PeekableReceiver { + pub fn from_recv(recv: Receiver) -> Self { + Self { recv, first: None } + } + pub fn has_incoming(&mut self) -> bool { + if self.first.is_some() { + return true; + } + match self.recv.try_recv() { + Ok(v) => { + self.first = Some(v); + return true; + } + Err(TryRecvError::Empty) => return false, + Err(TryRecvError::Disconnected) => { + warn!("Channel was disconnected when checking incoming"); + return false; + } + } + } + pub fn try_recv(&mut self) -> Result { + if let Some(first) = self.first.take() { + return Ok(first); + } + self.recv.try_recv() + } +} + +#[derive(Clone)] +struct SharedFlagSetter { + flag: Arc +} +impl SharedFlagSetter { + pub fn set(&self) -> bool { + match self.flag.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed) { + Ok(_) => true, + Err(_) => false + } + } +} + +struct SharedFlag { + flag: Arc +} + +// Used for queuing redraws from arbitrary threads. We don't care how many +// times a redraw is requested (so don't actually need to queue any data, +// we just need to know at the start of a main loop iteration if a redraw +// was queued and be able to read and clear the state atomically) +impl SharedFlag { + pub fn new() -> Self { + Self { flag: Arc::new(AtomicBool::new(false)) } + } + pub fn setter(&self) -> SharedFlagSetter { + SharedFlagSetter { flag: self.flag.clone() } + } + pub fn get_and_reset(&self) -> bool { + self.flag.swap(false, std::sync::atomic::Ordering::AcqRel) + } +} + +#[derive(Clone)] +pub struct RedrawRequester { + flag: SharedFlagSetter, + waker: AndroidAppWaker +} + +impl RedrawRequester { + fn new(flag: &SharedFlag, waker: AndroidAppWaker) -> Self { + RedrawRequester { flag: flag.setter(), waker } + } + pub fn request_redraw(&self) { + if self.flag.set() { + // Only explicitly try to wake up the main loop when the flag + // value changes + self.waker.wake(); + } } } pub struct EventLoop { + android_app: AndroidApp, window_target: event_loop::EventLoopWindowTarget, - user_queue: Arc>>, - first_event: Option, - start_cause: event::StartCause, - looper: ThreadLooper, + redraw_flag: SharedFlag, + user_events_tx: Sender, + user_events_rx: PeekableReceiver, //must wake looper whenever something gets sent running: bool, } -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub(crate) struct PlatformSpecificEventLoopAttributes {} +#[derive(Default, Debug, Clone, PartialEq)] +pub(crate) struct PlatformSpecificEventLoopAttributes { + pub(crate) android_app: Option +} -macro_rules! call_event_handler { - ( $event_handler:expr, $window_target:expr, $cf:expr, $event:expr ) => {{ - if let ControlFlow::ExitWithCode(code) = $cf { - $event_handler($event, $window_target, &mut ControlFlow::ExitWithCode(code)); - } else { - $event_handler($event, $window_target, &mut $cf); - } - }}; +fn sticky_exit_callback( + evt: event::Event<'_, T>, + target: &RootELW, + control_flow: &mut ControlFlow, + callback: &mut F, +) where + F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), +{ + // make ControlFlow::ExitWithCode sticky by providing a dummy + // control flow reference if it is already ExitWithCode. + if let ControlFlow::ExitWithCode(code) = *control_flow { + callback(evt, target, &mut ControlFlow::ExitWithCode(code)) + } else { + callback(evt, target, control_flow) + } +} + +struct IterationResult { + deadline: Option, + timeout: Option, + wait_start: Instant, } impl EventLoop { - pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Self { + pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self { + + let (user_events_tx, user_events_rx) = std::sync::mpsc::channel(); + + let android_app = attributes.android_app.as_ref().expect("An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on Android"); + let redraw_flag = SharedFlag::new(); + Self { + android_app: android_app.clone(), window_target: event_loop::EventLoopWindowTarget { p: EventLoopWindowTarget { + app: android_app.clone(), + redraw_requester: RedrawRequester::new(&redraw_flag, android_app.create_waker()), _marker: std::marker::PhantomData, }, _marker: std::marker::PhantomData, }, - user_queue: Default::default(), - first_event: None, - start_cause: event::StartCause::Init, - looper: ThreadLooper::for_thread().unwrap(), + redraw_flag, + user_events_tx, + user_events_rx: PeekableReceiver::from_recv(user_events_rx), running: false, } } - pub fn run(mut self, event_handler: F) -> ! + fn single_iteration<'a, F>( + &mut self, + //this: &mut EventLoop, + control_flow: &mut ControlFlow, + main_event: Option>, + pending_redraw: &mut bool, + cause: &mut StartCause, + callback: &mut F, + ) -> IterationResult where - F: 'static - + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), { - let exit_code = self.run_return(event_handler); - ::std::process::exit(exit_code); - } + trace!("Mainloop iteration"); - pub fn run_return(&mut self, mut event_handler: F) -> i32 - where - F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), - { - let mut control_flow = ControlFlow::default(); + sticky_exit_callback( + event::Event::NewEvents(*cause), + self.window_target(), + control_flow, + callback + ); - 'event_loop: loop { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::NewEvents(self.start_cause) - ); - - let mut redraw = false; - let mut resized = false; - - match self.first_event.take() { - Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() { - Event::WindowCreated => { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::Resumed - ); - } - Event::WindowResized => resized = true, - Event::WindowRedrawNeeded => redraw = true, - Event::WindowDestroyed => { - call_event_handler!( - event_handler, + //let mut redraw = false; + let mut resized = false; + + if let Some(event) = main_event { + + trace!("Handling main event {:?}", event); + + match event { + MainEvent::InitWindow { .. } => { + sticky_exit_callback( + event::Event::Resumed, + self.window_target(), + control_flow, + callback + ); + }, + MainEvent::TerminateWindow { .. } => { + sticky_exit_callback( + event::Event::Suspended, + self.window_target(), + control_flow, + callback + ); + }, + MainEvent::WindowResized { .. } => resized = true, + MainEvent::RedrawNeeded { .. } => *pending_redraw = true, + //{ + // self.redraw_flag.setter().set(); + //}, + MainEvent::ContentRectChanged => { + warn!("TODO: find a way to notify application of content rect change"); + }, + MainEvent::GainedFocus => { + sticky_exit_callback( + event::Event::WindowEvent { + window_id: window::WindowId(WindowId), + event: event::WindowEvent::Focused(true), + }, + self.window_target(), + control_flow, + callback + ); + }, + MainEvent::LostFocus => { + sticky_exit_callback( + event::Event::WindowEvent { + window_id: window::WindowId(WindowId), + event: event::WindowEvent::Focused(false), + }, + self.window_target(), + control_flow, + callback + ); + }, + MainEvent::ConfigChanged => { + let monitor = MonitorHandle::new(self.android_app.clone()); + let old_scale_factor = monitor.scale_factor(); + *CONFIG.write().unwrap() = self.android_app.config(); + let scale_factor = monitor.scale_factor(); + if (scale_factor - old_scale_factor).abs() < f64::EPSILON { + let mut size = MonitorHandle::new(self.android_app.clone()).size(); + let event = event::Event::WindowEvent { + window_id: window::WindowId(WindowId), + event: event::WindowEvent::ScaleFactorChanged { + new_inner_size: &mut size, + scale_factor, + }, + }; + sticky_exit_callback( + event, self.window_target(), control_flow, - event::Event::Suspended + callback ); } - Event::Pause => self.running = false, - Event::Resume => self.running = true, - Event::ConfigChanged => { - #[allow(deprecated)] // TODO: rust-windowing/winit#2196 - let am = ndk_glue::native_activity().asset_manager(); - let config = Configuration::from_asset_manager(&am); - let old_scale_factor = MonitorHandle.scale_factor(); - *CONFIG.write().unwrap() = config; - let scale_factor = MonitorHandle.scale_factor(); - if (scale_factor - old_scale_factor).abs() < f64::EPSILON { - let mut size = MonitorHandle.size(); + }, + MainEvent::LowMemory => { + // XXX: how to forward this state to applications? + // It seems like ideally winit should support lifecycle and + // low-memory events, especially for mobile platforms. + warn!("TODO: handle Android LowMemory notification"); + }, + MainEvent::Start => { + // XXX: how to forward this state to applications? + warn!("TODO: forward onStart notification to application"); + }, + MainEvent::Resume { .. } => { + debug!("App Resumed - is running"); + self.running = true; + }, + MainEvent::SaveState { .. } => { + // XXX: how to forward this state to applications? + // XXX: also how do we expose state restoration to apps? + warn!("TODO: forward saveState notification to application"); + }, + MainEvent::Pause => { + debug!("App Paused - stopped running"); + self.running = false; + }, + MainEvent::Stop => { + // XXX: how to forward this state to applications? + warn!("TODO: forward onStop notification to application"); + } + MainEvent::Destroy => { + // XXX: maybe exit mainloop to drop things before being + // killed by the OS? + warn!("TODO: forward onDestroy notification to application"); + }, + MainEvent::InsetsChanged { .. }=> { + // XXX: how to forward this state to applications? + warn!("TODO: handle Android InsetsChanged notification"); + }, + unknown => { + trace!("Unknown MainEvent {unknown:?} (ignored)"); + }, + + } + } else { + trace!("No main event to handle"); + } + + // Process input events + // + // Note that GameActivity doesn't integrate input events with the mainloop, + // they are double buffered and it's assumed that the application will be + // rendering continuously. + + self.android_app.input_events(|event| { + match event { + InputEvent::MotionEvent(motion_event) => { + let window_id = window::WindowId(WindowId); + let device_id = event::DeviceId(DeviceId); + + let phase = match motion_event.action() { + MotionAction::Down | MotionAction::PointerDown => { + Some(event::TouchPhase::Started) + } + MotionAction::Up | MotionAction::PointerUp => { + Some(event::TouchPhase::Ended) + } + MotionAction::Move => Some(event::TouchPhase::Moved), + MotionAction::Cancel => { + Some(event::TouchPhase::Cancelled) + } + _ => { + None // TODO mouse events + } + }; + if let Some(phase) = phase { + let pointers: Box< + dyn Iterator>, + > = match phase { + event::TouchPhase::Started + | event::TouchPhase::Ended => { + Box::new( + std::iter::once(motion_event.pointer_at_index( + motion_event.pointer_index(), + )) + ) + }, + event::TouchPhase::Moved + | event::TouchPhase::Cancelled => { + Box::new(motion_event.pointers()) + } + }; + + for pointer in pointers { + let location = PhysicalPosition { + x: pointer.x() as _, + y: pointer.y() as _, + }; + trace!("Input event {device_id:?}, {phase:?}, loc={location:?}, pointer={pointer:?}"); let event = event::Event::WindowEvent { - window_id: window::WindowId(WindowId), - event: event::WindowEvent::ScaleFactorChanged { - new_inner_size: &mut size, - scale_factor, - }, + window_id, + event: event::WindowEvent::Touch( + event::Touch { + device_id, + phase, + location, + id: pointer.pointer_id() as u64, + force: None, + }, + ), }; - call_event_handler!( - event_handler, + sticky_exit_callback( + event, self.window_target(), control_flow, - event + callback ); } } - Event::WindowHasFocus => { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::WindowEvent { - window_id: window::WindowId(WindowId), - event: event::WindowEvent::Focused(true), - } - ); - } - Event::WindowLostFocus => { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::WindowEvent { - window_id: window::WindowId(WindowId), - event: event::WindowEvent::Focused(false), - } - ); - } - _ => {} - }, - Some(EventSource::InputQueue) => { - if let Some(input_queue) = ndk_glue::input_queue().as_ref() { - while let Some(event) = input_queue.get_event() { - if let Some(event) = input_queue.pre_dispatch(event) { - let mut handled = true; - let window_id = window::WindowId(WindowId); - let device_id = event::DeviceId(DeviceId); - match &event { - InputEvent::MotionEvent(motion_event) => { - let phase = match motion_event.action() { - MotionAction::Down | MotionAction::PointerDown => { - Some(event::TouchPhase::Started) - } - MotionAction::Up | MotionAction::PointerUp => { - Some(event::TouchPhase::Ended) - } - MotionAction::Move => Some(event::TouchPhase::Moved), - MotionAction::Cancel => { - Some(event::TouchPhase::Cancelled) - } - _ => { - handled = false; - None // TODO mouse events - } - }; - if let Some(phase) = phase { - let pointers: Box< - dyn Iterator>, - > = match phase { - event::TouchPhase::Started - | event::TouchPhase::Ended => Box::new( - std::iter::once(motion_event.pointer_at_index( - motion_event.pointer_index(), - )), - ), - event::TouchPhase::Moved - | event::TouchPhase::Cancelled => { - Box::new(motion_event.pointers()) - } - }; - - for pointer in pointers { - let location = PhysicalPosition { - x: pointer.x() as _, - y: pointer.y() as _, - }; - let event = event::Event::WindowEvent { - window_id, - event: event::WindowEvent::Touch( - event::Touch { - device_id, - phase, - location, - id: pointer.pointer_id() as u64, - force: None, - }, - ), - }; - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event - ); - } - } - } - InputEvent::KeyEvent(key) => { - let state = match key.action() { - KeyAction::Down => event::ElementState::Pressed, - KeyAction::Up => event::ElementState::Released, - _ => event::ElementState::Released, - }; - #[allow(deprecated)] - let event = event::Event::WindowEvent { - window_id, - event: event::WindowEvent::KeyboardInput { - device_id, - input: event::KeyboardInput { - scancode: key.scan_code() as u32, - state, - virtual_keycode: ndk_keycode_to_virtualkeycode( - key.key_code(), - ), - modifiers: event::ModifiersState::default(), - }, - is_synthetic: false, - }, - }; - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event - ); - } - }; - input_queue.finish_event(event, handled); - } - } - } } - Some(EventSource::User) => { - let mut user_queue = self.user_queue.lock().unwrap(); - while let Some(event) = user_queue.pop_front() { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::UserEvent(event) - ); - } + InputEvent::KeyEvent(key) => { + let device_id = event::DeviceId(DeviceId); + + let state = match key.action() { + KeyAction::Down => event::ElementState::Pressed, + KeyAction::Up => event::ElementState::Released, + _ => event::ElementState::Released, + }; + #[allow(deprecated)] + let event = event::Event::WindowEvent { + window_id: window::WindowId(WindowId), + event: event::WindowEvent::KeyboardInput { + device_id, + input: event::KeyboardInput { + scancode: key.scan_code() as u32, + state, + virtual_keycode: ndk_keycode_to_virtualkeycode( + key.key_code(), + ), + modifiers: event::ModifiersState::default(), + }, + is_synthetic: false, + }, + }; + sticky_exit_callback( + event, + self.window_target(), + control_flow, + callback + ); + } + _ => { + warn!("Unknown android_activity input event {event:?}") } - Some(EventSource::Internal(internal)) => match internal { - InternalEvent::RedrawRequested => redraw = true, - }, - None => {} } + }); + + + // Empty the user event buffer + { + while let Ok(event) = self.user_events_rx.try_recv() { + sticky_exit_callback( + crate::event::Event::UserEvent(event), + &self.window_target(), + control_flow, + callback, + ); + } + } - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::MainEventsCleared - ); - - if resized && self.running { - let size = MonitorHandle.size(); + sticky_exit_callback( + event::Event::MainEventsCleared, + self.window_target(), + control_flow, + callback + ); + + if self.running { + if resized { + let size = if let Some(native_window) = self.android_app.native_window().as_ref() { + let width = native_window.width() as _; + let height = native_window.height() as _; + PhysicalSize::new(width, height) + } else { + PhysicalSize::new(0, 0) + }; let event = event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::Resized(size), }; - call_event_handler!(event_handler, self.window_target(), control_flow, event); + sticky_exit_callback(event, self.window_target(), control_flow, callback); } - if redraw && self.running { + *pending_redraw |= self.redraw_flag.get_and_reset(); + if *pending_redraw { let event = event::Event::RedrawRequested(window::WindowId(WindowId)); - call_event_handler!(event_handler, self.window_target(), control_flow, event); + sticky_exit_callback(event, self.window_target(), control_flow, callback); } + } - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::RedrawEventsCleared - ); - - match control_flow { - ControlFlow::ExitWithCode(code) => { - self.first_event = poll( - self.looper - .poll_once_timeout(Duration::from_millis(0)) - .unwrap(), - ); - self.start_cause = event::StartCause::WaitCancelled { - start: Instant::now(), - requested_resume: None, - }; - break 'event_loop code; - } - ControlFlow::Poll => { - self.first_event = poll( - self.looper - .poll_all_timeout(Duration::from_millis(0)) - .unwrap(), - ); - self.start_cause = event::StartCause::Poll; - } - ControlFlow::Wait => { - self.first_event = poll(self.looper.poll_all().unwrap()); - self.start_cause = event::StartCause::WaitCancelled { - start: Instant::now(), - requested_resume: None, + sticky_exit_callback( + event::Event::RedrawEventsCleared, + self.window_target(), + control_flow, + callback + ); + + let start = Instant::now(); + let (deadline, timeout); + + match control_flow { + ControlFlow::ExitWithCode(_) => { + deadline = None; + timeout = None; + } + ControlFlow::Poll => { + *cause = StartCause::Poll; + deadline = None; + timeout = Some(Duration::from_millis(0)); + } + ControlFlow::Wait => { + *cause = StartCause::WaitCancelled { + start, + requested_resume: None, + }; + deadline = None; + timeout = None; + } + ControlFlow::WaitUntil(wait_deadline) => { + *cause = StartCause::ResumeTimeReached { + start, + requested_resume: *wait_deadline, + }; + timeout = if *wait_deadline > start { + Some(*wait_deadline - start) + } else { + Some(Duration::from_millis(0)) + }; + deadline = Some(*wait_deadline); + } + } + + + return IterationResult { + wait_start: start, + deadline, + timeout, + }; + } + + pub fn run(mut self, event_handler: F) -> ! + where + F: 'static + + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + { + let exit_code = self.run_return(event_handler); + ::std::process::exit(exit_code); + } + + pub fn run_return(&mut self, mut callback: F) -> i32 + where + F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), + { + let mut control_flow = ControlFlow::default(); + let mut cause = StartCause::Init; + let mut pending_redraw = false; + + // run the initial loop iteration + let mut iter_result = self.single_iteration(&mut control_flow, None, &mut pending_redraw, &mut cause, &mut callback); + + let exit_code = loop { + if let ControlFlow::ExitWithCode(code) = control_flow { + break code; + } + + let mut timeout = iter_result.timeout; + + // If we already have work to do then we don't want to block on the next poll... + pending_redraw |= self.redraw_flag.get_and_reset(); + if self.running && (pending_redraw || self.user_events_rx.has_incoming()) { + timeout = Some(Duration::from_millis(0)) + } + + let app = self.android_app.clone(); // Don't borrow self as part of poll expression + app.poll_events(timeout, |poll_event| { + + let mut main_event = None; + + match poll_event { + ndk_glue::PollEvent::Wake => { + // In the X11 backend it's noted that too many false-positive wake ups + // would cause the event loop to run continuously. They handle this by re-checking + // for pending events (assuming they cover all valid reasons for a wake up). + // + // For now, user_events and redraw_requests are the only reasons to expect + // a wake up here so we can ignore the wake up if there are no events/requests. + // We also ignore wake ups while suspended. + pending_redraw |= self.redraw_flag.get_and_reset(); + if !self.running || (!pending_redraw && !self.user_events_rx.has_incoming()) { + return; + } + } + ndk_glue::PollEvent::Timeout => { } + ndk_glue::PollEvent::Main(event) => { + main_event = Some(event); } + ndk_glue::PollEvent::FdEvent { .. } => { + // XXX: How can we support applications registering their own file + // descriptors with the AndroidApp looper? We need some way to + // let apps also hook into this event processing somehow. + warn!("Unknown custom FdEvent") + } + ndk_glue::PollEvent::Error => { + panic!("Event polling error"); + } + unknown_event => { + warn!("Unknown poll event {unknown_event:?} (ignored)"); + }, } - ControlFlow::WaitUntil(instant) => { - let start = Instant::now(); - let duration = if instant <= start { - Duration::default() - } else { - instant - start + + let wait_cancelled = iter_result + .deadline + .map_or(false, |deadline| Instant::now() < deadline); + + if wait_cancelled { + cause = StartCause::WaitCancelled { + start: iter_result.wait_start, + requested_resume: iter_result.deadline, }; - self.first_event = poll(self.looper.poll_all_timeout(duration).unwrap()); - self.start_cause = if self.first_event.is_some() { - event::StartCause::WaitCancelled { - start, - requested_resume: Some(instant), - } - } else { - event::StartCause::ResumeTimeReached { - start, - requested_resume: instant, - } - } } - } - } + + iter_result = self.single_iteration(&mut control_flow, main_event, &mut pending_redraw, &mut cause, &mut callback); + }); + }; + + sticky_exit_callback( + event::Event::LoopDestroyed, + self.window_target(), + &mut control_flow, + &mut callback + ); + + exit_code } pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { @@ -572,48 +785,50 @@ impl EventLoop { pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { - queue: self.user_queue.clone(), - looper: ForeignLooper::for_thread().expect("called from event loop thread"), + user_events_tx: self.user_events_tx.clone(), + waker: self.android_app.create_waker() } } } pub struct EventLoopProxy { - queue: Arc>>, - looper: ForeignLooper, + user_events_tx: Sender, + waker: AndroidAppWaker, } -impl EventLoopProxy { - pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed> { - self.queue.lock().unwrap().push_back(event); - self.looper.wake(); - Ok(()) - } -} - -impl Clone for EventLoopProxy { +impl Clone for EventLoopProxy { fn clone(&self) -> Self { EventLoopProxy { - queue: self.queue.clone(), - looper: self.looper.clone(), + user_events_tx: self.user_events_tx.clone(), + waker: self.waker.clone(), } } } +impl EventLoopProxy { + pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed> { + self.user_events_tx.send(event).map_err(|err| event_loop::EventLoopClosed(err.0))?; + self.waker.wake(); + Ok(()) + } +} + pub struct EventLoopWindowTarget { + app: AndroidApp, + redraw_requester: RedrawRequester, _marker: std::marker::PhantomData, } impl EventLoopWindowTarget { pub fn primary_monitor(&self) -> Option { Some(monitor::MonitorHandle { - inner: MonitorHandle, + inner: MonitorHandle::new(self.app.clone()), }) } pub fn available_monitors(&self) -> VecDeque { let mut v = VecDeque::with_capacity(1); - v.push_back(MonitorHandle); + v.push_back(MonitorHandle::new(self.app.clone())); v } } @@ -651,16 +866,23 @@ impl DeviceId { #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct PlatformSpecificWindowBuilderAttributes; -pub struct Window; +pub struct Window { + app: AndroidApp, + redraw_requester: RedrawRequester +} impl Window { pub(crate) fn new( - _el: &EventLoopWindowTarget, + el: &EventLoopWindowTarget, _window_attrs: window::WindowAttributes, _: PlatformSpecificWindowBuilderAttributes, ) -> Result { // FIXME this ignores requested window attributes - Ok(Self) + + Ok(Self { + app: el.app.clone(), + redraw_requester: el.redraw_requester.clone() + }) } pub fn id(&self) -> WindowId { @@ -669,29 +891,28 @@ impl Window { pub fn primary_monitor(&self) -> Option { Some(monitor::MonitorHandle { - inner: MonitorHandle, + inner: MonitorHandle::new(self.app.clone()), }) } pub fn available_monitors(&self) -> VecDeque { let mut v = VecDeque::with_capacity(1); - v.push_back(MonitorHandle); + v.push_back(MonitorHandle::new(self.app.clone())); v } pub fn current_monitor(&self) -> Option { Some(monitor::MonitorHandle { - inner: MonitorHandle, + inner: MonitorHandle::new(self.app.clone()), }) } pub fn scale_factor(&self) -> f64 { - MonitorHandle.scale_factor() + MonitorHandle::new(self.app.clone()).scale_factor() } pub fn request_redraw(&self) { - *INTERNAL_EVENT.write().unwrap() = Some(InternalEvent::RedrawRequested); - ForeignLooper::for_thread().unwrap().wake(); + self.redraw_requester.request_redraw() } pub fn inner_position(&self) -> Result, error::NotSupportedError> { @@ -715,7 +936,7 @@ impl Window { } pub fn outer_size(&self) -> PhysicalSize { - MonitorHandle.size() + MonitorHandle::new(self.app.clone()).size() } pub fn set_min_inner_size(&self, _: Option) {} @@ -799,7 +1020,7 @@ impl Window { } pub fn raw_window_handle(&self) -> RawWindowHandle { - if let Some(native_window) = ndk_glue::native_window().as_ref() { + if let Some(native_window) = self.app.native_window().as_ref() { native_window.raw_window_handle() } else { panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events."); @@ -807,11 +1028,11 @@ impl Window { } pub fn config(&self) -> Configuration { - CONFIG.read().unwrap().clone() + self.app.config() } pub fn content_rect(&self) -> Rect { - ndk_glue::content_rect() + self.app.content_rect() } } @@ -827,16 +1048,33 @@ impl Display for OsError { pub(crate) use crate::icon::NoIcon as PlatformIcon; -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct MonitorHandle; +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct MonitorHandle { + app: AndroidApp, +} + +impl PartialOrd for MonitorHandle { + fn partial_cmp(&self, _other: &Self) -> Option { + Some(std::cmp::Ordering::Equal) + } +} +impl Ord for MonitorHandle { + fn cmp(&self, _other: &Self) -> std::cmp::Ordering { + std::cmp::Ordering::Equal + } +} impl MonitorHandle { + pub(crate) fn new(app: AndroidApp) -> Self { + Self { app } + } + pub fn name(&self) -> Option { Some("Android Device".to_owned()) } pub fn size(&self) -> PhysicalSize { - if let Some(native_window) = ndk_glue::native_window().as_ref() { + if let Some(native_window) = self.app.native_window().as_ref() { let width = native_window.width() as _; let height = native_window.height() as _; PhysicalSize::new(width, height)