use std::cell::Cell;
use std::future::Future;
use std::rc::Rc;
use crate::executor::{FilterResult, MessageLoop, block_on};
use event_listener::Event;
use tracing::{debug, info};
use windows::Win32::UI::WindowsAndMessaging::MSG;
pub(crate) struct MessageLoopDriver;
impl MessageLoopDriver {
pub(crate) fn block_on<T>(future: impl Future<Output = T>) -> T {
block_on(future)
}
#[allow(dead_code)] pub(crate) fn run() {
MessageLoop::run(|_loop, msg| Self::default_filter(msg));
}
pub(crate) fn default_filter(_msg: &MSG) -> FilterResult {
FilterResult::Forward
}
}
#[derive(Clone)]
pub struct AppExit {
requested: Rc<Cell<bool>>,
signal: Rc<Event>,
}
impl AppExit {
pub fn new() -> Self {
Self {
requested: Rc::new(Cell::new(false)),
signal: Rc::new(Event::new()),
}
}
pub fn request_exit(&self) {
if self.requested.replace(true) {
debug!("[AppExit] exit already requested — ignored");
return;
}
info!("[AppExit] exit requested");
ShutdownPolicy::notify_shutdown(&self.signal);
}
pub fn is_requested(&self) -> bool {
self.requested.get()
}
pub(crate) fn signal(&self) -> &Event {
&self.signal
}
}
impl Default for AppExit {
fn default() -> Self {
Self::new()
}
}
pub(crate) struct ShutdownPolicy;
impl ShutdownPolicy {
pub(crate) fn shutdown_future(exit: AppExit) -> impl Future<Output = ()> {
async move {
let listener = exit.signal().listen();
if exit.is_requested() {
return;
}
listener.await;
}
}
pub(crate) fn notify_shutdown(event: &Event) {
event.notify(usize::MAX);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn block_on_ready_future_returns_value() {
let v = MessageLoopDriver::block_on(async { 42u32 });
assert_eq!(v, 42);
}
#[test]
fn default_filter_forwards_arbitrary_message() {
let msg: MSG = unsafe { std::mem::zeroed() };
assert_eq!(
MessageLoopDriver::default_filter(&msg),
FilterResult::Forward
);
}
#[test]
fn shutdown_future_completes_when_event_notified() {
use event_listener::Listener;
let event = Rc::new(Event::new());
let listener = event.listen();
ShutdownPolicy::notify_shutdown(&event);
listener.wait();
let l2 = event.listen();
event.notify(usize::MAX);
l2.wait();
}
#[test]
fn notify_shutdown_wakes_armed_listener_and_is_idempotent() {
use event_listener::Listener;
let event = Event::new();
let listener = event.listen();
ShutdownPolicy::notify_shutdown(&event);
ShutdownPolicy::notify_shutdown(&event);
listener.wait();
}
struct LoopWatchdog {
_cancel: std::sync::mpsc::Sender<()>,
}
impl LoopWatchdog {
fn arm(timeout: std::time::Duration) -> Self {
use windows::Win32::Foundation::{LPARAM, WPARAM};
use windows::Win32::System::Threading::GetCurrentThreadId;
use windows::Win32::UI::WindowsAndMessaging::{PostThreadMessageW, WM_QUIT};
let tid = unsafe { GetCurrentThreadId() };
let (cancel, cancelled) = std::sync::mpsc::channel::<()>();
std::thread::spawn(move || {
if let Err(std::sync::mpsc::RecvTimeoutError::Timeout) =
cancelled.recv_timeout(timeout)
{
let _ = unsafe { PostThreadMessageW(tid, WM_QUIT, WPARAM(0), LPARAM(0)) };
}
});
Self { _cancel: cancel }
}
}
const WATCHDOG: std::time::Duration = std::time::Duration::from_secs(5);
struct YieldNow(bool);
impl Future for YieldNow {
type Output = ();
fn poll(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<()> {
if self.0 {
return std::task::Poll::Ready(());
}
self.0 = true;
cx.waker().wake_by_ref();
std::task::Poll::Pending
}
}
#[test]
fn exit_requested_from_a_ui_task_ends_the_loop() {
let _watchdog = LoopWatchdog::arm(WATCHDOG);
let exit = AppExit::new();
let waiting = Rc::new(Cell::new(false));
let from_task = exit.clone();
let seen = Rc::clone(&waiting);
let _task = crate::executor::spawn_local(async move {
while !seen.get() {
YieldNow(false).await;
}
from_task.request_exit();
});
let armed = Rc::clone(&waiting);
let shutdown = ShutdownPolicy::shutdown_future(exit.clone());
MessageLoopDriver::block_on(async move {
armed.set(true);
shutdown.await;
});
assert!(
exit.is_requested(),
"UI タスクの指示が受け口に残っているはず"
);
}
#[test]
fn exit_requested_before_the_loop_starts_completes_immediately() {
let _watchdog = LoopWatchdog::arm(WATCHDOG);
let exit = AppExit::new();
exit.request_exit();
MessageLoopDriver::block_on(ShutdownPolicy::shutdown_future(exit.clone()));
assert!(exit.is_requested());
}
#[test]
fn second_request_is_ignored_and_the_future_still_completes() {
let _watchdog = LoopWatchdog::arm(WATCHDOG);
let exit = AppExit::new();
exit.request_exit();
exit.request_exit();
assert!(exit.is_requested(), "2 回目の指示で指示済みが崩れないはず");
MessageLoopDriver::block_on(ShutdownPolicy::shutdown_future(exit.clone()));
assert!(exit.is_requested());
}
}