From c8418eb679bd41f86e17af1735cb4e5a0d3c233b Mon Sep 17 00:00:00 2001 From: Paul Young <84700+paulyoung@users.noreply.github.com> Date: Thu, 25 Apr 2024 00:33:51 -0700 Subject: [PATCH 1/4] Add binding to setSerialMessageCallback --- api/system/src/lib.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/api/system/src/lib.rs b/api/system/src/lib.rs index 68768162..1f4d4a07 100644 --- a/api/system/src/lib.rs +++ b/api/system/src/lib.rs @@ -4,6 +4,7 @@ extern crate sys; extern crate alloc; +use core::ffi::c_char; use core::ffi::c_float; use core::ffi::c_int; use core::ffi::c_uint; @@ -247,10 +248,25 @@ impl System { let _ = dt; // this to prevent earlier drop. epoch } -} + /// Equivalent to [`sys::ffi::playdate_sys::setSerialMessageCallback`] + #[doc(alias = "sys::ffi::playdate_sys::setSerialMessageCallback")] + // NOTE: ideally this would be similar to our implementation of `fs::read_dir` + // + // i.e. + // + // pub fn set_serial_message_callback(&self, callback: Fn) where Fn: FnMut(String) { + // + // but we can't do that because of + // https://devforum.play.date/t/feature-request-add-a-userdata-argument-to-setserialmessagecallback/17333 + pub fn set_serial_message_callback(&self, callback: Option) { + let f = self.0.set_serial_message_callback(); + unsafe { f(callback) } + } +} 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 +279,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 +437,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 +544,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) + } } } From af6962698864bbde31b9e704a4d1eb2e27a9ff81 Mon Sep 17 00:00:00 2001 From: Paul Young <84700+paulyoung@users.noreply.github.com> Date: Thu, 25 Apr 2024 01:28:23 -0700 Subject: [PATCH 2/4] Add example --- api/system/Cargo.toml | 6 +++ api/system/examples/README.md | 2 + .../examples/set-serial-message-callback.rs | 46 +++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 api/system/examples/set-serial-message-callback.rs 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..d6bc61d2 --- /dev/null +++ b/api/system/examples/set-serial-message-callback.rs @@ -0,0 +1,46 @@ +#![no_std] +extern crate alloc; + +#[macro_use] +extern crate sys; +extern crate playdate_system as system; + +use core::ffi::c_char; +use core::ffi::CStr; +use core::ptr::NonNull; + +use sys::EventLoopCtrl; +use sys::ffi::*; +use system::System; +use system::event::SystemEventExt as _; +use system::update::UpdateCtrl; + + +pub unsafe extern "C" fn serial_message_callback(data: *const c_char) { + let data = CStr::from_ptr(data as _).to_string_lossy().into_owned(); + println!("serial_message_callback: {}", data); +} + +/// Entry point, event handler +#[no_mangle] +fn event_handler(_api: NonNull, event: PDSystemEvent, _: u32) -> EventLoopCtrl { + if event == PDSystemEvent::Init { + System::Default().set_serial_message_callback(Some(serial_message_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!(); From c6ec4474a39c4ddd443abf1b37d4181f7d07f69f Mon Sep 17 00:00:00 2001 From: Paul Young <84700+paulyoung@users.noreply.github.com> Date: Thu, 25 Apr 2024 19:48:21 -0700 Subject: [PATCH 3/4] Workaround lack of userdata arg with typed storage --- Cargo.lock | 1 + api/system/Cargo.toml | 4 +++ .../examples/set-serial-message-callback.rs | 9 +------ api/system/src/lib.rs | 22 ++++++++-------- api/system/src/storage.rs | 26 +++++++++++++++++++ 5 files changed, 43 insertions(+), 19 deletions(-) create mode 100644 api/system/src/storage.rs diff --git a/Cargo.lock b/Cargo.lock index 5c42d894..cd6818f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4274,6 +4274,7 @@ dependencies = [ name = "playdate-system" version = "0.3.10" dependencies = [ + "erased_set", "playdate-sys", ] diff --git a/api/system/Cargo.toml b/api/system/Cargo.toml index 3ce5d410..993d906a 100644 --- a/api/system/Cargo.toml +++ b/api/system/Cargo.toml @@ -23,6 +23,10 @@ bindgen-static = ["sys/bindgen-static"] bindings-derive-debug = ["sys/bindings-derive-debug"] +[dependencies] +erased_set = "0.8" + + [dependencies.sys] workspace = true default-features = false diff --git a/api/system/examples/set-serial-message-callback.rs b/api/system/examples/set-serial-message-callback.rs index d6bc61d2..85188126 100644 --- a/api/system/examples/set-serial-message-callback.rs +++ b/api/system/examples/set-serial-message-callback.rs @@ -5,8 +5,6 @@ extern crate alloc; extern crate sys; extern crate playdate_system as system; -use core::ffi::c_char; -use core::ffi::CStr; use core::ptr::NonNull; use sys::EventLoopCtrl; @@ -16,16 +14,11 @@ use system::event::SystemEventExt as _; use system::update::UpdateCtrl; -pub unsafe extern "C" fn serial_message_callback(data: *const c_char) { - let data = CStr::from_ptr(data as _).to_string_lossy().into_owned(); - println!("serial_message_callback: {}", data); -} - /// Entry point, event handler #[no_mangle] fn event_handler(_api: NonNull, event: PDSystemEvent, _: u32) -> EventLoopCtrl { if event == PDSystemEvent::Init { - System::Default().set_serial_message_callback(Some(serial_message_callback)); + System::Default().set_serial_message_callback(|data| println!("serial_message_callback: {}", data)); } System::Default().set_update_callback_static(Some(on_update), ()); diff --git a/api/system/src/lib.rs b/api/system/src/lib.rs index 1f4d4a07..63992b6a 100644 --- a/api/system/src/lib.rs +++ b/api/system/src/lib.rs @@ -4,11 +4,14 @@ extern crate sys; extern crate alloc; -use core::ffi::c_char; use core::ffi::c_float; use core::ffi::c_int; use core::ffi::c_uint; use core::time::Duration; +use alloc::string::String; + +mod storage; +use storage::*; pub mod time; pub mod lang; @@ -251,17 +254,14 @@ impl System { /// Equivalent to [`sys::ffi::playdate_sys::setSerialMessageCallback`] #[doc(alias = "sys::ffi::playdate_sys::setSerialMessageCallback")] - // NOTE: ideally this would be similar to our implementation of `fs::read_dir` - // - // i.e. - // - // pub fn set_serial_message_callback(&self, callback: Fn) where Fn: FnMut(String) { - // - // but we can't do that because of - // https://devforum.play.date/t/feature-request-add-a-userdata-argument-to-setserialmessagecallback/17333 - pub fn set_serial_message_callback(&self, callback: Option) { + pub fn set_serial_message_callback(&self, callback: F) + where F: FnMut(String), + F: 'static + Send { + init_store(); + unsafe { STORE.as_mut() }.expect("impossible") + .insert::(callback); let f = self.0.set_serial_message_callback(); - unsafe { f(callback) } + unsafe { f(Some(proxy_serial_message_callback::)) } } } diff --git a/api/system/src/storage.rs b/api/system/src/storage.rs new file mode 100644 index 00000000..77796e1b --- /dev/null +++ b/api/system/src/storage.rs @@ -0,0 +1,26 @@ +use core::ffi::c_char; +use core::ffi::CStr; +use alloc::string::String; +use erased_set::ErasedSendSet; + +pub static mut STORE: Option = None; + +pub fn init_store() { + if unsafe { STORE.is_none() } { + unsafe { STORE = Some(ErasedSendSet::new()) } + } +} + +pub fn clean_store() { + if let Some(true) = unsafe { STORE.as_mut() }.map(|store| store.is_empty()) { + unsafe { STORE = 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(); + let f = unsafe { STORE.as_mut() }.map(|store| store.remove::()) + .flatten(); + f.map(|mut f| f(data)).or_else(|| panic!("missed callback")); + clean_store(); +} From c76c377f25c8989de0086c2bf30b37baa8c7e768 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Mon, 29 Apr 2024 20:02:57 +0400 Subject: [PATCH 4/4] Simple `set_serial_message_callback` --- Cargo.lock | 1 - api/system/Cargo.toml | 4 -- .../examples/set-serial-message-callback.rs | 24 +++++++++- api/system/src/lib.rs | 47 +++++++++++++++---- api/system/src/storage.rs | 26 ---------- 5 files changed, 60 insertions(+), 42 deletions(-) delete mode 100644 api/system/src/storage.rs diff --git a/Cargo.lock b/Cargo.lock index cd6818f2..5c42d894 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4274,7 +4274,6 @@ dependencies = [ name = "playdate-system" version = "0.3.10" dependencies = [ - "erased_set", "playdate-sys", ] diff --git a/api/system/Cargo.toml b/api/system/Cargo.toml index 993d906a..3ce5d410 100644 --- a/api/system/Cargo.toml +++ b/api/system/Cargo.toml @@ -23,10 +23,6 @@ bindgen-static = ["sys/bindgen-static"] bindings-derive-debug = ["sys/bindings-derive-debug"] -[dependencies] -erased_set = "0.8" - - [dependencies.sys] workspace = true default-features = false diff --git a/api/system/examples/set-serial-message-callback.rs b/api/system/examples/set-serial-message-callback.rs index 85188126..8699683c 100644 --- a/api/system/examples/set-serial-message-callback.rs +++ b/api/system/examples/set-serial-message-callback.rs @@ -17,10 +17,30 @@ use system::update::UpdateCtrl; /// Entry point, event handler #[no_mangle] fn event_handler(_api: NonNull, event: PDSystemEvent, _: u32) -> EventLoopCtrl { - if event == PDSystemEvent::Init { - System::Default().set_serial_message_callback(|data| println!("serial_message_callback: {}", data)); + // 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: diff --git a/api/system/src/lib.rs b/api/system/src/lib.rs index 63992b6a..c6c662d2 100644 --- a/api/system/src/lib.rs +++ b/api/system/src/lib.rs @@ -10,8 +10,6 @@ use core::ffi::c_uint; use core::time::Duration; use alloc::string::String; -mod storage; -use storage::*; pub mod time; pub mod lang; @@ -254,14 +252,45 @@ impl System { /// Equivalent to [`sys::ffi::playdate_sys::setSerialMessageCallback`] #[doc(alias = "sys::ffi::playdate_sys::setSerialMessageCallback")] - pub fn set_serial_message_callback(&self, callback: F) - where F: FnMut(String), - F: 'static + Send { - init_store(); - unsafe { STORE.as_mut() }.expect("impossible") - .insert::(callback); + 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(); - unsafe { f(Some(proxy_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 } + } } } diff --git a/api/system/src/storage.rs b/api/system/src/storage.rs deleted file mode 100644 index 77796e1b..00000000 --- a/api/system/src/storage.rs +++ /dev/null @@ -1,26 +0,0 @@ -use core::ffi::c_char; -use core::ffi::CStr; -use alloc::string::String; -use erased_set::ErasedSendSet; - -pub static mut STORE: Option = None; - -pub fn init_store() { - if unsafe { STORE.is_none() } { - unsafe { STORE = Some(ErasedSendSet::new()) } - } -} - -pub fn clean_store() { - if let Some(true) = unsafe { STORE.as_mut() }.map(|store| store.is_empty()) { - unsafe { STORE = 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(); - let f = unsafe { STORE.as_mut() }.map(|store| store.remove::()) - .flatten(); - f.map(|mut f| f(data)).or_else(|| panic!("missed callback")); - clean_store(); -}