use std::{
ffi::c_void,
ptr::NonNull,
sync::atomic::{AtomicBool, Ordering},
thread::{ThreadId, current},
time::Duration,
};
use anyhow::Context;
use gpui_util::ResultExt;
use windows::Win32::{
Foundation::{FILETIME, LPARAM, WPARAM},
Media::{timeBeginPeriod, timeEndPeriod},
System::Threading::{
CloseThreadpoolTimer, CreateThreadpoolTimer, GetCurrentThread, PTP_CALLBACK_INSTANCE,
PTP_TIMER, SetThreadPriority, SetThreadpoolTimer, THREAD_PRIORITY_TIME_CRITICAL,
TP_CALLBACK_ENVIRON_V3, TP_CALLBACK_PRIORITY, TP_CALLBACK_PRIORITY_HIGH,
TP_CALLBACK_PRIORITY_LOW, TP_CALLBACK_PRIORITY_NORMAL, TrySubmitThreadpoolCallback,
},
UI::WindowsAndMessaging::PostMessageW,
};
use crate::{HWND, SafeHwnd, WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD};
use gpui::{
PlatformDispatcher, Priority, PriorityQueueSender, RunnableVariant, TimerResolutionGuard,
};
pub(crate) struct WindowsDispatcher {
pub(crate) wake_posted: AtomicBool,
main_sender: PriorityQueueSender<RunnableVariant>,
main_thread_id: ThreadId,
pub(crate) platform_window_handle: SafeHwnd,
validation_number: usize,
}
impl WindowsDispatcher {
pub(crate) fn new(
main_sender: PriorityQueueSender<RunnableVariant>,
platform_window_handle: HWND,
validation_number: usize,
) -> Self {
let main_thread_id = current().id();
let platform_window_handle = platform_window_handle.into();
WindowsDispatcher {
main_sender,
main_thread_id,
platform_window_handle,
validation_number,
wake_posted: AtomicBool::new(false),
}
}
fn dispatch_on_threadpool(&self, priority: TP_CALLBACK_PRIORITY, runnable: RunnableVariant) {
let environ = TP_CALLBACK_ENVIRON_V3 {
Version: 3,
CallbackPriority: priority,
Size: size_of::<TP_CALLBACK_ENVIRON_V3>() as u32,
..Default::default()
};
let context = runnable.into_raw().as_ptr() as *mut c_void;
unsafe {
TrySubmitThreadpoolCallback(Some(run_work_callback), Some(context), Some(&environ))
.log_err();
}
}
fn dispatch_on_threadpool_after(&self, runnable: RunnableVariant, duration: Duration) {
let context = runnable.into_raw().as_ptr() as *mut c_void;
unsafe {
if let Ok(timer) = CreateThreadpoolTimer(Some(run_timer_callback), Some(context), None)
{
let ticks = (duration.as_nanos() / 100).min(i64::MAX as u128) as i64;
let due = (-ticks) as u64;
let due_time = FILETIME {
dwLowDateTime: due as u32,
dwHighDateTime: (due >> 32) as u32,
};
SetThreadpoolTimer(timer, Some(&due_time), 0, None);
}
}
}
#[inline(always)]
pub(crate) fn execute_runnable(runnable: RunnableVariant) {
let location = runnable.metadata().location;
let spawned = runnable.metadata().spawned;
gpui::profiler::update_running_task(spawned, location);
runnable.run();
gpui::profiler::save_task_timing();
}
}
impl PlatformDispatcher for WindowsDispatcher {
fn is_main_thread(&self) -> bool {
current().id() == self.main_thread_id
}
fn dispatch(&self, runnable: RunnableVariant, priority: Priority) {
let priority = match priority {
Priority::RealtimeAudio => {
panic!("RealtimeAudio priority should use spawn_realtime, not dispatch")
}
Priority::High => TP_CALLBACK_PRIORITY_HIGH,
Priority::Medium => TP_CALLBACK_PRIORITY_NORMAL,
Priority::Low => TP_CALLBACK_PRIORITY_LOW,
};
self.dispatch_on_threadpool(priority, runnable);
}
fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority) {
match self.main_sender.send(priority, runnable) {
Ok(_) => {
if !self.wake_posted.swap(true, Ordering::AcqRel) {
unsafe {
PostMessageW(
Some(self.platform_window_handle.as_raw()),
WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD,
WPARAM(self.validation_number),
LPARAM(0),
)
.log_err();
}
}
}
Err(runnable) => {
std::mem::forget(runnable);
}
}
}
fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant) {
self.dispatch_on_threadpool_after(runnable, duration);
}
fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>) {
std::thread::spawn(move || {
let thread_handle = unsafe { GetCurrentThread() };
unsafe { SetThreadPriority(thread_handle, THREAD_PRIORITY_TIME_CRITICAL) }
.context("thread priority")
.log_err();
f();
});
}
fn increase_timer_resolution(&self) -> TimerResolutionGuard {
unsafe {
timeBeginPeriod(1);
}
gpui_util::defer(Box::new(|| unsafe {
timeEndPeriod(1);
}))
}
}
unsafe extern "system" fn run_work_callback(
_instance: PTP_CALLBACK_INSTANCE,
context: *mut c_void,
) {
let runnable = unsafe { RunnableVariant::from_raw(NonNull::new_unchecked(context as *mut ())) };
WindowsDispatcher::execute_runnable(runnable);
}
unsafe extern "system" fn run_timer_callback(
_instance: PTP_CALLBACK_INSTANCE,
context: *mut c_void,
timer: PTP_TIMER,
) {
let runnable = unsafe { RunnableVariant::from_raw(NonNull::new_unchecked(context as *mut ())) };
WindowsDispatcher::execute_runnable(runnable);
unsafe { CloseThreadpoolTimer(timer) };
}