1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
//! Thread spawning, joining, and detaching.
use crate::{
    alloc::CBox,
    core::{
        cstr::nstd_core_cstr_get_null,
        optional::{gen_optional, NSTDOptional},
        result::NSTDResult,
        str::{nstd_core_str_as_cstr, NSTDOptionalStr, NSTDStr},
        time::NSTDDuration,
    },
    heap_ptr::NSTDOptionalHeapPtr,
    io::NSTDIOError,
    NSTDBool, NSTDUInt,
};
use nstdapi::nstdapi;
use std::thread::{Builder, JoinHandle, Thread, ThreadId};

/// Represents a running thread.
#[nstdapi]
pub struct NSTDThread {
    /// The thread join handle.
    thread: CBox<JoinHandle<NSTDThreadResult>>,
}
gen_optional!(NSTDOptionalThread, NSTDThread);

/// A handle to a running thread.
#[nstdapi]
pub struct NSTDThreadHandle {
    /// A handle to the thread.
    handle: CBox<Thread>,
}

/// A thread's unique identifier.
#[nstdapi]
pub struct NSTDThreadID {
    /// The thread ID.
    id: CBox<ThreadId>,
}

/// Describes the creation of a new thread.
///
/// This type is passed to the `nstd_thread_spawn_with_desc` function.
#[nstdapi]
#[derive(Clone, Copy)]
pub struct NSTDThreadDescriptor {
    /// The name of the thread.
    ///
    /// If present, this must not contain any null bytes.
    pub name: NSTDOptionalStr,
    /// The number of bytes that the thread's stack should have.
    ///
    /// Set this to 0 to let the host decide how much stack memory should be allocated.
    pub stack_size: NSTDUInt,
}

/// A thread function's return value.
pub type NSTDThreadResult = NSTDOptionalHeapPtr<'static>;

/// Returned from `nstd_thread_join`, contains the thread function's return value on success.
pub type NSTDOptionalThreadResult = NSTDOptional<NSTDThreadResult>;

/// Returned from `nstd_thread_count`, contains the number of threads detected on the system on
/// success.
pub type NSTDThreadCountResult = NSTDResult<NSTDUInt, NSTDIOError>;

/// Spawns a new thread executing the function `thread_fn` and returns a handle to the new thread.
///
/// # Parameters:
///
/// - `NSTDThreadResult (*thread_fn)(NSTDOptionalHeapPtr)` - The thread function.
///
/// - `NSTDOptionalHeapPtr data` - Data to send to the thread.
///
/// - `const NSTDThreadDescriptor *desc` - The thread descriptor. This value may be null.
///
/// # Returns
///
/// `NSTDOptionalThread thread` - A handle to the new thread on success, or an uninitialized "none"
/// variant on error.
///
/// # Safety
///
/// - The caller of this function must guarantee that `thread_fn` is a valid function pointer.
///
/// - This operation can cause undefined behavior if `desc.name`'s data is invalid.
///
/// - The data type that `data` holds must be able to be safely sent between threads.
///
/// # Example
///
/// ```
/// use nstd_sys::{
///     core::optional::NSTDOptional,
///     heap_ptr::NSTDOptionalHeapPtr,
///     thread::{nstd_thread_join, nstd_thread_spawn, NSTDThreadResult},
/// };
///
/// unsafe extern "C" fn thread_fn(data: NSTDOptionalHeapPtr) -> NSTDThreadResult {
///     NSTDOptional::None
/// }
///
/// let thread = unsafe { nstd_thread_spawn(thread_fn, NSTDOptional::None, None) };
/// if let NSTDOptional::Some(thread) = thread {
///     if let NSTDOptional::Some(ret) = unsafe { nstd_thread_join(thread) } {
///         if let NSTDOptional::Some(_) = ret {
///             panic!("this shouldn't be here");
///         }
///     }
/// }
/// ```
#[nstdapi]
pub unsafe fn nstd_thread_spawn(
    #[cfg(not(feature = "capi"))] thread_fn: unsafe fn(NSTDOptionalHeapPtr<'_>) -> NSTDThreadResult,
    #[cfg(feature = "capi")] thread_fn: unsafe extern "C" fn(
        NSTDOptionalHeapPtr<'_>,
    ) -> NSTDThreadResult,
    data: NSTDOptionalHeapPtr<'static>,
    desc: Option<&NSTDThreadDescriptor>,
) -> NSTDOptionalThread {
    // Create the thread builder.
    let mut builder = Builder::new();
    if let Some(desc) = desc {
        // Set the thread name.
        if let NSTDOptional::Some(name) = &desc.name {
            // Make sure `name` doesn't contain any null bytes.
            let c_name = nstd_core_str_as_cstr(name);
            if !nstd_core_cstr_get_null(&c_name).is_null() {
                return NSTDOptional::None;
            }
            builder = builder.name(name.as_str().to_string());
        }
        // Set the thread stack size.
        if desc.stack_size != 0 {
            builder = builder.stack_size(desc.stack_size);
        }
    }
    // Spawn the new thread.
    if let Ok(thread) = builder.spawn(move || thread_fn(data)) {
        if let Some(thread) = CBox::new(thread) {
            return NSTDOptional::Some(NSTDThread { thread });
        }
    }
    NSTDOptional::None
}

