Skip to content

Commit 1839cf2

Browse files
committed
Add AndroidGameLoop
1 parent c2a492c commit 1839cf2

14 files changed

Lines changed: 828 additions & 4 deletions

File tree

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ license = "Zlib"
77
[workspace]
88
members = [
99
"crates/android",
10+
"crates/android_game_loop",
1011
"crates/cfg_macros",
1112
"crates/framework_core",
1213
"crates/input",
@@ -20,6 +21,7 @@ members = [
2021

2122
[workspace.dependencies]
2223
log = { version = "0.4", features = ["std"] }
24+
android-activity = "0.6"
2325

2426
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
2527
[dependencies]
@@ -30,6 +32,7 @@ logging = { path = "crates/logging" }
3032
math = { path = "crates/math" }
3133
winit_game_loop = { path = "crates/winit_game_loop", optional = true }
3234
sdl2_game_loop = { path = "crates/sdl2_game_loop", optional = true }
35+
android_game_loop = { path = "crates/android_game_loop", optional = true }
3336

3437
# web dependencies
3538
[target.'cfg(target_arch = "wasm32")'.dependencies]
@@ -41,6 +44,7 @@ default_image_formats = ["framework_core/default_image_formats"]
4144
all_image_formats = ["framework_core/all_image_formats"]
4245
sdl2 = ["dep:sdl2_game_loop"]
4346
winit = ["dep:winit_game_loop"]
47+
android = ["dep:android_game_loop"]
4448

4549
# dependencies for examples
4650
[dev-dependencies]

crates/android/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ license = "Zlib"
66

77
[dependencies]
88
jni = "0.21"
9-
android-activity = "0.6"
9+
android-activity = { workspace = true }
1010
log = { workspace = true }
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "android_game_loop"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "Zlib"
6+
7+
[dependencies]
8+
android = { path = "../android" }
9+
input = { path = "../input" }
10+
math = { path = "../math" }
11+
framework_core = { path = "../framework_core" }
12+
android-activity = { workspace = true, features = ["native-activity"] }
13+
ndk = { version = "0.9", features = ["rwh_06"] }
14+
log = { workspace = true }
15+
anyhow = "1"
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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

Comments
 (0)