playdate_scoreboards/
lib.rs1#![cfg_attr(not(test), no_std)]
7
8#[macro_use]
9extern crate sys;
10extern crate alloc;
11
12use core::ffi::c_char;
13use core::ffi::c_uint;
14use alloc::borrow::Cow;
15
16use sys::ffi::CStr;
17use sys::ffi::CString;
18use sys::ffi::PDBoard;
19use sys::ffi::PDBoardsList;
20use sys::ffi::PDScore;
21use sys::ffi::PDScoresList;
22
23
24pub mod error;
25mod storage;
26
27use error::*;
28use storage::*;
29
30
31pub type ScoresResult<T> = Result<T, Error>;
32
33
34#[derive(Debug, Clone, Copy)]
35pub struct Scoreboards<Api = api::Default>(Api);
36
37impl Scoreboards<api::Default> {
38 #[allow(non_snake_case)]
42 pub fn Default() -> Self { Self(Default::default()) }
43}
44
45impl Scoreboards<api::Cache> {
46 #[allow(non_snake_case)]
50 pub fn Cached() -> Self { Self(Default::default()) }
51}
52
53impl<Api: Default + api::Api> Default for Scoreboards<Api> {
54 fn default() -> Self { Self(Default::default()) }
55}
56
57impl<Api: Default + api::Api> Scoreboards<Api> {
58 pub fn new() -> Self { Self(Default::default()) }
59}
60
61impl<Api: api::Api> Scoreboards<Api> {
62 pub fn new_with(api: Api) -> Self { Self(api) }
63}
64
65
66impl<Api: api::Api> Scoreboards<Api> {
67 #[doc(alias = "sys::ffi::scoreboards::addScore")]
73 pub fn add_score<S: AsRef<str>, F: FnMut(ScoresResult<ScoreRef>)>(&self,
74 board_id: S,
75 value: u32,
76 callback: F)
77 -> Result<Option<F>, ApiError>
78 where F: 'static + Send
79 {
80 let id = CString::new(board_id.as_ref())?;
81
82 init_store();
83 let prev = unsafe { STORE.as_mut() }.expect("impossible")
84 .insert::<F>(callback);
85 let f = self.0.add_score();
86
87 let result = unsafe { f(id.as_ptr() as _, value, Some(proxy_score::<F>)) };
88
89 if result != 0 {
90 Err(Error::Unknown.into())
91 } else {
92 Ok(prev)
93 }
94 }
95
96
97 #[doc(alias = "sys::ffi::scoreboards::getPersonalBest")]
103 pub fn get_personal_best_for<F: FnMut(ScoresResult<ScoreRef>)>(&self,
104 board: &Board,
105 callback: F)
106 -> Result<Option<F>, ApiError>
107 where F: 'static + Send
108 {
109 self.get_personal_best(board.id().expect("board.id"), callback)
110 }
111
112 #[doc(alias = "sys::ffi::scoreboards::getPersonalBest")]
118 pub fn get_personal_best<S: AsRef<str>, F: FnMut(ScoresResult<ScoreRef>)>(&self,
119 board_id: S,
120 callback: F)
121 -> Result<Option<F>, ApiError>
122 where F: 'static + Send
123 {
124 let id = CString::new(board_id.as_ref())?;
125
126 init_store();
127 let prev = unsafe { STORE.as_mut() }.expect("impossible")
128 .insert::<F>(callback);
129 let f = self.0.get_personal_best();
130
131 let result = unsafe { f(id.as_ptr() as _, Some(proxy_score::<F>)) };
132
133 if result != 0 {
134 Err(Error::Unknown.into())
135 } else {
136 Ok(prev)
137 }
138 }
139
140
141 #[doc(alias = "sys::ffi::scoreboards::getScores")]
147 pub fn get_scores<S: AsRef<str>, F: FnMut(ScoresResult<Scores>)>(&self,
148 board_id: S,
149 callback: F)
150 -> Result<Option<F>, ApiError>
151 where F: 'static + Send
152 {
153 let id = CString::new(board_id.as_ref())?;
154
155 init_store();
156 let prev = unsafe { STORE.as_mut() }.expect("impossible")
157 .insert::<F>(callback);
158 let f = self.0.get_scores();
159
160 let result = unsafe { f(id.as_ptr() as _, Some(proxy_scores::<F>)) };
161
162 if result != 0 {
163 Err(Error::Unknown.into())
164 } else {
165 Ok(prev)
166 }
167 }
168
169
170 #[doc(alias = "sys::ffi::scoreboards::getScoreboards")]
179 pub fn get_scoreboards<F: FnMut(ScoresResult<Boards>)>(&self, callback: F) -> Option<F>
180 where F: 'static + Send {
181 init_store();
182 let prev = unsafe { STORE.as_mut() }.expect("impossible")
183 .insert::<F>(callback);
184 let f = self.0.get_scoreboards();
185 unsafe { f(Some(proxy_boards::<F>)) };
186
187 prev
188 }
189}
190
191
192pub struct Boards(*mut PDBoardsList);
193
194impl core::fmt::Debug for Boards {
195 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
196 let mut t = f.debug_tuple("Boards");
197 self.boards().into_iter().for_each(|board| {
198 t.field(board);
199 });
200 t.finish()
201 }
202}
203
204impl Boards {
205 pub fn last_updated(&self) -> u32 { unsafe { (*self.0).lastUpdated } }
206
207 pub fn boards(&self) -> &[Board] {
208 let count = unsafe { (*self.0).count };
209 let ptr = unsafe { (*self.0).boards };
210 let slice = unsafe { core::slice::from_raw_parts(ptr, count as _) };
211 unsafe { core::mem::transmute(slice) }
212 }
213
214 pub fn boards_mut(&mut self) -> &mut [Board] {
215 let count = unsafe { (*self.0).count };
216 let ptr = unsafe { (*self.0).boards };
217 let slice = unsafe { core::slice::from_raw_parts_mut(ptr, count as _) };
218 unsafe { core::mem::transmute(slice) }
219 }
220}
221
222
223#[repr(transparent)]
224pub struct Board(PDBoard);
225
226impl core::fmt::Debug for Board {
227 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
228 f.debug_struct("Board")
229 .field("id", &self.id())
230 .field("name", &self.name())
231 .finish()
232 }
233}
234
235impl Board {
236 pub fn id<'s>(&'s self) -> Option<Cow<'s, str>> {
237 let ptr = self.0.boardID;
238 if ptr.is_null() {
239 None
240 } else {
241 unsafe { CStr::from_ptr(ptr as _) }.to_string_lossy().into()
242 }
243 }
244
245 pub fn name<'s>(&'s self) -> Option<Cow<'s, str>> {
246 let ptr = self.0.name;
247 if ptr.is_null() {
248 None
249 } else {
250 unsafe { CStr::from_ptr(ptr as _) }.to_string_lossy().into()
251 }
252 }
253}
254
255impl Drop for Boards {
256 fn drop(&mut self) {
257 if !self.0.is_null() {
258 let get_fn = || sys::api_opt!(scoreboards.freeBoardsList);
259 if let Some(f) = get_fn() {
260 unsafe { f(self.0) }
261 }
262 }
263 }
264}
265
266
267pub struct Scores(*mut PDScoresList);
268
269impl core::fmt::Debug for Scores {
270 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
271 f.debug_struct("Scores")
272 .field("id", &self.id())
273 .field("count", &self.len())
274 .field("capacity", &self.capacity())
275 .field("last_updated", &self.last_updated())
276 .field("playerIncluded", &self.player_included())
277 .finish()
278 }
279}
280
281impl Drop for Scores {
282 fn drop(&mut self) {
283 if !self.0.is_null() {
284 let get_fn = || sys::api_opt!(scoreboards.freeScoresList);
285 if let Some(f) = get_fn() {
286 unsafe { f(self.0) }
287 }
288 }
289 }
290}
291
292impl Scores {
293 pub fn id<'s>(&'s self) -> Option<Cow<'s, str>> {
295 let ptr = unsafe { (*self.0).boardID };
296 if ptr.is_null() {
297 None
298 } else {
299 unsafe { CStr::from_ptr(ptr as _) }.to_string_lossy().into()
300 }
301 }
302
303 pub fn last_updated(&self) -> u32 { unsafe { (*self.0).lastUpdated } }
304 pub fn player_included(&self) -> bool { unsafe { (*self.0).playerIncluded == 1 } }
305
306 pub fn len(&self) -> c_uint { unsafe { (*self.0).count } }
307 pub fn capacity(&self) -> c_uint { unsafe { (*self.0).limit } }
308
309 pub fn scores(&self) -> &[Score] {
310 let count = self.len();
311 let ptr = unsafe { (*self.0).scores };
312 let slice = unsafe { core::slice::from_raw_parts(ptr, count as _) };
313 unsafe { core::mem::transmute(slice) }
314 }
315}
316
317
318#[repr(transparent)]
319pub struct Score(PDScore);
320
321impl core::fmt::Debug for Score {
322 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
323 f.debug_struct("Score")
324 .field("rank", &self.rank())
325 .field("value", &self.value())
326 .field("player", &self.player())
327 .finish()
328 }
329}
330
331impl Score {
332 pub fn rank(&self) -> u32 { self.0.rank }
333 pub fn value(&self) -> u32 { self.0.value }
334
335 pub fn player<'s>(&'s self) -> Option<Cow<'s, str>> {
336 let ptr = self.0.player;
337 if ptr.is_null() {
338 None
339 } else {
340 unsafe { CStr::from_ptr(ptr as _) }.to_string_lossy().into()
341 }
342 }
343}
344
345#[repr(transparent)]
346pub struct ScoreRef(*mut PDScore);
347
348impl Drop for ScoreRef {
349 fn drop(&mut self) {
350 if !self.0.is_null() {
351 let get_fn = || sys::api_opt!(scoreboards.freeScore);
352 if let Some(f) = get_fn() {
353 unsafe { f(self.0) }
354 }
355 }
356 }
357}
358
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363 use core::mem::size_of;
364
365
366 #[test]
367 fn board_size() {
368 assert_eq!(size_of::<Board>(), size_of::<PDBoard>());
369 }
370
371 #[test]
372 fn score_size() {
373 assert_eq!(size_of::<Score>(), size_of::<PDScore>());
374 }
375
376 #[test]
377 fn score_ref_size() {
378 assert_eq!(size_of::<ScoreRef>(), size_of::<*mut PDScore>());
379 }
380}
381
382
383pub mod api {
384 use core::ffi::c_char;
385 use core::ffi::c_int;
386 use core::ptr::NonNull;
387
388 use sys::ffi::AddScoreCallback;
389 use sys::ffi::PDBoardsList;
390 use sys::ffi::PDScore;
391 use sys::ffi::PDScoresList;
392 use sys::ffi::ScoresCallback;
393 use sys::ffi::BoardsListCallback;
394 use sys::ffi::PersonalBestCallback;
395 use sys::ffi::playdate_scoreboards;
396
397
398 #[derive(Debug, Clone, Copy, core::default::Default)]
402 pub struct Default;
403 impl Api for Default {}
404
405
406 #[derive(Clone, Copy)]
412 #[cfg_attr(feature = "bindings-derive-debug", derive(Debug))]
413 pub struct Cache(&'static playdate_scoreboards);
414
415 impl core::default::Default for Cache {
416 fn default() -> Self { Self(sys::api!(scoreboards)) }
417 }
418
419 impl From<*const playdate_scoreboards> for Cache {
420 #[inline(always)]
421 fn from(ptr: *const playdate_scoreboards) -> Self { Self(unsafe { ptr.as_ref() }.expect("scoreboards")) }
422 }
423
424 impl From<&'static playdate_scoreboards> for Cache {
425 #[inline(always)]
426 fn from(r: &'static playdate_scoreboards) -> Self { Self(r) }
427 }
428
429 impl From<NonNull<playdate_scoreboards>> for Cache {
430 #[inline(always)]
431 fn from(ptr: NonNull<playdate_scoreboards>) -> Self { Self(unsafe { ptr.as_ref() }) }
432 }
433
434 impl From<&'_ NonNull<playdate_scoreboards>> for Cache {
435 #[inline(always)]
436 fn from(ptr: &NonNull<playdate_scoreboards>) -> Self { Self(unsafe { ptr.as_ref() }) }
437 }
438
439 impl Api for Cache {
440 fn add_score(
441 &self)
442 -> unsafe extern "C" fn(boardId: *const c_char, value: u32, callback: AddScoreCallback) -> c_int {
443 self.0.addScore.expect("addScore")
444 }
445
446 fn get_personal_best(
447 &self)
448 -> unsafe extern "C" fn(boardId: *const c_char, callback: PersonalBestCallback) -> c_int {
449 self.0.getPersonalBest.expect("getPersonalBest")
450 }
451
452 fn free_score(&self) -> unsafe extern "C" fn(score: *mut PDScore) { self.0.freeScore.expect("freeScore") }
453
454 fn get_scoreboards(&self) -> unsafe extern "C" fn(callback: BoardsListCallback) -> c_int {
455 self.0.getScoreboards.expect("getScoreboards")
456 }
457
458 fn free_boards_list(&self) -> unsafe extern "C" fn(boardsList: *mut PDBoardsList) {
459 self.0.freeBoardsList.expect("freeBoardsList")
460 }
461
462 fn get_scores(&self) -> unsafe extern "C" fn(board_id: *const c_char, callback: ScoresCallback) -> c_int {
463 self.0.getScores.expect("getScores")
464 }
465
466 fn free_scores_list(&self) -> unsafe extern "C" fn(scores_list: *mut PDScoresList) {
467 self.0.freeScoresList.expect("freeScoresList")
468 }
469 }
470
471
472 pub trait Api {
473 #[doc(alias = "sys::ffi::scoreboards::addScore")]
475 #[inline(always)]
476 fn add_score(
477 &self)
478 -> unsafe extern "C" fn(boardId: *const c_char, value: u32, callback: AddScoreCallback) -> c_int {
479 *sys::api!(scoreboards.addScore)
480 }
481
482 #[doc(alias = "sys::ffi::scoreboards::getPersonalBest")]
484 #[inline(always)]
485 fn get_personal_best(
486 &self)
487 -> unsafe extern "C" fn(boardId: *const c_char, callback: PersonalBestCallback) -> c_int {
488 *sys::api!(scoreboards.getPersonalBest)
489 }
490
491 #[doc(alias = "sys::ffi::scoreboards::freeScore")]
493 #[inline(always)]
494 fn free_score(&self) -> unsafe extern "C" fn(score: *mut PDScore) { *sys::api!(scoreboards.freeScore) }
495
496 #[doc(alias = "sys::ffi::scoreboards::getScoreboards")]
498 #[inline(always)]
499 fn get_scoreboards(&self) -> unsafe extern "C" fn(callback: BoardsListCallback) -> c_int {
500 *sys::api!(scoreboards.getScoreboards)
501 }
502
503 #[doc(alias = "sys::ffi::scoreboards::freeBoardsList")]
505 #[inline(always)]
506 fn free_boards_list(&self) -> unsafe extern "C" fn(boardsList: *mut PDBoardsList) {
507 *sys::api!(scoreboards.freeBoardsList)
508 }
509
510 #[doc(alias = "sys::ffi::scoreboards::getScores")]
512 #[inline(always)]
513 fn get_scores(&self) -> unsafe extern "C" fn(board_id: *const c_char, callback: ScoresCallback) -> c_int {
514 *sys::api!(scoreboards.getScores)
515 }
516
517 #[doc(alias = "sys::ffi::scoreboards::freeScoresList")]
519 #[inline(always)]
520 fn free_scores_list(&self) -> unsafe extern "C" fn(scores_list: *mut PDScoresList) {
521 *sys::api!(scoreboards.freeScoresList)
522 }
523 }
524}