use crate::filter::ReactorWaker;
use crate::handler::Handler;
use crate::oneoff::Complete;
use crate::sync::{ThreadSafety, __private::*};
use crate::window::registration::Registration as WinRegistration;
use crate::window::WindowBuilder;
use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::task::Waker;
use std::time::{Duration, Instant};
use winit::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use winit::error::{ExternalError, NotSupportedError, OsError};
use winit::event_loop::DeviceEventFilter;
use winit::monitor::MonitorHandle;
use winit::window::{
CursorGrabMode, CursorIcon, Fullscreen, Icon, ImePurpose, ResizeDirection, Theme,
UserAttentionType, Window, WindowId, WindowLevel,
};
const NEEDS_EXIT: i64 = 0x1;
const EXIT_CODE_SHIFT: u32 = 1;
#[doc(hidden)]
pub struct Reactor<T: ThreadSafety> {
exit_code: T::AtomicI64,
evl_ops: (T::Sender<EventLoopOp<T>>, T::Receiver<EventLoopOp<T>>),
windows: T::Mutex<HashMap<WindowId, T::Rc<WinRegistration<T>>>>,
proxy: T::OnceLock<Arc<ReactorWaker>>,
timers: T::Mutex<BTreeMap<(Instant, usize), Waker>>,
timer_op_queue: T::ConcurrentQueue<TimerOp>,
timer_id: T::AtomicUsize,
pub(crate) evl_registration: GlobalRegistration<T>,
}
enum TimerOp {
InsertTimer(Instant, usize, Waker),
RemoveTimer(Instant, usize),
}
impl<TS: ThreadSafety> Reactor<TS> {
pub(crate) fn new() -> Self {
static ALREADY_EXISTS: AtomicBool = AtomicBool::new(false);
if ALREADY_EXISTS.swap(true, Ordering::SeqCst) {
panic!("Only one instance of `Reactor` can exist at a time");
}
Reactor {
exit_code: <TS::AtomicI64>::new(0),
proxy: TS::OnceLock::new(),
evl_ops: TS::channel_bounded(1024),
windows: TS::Mutex::new(HashMap::new()),
timers: TS::Mutex::new(BTreeMap::new()),
timer_op_queue: TS::ConcurrentQueue::bounded(1024),
timer_id: TS::AtomicUsize::new(1),
evl_registration: GlobalRegistration::new(),
}
}
pub(crate) fn get() -> TS::Rc<Self> {
TS::get_reactor()
}
pub(crate) fn set_proxy(&self, proxy: Arc<ReactorWaker>) {
self.proxy.set(proxy).ok();
}
pub(crate) fn exit_requested(&self) -> Option<i32> {
let value = self.exit_code.load(Ordering::SeqCst);
if value & NEEDS_EXIT != 0 {
Some((value >> EXIT_CODE_SHIFT) as i32)
} else {
None
}
}
pub(crate) fn request_exit(&self, code: i32) {
let value = NEEDS_EXIT | (code as i64) << EXIT_CODE_SHIFT;
self.exit_code.store(value, Ordering::SeqCst);
self.notify();
}
pub(crate) fn insert_timer(&self, deadline: Instant, waker: &Waker) -> usize {
let id = self.timer_id.fetch_add(1, Ordering::Relaxed);
let mut op = TimerOp::InsertTimer(deadline, id, waker.clone());
while let Err(e) = self.timer_op_queue.push(op) {
let mut timers = self.timers.lock().unwrap();
self.process_timer_ops(&mut timers);
op = e;
}
self.notify();
id
}
pub(crate) fn remove_timer(&self, deadline: Instant, id: usize) {
let mut op = TimerOp::RemoveTimer(deadline, id);
while let Err(e) = self.timer_op_queue.push(op) {
let mut timers = self.timers.lock().unwrap();
self.process_timer_ops(&mut timers);
op = e;
}
}
pub(crate) fn insert_window(&self, id: WindowId) -> TS::Rc<WinRegistration<TS>> {
let mut windows = self.windows.lock().unwrap();
let registration = TS::Rc::new(WinRegistration::new());
windows.insert(id, registration.clone());
registration
}
pub(crate) fn remove_window(&self, id: WindowId) {
let mut windows = self.windows.lock().unwrap();
windows.remove(&id);
}
fn process_timer_ops(&self, timers: &mut BTreeMap<(Instant, usize), Waker>) {
let limit = self.timer_op_queue.capacity();
self.timer_op_queue
.try_iter()
.take(limit)
.for_each(|op| match op {
TimerOp::InsertTimer(deadline, id, waker) => {
timers.insert((deadline, id), waker);
}
TimerOp::RemoveTimer(deadline, id) => {
if let Some(waker) = timers.remove(&(deadline, id)) {
std::panic::catch_unwind(|| drop(waker)).ok();
}
}
});
}
pub(crate) fn process_timers(&self, wakers: &mut Vec<Waker>) -> Option<Instant> {
let mut timers = self.timers.lock().unwrap();
self.process_timer_ops(&mut timers);
let now = Instant::now();
let pending = timers.split_off(&(now + Duration::from_nanos(1), 0));
let ready = std::mem::replace(&mut *timers, pending);
let deadline = if ready.is_empty() {
timers.keys().next().map(|(deadline, _)| *deadline)
} else {
Some(now)
};
drop(timers);
wakers.extend(ready.into_values());
deadline
}
pub(crate) fn notify(&self) {
if let Some(proxy) = self.proxy.get() {
proxy.notify();
}
}
pub(crate) async fn push_event_loop_op(&self, op: EventLoopOp<TS>) {
if self.evl_ops.0.send(op).await.is_err() {
panic!("Failed to push event loop operation");
}
self.notify();
}
pub(crate) fn drain_loop_queue<T: 'static>(
&self,
elwt: &winit::event_loop::EventLoopWindowTarget<T>,
) {
for _ in 0..self.evl_ops.1.capacity() {
if let Some(op) = self.evl_ops.1.try_recv() {
op.run(elwt);
} else {
break;
}
}
}
pub fn evl_ops_len(&self) -> usize {
self.evl_ops.1.len()
}
pub(crate) async fn post_event<T: 'static>(&self, event: winit::event::Event<'_, T>) {
use winit::event::Event;
match event {
Event::WindowEvent { window_id, event } => {
let registration = {
let windows = self.windows.lock().unwrap();
windows.get(&window_id).cloned()
};
if let Some(registration) = registration {
registration.signal(event).await;
}
}
Event::Resumed => {
self.evl_registration.resumed.run_with(&mut ()).await;
}
Event::Suspended => self.evl_registration.suspended.run_with(&mut ()).await,
Event::RedrawRequested(id) => {
let registration = {
let windows = self.windows.lock().unwrap();
windows.get(&id).cloned()
};
if let Some(registration) = registration {
registration.redraw_requested.run_with(&mut ()).await;
}
}
_ => {}
}
}
}
pub(crate) enum EventLoopOp<TS: ThreadSafety> {
BuildWindow {
builder: Box<WindowBuilder>,
waker: Complete<Result<winit::window::Window, OsError>, TS>,
},
PrimaryMonitor(Complete<Option<MonitorHandle>, TS>),
AvailableMonitors(Complete<Vec<MonitorHandle>, TS>),
SetDeviceFilter {
filter: DeviceEventFilter,
waker: Complete<(), TS>,
},
InnerPosition {
window: TS::Rc<Window>,
waker: Complete<Result<PhysicalPosition<i32>, NotSupportedError>, TS>,
},
OuterPosition {
window: TS::Rc<Window>,
waker: Complete<Result<PhysicalPosition<i32>, NotSupportedError>, TS>,
},
SetOuterPosition {
window: TS::Rc<Window>,
position: Position,
waker: Complete<(), TS>,
},
InnerSize {
window: TS::Rc<Window>,
waker: Complete<PhysicalSize<u32>, TS>,
},
SetInnerSize {
window: TS::Rc<Window>,
size: Size,
waker: Complete<(), TS>,
},
OuterSize {
window: TS::Rc<Window>,
waker: Complete<PhysicalSize<u32>, TS>,
},
SetMinInnerSize {
window: TS::Rc<Window>,
size: Option<Size>,
waker: Complete<(), TS>,
},
SetMaxInnerSize {
window: TS::Rc<Window>,
size: Option<Size>,
waker: Complete<(), TS>,
},
ResizeIncrements {
window: TS::Rc<Window>,
waker: Complete<Option<PhysicalSize<u32>>, TS>,
},
SetResizeIncrements {
window: TS::Rc<Window>,
size: Option<Size>,
waker: Complete<(), TS>,
},
SetTitle {
window: TS::Rc<Window>,
title: String,
waker: Complete<(), TS>,
},
SetTransparent {
window: TS::Rc<Window>,
transparent: bool,
waker: Complete<(), TS>,
},
SetResizable {
window: TS::Rc<Window>,
resizable: bool,
waker: Complete<(), TS>,
},
SetVisible {
window: TS::Rc<Window>,
visible: bool,
waker: Complete<(), TS>,
},
Resizable {
window: TS::Rc<Window>,
waker: Complete<bool, TS>,
},
Visible {
window: TS::Rc<Window>,
waker: Complete<Option<bool>, TS>,
},
SetMinimized {
window: TS::Rc<Window>,
minimized: bool,
waker: Complete<(), TS>,
},
Minimized {
window: TS::Rc<Window>,
waker: Complete<Option<bool>, TS>,
},
SetMaximized {
window: TS::Rc<Window>,
maximized: bool,
waker: Complete<(), TS>,
},
Maximized {
window: TS::Rc<Window>,
waker: Complete<bool, TS>,
},
SetFullscreen {
window: TS::Rc<Window>,
fullscreen: Option<Fullscreen>,
waker: Complete<(), TS>,
},
Fullscreen {
window: TS::Rc<Window>,
waker: Complete<Option<Fullscreen>, TS>,
},
SetDecorated {
window: TS::Rc<Window>,
decorated: bool,
waker: Complete<(), TS>,
},
Decorated {
window: TS::Rc<Window>,
waker: Complete<bool, TS>,
},
SetWindowLevel {
window: TS::Rc<Window>,
level: WindowLevel,
waker: Complete<(), TS>,
},
SetWindowIcon {
window: TS::Rc<Window>,
icon: Option<Icon>,
waker: Complete<(), TS>,
},
SetImePosition {
window: TS::Rc<Window>,
position: Position,
waker: Complete<(), TS>,
},
SetImeAllowed {
window: TS::Rc<Window>,
allowed: bool,
waker: Complete<(), TS>,
},
SetImePurpose {
window: TS::Rc<Window>,
purpose: ImePurpose,
waker: Complete<(), TS>,
},
FocusWindow {
window: TS::Rc<Window>,
waker: Complete<(), TS>,
},
Focused {
window: TS::Rc<Window>,
waker: Complete<bool, TS>,
},
RequestUserAttention {
window: TS::Rc<Window>,
request_type: Option<UserAttentionType>,
waker: Complete<(), TS>,
},
SetTheme {
window: TS::Rc<Window>,
theme: Option<Theme>,
waker: Complete<(), TS>,
},
Theme {
window: TS::Rc<Window>,
waker: Complete<Option<Theme>, TS>,
},
SetProtectedContent {
window: TS::Rc<Window>,
protected: bool,
waker: Complete<(), TS>,
},
Title {
window: TS::Rc<Window>,
waker: Complete<String, TS>,
},
SetCursorIcon {
window: TS::Rc<Window>,
icon: CursorIcon,
waker: Complete<(), TS>,
},
SetCursorPosition {
window: TS::Rc<Window>,
position: Position,
waker: Complete<Result<(), ExternalError>, TS>,
},
SetCursorGrab {
window: TS::Rc<Window>,
mode: CursorGrabMode,
waker: Complete<Result<(), ExternalError>, TS>,
},
SetCursorVisible {
window: TS::Rc<Window>,
visible: bool,
waker: Complete<(), TS>,
},
DragWindow {
window: TS::Rc<Window>,
waker: Complete<Result<(), ExternalError>, TS>,
},
DragResizeWindow {
window: TS::Rc<Window>,
direction: ResizeDirection,
waker: Complete<Result<(), ExternalError>, TS>,
},
SetCursorHitTest {
window: TS::Rc<Window>,
hit_test: bool,
waker: Complete<Result<(), ExternalError>, TS>,
},
CurrentMonitor {
window: TS::Rc<Window>,
waker: Complete<Option<MonitorHandle>, TS>,
},
}
impl<TS: ThreadSafety> fmt::Debug for EventLoopOp<TS> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EventLoopOp::BuildWindow { .. } => f
.debug_struct("BuildWindow")
.field("builder", &"...")
.field("waker", &"...")
.finish(),
EventLoopOp::PrimaryMonitor(_) => f.debug_struct("PrimaryMonitor").finish(),
EventLoopOp::AvailableMonitors(_) => f.debug_struct("AvailableMonitors").finish(),
EventLoopOp::SetDeviceFilter { .. } => f
.debug_struct("SetDeviceFilter")
.field("filter", &"...")
.field("waker", &"...")
.finish(),
EventLoopOp::InnerPosition { .. } => f
.debug_struct("InnerPosition")
.field("window", &"...")
.field("waker", &"...")
.finish(),
EventLoopOp::OuterPosition { .. } => f
.debug_struct("OuterPosition")
.field("window", &"...")
.field("waker", &"...")
.finish(),
EventLoopOp::SetOuterPosition { .. } => f
.debug_struct("SetOuterPosition")
.field("window", &"...")
.field("position", &"...")
.field("waker", &"...")
.finish(),
EventLoopOp::InnerSize { .. } => f
.debug_struct("InnerSize")
.field("window", &"...")
.field("waker", &"...")
.finish(),
EventLoopOp::SetInnerSize { .. } => f
.debug_struct("SetInnerSize")
.field("window", &"...")
.field("size", &"...")
.field("waker", &"...")
.finish(),
EventLoopOp::OuterSize { .. } => f
.debug_struct("OuterSize")
.field("window", &"...")
.field("waker", &"...")
.finish(),
EventLoopOp::SetMinInnerSize { .. } => f
.debug_struct("SetMinInnerSize")
.field("window", &"...")
.field("size", &"...")
.field("waker", &"...")
.finish(),
EventLoopOp::SetMaxInnerSize { .. } => f
.debug_struct("SetMaxInnerSize")
.field("window", &"...")
.field("size", &"...")
.field("waker", &"...")
.finish(),
_ => {
f.debug_struct("EventLoopOp").finish()
}
}
}
}
impl<TS: ThreadSafety> EventLoopOp<TS> {
fn run<T: 'static>(self, target: &winit::event_loop::EventLoopWindowTarget<T>) {
match self {
EventLoopOp::BuildWindow { builder, waker } => {
waker.send(builder.into_winit_builder().build(target));
}
EventLoopOp::PrimaryMonitor(waker) => {
waker.send(target.primary_monitor());
}
EventLoopOp::AvailableMonitors(waker) => {
waker.send(target.available_monitors().collect());
}
EventLoopOp::SetDeviceFilter { filter, waker } => {
target.set_device_event_filter(filter);
waker.send(());
}
EventLoopOp::InnerPosition { window, waker } => {
waker.send(window.inner_position());
}
EventLoopOp::OuterPosition { window, waker } => {
waker.send(window.outer_position());
}
EventLoopOp::SetOuterPosition {
window,
position,
waker,
} => {
window.set_outer_position(position);
waker.send(());
}
EventLoopOp::InnerSize { window, waker } => {
waker.send(window.inner_size());
}
EventLoopOp::SetInnerSize {
window,
size,
waker,
} => {
window.set_inner_size(size);
waker.send(());
}
EventLoopOp::OuterSize { window, waker } => {
waker.send(window.outer_size());
}
EventLoopOp::SetMinInnerSize {
window,
size,
waker,
} => {
window.set_min_inner_size(size);
waker.send(());
}
EventLoopOp::SetMaxInnerSize {
window,
size,
waker,
} => {
window.set_max_inner_size(size);
waker.send(());
}
EventLoopOp::ResizeIncrements { window, waker } => {
waker.send(window.resize_increments());
}
EventLoopOp::SetResizeIncrements {
window,
size,
waker,
} => {
window.set_resize_increments(size);
waker.send(());
}
EventLoopOp::SetTitle {
window,
title,
waker,
} => {
window.set_title(&title);
waker.send(());
}
EventLoopOp::SetWindowIcon {
window,
icon,
waker,
} => {
window.set_window_icon(icon);
waker.send(());
}
EventLoopOp::Fullscreen { window, waker } => {
waker.send(window.fullscreen());
}
EventLoopOp::SetFullscreen {
window,
fullscreen,
waker,
} => {
window.set_fullscreen(fullscreen);
waker.send(());
}
EventLoopOp::Maximized { window, waker } => {
waker.send(window.is_maximized());
}
EventLoopOp::SetMaximized {
window,
maximized,
waker,
} => {
window.set_maximized(maximized);
waker.send(());
}
EventLoopOp::Minimized { window, waker } => {
waker.send(window.is_minimized());
}
EventLoopOp::SetMinimized {
window,
minimized,
waker,
} => {
window.set_minimized(minimized);
waker.send(());
}
EventLoopOp::Visible { window, waker } => {
waker.send(window.is_visible());
}
EventLoopOp::SetVisible {
window,
visible,
waker,
} => {
window.set_visible(visible);
waker.send(());
}
EventLoopOp::Decorated { window, waker } => {
waker.send(window.is_decorated());
}
EventLoopOp::SetDecorated {
window,
decorated,
waker,
} => {
window.set_decorations(decorated);
waker.send(());
}
EventLoopOp::SetWindowLevel {
window,
level,
waker,
} => {
window.set_window_level(level);
waker.send(());
}
EventLoopOp::SetImePosition {
window,
position,
waker,
} => {
window.set_ime_position(position);
waker.send(());
}
EventLoopOp::SetImeAllowed {
window,
allowed,
waker,
} => {
window.set_ime_allowed(allowed);
waker.send(());
}
EventLoopOp::SetImePurpose {
window,
purpose,
waker,
} => {
window.set_ime_purpose(purpose);
waker.send(());
}
EventLoopOp::FocusWindow { window, waker } => {
window.focus_window();
waker.send(());
}
EventLoopOp::Focused { window, waker } => {
waker.send(window.has_focus());
}
EventLoopOp::RequestUserAttention {
window,
request_type,
waker,
} => {
window.request_user_attention(request_type);
waker.send(());
}
EventLoopOp::SetTheme {
window,
theme,
waker,
} => {
window.set_theme(theme);
waker.send(());
}
EventLoopOp::Theme { window, waker } => {
waker.send(window.theme());
}
EventLoopOp::SetProtectedContent {
window,
protected,
waker,
} => {
window.set_content_protected(protected);
waker.send(());
}
EventLoopOp::Title { window, waker } => {
waker.send(window.title());
}
EventLoopOp::SetCursorIcon {
window,
icon,
waker,
} => {
window.set_cursor_icon(icon);
waker.send(());
}
EventLoopOp::SetCursorGrab {
window,
mode,
waker,
} => {
waker.send(window.set_cursor_grab(mode));
}
EventLoopOp::SetCursorVisible {
window,
visible,
waker,
} => {
window.set_cursor_visible(visible);
waker.send(());
}
EventLoopOp::DragWindow { window, waker } => {
waker.send(window.drag_window());
}
EventLoopOp::DragResizeWindow {
window,
direction,
waker,
} => {
waker.send(window.drag_resize_window(direction));
}
EventLoopOp::SetCursorHitTest {
window,
hit_test,
waker,
} => {
waker.send(window.set_cursor_hittest(hit_test));
}
EventLoopOp::CurrentMonitor { window, waker } => {
waker.send(window.current_monitor());
}
EventLoopOp::SetTransparent {
window,
transparent,
waker,
} => {
window.set_transparent(transparent);
waker.send(());
}
EventLoopOp::SetResizable {
window,
resizable,
waker,
} => {
window.set_resizable(resizable);
waker.send(());
}
EventLoopOp::Resizable { window, waker } => {
waker.send(window.is_resizable());
}
EventLoopOp::SetCursorPosition {
window,
position,
waker,
} => {
waker.send(window.set_cursor_position(position));
}
}
}
}
pub(crate) struct GlobalRegistration<T: ThreadSafety> {
pub(crate) resumed: Handler<(), T>,
pub(crate) suspended: Handler<(), T>,
}
impl<TS: ThreadSafety> GlobalRegistration<TS> {
pub(crate) fn new() -> Self {
Self {
resumed: Handler::new(),
suspended: Handler::new(),
}
}
}