shuyi_playdate/
backend.rs1use alloc::boxed::Box;
4use alloc::string::String;
5use alloc::vec::Vec;
6
7use shuyi_assets::keymap::ActionSet;
8use shuyi_assets::target::TargetProfile;
9use shuyi_platform::audio::player::PlayerDevice;
10use shuyi_platform::{
11 Audio, DisplayInfo, Frame, Millis, Platform, Storage, SystemEvent, TextInput,
12};
13use shuyi_ui::frame_cap::{FrameCap, Pacing};
14use shuyi_ui::input::pad::PadKind;
15use shuyi_ui::input::state::Device;
16use shuyi_ui::perf::{MemoryReading, PerfClock};
17use shuyi_ui::{Game, GameData, PlatformFacts, RawInputEvent};
18
19use crate::audio::{self, Speaker};
20use crate::display::{Screen, Showing};
21use crate::input::{PadInput, menu_button, menu_press};
22use crate::report::Measured;
23use crate::sdk::{PACK, Sdk, SdkEvent};
24use crate::storage::SdkStorage;
25
26const PROFILE: &str = include_str!("../../../targets/playdate.ron");
29
30pub const MENU_TITLE: &str = "system_menu.";
33
34fn profile() -> TargetProfile {
36 TargetProfile::from_source("targets/playdate.ron", "playdate", PROFILE)
37 .unwrap_or_else(|errors| panic!("targets/playdate.ron: {errors:?}"))
38}
39
40pub fn read_pack(sdk: &impl Sdk) -> Result<&'static [u8], String> {
43 let bytes = sdk.read_bundled(PACK)?;
44 Ok(Vec::leak(bytes))
45}
46
47#[derive(Debug)]
49pub struct Playdate<S: Sdk> {
50 sdk: S,
51 facts: PlatformFacts,
52 rate: u16,
54 menu_slots: u8,
56 pad: PadInput,
57 docked: Option<bool>,
59 chosen: Vec<u8>,
61 screen: Screen,
62 audio: PlayerDevice<Speaker<S>>,
64 events: Vec<SystemEvent>,
66 holds: u8,
69 serial: u32,
71}
72
73impl<S: Sdk> Playdate<S> {
74 pub fn new(sdk: S) -> Self {
76 let profile = profile();
77 let rate = profile.frame_rate.target;
78 sdk.set_refresh_rate(rate);
79 let audio = PlayerDevice::new(Speaker::new(sdk.clone()));
80 Self {
81 sdk,
82 facts: PlatformFacts::from_profile(&profile)
83 .with_device(Device::Pad(PadKind::Playdate)),
84 rate,
85 menu_slots: profile.input.system_menu_slots,
86 pad: PadInput::new(shuyi_assets::keymap::CrankDef::default()),
87 docked: None,
88 chosen: Vec::new(),
89 screen: Screen::default(),
90 audio,
91 events: Vec::new(),
92 holds: 0,
93 serial: 0,
94 }
95 }
96
97 pub fn sdk(&self) -> &S {
99 &self.sdk
100 }
101
102 pub fn start<G: GameData>(&mut self, game: &Game<G>) {
106 let data = &game.ctx().game;
107 let manifest = data.audio_manifest().clone();
108 let seed = u64::from(self.sdk.millis());
109 let player = audio::player(self.audio.backend_mut(), manifest, seed);
110 self.audio.set_player(player);
111 self.pad.set_crank(data.keymap_def().crank);
112 self.docked = None;
113 for slot in 0..self.menu_slots {
114 let bound =
115 menu_button(slot).and_then(|button| game.input().keymap().pad_action(button));
116 let Some(action) = bound else {
117 continue;
118 };
119 let name = action.name();
120 let key = alloc::format!("{MENU_TITLE}{name}");
121 let title = if data.lang().has(&key) {
122 game.ctx().text(&key)
123 } else {
124 name
125 };
126 if !self.sdk.add_menu_item(title, slot) {
127 self.sdk.log("the system menu refused an item");
129 }
130 }
131 }
132
133 pub fn menu_chosen(&mut self, slot: u8) {
136 if slot < self.menu_slots {
137 self.chosen.push(slot);
138 }
139 }
140
141 pub fn event(&mut self, event: SdkEvent) -> bool {
145 match event {
146 SdkEvent::Lock | SdkEvent::Pause => {
147 self.holds = self.holds.saturating_add(1);
148 if self.holds > 1 {
149 return false;
150 }
151 self.events.push(SystemEvent::Suspend);
152 true
153 }
154 SdkEvent::Unlock | SdkEvent::Resume => {
155 self.holds = self.holds.saturating_sub(1);
156 if self.holds == 0 {
157 self.events.push(SystemEvent::Resume);
158 self.screen.invalidate();
159 }
160 false
161 }
162 SdkEvent::Terminate => {
163 self.events.push(SystemEvent::QuitRequested);
164 true
165 }
166 SdkEvent::LowPower => {
167 self.events.push(SystemEvent::LowBattery);
168 true
169 }
170 }
171 }
172
173 pub fn take_changed(&mut self) -> bool {
176 self.screen.take_changed()
177 }
178
179 pub(crate) fn showing(&self) -> Showing {
181 self.screen.showing()
182 }
183
184 pub(crate) fn measure(&mut self, phases: bool) {
186 self.screen.measure(phases);
187 }
188
189 pub(crate) fn take_measured(&mut self) -> Measured {
191 self.screen.take_measured()
192 }
193
194 pub(crate) fn music(&self, now: Millis) -> Option<&str> {
196 self.audio.music_playing(now).map(|(cue, _)| cue)
197 }
198
199 pub(crate) fn underran(&self) -> bool {
201 self.sdk.file_did_underrun()
202 }
203
204 pub fn pacing(&self, fps: u16) -> Pacing {
207 let mut pacing = Pacing::new(FrameCap::Fps(self.rate), self.serial);
208 pacing.fps = fps;
209 pacing
210 }
211}
212
213impl<S: Sdk> Platform for Playdate<S> {
214 fn display(&self) -> DisplayInfo {
215 DisplayInfo::new(400, 240).one_bit()
216 }
217
218 fn present(&mut self, frame: &Frame<'_>) {
219 self.serial = frame.frame_cap_serial;
220 self.screen.present(&self.sdk, frame);
221 }
222
223 fn poll_input(&mut self, out: &mut Vec<RawInputEvent>) {
224 let (current, pushed) = self.sdk.buttons();
225 let turned = self.sdk.crank_change();
226 let docked = self.sdk.crank_docked();
227 out.extend(self.pad.poll(current, pushed, turned, docked));
228 for slot in self.chosen.drain(..) {
229 out.extend(menu_press(slot).into_iter().flatten());
230 }
231 }
232
233 fn system_events(&mut self, out: &mut Vec<SystemEvent>) {
234 out.append(&mut self.events);
235 let docked = self.sdk.crank_docked();
236 if self.docked != Some(docked) {
237 self.docked = Some(docked);
238 out.push(if docked {
239 SystemEvent::CrankDocked
240 } else {
241 SystemEvent::CrankUndocked
242 });
243 }
244 }
245
246 fn storage(&mut self) -> Box<dyn Storage> {
247 Box::new(SdkStorage::new(self.sdk.clone()))
248 }
249
250 fn audio(&mut self) -> &mut dyn Audio {
251 &mut self.audio
252 }
253
254 fn text_input(&mut self) -> Option<&mut dyn TextInput> {
255 None
256 }
257
258 fn facts(&self) -> PlatformFacts {
259 self.facts.clone()
260 }
261
262 fn clock(&self) -> Option<PerfClock> {
263 cfg!(debug_assertions).then_some(PerfClock::Real(S::clock_us))
264 }
265
266 fn memory(&mut self) -> Option<MemoryReading> {
267 S::heap().map(MemoryReading::new)
268 }
269}