diff --git a/api/system/Cargo.toml b/api/system/Cargo.toml index 8318be1c..3ce5d410 100644 --- a/api/system/Cargo.toml +++ b/api/system/Cargo.toml @@ -46,6 +46,12 @@ crate-type = ["dylib", "staticlib"] path = "examples/handler-pinned.rs" required-features = ["sys/entry-point", "sys/lang-items"] +[[example]] +name = "set-serial-message-callback" +crate-type = ["dylib", "staticlib"] +path = "examples/set-serial-message-callback.rs" +required-features = ["sys/entry-point", "sys/lang-items"] + [package.metadata.playdate] bundle-id = "rs.playdate.system" diff --git a/api/system/examples/README.md b/api/system/examples/README.md index f9cb9853..0758d4a7 100644 --- a/api/system/examples/README.md +++ b/api/system/examples/README.md @@ -9,6 +9,8 @@ cargo playdate run -p=playdate-system --example=handler-static --features=sys/la cargo playdate run -p=playdate-system --example=handler-boxed --features=sys/lang-items,sys/entry-point cargo playdate run -p=playdate-system --example=handler-pinned --features=sys/lang-items,sys/entry-point + +cargo playdate run -p=playdate-system --example=set-serial-message-callback --features=sys/lang-items,sys/entry-point ``` More information how to use [cargo-playdate][] in help: `cargo playdate --help`. diff --git a/api/system/examples/set-serial-message-callback.rs b/api/system/examples/set-serial-message-callback.rs new file mode 100644 index 00000000..8699683c --- /dev/null +++ b/api/system/examples/set-serial-message-callback.rs @@ -0,0 +1,59 @@ +#![no_std] +extern crate alloc; + +#[macro_use] +extern crate sys; +extern crate playdate_system as system; + +use core::ptr::NonNull; + +use sys::EventLoopCtrl; +use sys::ffi::*; +use system::System; +use system::event::SystemEventExt as _; +use system::update::UpdateCtrl; + + +/// Entry point, event handler +#[no_mangle] +fn event_handler(_api: NonNull, event: PDSystemEvent, _: u32) -> EventLoopCtrl { + // Just for this example, ignore all events except init: + if event != PDSystemEvent::Init { + return EventLoopCtrl::Continue; + } + + + let mut counter: u32 = 0; + + let callback = move |msg| { + counter += 1; + + println!("[{counter}/3] serial_message_callback: '{}'", msg); + + if counter == 3 { + println!("stop receiving serial messages"); + System::Default().set_serial_message_callback(None::); + } + }; + + // Register callback to start receiving serial messages: + System::Default().set_serial_message_callback(Some(callback)); + + + // Also set update callback: + System::Default().set_update_callback_static(Some(on_update), ()); + + // Continue event-loop: + EventLoopCtrl::Continue +} + + +/// Update handler +fn on_update(_: &mut ()) -> UpdateCtrl { + // Continue updates + UpdateCtrl::Continue +} + + +// Needed for debug build +ll_symbols!(); diff --git a/api/system/src/lib.rs b/api/system/src/lib.rs index 68768162..c6c662d2 100644 --- a/api/system/src/lib.rs +++ b/api/system/src/lib.rs @@ -8,6 +8,8 @@ use core::ffi::c_float; use core::ffi::c_int; use core::ffi::c_uint; use core::time::Duration; +use alloc::string::String; + pub mod time; pub mod lang; @@ -247,10 +249,53 @@ impl System { let _ = dt; // this to prevent earlier drop. epoch } -} + /// Equivalent to [`sys::ffi::playdate_sys::setSerialMessageCallback`] + #[doc(alias = "sys::ffi::playdate_sys::setSerialMessageCallback")] + pub fn set_serial_message_callback(&self, callback: Option) + where F: 'static + FnMut(String) + Sized { + use core::ffi::c_char; + use core::ffi::CStr; + use alloc::boxed::Box; + use alloc::string::String; + + + static mut STORE: Option> = None; + + pub unsafe extern "C" fn proxy_serial_message_callback(data: *const c_char) { + let data = CStr::from_ptr(data as _).to_string_lossy().into_owned(); + if let Some(ref mut f) = STORE.as_mut() { + f(data) + } else { + // Highly unlikely, mostly impossible case. + // Should be unreachable, but still possible in case when + // 0. new callback is None, we have to register it in the System; + // 1. write callback to `STORE` + // 2. interrupt, proxy_serial_message_callback called, BOOM! + // 3. call API::set_serial_message_callback to set our new (None) callback + // So, see difference in how to store & reg callback at couple lines below. + panic!("missed callback") + } + } + + + let f = self.0.set_serial_message_callback(); + + if let Some(callback) = callback { + let boxed = Box::new(callback); + // Store firstly, then register it. + unsafe { STORE = Some(boxed as _) } + unsafe { f(Some(proxy_serial_message_callback::)) } + } else { + // Set firstly, then clear the `STORE`. + unsafe { f(None) } + unsafe { STORE = None } + } + } +} pub mod api { + use core::ffi::c_char; use core::ffi::c_float; use core::ffi::c_int; use core::ffi::c_uint; @@ -263,6 +308,9 @@ pub mod api { use sys::ffi::playdate_sys; + pub type FnSerialMessageCallback = Option; + + /// Default system api end-point, ZST. /// /// All calls approximately costs ~3 derefs. @@ -418,6 +466,13 @@ pub mod api { fn convert_date_time_to_epoch(&self) -> unsafe extern "C" fn(datetime: *mut PDDateTime) -> u32 { self.0.convertDateTimeToEpoch.expect("convertDateTimeToEpoch") } + + /// Equivalent to [`sys::ffi::playdate_sys::setSerialMessageCallback`] + #[doc(alias = "sys::ffi::playdate_sys::setSerialMessageCallback")] + #[inline(always)] + fn set_serial_message_callback(&self) -> unsafe extern "C" fn(callback: FnSerialMessageCallback) { + self.0.setSerialMessageCallback.expect("setSerialMessageCallback") + } } @@ -518,5 +573,12 @@ pub mod api { fn convert_date_time_to_epoch(&self) -> unsafe extern "C" fn(datetime: *mut PDDateTime) -> u32 { *sys::api!(system.convertDateTimeToEpoch) } + + /// Equivalent to [`sys::ffi::playdate_sys::setSerialMessageCallback`] + #[doc(alias = "sys::ffi::playdate_sys::setSerialMessageCallback")] + #[inline(always)] + fn set_serial_message_callback(&self) -> unsafe extern "C" fn(callback: FnSerialMessageCallback) { + *sys::api!(system.setSerialMessageCallback) + } } }