use std::ffi::c_void;
use std::time::{Duration, Instant};
pub type CFRunLoopRef = *mut c_void;
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CFRunLoopRunResult {
Finished = 1,
Stopped = 2,
TimedOut = 3,
HandledSource = 4,
}
impl From<i32> for CFRunLoopRunResult {
fn from(value: i32) -> Self {
match value {
1 => Self::Finished,
2 => Self::Stopped,
3 => Self::TimedOut,
4 => Self::HandledSource,
_ => Self::TimedOut,
}
}
}
unsafe extern "C" {
fn CFRunLoopGetMain() -> CFRunLoopRef;
fn CFRunLoopGetCurrent() -> CFRunLoopRef;
fn CFRunLoopRun();
fn CFRunLoopStop(rl: CFRunLoopRef);
fn CFRunLoopRunInMode(mode: *const c_void, seconds: f64, returnAfterSourceHandled: bool)
-> i32;
static kCFRunLoopDefaultMode: *const c_void;
}
#[must_use]
pub fn cf_run_loop_get_main() -> CFRunLoopRef {
unsafe { CFRunLoopGetMain() }
}
#[must_use]
pub fn cf_run_loop_get_current() -> CFRunLoopRef {
unsafe { CFRunLoopGetCurrent() }
}
pub fn cf_run_loop_run() {
unsafe { CFRunLoopRun() }
}
pub unsafe fn cf_run_loop_stop(rl: CFRunLoopRef) {
unsafe { CFRunLoopStop(rl) }
}
#[must_use]
pub fn cf_run_loop_run_in_mode(
seconds: f64,
return_after_source_handled: bool,
) -> CFRunLoopRunResult {
unsafe {
let result =
CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, return_after_source_handled);
CFRunLoopRunResult::from(result)
}
}
pub fn run_loop_until<F>(mut predicate: F, timeout_secs: f64) -> bool
where
F: FnMut() -> bool,
{
let start = Instant::now();
let timeout = Duration::from_secs_f64(timeout_secs);
while !predicate() {
if start.elapsed() > timeout {
return false;
}
let _ = cf_run_loop_run_in_mode(0.01, true);
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_main_run_loop() {
let main = cf_run_loop_get_main();
assert!(!main.is_null());
}
#[test]
fn test_get_current_run_loop() {
let current = cf_run_loop_get_current();
assert!(!current.is_null());
}
#[test]
fn test_run_loop_until_immediate() {
let result = run_loop_until(|| true, 1.0);
assert!(result);
}
#[test]
fn test_run_loop_until_timeout() {
let start = Instant::now();
let result = run_loop_until(|| false, 0.1);
let elapsed = start.elapsed();
assert!(!result);
assert!(elapsed.as_secs_f64() >= 0.1);
assert!(elapsed.as_secs_f64() < 0.5);
}
#[test]
fn test_run_loop_run_in_mode() {
let result = cf_run_loop_run_in_mode(0.01, true);
assert!(
result == CFRunLoopRunResult::Finished || result == CFRunLoopRunResult::TimedOut,
"Expected Finished or TimedOut, got {:?}",
result
);
}
}