1
1
//! Furi Thread API.
2
2
3
+ use core:: {
4
+ ffi:: { c_void, CStr } ,
5
+ fmt, str,
6
+ } ;
7
+
8
+ #[ cfg( feature = "alloc" ) ]
9
+ use alloc:: {
10
+ boxed:: Box ,
11
+ ffi:: { CString , NulError } ,
12
+ string:: String ,
13
+ } ;
14
+
3
15
use flipperzero_sys as sys;
4
16
17
+ const MIN_STACK_SIZE : usize = 1024 ;
18
+
19
+ /// Thread factory, which can be used in order to configure the properties of a new thread.
20
+ #[ cfg( feature = "alloc" ) ]
21
+ pub struct Builder {
22
+ /// Guaranteed to be UTF-8.
23
+ name : Option < CString > ,
24
+ stack_size : Option < usize > ,
25
+ }
26
+
27
+ #[ cfg( feature = "alloc" ) ]
28
+ impl Builder {
29
+ /// Generates the base configuration for spawning a thread, from which configuration
30
+ /// methods can be chained.
31
+ pub fn new ( ) -> Self {
32
+ Self {
33
+ name : None ,
34
+ stack_size : None ,
35
+ }
36
+ }
37
+
38
+ /// Names the thread-to-be.
39
+ ///
40
+ /// Returns an error if the name contains null bytes (`\0`).
41
+ pub fn name ( mut self , name : String ) -> Result < Self , NulError > {
42
+ CString :: new ( name) . map ( |name| {
43
+ self . name = Some ( name) ;
44
+ self
45
+ } )
46
+ }
47
+
48
+ /// Sets the size of the stack (in bytes) for the new thread.
49
+ pub fn stack_size ( mut self , size : usize ) -> Self {
50
+ self . stack_size = Some ( size) ;
51
+ self
52
+ }
53
+
54
+ /// Spawns a new thread by taking ownership of the `Builder`, and returns its
55
+ /// [`JoinHandle`].
56
+ pub fn spawn < F > ( self , f : F ) -> JoinHandle
57
+ where
58
+ F : FnOnce ( ) -> i32 ,
59
+ F : Send + ' static ,
60
+ {
61
+ let Builder { name, stack_size } = self ;
62
+ let thread = Thread :: new ( name, stack_size) ;
63
+
64
+ // We need to box twice because trait objects are fat pointers, so we need the
65
+ // second box to obtain a thin pointer to use as the context.
66
+ type ThreadBody = Box < dyn FnOnce ( ) -> i32 > ;
67
+ let thread_body: Box < ThreadBody > = Box :: new ( Box :: new ( f) ) ;
68
+ unsafe extern "C" fn run_thread_body ( context : * mut c_void ) -> i32 {
69
+ let thread_body = unsafe { Box :: from_raw ( context as * mut ThreadBody ) } ;
70
+ thread_body ( )
71
+ }
72
+
73
+ let callback: sys:: FuriThreadCallback = Some ( run_thread_body) ;
74
+ let context = Box :: into_raw ( thread_body) ;
75
+ unsafe {
76
+ sys:: furi_thread_set_callback ( thread. thread , callback) ;
77
+ sys:: furi_thread_set_context ( thread. thread , context as * mut c_void ) ;
78
+ sys:: furi_thread_start ( thread. thread ) ;
79
+ }
80
+
81
+ JoinHandle ( thread)
82
+ }
83
+ }
84
+
85
+ /// Spawns a new thread, returning a [`JoinHandle`] for it.
86
+ ///
87
+ /// This call will create a thread using default parameters of [`Builder`]. If you want to
88
+ /// specify the stack size or the name of the thread, use that API instead.
89
+ #[ cfg( feature = "alloc" ) ]
90
+ pub fn spawn < F > ( f : F ) -> JoinHandle
91
+ where
92
+ F : FnOnce ( ) -> i32 ,
93
+ F : Send + ' static ,
94
+ {
95
+ Builder :: new ( ) . spawn ( f)
96
+ }
97
+
98
+ /// Gets a handle to the thread that invokes it.
99
+ #[ cfg( feature = "alloc" ) ]
100
+ pub fn current ( ) -> Thread {
101
+ use alloc:: borrow:: ToOwned ;
102
+
103
+ let thread = unsafe { sys:: furi_thread_get_current ( ) } ;
104
+
105
+ let name = {
106
+ let name = unsafe { sys:: furi_thread_get_name ( sys:: furi_thread_get_current_id ( ) ) } ;
107
+ ( !name. is_null ( ) )
108
+ . then ( || {
109
+ // SAFETY: The Flipper Zero firmware ensures that all thread names have a
110
+ // null terminator.
111
+ unsafe { CStr :: from_ptr ( name) } . to_owned ( )
112
+ } )
113
+ . and_then ( |name| {
114
+ // Ensure that the name is valid UTF-8. This will be true for threads
115
+ // created via `Builder`, but may not be true for the current thread.
116
+ name. to_str ( ) . is_ok ( ) . then_some ( name)
117
+ } )
118
+ } ;
119
+
120
+ Thread { name, thread }
121
+ }
122
+
123
+ /// Cooperatively gives up a timeslice to the OS scheduler.
124
+ pub fn yield_now ( ) {
125
+ unsafe { sys:: furi_thread_yield ( ) } ;
126
+ }
127
+
5
128
/// Puts the current thread to sleep for at least the specified amount of time.
6
129
pub fn sleep ( duration : core:: time:: Duration ) {
7
130
unsafe {
@@ -13,3 +136,132 @@ pub fn sleep(duration: core::time::Duration) {
13
136
}
14
137
}
15
138
}
139
+
140
+ /// A unique identifier for a running thread.
141
+ #[ cfg( feature = "alloc" ) ]
142
+ pub struct ThreadId ( sys:: FuriThreadId ) ;
143
+
144
+ /// A handle to a thread.
145
+ #[ cfg( feature = "alloc" ) ]
146
+ pub struct Thread {
147
+ /// Guaranteed to be UTF-8.
148
+ name : Option < CString > ,
149
+ thread : * mut sys:: FuriThread ,
150
+ }
151
+
152
+ #[ cfg( feature = "alloc" ) ]
153
+ impl Drop for Thread {
154
+ fn drop ( & mut self ) {
155
+ // TODO: is this safe to do while the thread is running?
156
+ unsafe { sys:: furi_thread_free ( self . thread ) } ;
157
+ }
158
+ }
159
+
160
+ #[ cfg( feature = "alloc" ) ]
161
+ impl Thread {
162
+ fn new ( name : Option < CString > , stack_size : Option < usize > ) -> Self {
163
+ let stack_size = stack_size. unwrap_or ( MIN_STACK_SIZE ) ;
164
+
165
+ unsafe {
166
+ let thread = sys:: furi_thread_alloc ( ) ;
167
+ if let Some ( name) = name. as_deref ( ) {
168
+ sys:: furi_thread_set_name ( thread, name. as_ptr ( ) ) ;
169
+ }
170
+ sys:: furi_thread_set_stack_size ( thread, stack_size) ;
171
+ Thread { name, thread }
172
+ }
173
+ }
174
+
175
+ /// Gets the thread's unique identifier.
176
+ ///
177
+ /// Returns `None` if the thread has terminated.
178
+ pub fn id ( & self ) -> Option < ThreadId > {
179
+ // TODO: The Rust stdlib generates its own unique IDs for threads that are valid
180
+ // even after a thread terminates.
181
+ let id = unsafe { sys:: furi_thread_get_id ( self . thread ) } ;
182
+ if id. is_null ( ) {
183
+ None
184
+ } else {
185
+ Some ( ThreadId ( id) )
186
+ }
187
+ }
188
+
189
+ /// Gets the thread's name.
190
+ ///
191
+ /// Returns `None` if the thread has terminated, or is unnamed, or has a name that is
192
+ /// not valid UTF-8.
193
+ pub fn name ( & self ) -> Option < & str > {
194
+ self . cname ( )
195
+ . map ( |s| unsafe { str:: from_utf8_unchecked ( s. to_bytes ( ) ) } )
196
+ }
197
+
198
+ fn cname ( & self ) -> Option < & CStr > {
199
+ self . name . as_deref ( )
200
+ }
201
+ }
202
+
203
+ #[ cfg( feature = "alloc" ) ]
204
+ impl fmt:: Debug for Thread {
205
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
206
+ f. debug_struct ( "Thread" )
207
+ . field ( "name" , & self . name ( ) )
208
+ . finish_non_exhaustive ( )
209
+ }
210
+ }
211
+
212
+ #[ cfg( feature = "alloc" ) ]
213
+ impl ufmt:: uDebug for Thread {
214
+ fn fmt < W > ( & self , f : & mut ufmt:: Formatter < ' _ , W > ) -> Result < ( ) , W :: Error >
215
+ where
216
+ W : ufmt:: uWrite + ?Sized ,
217
+ {
218
+ // TODO: ufmt doesn't provide an impl of uDebug for &str.
219
+ f. debug_struct ( "Thread" ) ?. finish ( )
220
+ }
221
+ }
222
+
223
+ /// An owned permission to join on a thread (block on its termination).
224
+ #[ cfg( feature = "alloc" ) ]
225
+ pub struct JoinHandle ( Thread ) ;
226
+
227
+ #[ cfg( feature = "alloc" ) ]
228
+ impl JoinHandle {
229
+ /// Extracts a handle to the underlying thread.
230
+ pub fn thread ( & self ) -> & Thread {
231
+ & self . 0
232
+ }
233
+
234
+ /// Waits for the associated thread to finish.
235
+ ///
236
+ /// This function will return immediately if the associated thread has already
237
+ /// finished.
238
+ pub fn join ( self ) -> i32 {
239
+ let JoinHandle ( Thread { thread, .. } ) = self ;
240
+ unsafe {
241
+ sys:: furi_thread_join ( thread) ;
242
+ sys:: furi_thread_get_return_code ( thread)
243
+ }
244
+ }
245
+
246
+ /// Checks if the associated thread has finished running its main function.
247
+ pub fn is_finished ( & self ) -> bool {
248
+ self . 0 . id ( ) . is_none ( )
249
+ }
250
+ }
251
+
252
+ #[ cfg( feature = "alloc" ) ]
253
+ impl fmt:: Debug for JoinHandle {
254
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
255
+ f. debug_struct ( "JoinHandle" ) . finish_non_exhaustive ( )
256
+ }
257
+ }
258
+
259
+ #[ cfg( feature = "alloc" ) ]
260
+ impl ufmt:: uDebug for JoinHandle {
261
+ fn fmt < W > ( & self , f : & mut ufmt:: Formatter < ' _ , W > ) -> Result < ( ) , W :: Error >
262
+ where
263
+ W : ufmt:: uWrite + ?Sized ,
264
+ {
265
+ f. debug_struct ( "JoinHandle" ) ?. finish ( )
266
+ }
267
+ }
0 commit comments