1use std::collections::HashSet;
5use web_time::Duration;
6
7use anyhow::Result;
8pub use repame_sim::{Sim, SimTime};
9pub use repame_sprite::{Camera2d, FrameInput, PickEvent, SpriteInstance};
10use repose_core::View;
11pub use repose_core::input::{GamepadEvent, GamepadId};
12use repose_platform::gamepad::{GamepadBackend, create_backend};
13
14mod audio;
15mod levels;
16mod pads;
17mod shortcuts;
18mod staging;
19pub use audio::drain_cues;
20pub use levels::apply_scheduler_levels;
21pub use pads::{PadBank, PadBridge, TRIGGER_HELD};
22pub use shortcuts::{
23 SharedEdges, ShortcutEdges, game_shortcut_map, install_game_shortcuts, install_handler_into,
24 install_map, shared_edges, shortcut_handler, take as take_shortcut_edges,
25};
26pub use staging::Staging;
27
28pub trait ShellHooks {
30 fn frame_input(&mut self, sim: &Sim) -> FrameInput;
32 fn on_frame(&mut self, _sim: &mut Sim, _dt: Duration) {}
34}
35
36pub struct RetainedRoot {
37 key: String,
38 generation: u64,
39}
40
41impl RetainedRoot {
42 pub fn new() -> Self {
43 Self {
44 key: format!(
45 "repame.retained_root.{}",
46 repose_core::unique_component_id()
47 ),
48 generation: 0,
49 }
50 }
51
52 pub fn call<F>(&mut self, sched: &mut repose_core::runtime::Scheduler, build: F) -> View
53 where
54 F: FnOnce(&mut repose_core::runtime::Scheduler) -> View,
55 {
56 let key = &self.key;
57 let epoch = repose_core::timer::frame_count();
58 let generation = self.generation;
59 repose_core::scope!(key, sched, [epoch, generation], { build(sched) })
60 }
61
62 pub fn clear(&mut self) {
63 self.generation = self.generation.wrapping_add(1);
64 }
65}
66
67impl Default for RetainedRoot {
68 fn default() -> Self {
69 Self::new()
70 }
71}
72
73#[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
80pub fn run_desktop(
81 title: &str,
82 size: (u32, u32),
83 mut root: impl FnMut(
84 &mut repose_core::runtime::Scheduler,
85 &repose_core::RenderContext,
86 ) -> repose_core::View
87 + 'static,
88) -> Result<()> {
89 let config = repose_app::AppConfig {
90 window_title: title.to_string(),
91 window_size: size,
92 ..Default::default()
93 };
94 let mut retained = RetainedRoot::new();
95 repose_platform::run_desktop_app_with_config(
96 move |sched, ctx| retained.call(sched, |sched| root(sched, ctx)),
97 config,
98 )?;
99 Ok(())
100}
101
102#[cfg(target_arch = "wasm32")]
104pub fn run_web(
105 mut root: impl FnMut(
106 &mut repose_core::runtime::Scheduler,
107 &repose_core::RenderContext,
108 ) -> repose_core::View
109 + 'static,
110) -> Result<(), wasm_bindgen::JsValue> {
111 let mut options = repose_platform::web::WebOptions::new(None);
112 options.set_prevent_default(true);
113 let mut retained = RetainedRoot::new();
114 repose_platform::web::run_web_app(
115 move |sched, ctx| retained.call(sched, |sched| root(sched, ctx)),
116 options,
117 )
118}
119
120#[cfg(target_os = "android")]
122pub fn run_android(
123 app: winit::platform::android::activity::AndroidApp,
124 mut root: impl FnMut(
125 &mut repose_core::runtime::Scheduler,
126 &repose_core::RenderContext,
127 ) -> repose_core::View
128 + 'static,
129) -> Result<()> {
130 let mut retained = RetainedRoot::new();
131 repose_platform::android::run_android_app_with_options(
132 app,
133 move |sched, ctx| retained.call(sched, |sched| root(sched, ctx)),
134 repose_app::AndroidOptions::default(),
135 )
136}
137
138pub struct GamepadPoller {
141 backend: Option<Box<dyn GamepadBackend>>,
142 connected: HashSet<u32>,
143}
144
145impl GamepadPoller {
146 pub fn new() -> Self {
147 Self {
148 backend: create_backend().map(|b| Box::new(b) as Box<dyn GamepadBackend>),
149 connected: HashSet::new(),
151 }
152 }
153
154 pub fn poll(&mut self) -> Vec<GamepadEvent> {
157 let Some(backend) = &mut self.backend else {
158 return Vec::new();
159 };
160 let events = backend.poll();
161 for ev in &events {
162 match ev {
163 GamepadEvent::Connected { id, .. } => {
164 self.connected.insert(id.0);
165 }
166 GamepadEvent::Disconnected { id } => {
167 self.connected.remove(&id.0);
168 }
169 GamepadEvent::Button { id, .. } | GamepadEvent::Axis { id, .. } => {
170 self.connected.insert(id.0);
171 }
172 }
173 }
174 events
175 }
176
177 pub fn connected_ids(&self) -> Vec<GamepadId> {
178 let mut ids: Vec<GamepadId> = self.connected.iter().copied().map(GamepadId).collect();
179 ids.sort_by_key(|id| id.0);
180 ids
181 }
182
183 pub fn connected_count(&self) -> usize {
184 self.connected.len()
185 }
186}
187
188impl Default for GamepadPoller {
189 fn default() -> Self {
190 Self::new()
191 }
192}