/// Returns a handle to the calling thread.
///
/// # Returns
///
/// `NSTDThreadHandle handle` - A handle to the current thread.
///
/// # Panics
///
/// Panics if allocating for the thread handle fails.
#[inline]
#[nstdapi]
pub fn nstd_thread_current() -> NSTDThreadHandle {
    NSTDThreadHandle {
        handle: CBox::new(std::thread::current()).expect("failed to allocate for a thread handle"),
    }
}

/// Retrieves a raw handle to a thread.
///
/// # Parameters:
///
/// - `const NSTDThread *thread` - A handle to the thread.
///
/// # Returns
///
/// `NSTDThreadHandle handle` - A raw handle to the thread.
///
/// # Panics
///
/// Panics if allocating for the thread handle fails.
#[inline]
#[nstdapi]
pub fn nstd_thread_handle(thread: &NSTDThread) -> NSTDThreadHandle {
    NSTDThreadHandle {
        handle: CBox::new(thread.thread.thread().clone())
            .expect("failed to allocate for a thread handle"),
    }
}

/// Checks if a thread has finished running.
///
/// # Parameters:
///
/// - `const NSTDThread *thread` - A handle to the thread.
///
/// # Returns
///
/// `NSTDBool is_finished` - True if the thread associated with the handle has finished executing.
#[inline]
#[nstdapi]
pub fn nstd_thread_is_finished(thread: &NSTDThread) -> NSTDBool {
    thread.thread.is_finished()
}

/// Joins a thread by it's handle.
///
/// # Parameters:
///
/// - `NSTDThread thread` - The thread handle.
///
/// # Returns
///
/// `NSTDOptionalThreadResult errc` - The thread function's return code, or none if joining the
/// thread fails.
///
/// # Safety
///
/// The data type that the thread function returns must be able to be safely sent between threads.
#[inline]
#[nstdapi]
pub unsafe fn nstd_thread_join(thread: NSTDThread) -> NSTDOptionalThreadResult {
    thread
        .thread
        .into_inner()
        .join()
        .map_or(NSTDOptional::None, NSTDOptional::Some)
}

/// Detaches a thread from it's handle, allowing it to run in the background.
///
/// # Parameters:
///
/// - `NSTDThread thread` - The thread handle.
#[inline]
#[nstdapi]
#[allow(
    unused_variables,
    clippy::missing_const_for_fn,
    clippy::needless_pass_by_value
)]
pub fn nstd_thread_detach(thread: NSTDThread) {}

