From 1da2ad4a2e474f3bfca7d51fcb56de8e695a3210 Mon Sep 17 00:00:00 2001 From: Alexander Koz <888526+boozook@users.noreply.github.com> Date: Fri, 15 Sep 2023 22:08:03 +0400 Subject: [PATCH] Add system api (#47) --- .github/workflows/tests.yml | 3 + Cargo.lock | 7 + api/system/Cargo.toml | 40 ++++ api/system/README.md | 29 +++ api/system/src/lang.rs | 11 ++ api/system/src/lib.rs | 371 ++++++++++++++++++++++++++++++++++++ api/system/src/time.rs | 37 ++++ 7 files changed, 498 insertions(+) create mode 100644 api/system/Cargo.toml create mode 100644 api/system/README.md create mode 100644 api/system/src/lang.rs create mode 100644 api/system/src/lib.rs create mode 100644 api/system/src/time.rs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d54e966b..fbb0b742 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -81,6 +81,9 @@ jobs: cargo test -p=playdate-display --lib --no-default-features --features=$FEATURES_1 -- --nocapture cargo test -p=playdate-display --lib --no-default-features --features=$FEATURES_2 -- --nocapture + cargo test -p=playdate-system --lib --no-default-features --features=$FEATURES_1 -- --nocapture + cargo test -p=playdate-system --lib --no-default-features --features=$FEATURES_2 -- --nocapture + - name: Examples run: | FEATURES=bindgen-runtime,bindings-derive-debug diff --git a/Cargo.lock b/Cargo.lock index a2f7be03..a96b3b0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2634,6 +2634,13 @@ dependencies = [ "semver", ] +[[package]] +name = "playdate-system" +version = "0.1.0" +dependencies = [ + "playdate-sys", +] + [[package]] name = "playdate-tool" version = "0.1.0" diff --git a/api/system/Cargo.toml b/api/system/Cargo.toml new file mode 100644 index 00000000..8a27bfd6 --- /dev/null +++ b/api/system/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "playdate-system" +version = "0.1.0" +edition = "2021" + +readme = "README.md" +license = "MIT OR Apache-2.0" +authors = ["Alex Koz "] +description = "High-level System API built on-top of Playdate API" +homepage = "https://github.com/boozook/playdate" +repository = "https://github.com/boozook/playdate.git" + + +[features] +default = ["sys/default"] +# sys- features: +lang-items = ["sys/lang-items"] +allocator = ["sys/allocator"] +panic-handler = ["sys/panic-handler"] +eh-personality = ["sys/eh-personality"] +error-ctx = ["sys/error-ctx"] +bindgen-runtime = ["sys/bindgen-runtime"] +bindgen-static = ["sys/bindgen-static"] +bindings-derive-default = ["sys/bindings-derive-default"] +bindings-derive-eq = ["sys/bindings-derive-eq"] +bindings-derive-copy = ["sys/bindings-derive-copy"] +bindings-derive-debug = ["sys/bindings-derive-debug"] +bindings-derive-hash = ["sys/bindings-derive-hash"] +bindings-derive-ord = ["sys/bindings-derive-ord"] +bindings-derive-partialeq = ["sys/bindings-derive-partialeq"] +bindings-derive-partialord = ["sys/bindings-derive-partialord"] +bindings-derive-constparamty = ["sys/bindings-derive-constparamty"] +bindings-documentation = ["sys/bindings-documentation"] + + +[dependencies.sys] +version = "0.1" +path = "../sys" +package = "playdate-sys" +default-features = false diff --git a/api/system/README.md b/api/system/README.md new file mode 100644 index 00000000..dbd5a5fd --- /dev/null +++ b/api/system/README.md @@ -0,0 +1,29 @@ +# System API for PlayDate + +High-level system API built on-top of [playdate-sys][]. + + +## Usage + +```rust +use playdate_system::*; +use playdate_sys::println; + +let system = System::new(); + +match system.language() { + PDLanguage::English => println!("Hello"), + PDLanguage::Japanese => println!("こんにちは"), + PDLanguage::Unknown => println!("Привет"), +} +system.draw_fps(20, 20); +``` + + +[playdate-sys]: https://crates.io/crates/playdate-sys + + + +- - - + +This software is not sponsored or supported by Panic. diff --git a/api/system/src/lang.rs b/api/system/src/lang.rs new file mode 100644 index 00000000..438204b0 --- /dev/null +++ b/api/system/src/lang.rs @@ -0,0 +1,11 @@ +pub use sys::ffi::PDLanguage; + +pub trait PDLanguageExt { + #![allow(non_upper_case_globals)] + const English: PDLanguage = PDLanguage::kPDLanguageEnglish; + const Japanese: PDLanguage = PDLanguage::kPDLanguageJapanese; + const Unknown: PDLanguage = PDLanguage::kPDLanguageUnknown; +} + + +impl PDLanguageExt for PDLanguage {} diff --git a/api/system/src/lib.rs b/api/system/src/lib.rs new file mode 100644 index 00000000..387ca9ba --- /dev/null +++ b/api/system/src/lib.rs @@ -0,0 +1,371 @@ +#![cfg_attr(not(test), no_std)] +extern crate sys; +extern crate alloc; + +use core::ffi::c_float; +use core::ffi::c_int; +use core::ffi::c_uint; +use core::ffi::c_void; +use core::marker::PhantomData; +use core::pin::Pin; +use core::time::Duration; + +pub mod time; +pub mod lang; + +pub use time::*; +pub use lang::*; + + +#[derive(Debug, Clone, Copy)] +pub struct System(Api); + +impl Default for System { + fn default() -> Self { Self(Default::default()) } +} + +impl System { + pub fn new(api: Api) -> Self { Self(api) } +} + +impl System { + pub fn new_with(api: Api) -> Self { Self(api) } +} + + +pub struct CallbackHandler<'t, F, U>(Option>>, PhantomData<&'t ()>); + +impl<'t, F, U> Drop for CallbackHandler<'t, F, U> { + fn drop(&mut self) { + let get_fn = || sys::api_opt!(system.setUpdateCallback); + if self.0.is_some() { + if let Some(f) = get_fn() { + unsafe { + f(None, core::ptr::null_mut()); + } + } + } + } +} + + +impl System { + /// Takes an any function with `userdata` into the `Pin`, + /// registers callback in the system and returns this wrapped function with userdata. + /// + /// For register a fn-ptr you could better use [`set_update_callback_static`]. + /// + /// Wrapping [`sys::ffi::playdate_sys::setUpdateCallback`] + #[doc(alias = "sys::ffi::playdate_sys::setUpdateCallback")] + pub fn set_update_callback<'u, U, F>(&self, on_update: F, userdata: U) -> CallbackHandler<'u, F, U> + where U: 'u, + F: 'u + FnMut(&mut U) -> bool { + unsafe extern "C" fn proxy bool>(fn_ud: *mut c_void) -> c_int { + if let Some((callback, userdata)) = (fn_ud as *mut (Fn, UD)).as_mut() { + callback(userdata).into() + } else { + panic!("user callback missed"); + } + } + + let f = self.0.set_update_callback(); + + let mut userdata = Box::pin((on_update, userdata)); + let ptr = unsafe { userdata.as_mut().get_unchecked_mut() } as *mut _ as *mut c_void; + + unsafe { f(Some(proxy::), ptr) }; + + CallbackHandler(userdata.into(), PhantomData) + } + + + /// Takes `on_update` and `userdata` and wraps it into the `Box`, + /// then registers callback. + /// + /// Wrapping [`sys::ffi::playdate_sys::setUpdateCallback`] + #[doc(alias = "sys::ffi::playdate_sys::setUpdateCallback")] + pub fn set_update_callback_static(&self, + on_update: Option bool>, + userdata: U) { + unsafe extern "C" fn proxy(fn_ud: *mut c_void) -> c_int { + if let Some((callback, userdata)) = (fn_ud as *mut (fn(userdata: &mut UD) -> bool, UD)).as_mut() { + callback(userdata).into() + } else { + panic!("user callback missed"); + } + } + + let f = self.0.set_update_callback(); + + if let Some(callback) = on_update { + let ptr = Box::into_raw(Box::new((callback, userdata))); + unsafe { f(Some(proxy::), ptr as *mut _) }; + } else { + unsafe { f(None, core::ptr::null_mut()) }; + } + } +} + + +impl System { + /// Equivalent to [`sys::ffi::playdate_sys::getLanguage`] + #[doc(alias = "sys::ffi::playdate_sys::getLanguage")] + #[inline(always)] + pub fn language(&self) -> PDLanguage { + let f = self.0.get_language(); + unsafe { f() } + } + + /// Equivalent to [`sys::ffi::playdate_sys::getCurrentTimeMilliseconds`] + #[doc(alias = "sys::ffi::playdate_sys::getCurrentTimeMilliseconds")] + #[inline(always)] + pub fn current_time_milliseconds(&self) -> Duration { + let f = self.0.get_current_time_milliseconds(); + let t = unsafe { f() }; + Duration::from_millis(t.into()) + } + + /// Equivalent to [`sys::ffi::playdate_sys::getSecondsSinceEpoch`] + #[doc(alias = "sys::ffi::playdate_sys::getSecondsSinceEpoch")] + #[inline(always)] + pub fn seconds_since_epoch(&self) -> Duration { + let f = self.0.get_seconds_since_epoch(); + let mut millis: c_uint = 0; + let secs = unsafe { f(&mut millis) }; + Duration::new(secs.into(), 0) + Duration::from_millis(millis.into()) + } + + /// Equivalent to [`sys::ffi::playdate_sys::drawFPS`] + #[doc(alias = "sys::ffi::playdate_sys::drawFPS")] + #[inline(always)] + pub fn draw_fps(&self, x: c_int, y: c_int) { + let f = self.0.draw_fps(); + unsafe { f(x, y) } + } + + /// Equivalent to [`sys::ffi::playdate_sys::getFlipped`] + #[doc(alias = "sys::ffi::playdate_sys::getFlipped")] + #[inline(always)] + pub fn flipped(&self) -> bool { + let f = self.0.get_flipped(); + unsafe { f() == 1 } + } + + /// Equivalent to [`sys::ffi::playdate_sys::setAutoLockDisabled`] + #[doc(alias = "sys::ffi::playdate_sys::setAutoLockDisabled")] + #[inline(always)] + pub fn set_auto_lock_disabled(&self, disable: bool) { + let f = self.0.set_auto_lock_disabled(); + unsafe { f(disable as _) } + } + + /// Equivalent to [`sys::ffi::playdate_sys::getReduceFlashing`] + #[doc(alias = "sys::ffi::playdate_sys::getReduceFlashing")] + #[inline(always)] + pub fn reduce_flashing(&self) -> bool { + let f = self.0.get_reduce_flashing(); + unsafe { f() == 1 } + } + + // TODO: invent analog of `std::time::Instant` + + /// Equivalent to [`sys::ffi::playdate_sys::getElapsedTime`] + #[doc(alias = "sys::ffi::playdate_sys::getElapsedTime")] + #[inline(always)] + pub fn elapsed_time(&self) -> Duration { + let f = self.0.get_elapsed_time(); + let secs = unsafe { f() }; + Duration::from_secs_f32(secs) + } + + /// Equivalent to [`sys::ffi::playdate_sys::resetElapsedTime`] + #[doc(alias = "sys::ffi::playdate_sys::resetElapsedTime")] + #[inline(always)] + pub fn reset_elapsed_time(&self) { + let f = self.0.reset_elapsed_time(); + unsafe { f() } + } + + /// Equivalent to [`sys::ffi::playdate_sys::getBatteryPercentage`] + #[doc(alias = "sys::ffi::playdate_sys::getBatteryPercentage")] + #[inline(always)] + pub fn battery_percentage(&self) -> c_float { + let f = self.0.get_battery_percentage(); + unsafe { f() } + } + + + /// Equivalent to [`sys::ffi::playdate_sys::getBatteryVoltage`] + #[doc(alias = "sys::ffi::playdate_sys::getBatteryVoltage")] + #[inline(always)] + pub fn battery_voltage(&self) -> c_float { + let f = self.0.get_battery_voltage(); + unsafe { f() } + } + + /// Equivalent to [`sys::ffi::playdate_sys::getTimezoneOffset`] + #[doc(alias = "sys::ffi::playdate_sys::getTimezoneOffset")] + #[inline(always)] + pub fn timezone_offset(&self) -> i32 { + let f = self.0.get_timezone_offset(); + unsafe { f() } + } + + /// Equivalent to [`sys::ffi::playdate_sys::shouldDisplay24HourTime`] + #[doc(alias = "sys::ffi::playdate_sys::shouldDisplay24HourTime")] + #[inline(always)] + pub fn should_display_24_hour_time(&self) -> bool { + let f = self.0.should_display_24_hour_time(); + unsafe { f() == 1 } + } + + /// Equivalent to [`sys::ffi::playdate_sys::convertEpochToDateTime`] + #[doc(alias = "sys::ffi::playdate_sys::convertEpochToDateTime")] + #[inline(always)] + pub fn convert_epoch_to_date_time(&self, epoch: u32) -> PDDateTime { + let mut dt = PDDateTime { year: 0, + month: 0, + day: 0, + weekday: 0, + hour: 0, + minute: 0, + second: 0 }; + self.convert_epoch_to_date_time_to(epoch, &mut dt); + dt + } + + /// Equivalent to [`sys::ffi::playdate_sys::convertEpochToDateTime`] + #[doc(alias = "sys::ffi::playdate_sys::convertEpochToDateTime")] + #[inline(always)] + pub fn convert_epoch_to_date_time_to(&self, epoch: u32, dt: &mut PDDateTime) { + let f = self.0.convert_epoch_to_date_time(); + unsafe { f(epoch, dt) } + } + + /// Equivalent to [`sys::ffi::playdate_sys::convertDateTimeToEpoch`] + #[doc(alias = "sys::ffi::playdate_sys::convertDateTimeToEpoch")] + pub fn convert_date_time_to_epoch(&self, dt: &PDDateTime) -> u32 { + let f = self.0.convert_date_time_to_epoch(); + let epoch = unsafe { f(dt as *const _ as *mut _) }; + let _ = dt; // this to prevent earlier drop. + epoch + } +} + + +pub mod api { + use core::ffi::c_float; + use core::ffi::c_int; + use core::ffi::c_uint; + use core::ffi::c_void; + + use sys::ffi::PDCallbackFunction; + use sys::ffi::PDDateTime; + use sys::ffi::PDLanguage; + + + #[derive(Debug, Clone, Copy, core::default::Default)] + pub struct Default; + + impl Api for Default {} + + + pub trait Api { + /// Equivalent to [`sys::ffi::playdate_sys::getLanguage`] + #[doc(alias = "sys::ffi::playdate_sys::getLanguage")] + #[inline(always)] + fn get_language(&self) -> unsafe extern "C" fn() -> PDLanguage { *sys::api!(system.getLanguage) } + + /// Equivalent to [`sys::ffi::playdate_sys::getCurrentTimeMilliseconds`] + #[doc(alias = "sys::ffi::playdate_sys::getCurrentTimeMilliseconds")] + #[inline(always)] + fn get_current_time_milliseconds(&self) -> unsafe extern "C" fn() -> c_uint { + *sys::api!(system.getCurrentTimeMilliseconds) + } + + /// Equivalent to [`sys::ffi::playdate_sys::getSecondsSinceEpoch`] + #[doc(alias = "sys::ffi::playdate_sys::getSecondsSinceEpoch")] + #[inline(always)] + fn get_seconds_since_epoch(&self) -> unsafe extern "C" fn(milliseconds: *mut c_uint) -> c_uint { + *sys::api!(system.getSecondsSinceEpoch) + } + + /// Equivalent to [`sys::ffi::playdate_sys::drawFPS`] + #[doc(alias = "sys::ffi::playdate_sys::drawFPS")] + #[inline(always)] + fn draw_fps(&self) -> unsafe extern "C" fn(x: c_int, y: c_int) { *sys::api!(system.drawFPS) } + + /// Equivalent to [`sys::ffi::playdate_sys::setUpdateCallback`] + #[doc(alias = "sys::ffi::playdate_sys::setUpdateCallback")] + #[inline(always)] + fn set_update_callback(&self) -> unsafe extern "C" fn(update: PDCallbackFunction, userdata: *mut c_void) { + *sys::api!(system.setUpdateCallback) + } + + /// Equivalent to [`sys::ffi::playdate_sys::getFlipped`] + #[doc(alias = "sys::ffi::playdate_sys::getFlipped")] + #[inline(always)] + fn get_flipped(&self) -> unsafe extern "C" fn() -> c_int { *sys::api!(system.getFlipped) } + + /// Equivalent to [`sys::ffi::playdate_sys::setAutoLockDisabled`] + #[doc(alias = "sys::ffi::playdate_sys::setAutoLockDisabled")] + #[inline(always)] + fn set_auto_lock_disabled(&self) -> unsafe extern "C" fn(disable: c_int) { + *sys::api!(system.setAutoLockDisabled) + } + + /// Equivalent to [`sys::ffi::playdate_sys::getReduceFlashing`] + #[doc(alias = "sys::ffi::playdate_sys::getReduceFlashing")] + #[inline(always)] + fn get_reduce_flashing(&self) -> unsafe extern "C" fn() -> c_int { *sys::api!(system.getReduceFlashing) } + + /// Equivalent to [`sys::ffi::playdate_sys::getElapsedTime`] + #[doc(alias = "sys::ffi::playdate_sys::getElapsedTime")] + #[inline(always)] + fn get_elapsed_time(&self) -> unsafe extern "C" fn() -> c_float { *sys::api!(system.getElapsedTime) } + + /// Equivalent to [`sys::ffi::playdate_sys::resetElapsedTime`] + #[doc(alias = "sys::ffi::playdate_sys::resetElapsedTime")] + #[inline(always)] + fn reset_elapsed_time(&self) -> unsafe extern "C" fn() { *sys::api!(system.resetElapsedTime) } + + /// Equivalent to [`sys::ffi::playdate_sys::getBatteryPercentage`] + #[doc(alias = "sys::ffi::playdate_sys::getBatteryPercentage")] + #[inline(always)] + fn get_battery_percentage(&self) -> unsafe extern "C" fn() -> c_float { + *sys::api!(system.getBatteryPercentage) + } + + + /// Equivalent to [`sys::ffi::playdate_sys::getBatteryVoltage`] + #[doc(alias = "sys::ffi::playdate_sys::getBatteryVoltage")] + #[inline(always)] + fn get_battery_voltage(&self) -> unsafe extern "C" fn() -> c_float { *sys::api!(system.getBatteryVoltage) } + + /// Equivalent to [`sys::ffi::playdate_sys::getTimezoneOffset`] + #[doc(alias = "sys::ffi::playdate_sys::getTimezoneOffset")] + #[inline(always)] + fn get_timezone_offset(&self) -> unsafe extern "C" fn() -> i32 { *sys::api!(system.getTimezoneOffset) } + + /// Equivalent to [`sys::ffi::playdate_sys::shouldDisplay24HourTime`] + #[doc(alias = "sys::ffi::playdate_sys::shouldDisplay24HourTime")] + #[inline(always)] + fn should_display_24_hour_time(&self) -> unsafe extern "C" fn() -> c_int { + *sys::api!(system.shouldDisplay24HourTime) + } + + /// Equivalent to [`sys::ffi::playdate_sys::convertEpochToDateTime`] + #[doc(alias = "sys::ffi::playdate_sys::convertEpochToDateTime")] + #[inline(always)] + fn convert_epoch_to_date_time(&self) -> unsafe extern "C" fn(epoch: u32, datetime: *mut PDDateTime) { + *sys::api!(system.convertEpochToDateTime) + } + + /// Equivalent to [`sys::ffi::playdate_sys::convertDateTimeToEpoch`] + #[doc(alias = "sys::ffi::playdate_sys::convertDateTimeToEpoch")] + #[inline(always)] + fn convert_date_time_to_epoch(&self) -> unsafe extern "C" fn(datetime: *mut PDDateTime) -> u32 { + *sys::api!(system.convertDateTimeToEpoch) + } + } +} diff --git a/api/system/src/time.rs b/api/system/src/time.rs new file mode 100644 index 00000000..cc23ce19 --- /dev/null +++ b/api/system/src/time.rs @@ -0,0 +1,37 @@ +pub use sys::ffi::PDDateTime; + +use crate::api::Api; + + +pub trait PDDateTimeExt { + fn to_epoch(&self) -> u32; + fn from_epoch(epoch: u32) -> Self; + + fn from_epoch_to(dt: &mut Self, epoch: u32); +} + + +impl PDDateTimeExt for PDDateTime { + fn to_epoch(&self) -> u32 { + let f = super::api::Default::default().convert_date_time_to_epoch(); + unsafe { f(self as *const _ as *mut _) } + } + + fn from_epoch(epoch: u32) -> Self { + let mut dt = PDDateTime { year: 0, + month: 0, + day: 0, + weekday: 0, + hour: 0, + minute: 0, + second: 0 }; + let f = super::api::Default::default().convert_epoch_to_date_time(); + unsafe { f(epoch, &mut dt) }; + dt + } + + fn from_epoch_to(dt: &mut Self, epoch: u32) { + let f = super::api::Default::default().convert_epoch_to_date_time(); + unsafe { f(epoch, dt) } + } +}