use core::time;
#[cfg(feature = "alloc")]
use core::{
ffi::{CStr, c_void},
fmt,
ptr::NonNull,
str,
};
#[cfg(feature = "alloc")]
use alloc::{
boxed::Box,
ffi::{CString, NulError},
string::String,
sync::Arc,
};
use flipperzero_sys::{self as sys, FuriFlagNoClear, FuriFlagWaitAll, FuriFlagWaitAny, HasFlag};
use crate::furi::time::FuriDuration;
#[cfg(feature = "alloc")]
const MIN_STACK_SIZE: usize = 1024;
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub struct Builder {
name: Option<CString>,
stack_size: Option<usize>,
heap_trace_enabled: Option<bool>,
}
#[cfg(feature = "alloc")]
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "alloc")]
impl Builder {
pub fn new() -> Self {
Self {
name: None,
stack_size: None,
heap_trace_enabled: None,
}
}
pub fn name(mut self, name: String) -> Result<Self, NulError> {
CString::new(name).map(|name| {
self.name = Some(name);
self
})
}
pub fn stack_size(mut self, size: usize) -> Self {
self.stack_size = Some(size);
self
}
pub fn enable_heap_trace(mut self) -> Self {
self.heap_trace_enabled = Some(true);
self
}
pub fn spawn<F>(self, f: F) -> JoinHandle
where
F: FnOnce() -> i32,
F: Send + 'static,
{
let Builder {
name,
stack_size,
heap_trace_enabled,
} = self;
#[allow(clippy::arc_with_non_send_sync)] let thread = Arc::new(Thread::new(name, stack_size, heap_trace_enabled));
type ThreadBody = Box<dyn FnOnce() -> i32>;
let thread_body: Box<ThreadBody> = Box::new(Box::new(f));
unsafe extern "C" fn run_thread_body(context: *mut c_void) -> i32 {
let thread_body = unsafe { Box::from_raw(context as *mut ThreadBody) };
thread_body()
}
let callback: sys::FuriThreadCallback = Some(run_thread_body);
let context = Box::into_raw(thread_body);
unsafe extern "C" fn run_state_callback(
_thread: *mut sys::FuriThread,
state: sys::FuriThreadState,
context: *mut c_void,
) {
if state == sys::FuriThreadStateStopped {
let context = unsafe { Arc::from_raw(context as *mut Thread) };
if let Some(thread) = Arc::into_inner(context) {
unsafe { sys::furi_thread_free(thread.thread.as_ptr()) };
}
}
}
let state_callback: sys::FuriThreadStateCallback = Some(run_state_callback);
let state_context = Arc::into_raw(thread.clone());
unsafe {
sys::furi_thread_set_callback(thread.thread.as_ptr(), callback);
sys::furi_thread_set_context(thread.thread.as_ptr(), context as *mut c_void);
sys::furi_thread_set_state_callback(thread.thread.as_ptr(), state_callback);
sys::furi_thread_set_state_context(
thread.thread.as_ptr(),
state_context as *mut c_void,
);
sys::furi_thread_start(thread.thread.as_ptr());
}
JoinHandle {
context: Some(thread),
}
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn spawn<F>(f: F) -> JoinHandle
where
F: FnOnce() -> i32,
F: Send + 'static,
{
Builder::new().spawn(f)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn current() -> Thread {
use alloc::borrow::ToOwned;
let thread = NonNull::new(unsafe { sys::furi_thread_get_current() })
.expect("furi_thread_get_current shouldn't return null");
let name = {
let name = unsafe { sys::furi_thread_get_name(sys::furi_thread_get_current_id()) };
(!name.is_null())
.then(|| {
unsafe { CStr::from_ptr(name) }.to_owned()
})
.and_then(|name| {
name.to_str().is_ok().then_some(name)
})
};
Thread { name, thread }
}
pub fn yield_now() {
unsafe { sys::furi_thread_yield() };
}
pub fn sleep(duration: core::time::Duration) {
if duration > time::Duration::from_millis(u32::MAX as u64) {
panic!("sleep exceeds maximum supported duration")
}
unsafe {
if duration < time::Duration::from_secs(3600) {
sys::furi_delay_us(duration.as_micros() as u32);
} else {
sys::furi_delay_ms(duration.as_millis() as u32);
}
}
}
pub fn sleep_ticks(duration: FuriDuration) {
unsafe {
sys::furi_delay_tick(duration.as_ticks());
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
pub struct ThreadId(sys::FuriThreadId);
impl ThreadId {
pub fn current() -> Self {
ThreadId(unsafe { sys::furi_thread_get_current_id() })
}
pub unsafe fn from_furi_thread(thread: *mut sys::FuriThread) -> ThreadId {
ThreadId(unsafe { sys::furi_thread_get_id(thread) })
}
}
pub fn set_flags(thread_id: ThreadId, flags: u32) -> Result<u32, sys::furi::Status> {
let result = unsafe { sys::furi_thread_flags_set(thread_id.0, flags) };
if sys::FuriFlag(result).has_flag(sys::FuriFlagError) {
return Err((result as i32).into());
}
Ok(result)
}
pub fn clear_flags(flags: u32) -> Result<u32, sys::furi::Status> {
let result = unsafe { sys::furi_thread_flags_clear(flags) };
if sys::FuriFlag(result).has_flag(sys::FuriFlagError) {
return Err((result as i32).into());
}
Ok(result)
}
pub fn get_flags() -> Result<u32, sys::furi::Status> {
let result = unsafe { sys::furi_thread_flags_get() };
if sys::FuriFlag(result).has_flag(sys::FuriFlagError) {
return Err((result as i32).into());
}
Ok(result)
}
pub fn wait_any_flags(
flags: u32,
clear: bool,
timeout: FuriDuration,
) -> Result<u32, sys::furi::Status> {
let options = FuriFlagWaitAny.0 | (if clear { 0 } else { FuriFlagNoClear.0 });
let result = unsafe { sys::furi_thread_flags_wait(flags, options, timeout.0) };
if sys::FuriFlag(result).has_flag(sys::FuriFlagError) {
return Err((result as i32).into());
}
Ok(result)
}
pub fn wait_all_flags(
flags: u32,
clear: bool,
timeout: FuriDuration,
) -> Result<u32, sys::furi::Status> {
let options = FuriFlagWaitAll.0 | (if clear { 0 } else { FuriFlagNoClear.0 });
let result = unsafe { sys::furi_thread_flags_wait(flags, options, timeout.0) };
if sys::FuriFlag(result).has_flag(sys::FuriFlagError) {
return Err((result as i32).into());
}
Ok(result)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub struct Thread {
name: Option<CString>,
thread: NonNull<sys::FuriThread>,
}
#[cfg(feature = "alloc")]
impl Thread {
fn new(
name: Option<CString>,
stack_size: Option<usize>,
heap_trace_enabled: Option<bool>,
) -> Self {
let stack_size = stack_size.unwrap_or(MIN_STACK_SIZE);
unsafe {
let thread = sys::furi_thread_alloc();
if let Some(name) = name.as_deref() {
sys::furi_thread_set_name(thread, name.as_ptr());
}
sys::furi_thread_set_stack_size(thread, stack_size);
if let Some(heap_trace_enabled) = heap_trace_enabled {
if heap_trace_enabled {
sys::furi_thread_enable_heap_trace(thread);
}
}
Thread {
name,
thread: NonNull::new_unchecked(thread),
}
}
}
pub fn id(&self) -> Option<ThreadId> {
let id = unsafe { sys::furi_thread_get_id(self.thread.as_ptr()) };
if id.is_null() {
None
} else {
Some(ThreadId(id))
}
}
pub fn name(&self) -> Option<&str> {
self.cname()
.map(|s| unsafe { str::from_utf8_unchecked(s.to_bytes()) })
}
fn cname(&self) -> Option<&CStr> {
self.name.as_deref()
}
}
#[cfg(feature = "alloc")]
impl fmt::Debug for Thread {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Thread")
.field("name", &self.name())
.finish_non_exhaustive()
}
}
#[cfg(feature = "alloc")]
impl ufmt::uDebug for Thread {
fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
where
W: ufmt::uWrite + ?Sized,
{
f.debug_struct("Thread")?.finish()
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub struct JoinHandle {
context: Option<Arc<Thread>>,
}
#[cfg(feature = "alloc")]
impl Drop for JoinHandle {
fn drop(&mut self) {
let context = self
.context
.take()
.expect("Drop should only be called once");
if let Some(thread) = Arc::into_inner(context) {
unsafe { sys::furi_thread_free(thread.thread.as_ptr()) };
}
}
}
#[cfg(feature = "alloc")]
impl JoinHandle {
pub fn thread(&self) -> &Thread {
self.context.as_ref().expect("Drop has not been called")
}
pub fn join(self) -> i32 {
let thread = self.thread();
unsafe {
sys::furi_thread_join(thread.thread.as_ptr());
sys::furi_thread_get_return_code(thread.thread.as_ptr())
}
}
pub fn is_finished(&self) -> bool {
self.thread().id().is_none()
}
}
#[cfg(feature = "alloc")]
impl fmt::Debug for JoinHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("JoinHandle").finish_non_exhaustive()
}
}
#[cfg(feature = "alloc")]
impl ufmt::uDebug for JoinHandle {
fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
where
W: ufmt::uWrite + ?Sized,
{
f.debug_struct("JoinHandle")?.finish()
}
}