Skip to main content

shuyi_playdate/
sdk.rs

1//! [`Sdk`]: the calls of Panic's SDK the backend is written against, small
2//! enough that a fake stands in for them in the tests (`crate::fake`) and
3//! `crate::device` puts them on the real SDK through `shim/shim.c`.
4
5use alloc::string::String;
6use alloc::vec::Vec;
7use core::fmt;
8
9use shuyi_ui::frame::onebit::Frame;
10use shuyi_ui::perf::HeapCounts;
11use shuyi_ui::storage::StorageError;
12
13/// The file of the game's baked pack in the `.pdx`: `shuyi export playdate`
14/// places it, and `pdc` copies a file it doesn't know as it is.
15pub const PACK: &str = "game.pack";
16
17/// What the SDK tells a game between updates (`PDSystemEvent`, by number;
18/// `shim/shim.c` checks them against the header).
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum SdkEvent {
21    /// The device was locked (`kEventLock`).
22    Lock,
23    /// The device was unlocked (`kEventUnlock`).
24    Unlock,
25    /// The system menu opened (`kEventPause`).
26    Pause,
27    /// The system menu closed (`kEventResume`).
28    Resume,
29    /// The system is closing the game (`kEventTerminate`).
30    Terminate,
31    /// The battery is nearly empty (`kEventLowPower`).
32    LowPower,
33}
34
35impl SdkEvent {
36    /// The event of the SDK's number `event`, if the backend hears it.
37    pub const fn from_raw(event: i32) -> Option<Self> {
38        match event {
39            2 => Some(Self::Lock),
40            3 => Some(Self::Unlock),
41            4 => Some(Self::Pause),
42            5 => Some(Self::Resume),
43            6 => Some(Self::Terminate),
44            9 => Some(Self::LowPower),
45            _ => None,
46        }
47    }
48}
49
50/// The SDK's calls. A clone is the same SDK: the storage keeps its own, as a
51/// game's context outlives nothing the backend holds.
52pub trait Sdk: Clone + fmt::Debug + 'static {
53    /// A sound the SDK holds in memory (`AudioSample`).
54    type Sample: Copy + PartialEq + fmt::Debug;
55    /// A sample player (`SamplePlayer`).
56    type SamplePlayer: Copy + fmt::Debug;
57    /// A file player (`FilePlayer`).
58    type FilePlayer: Copy + fmt::Debug;
59
60    /// `getCurrentTimeMilliseconds`: milliseconds from a fixed start, which
61    /// stands still while the device sleeps and wraps at `u32::MAX`.
62    fn millis(&self) -> u32;
63    /// The high-resolution timer in whole microseconds from its start
64    /// (`getElapsedTime`), never going back; never reset by the backend.
65    fn clock_us() -> u64;
66    /// The debug build's heap counter, read: `None` in a release build.
67    fn heap() -> Option<HeapCounts>;
68    /// `getButtonState`: the bits held now, and those pressed since the
69    /// last call.
70    fn buttons(&self) -> (u32, u32);
71    /// `getCrankChange`: degrees turned since the last call, clockwise
72    /// positive.
73    fn crank_change(&self) -> f32;
74    /// `isCrankDocked`.
75    fn crank_docked(&self) -> bool;
76    /// `addMenuItem`: an item named `title` that gives `slot` back when
77    /// chosen; false if the SDK refused it.
78    fn add_menu_item(&self, title: &str, slot: u8) -> bool;
79    /// `display->setRefreshRate`.
80    fn set_refresh_rate(&self, fps: u16);
81    /// Runs `draw` on the frame buffer (`getFrame`).
82    fn with_frame(&self, draw: impl FnOnce(&mut Frame));
83    /// `markUpdatedRows`: rows `first` to `last` of the frame buffer changed.
84    fn mark_rows(&self, first: i32, last: i32);
85    /// Clears the screen and draws `text`, centred and wrapped, in the
86    /// system's font: black on white if `white`, else white on black.
87    fn draw_text(&self, text: &str, white: bool);
88    /// `logToConsole`: one line.
89    fn log(&self, line: &str);
90    /// Whether `path` in the game's data folder is a file.
91    fn is_file(&self, path: &str) -> bool;
92    /// The whole of file `path` in the game's data folder.
93    fn read_data(&self, path: &str) -> Result<Vec<u8>, String>;
94    /// The whole of file `path` in the game's `.pdx`.
95    fn read_bundled(&self, path: &str) -> Result<Vec<u8>, String>;
96    /// Replaces file `path` in the game's data folder with `bytes`, flushed.
97    fn write_data(&self, path: &str, bytes: &[u8]) -> Result<(), String>;
98    /// Moves file `from` to `to` in the game's data folder.
99    fn rename_data(&self, from: &str, to: &str) -> Result<(), String>;
100    /// Deletes file `path` in the game's data folder.
101    fn remove_data(&self, path: &str) -> Result<(), String>;
102    /// The names in `folder` of the game's data folder (`""`: the data
103    /// folder itself); a folder's has a `/` after it.
104    fn data_names(&self, folder: &str) -> Result<Vec<String>, String>;
105    /// Makes `folder` in the game's data folder; an error if it is there.
106    fn make_data_folder(&self, folder: &str) -> Result<(), String>;
107    /// `sample->load`: the sound at `path` in the `.pdx`, without its
108    /// extension; `None` if there is none.
109    fn sample_load(&self, path: &str) -> Option<Self::Sample>;
110    /// `sample->getLength`, in seconds.
111    fn sample_length(&self, sample: Self::Sample) -> f32;
112    /// `sample->newSampleFromData`: a sound of `samples`, 16 bits each,
113    /// interleaved, at `rate` a second, mono or `stereo`. The SDK keeps a copy
114    /// of its own. `None` if it made none (no memory, or no samples). Without
115    /// it, none.
116    fn sample_from_data(&self, samples: &[i16], rate: u32, stereo: bool) -> Option<Self::Sample> {
117        let _ = (samples, rate, stereo);
118        None
119    }
120    /// `sample->freeSample`, with its copy of the samples: a sample
121    /// [`sample_from_data`](Self::sample_from_data) made, which no sample
122    /// player plays any more. Without it, nothing.
123    fn sample_free(&self, sample: Self::Sample) {
124        let _ = sample;
125    }
126    /// `sampleplayer->newPlayer`.
127    fn sample_player_new(&self) -> Option<Self::SamplePlayer>;
128    /// Plays `sample` from its start on `player`, stopping what it played, at
129    /// `volume`, once or looped, at `rate` (1 as recorded).
130    fn sample_player_play(
131        &self,
132        player: Self::SamplePlayer,
133        sample: Self::Sample,
134        volume: f32,
135        looped: bool,
136        rate: f32,
137    );
138    /// `sampleplayer->setVolume`, the same on both sides.
139    fn sample_player_set_volume(&self, player: Self::SamplePlayer, volume: f32);
140    /// `sampleplayer->stop`.
141    fn sample_player_stop(&self, player: Self::SamplePlayer);
142    /// `sampleplayer->isPlaying`.
143    fn sample_player_is_playing(&self, player: Self::SamplePlayer) -> bool;
144    /// `sampleplayer->setPaused`.
145    fn sample_player_set_paused(&self, player: Self::SamplePlayer, paused: bool);
146    /// A new file player on the file at `path` in the `.pdx`, without its
147    /// extension; `None` if there is no such file.
148    fn file_open(&self, path: &str) -> Option<Self::FilePlayer>;
149    /// Starts `player`, or goes on from where its pause left it, once or
150    /// looped: false if it could not (`fileplayer->play`).
151    fn file_play(&self, player: Self::FilePlayer, looped: bool) -> bool;
152    /// `fileplayer->setVolume`, the same on both sides.
153    fn file_set_volume(&self, player: Self::FilePlayer, volume: f32);
154    /// `fileplayer->setRate`.
155    fn file_set_rate(&self, player: Self::FilePlayer, rate: f32);
156    /// `fileplayer->pause`.
157    fn file_pause(&self, player: Self::FilePlayer);
158    /// `fileplayer->stop`.
159    fn file_stop(&self, player: Self::FilePlayer);
160    /// `fileplayer->setOffset` to the file's start.
161    fn file_rewind(&self, player: Self::FilePlayer);
162    /// `fileplayer->isPlaying`.
163    fn file_is_playing(&self, player: Self::FilePlayer) -> bool;
164    /// `fileplayer->getLength`, in seconds.
165    fn file_length(&self, player: Self::FilePlayer) -> f32;
166    /// Stops `player` and frees it: it isn't used again.
167    fn file_close(&self, player: Self::FilePlayer);
168    /// Whether a file player open now ran out of data to play
169    /// (`fileplayer->didUnderrun`): the music stuttered. Without it, never.
170    fn file_did_underrun(&self) -> bool {
171        false
172    }
173
174    /// The bytes of the game's own stack it has ever used
175    /// ([`stack_high_water`](crate::stack_high_water)): `None` where they
176    /// aren't measured, as on the Simulator.
177    fn stack_used(&self) -> Option<usize> {
178        None
179    }
180}
181
182/// A file call's error, as storage reports it.
183pub(crate) fn backend(path: &str, message: &str) -> StorageError {
184    // check-text: not player text (for the log)
185    StorageError::Backend(alloc::format!("{path}: {message}"))
186}