use std::sync::{Arc, OnceLock};
use std::thread::ThreadId;
use teksilo_canvas::sync::{AtomicBool, AtomicU64, Mutex, Ordering};
use teksilo_canvas::wake::{RedrawWaker, WakeFlag, WakeGate, WakeKind};
pub(crate) trait RequestRedraw: Send + Sync {
fn request_redraw(&self);
}
impl RequestRedraw for winit::window::Window {
fn request_redraw(&self) {
winit::window::Window::request_redraw(self);
}
}
pub(crate) type WakeRoute = Arc<dyn Fn() + Send + Sync>;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct LiveWakeStats {
pub wakes: u64,
pub dropped_hidden: u64,
pub hidden: bool,
}
pub(crate) struct WindowWakeTarget {
connected: AtomicBool,
slot: Mutex<Option<Arc<dyn RequestRedraw>>>,
main_thread: ThreadId,
state_route: OnceLock<WakeRoute>,
off_main_route: OnceLock<WakeRoute>,
gate: WakeGate,
state_posted: WakeFlag,
draw_posted: WakeFlag,
wakes: AtomicU64,
dropped_hidden: AtomicU64,
#[cfg(target_os = "macos")]
this: std::sync::Weak<Self>,
}
impl WindowWakeTarget {
pub(crate) fn new(window: Arc<dyn RequestRedraw>) -> Arc<Self> {
Arc::new_cyclic(|_this| Self {
connected: AtomicBool::new(true),
slot: Mutex::new(Some(window)),
main_thread: std::thread::current().id(),
state_route: OnceLock::new(),
off_main_route: OnceLock::new(),
gate: WakeGate::new(),
state_posted: WakeFlag::new(),
draw_posted: WakeFlag::new(),
wakes: AtomicU64::new(0),
dropped_hidden: AtomicU64::new(0),
#[cfg(target_os = "macos")]
this: _this.clone(),
})
}
pub(crate) fn set_state_route(&self, route: WakeRoute) {
let _ = self.state_route.set(route);
}
pub(crate) fn set_off_main_route(&self, route: WakeRoute) {
let _ = self.off_main_route.set(route);
}
pub(crate) fn wake_draw(&self) {
if !self.connected.load(Ordering::Acquire) {
return;
}
if !self.gate.pass() {
self.dropped_hidden.fetch_add(1, Ordering::Relaxed);
return;
}
self.wakes.fetch_add(1, Ordering::Relaxed);
if std::thread::current().id() != self.main_thread {
if let Some(route) = self.off_main_route.get() {
if self.draw_posted.raise() {
route();
}
return;
}
#[cfg(target_os = "macos")]
{
self.request_on_main_queue();
return;
}
}
self.request_on_slot();
}
#[cfg(target_os = "macos")]
fn request_on_main_queue(&self) {
if !self.draw_posted.raise() {
return;
}
let this = self.this.clone();
dispatch2::DispatchQueue::main().exec_async(move || {
if let Some(target) = this.upgrade() {
target.draw_posted.take();
target.request_on_slot();
}
});
}
pub(crate) fn wake_state(&self) {
if !self.connected.load(Ordering::Acquire) {
return;
}
self.wakes.fetch_add(1, Ordering::Relaxed);
match self.state_route.get() {
Some(route) => {
if self.state_posted.raise() {
route();
}
}
None => self.request_on_slot(),
}
}
pub(crate) fn take_posted(&self, kind: WakeKind) -> bool {
match kind {
WakeKind::Draw => self.draw_posted.take(),
_ => self.state_posted.take(),
}
}
pub(crate) fn defer_until_shown(&self) -> bool {
if self.gate.pass() {
false
} else {
self.dropped_hidden.fetch_add(1, Ordering::Relaxed);
true
}
}
pub(crate) fn set_hidden(&self, hidden: bool) {
if hidden {
self.gate.hide();
} else if self.gate.show() && self.connected.load(Ordering::Acquire) {
self.wakes.fetch_add(1, Ordering::Relaxed);
self.request_on_slot();
}
}
pub(crate) fn show_for_drawing(&self) {
let _ = self.gate.show();
}
pub(crate) fn is_hidden(&self) -> bool {
self.gate.is_hidden()
}
pub(crate) fn disconnect(&self) {
self.connected.store(false, Ordering::Release);
let window = self
.slot
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take();
drop(window);
}
pub(crate) fn request_on_slot(&self) {
let slot = self
.slot
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(window) = slot.as_ref() {
window.request_redraw();
}
}
pub(crate) fn stats(&self) -> LiveWakeStats {
LiveWakeStats {
wakes: self.wakes.load(Ordering::Relaxed),
dropped_hidden: self.dropped_hidden.load(Ordering::Relaxed),
hidden: self.gate.is_hidden(),
}
}
#[cfg(all(test, not(teksilo_loom)))]
fn hold_slot(&self) -> impl Drop + '_ {
self.slot
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
}
impl RedrawWaker for WindowWakeTarget {
fn wake(&self, kind: WakeKind) {
match kind {
WakeKind::Draw => self.wake_draw(),
_ => self.wake_state(),
}
}
fn is_hidden(&self) -> bool {
self.gate.is_hidden()
}
}
impl std::fmt::Debug for WindowWakeTarget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WindowWakeTarget")
.field("connected", &self.connected.load(Ordering::Relaxed))
.field("gate", &self.gate)
.field("stats", &self.stats())
.finish_non_exhaustive()
}
}
#[cfg(all(test, not(teksilo_loom)))]
mod tests;
#[cfg(all(test, teksilo_loom))]
mod loom_tests;