use std::ffi::c_void;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
use std::time::Duration;
use super::{
Id, Sel, class, class_addMethod, msg_id, msg_void_bool, nsstring, objc_allocateClassPair,
objc_getClass, objc_msgSend, objc_registerClassPair, sel,
};
#[link(name = "QuartzCore", kind = "framework")]
unsafe extern "C" {}
static LINK: AtomicUsize = AtomicUsize::new(0);
static RUNNING: AtomicBool = AtomicBool::new(false);
static IDLE_TICKS: AtomicU32 = AtomicU32::new(0);
const IDLE_LIMIT: u32 = 3;
extern "C" fn on_refresh(_this: Id, _cmd: Sel, _link: Id) {
if crate::platform::frame::tick() {
IDLE_TICKS.store(0, Ordering::Relaxed);
} else if IDLE_TICKS.fetch_add(1, Ordering::Relaxed) + 1 == IDLE_LIMIT {
crate::platform::frame::idle();
}
}
fn target_class() -> *mut c_void {
let name = c"StippleFrameTarget";
unsafe {
let existing = objc_getClass(name.as_ptr());
if !existing.is_null() {
return existing;
}
let cls = objc_allocateClassPair(class("NSObject"), name.as_ptr(), 0);
if cls.is_null() {
return cls;
}
class_addMethod(
cls,
sel("frame:"),
on_refresh as *const c_void,
c"v@:@".as_ptr(),
);
objc_registerClassPair(cls);
cls
}
}
pub(super) fn install() {
unsafe {
let cls = target_class();
let link_cls = class("CADisplayLink");
if cls.is_null() || link_cls.is_null() {
return; }
let target = msg_id(msg_id(cls, sel("alloc")), sel("init"));
let make: unsafe extern "C" fn(Id, Sel, Id, Sel) -> Id =
std::mem::transmute(objc_msgSend as *const c_void);
let link = make(
link_cls,
sel("displayLinkWithTarget:selector:"),
target,
sel("frame:"),
);
if link.is_null() {
return;
}
let link = msg_id(link, sel("retain"));
msg_void_bool(link, sel("setPaused:"), true);
let add: unsafe extern "C" fn(Id, Sel, Id, Id) =
std::mem::transmute(objc_msgSend as *const c_void);
let run_loop = msg_id(class("NSRunLoop"), sel("mainRunLoop"));
add(
link,
sel("addToRunLoop:forMode:"),
run_loop,
nsstring("kCFRunLoopCommonModes"),
);
LINK.store(link as usize, Ordering::Release);
let screen = msg_id(class("UIScreen"), sel("mainScreen"));
let fps: unsafe extern "C" fn(Id, Sel) -> isize =
std::mem::transmute(objc_msgSend as *const c_void);
let hz = fps(screen, sel("maximumFramesPerSecond"));
if hz > 0 {
crate::platform::frame::set_interval(Duration::from_secs_f64(1.0 / hz as f64));
}
}
crate::platform::frame::install(Arc::new(start), Some(Arc::new(stop)));
}
fn set_paused(paused: bool) {
let link = LINK.load(Ordering::Acquire) as Id;
if !link.is_null() {
unsafe { msg_void_bool(link, sel("setPaused:"), paused) };
}
}
fn start() {
if !RUNNING.swap(true, Ordering::AcqRel) {
IDLE_TICKS.store(0, Ordering::Relaxed);
set_paused(false);
}
}
fn stop() {
if RUNNING.swap(false, Ordering::AcqRel) {
set_paused(true);
}
}