/// Returns the name of a thread.
///
/// # Parameters:
///
/// - `const NSTDThreadHandle *handle` - A handle to the thread.
///
/// # Returns
///
/// `NSTDOptionalStr name` - The name of the thread, or none if the thread is unnamed.
#[inline]
#[nstdapi]
pub fn nstd_thread_name(handle: &NSTDThreadHandle) -> NSTDOptionalStr {
    handle.handle.name().map_or(NSTDOptional::None, |name| {
        NSTDOptional::Some(NSTDStr::from_str(name))
    })
}

/// Returns a thread's unique identifier.
///
/// # Parameters:
///
/// - `const NSTDThreadHandle *handle` - A handle to the thread.
///
/// # Returns
///
/// `NSTDThreadID id` - The thread's unique ID.
///
/// # Panics
///
/// Panics if allocating for the thread ID fails.
#[inline]
#[nstdapi]
pub fn nstd_thread_id(handle: &NSTDThreadHandle) -> NSTDThreadID {
    NSTDThreadID {
        id: CBox::new(handle.handle.id()).expect("failed to allocate for a thread ID"),
    }
}

/// Frees an instance of `NSTDThreadHandle`.
///
/// # Parameters:
///
/// - `NSTDThreadHandle handle` - The handle to free.
#[inline]
#[nstdapi]
#[allow(
    unused_variables,
    clippy::missing_const_for_fn,
    clippy::needless_pass_by_value
)]
pub fn nstd_thread_handle_free(handle: NSTDThreadHandle) {}

/// Puts the current thread to sleep for a specified duration.
///
/// # Parameters:
///
/// - `NSTDDuration duration` - The duration to put the thread to sleep for.
///
/// # Panics
///
/// Panics if `duration` is negative, overflows Rust's `Duration` structure, or is non-finite.
#[inline]
#[nstdapi]
pub fn nstd_thread_sleep(duration: NSTDDuration) {
    std::thread::sleep(duration.into_duration());
}

/// Returns the number of recommended threads that a program should use.
///
/// # Returns
///
/// `NSTDThreadCountResult threads` - The estimated default amount of parallelism a program should
/// use on success, or the I/O error code on failure.
#[inline]
#[nstdapi]
pub fn nstd_thread_count() -> NSTDThreadCountResult {
    match std::thread::available_parallelism() {
        Ok(threads) => NSTDResult::Ok(threads.get()),
        Err(err) => NSTDResult::Err(NSTDIOError::from_err(err.kind())),
    }
}

/// Checks if the current thread is unwinding due to a panic.
///
/// # Returns
///
/// `NSTDBool is_panicking` - Determines whether or not the calling thread is panicking.
#[inline]
#[nstdapi]
#[allow(clippy::missing_const_for_fn)]
pub fn nstd_thread_is_panicking() -> NSTDBool {
    #[cfg(panic = "unwind")]
    return std::thread::panicking();
    #[cfg(panic = "abort")]
    return false;
}

/// Compares two thread identifiers.
///
/// # Parameters:
///
/// - `const NSTDThreadID *xid` - The first identifier.
///
/// - `const NSTDThreadID *yid` - The second identifier.
///
/// # Returns
///
/// `NSTDBool is_eq` - True if the two identifiers refer to the same thread.
#[inline]
#[nstdapi]
pub fn nstd_thread_id_compare(x_id: &NSTDThreadID, y_id: &NSTDThreadID) -> NSTDBool {
    *x_id.id == *y_id.id
}

/// Frees an instance of `NSTDThreadID`.
///
/// # Parameters:
///
/// - `NSTDThreadID id` - A thread identifier.
#[inline]
#[nstdapi]
#[allow(
    unused_variables,
    clippy::missing_const_for_fn,
    clippy::needless_pass_by_value
)]
pub fn nstd_thread_id_free(id: NSTDThreadID) {}