use std::cell::Cell;
use std::time::{Duration, Instant};
use objc2::rc::Retained;
use objc2::runtime::{AnyObject, NSObjectProtocol};
use objc2::{DefinedClass, MainThreadMarker, MainThreadOnly, define_class, msg_send, sel};
use objc2_app_kit::NSView;
use objc2_foundation::{NSObject, NSRunLoop, NSRunLoopCommonModes};
use objc2_quartz_core::{CADisplayLink, CAFrameRateRange};
use winit::raw_window_handle::{HasWindowHandle, RawWindowHandle};
use winit::window::Window as NativeWindow;
const FRAME_RATE_MIN: f32 = 60.0;
const FRAME_RATE_MAX: f32 = 120.0;
const DEMAND_HOLD: Duration = Duration::from_millis(250);
pub(super) struct FrameRateDemandLink {
link: Retained<CADisplayLink>,
target: Retained<DemandLinkTarget>,
}
impl core::fmt::Debug for FrameRateDemandLink {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("FrameRateDemandLink")
.field("paused", &self.link.isPaused())
.finish_non_exhaustive()
}
}
struct DemandLinkTargetIvars {
demand_until: Cell<Instant>,
}
define_class!(
#[unsafe(super(NSObject))]
#[name = "WuiHydrolysisFrameRateDemand"]
#[thread_kind = MainThreadOnly]
#[ivars = DemandLinkTargetIvars]
struct DemandLinkTarget;
unsafe impl NSObjectProtocol for DemandLinkTarget {}
impl DemandLinkTarget {
#[unsafe(method(step:))]
fn step(&self, link: &CADisplayLink) {
if Instant::now() >= self.ivars().demand_until.get() {
link.setPaused(true);
}
}
}
);
impl DemandLinkTarget {
fn new(mtm: MainThreadMarker) -> Retained<Self> {
let this = Self::alloc(mtm).set_ivars(DemandLinkTargetIvars {
demand_until: Cell::new(Instant::now()),
});
unsafe { msg_send![super(this), init] }
}
}
impl FrameRateDemandLink {
pub(super) fn attach(window: &NativeWindow) -> Option<Self> {
let mtm = MainThreadMarker::new()?;
let _ = mtm;
let handle = window.window_handle().ok()?;
let RawWindowHandle::AppKit(appkit) = handle.as_raw() else {
return None;
};
let view = unsafe { appkit.ns_view.cast::<NSView>().as_ref() };
if !view.respondsToSelector(sel!(displayLinkWithTarget:selector:)) {
return None;
}
let target = DemandLinkTarget::new(mtm);
let target_object: &AnyObject = ⌖
let link = unsafe { view.displayLinkWithTarget_selector(target_object, sel!(step:)) };
link.setPreferredFrameRateRange(CAFrameRateRange::new(
FRAME_RATE_MIN,
FRAME_RATE_MAX,
FRAME_RATE_MAX,
));
link.setPaused(true);
unsafe { link.addToRunLoop_forMode(&NSRunLoop::mainRunLoop(), NSRunLoopCommonModes) };
Some(Self { link, target })
}
pub(super) fn hold_demand(&self) {
self.target
.ivars()
.demand_until
.set(Instant::now() + DEMAND_HOLD);
if self.link.isPaused() {
self.link.setPaused(false);
}
}
}
impl Drop for FrameRateDemandLink {
fn drop(&mut self) {
self.link.invalidate();
}
}