|
| 1 | +use crate::android_rumble_pack::AndroidRumblePack; |
| 2 | +use crate::{android_game_window::AndroidGameWindow, event_translation::translate_input_event}; |
| 3 | +use android::activity::{MainEvent as AndroidMainEvent, PollEvent as AndroidPollEvent}; |
| 4 | +use android_activity::input::Axis as AndroidAxis; |
| 5 | +use android_activity::InputStatus as AndroidInputStatus; |
| 6 | +use framework_core::runtime::*; |
| 7 | +use math::UVec2; |
| 8 | +use std::future::Future; |
| 9 | +use std::time::Instant; |
| 10 | + |
| 11 | +pub struct AndroidGameLoop {} |
| 12 | + |
| 13 | +impl GameWindowLoop for AndroidGameLoop { |
| 14 | + type PlatformApp = crate::AndroidPlatformApp; |
| 15 | + |
| 16 | + fn run( |
| 17 | + window_config: GameWindowConfig<Self::PlatformApp>, |
| 18 | + runtime_params: GameRuntimeCoreParams, |
| 19 | + ) -> Box<dyn Future<Output = anyhow::Result<()>>> { |
| 20 | + Box::new(run(window_config, runtime_params)) |
| 21 | + } |
| 22 | +} |
| 23 | + |
| 24 | +async fn run( |
| 25 | + window_config: GameWindowConfig<crate::AndroidPlatformApp>, |
| 26 | + runtime_params: GameRuntimeCoreParams, |
| 27 | +) -> anyhow::Result<()> { |
| 28 | + let app = window_config.platform_app.clone().unwrap(); |
| 29 | + |
| 30 | + // wait until we're ready to create a window |
| 31 | + loop { |
| 32 | + let mut terminated = false; |
| 33 | + let mut ready = false; |
| 34 | + |
| 35 | + app.poll_events(None, |event| { |
| 36 | + if let AndroidPollEvent::Main(main_event) = event { |
| 37 | + match main_event { |
| 38 | + AndroidMainEvent::InitWindow { .. } => ready = true, |
| 39 | + AndroidMainEvent::Destroy => terminated = true, |
| 40 | + _ => {} |
| 41 | + } |
| 42 | + } |
| 43 | + }); |
| 44 | + |
| 45 | + if terminated { |
| 46 | + return Ok(()); |
| 47 | + } |
| 48 | + |
| 49 | + if ready { |
| 50 | + break; |
| 51 | + } |
| 52 | + } |
| 53 | + |
| 54 | + // only AndroidAxis::X and AndroidAxis::Y are enabled by default |
| 55 | + app.enable_motion_axis(AndroidAxis::Ltrigger); |
| 56 | + app.enable_motion_axis(AndroidAxis::Rtrigger); |
| 57 | + // Dpad |
| 58 | + app.enable_motion_axis(AndroidAxis::HatX); |
| 59 | + app.enable_motion_axis(AndroidAxis::HatY); |
| 60 | + // Right thumbstick |
| 61 | + app.enable_motion_axis(AndroidAxis::Z); |
| 62 | + app.enable_motion_axis(AndroidAxis::Rz); |
| 63 | + |
| 64 | + // init the window and runtime |
| 65 | + let window = AndroidGameWindow::new(window_config).await?; |
| 66 | + let mut game_runtime = GameRuntimeCore::new(Box::new(window), runtime_params)?; |
| 67 | + |
| 68 | + let mut combining_accent = None; |
| 69 | + |
| 70 | + game_runtime.push_event(GameWindowEvent::InputEvent( |
| 71 | + InputEvent::ControllerConnected { |
| 72 | + controller_id: 0, |
| 73 | + rumble_pack: Box::new(AndroidRumblePack), |
| 74 | + }, |
| 75 | + )); |
| 76 | + |
| 77 | + while !game_runtime.quitting() { |
| 78 | + let wake_instant = game_runtime.target_wake_instant(); |
| 79 | + let mut timeout = wake_instant.saturating_duration_since(Instant::now()); |
| 80 | + |
| 81 | + // tick check |
| 82 | + if timeout.is_zero() { |
| 83 | + game_runtime.tick(); |
| 84 | + |
| 85 | + // update timeout |
| 86 | + let wake_instant = game_runtime.target_wake_instant(); |
| 87 | + timeout = wake_instant.saturating_duration_since(Instant::now()); |
| 88 | + } |
| 89 | + |
| 90 | + let mut terminated = false; |
| 91 | + |
| 92 | + app.poll_events(Some(timeout), |event| { |
| 93 | + if let AndroidPollEvent::Main(main_event) = event { |
| 94 | + match main_event { |
| 95 | + AndroidMainEvent::InitWindow { .. } => { |
| 96 | + game_runtime.push_event(GameWindowEvent::Resumed); |
| 97 | + } |
| 98 | + AndroidMainEvent::WindowResized { .. } => { |
| 99 | + if let Some(window) = app.native_window() { |
| 100 | + let size = UVec2::new(window.width() as _, window.height() as _); |
| 101 | + game_runtime.push_event(GameWindowEvent::Resized(size)); |
| 102 | + } |
| 103 | + } |
| 104 | + AndroidMainEvent::Destroy => { |
| 105 | + terminated = true; |
| 106 | + } |
| 107 | + _ => {} |
| 108 | + } |
| 109 | + } |
| 110 | + }); |
| 111 | + |
| 112 | + if terminated { |
| 113 | + // exit before processing events |
| 114 | + break; |
| 115 | + } |
| 116 | + |
| 117 | + if let Ok(mut iter) = app.input_events_iter() { |
| 118 | + let mut translated_events = Vec::new(); |
| 119 | + |
| 120 | + loop { |
| 121 | + let event_procesed = iter.next(|event| { |
| 122 | + let mut status = AndroidInputStatus::Unhandled; |
| 123 | + |
| 124 | + translate_input_event( |
| 125 | + &app, |
| 126 | + game_runtime.game_io().window(), |
| 127 | + &mut combining_accent, |
| 128 | + event, |
| 129 | + |translated| { |
| 130 | + translated_events.push(translated); |
| 131 | + status = AndroidInputStatus::Handled; |
| 132 | + }, |
| 133 | + ); |
| 134 | + |
| 135 | + for event in translated_events.drain(..) { |
| 136 | + game_runtime.push_event(event.into()); |
| 137 | + } |
| 138 | + |
| 139 | + status |
| 140 | + }); |
| 141 | + |
| 142 | + if !event_procesed { |
| 143 | + break; |
| 144 | + } |
| 145 | + } |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + Ok(()) |
| 150 | +} |
0 commit comments