use std::cell::RefCell;
use std::pin::Pin;
use std::rc::Weak;
use crate::executor::util::WindowMessage;
use bevy_ecs::prelude::Entity;
use tracing::warn;
use windows::Win32::Foundation::LRESULT;
use windows::Win32::UI::WindowsAndMessaging::WM_ENDSESSION;
use crate::ecs::dispatch_window_message;
use crate::ecs::drag::handle_message_while_world_busy;
use crate::ecs::world::EcsWorld;
pub(crate) struct WndState {
pub world: Weak<RefCell<EcsWorld>>,
pub entity: Entity,
}
pub(crate) fn make_wndproc() -> impl Fn(Pin<&WndState>, WindowMessage) -> Option<LRESULT> {
move |state: Pin<&WndState>, msg: WindowMessage| -> Option<LRESULT> {
let entity = state.entity;
let world = state.world.upgrade()?;
if world.try_borrow().is_err() {
if msg.msg == WM_ENDSESSION && msg.wparam.0 != 0 {
warn!(
event = "os_session_end_world_busy",
entity = ?entity,
"[WM_ENDSESSION] World が借用中でセッションの終了の受け手を呼べない → \
後始末をせずに終わるので起動中の印が残り、次の起動は前回落ちた扱い(Ref6/7 付き)になる"
);
}
handle_message_while_world_busy(entity, &msg);
return None;
}
dispatch_window_message(&world, entity, &msg)
}
}
#[cfg(test)]
#[path = "wndproc_bridge_drag_test_support.rs"]
mod wndproc_bridge_drag_test_support;
#[cfg(test)]
#[path = "wndproc_bridge_drag_cancel_tests.rs"]
mod wndproc_bridge_drag_cancel_tests;
#[cfg(test)]
#[path = "wndproc_bridge_drag_release_tests.rs"]
mod wndproc_bridge_drag_release_tests;
#[cfg(test)]
mod tests {
use super::*;
use std::rc::Rc;
use windows::Win32::Foundation::{HWND, LPARAM, WPARAM};
use windows::Win32::UI::WindowsAndMessaging::{WM_ERASEBKGND, WM_USER};
fn make_state(world: &Rc<RefCell<EcsWorld>>) -> WndState {
let entity = world.borrow_mut().world_mut().spawn(()).id();
WndState {
world: Rc::downgrade(world),
entity,
}
}
fn msg(message: u32) -> WindowMessage {
WindowMessage {
hwnd: HWND(std::ptr::null_mut()),
msg: message,
wparam: WPARAM(0),
lparam: LPARAM(0),
}
}
#[test]
fn closure_safe_skips_when_world_dropped() {
let world = Rc::new(RefCell::new(EcsWorld::new()));
let state = make_state(&world);
let wndproc = make_wndproc();
drop(world);
let boxed = Box::pin(state);
let ret = wndproc(boxed.as_ref(), msg(WM_ERASEBKGND));
assert!(
ret.is_none(),
"World drop 後は upgrade→None で安全スキップ(None)すべき"
);
}
#[test]
fn closure_safe_skips_when_world_borrowed() {
let world = Rc::new(RefCell::new(EcsWorld::new()));
let state = make_state(&world);
let wndproc = make_wndproc();
let _held = world.borrow_mut();
let boxed = Box::pin(state);
let ret = wndproc(boxed.as_ref(), msg(WM_ERASEBKGND));
assert!(
ret.is_none(),
"World 借用中は try_borrow 失敗で安全スキップ(None)すべき"
);
}
#[test]
fn closure_warns_session_end_only_while_world_borrowed() {
let world = Rc::new(RefCell::new(EcsWorld::new()));
let state = make_state(&world);
let wndproc = make_wndproc();
let boxed = Box::pin(state);
let busy_warns = |m: WindowMessage| {
let (ret, events) = log_capture_kit::capture(|| wndproc(boxed.as_ref(), m));
let n = events
.iter()
.filter(|e| {
e.field_str("event") == Some("os_session_end_world_busy")
&& e.level == tracing::Level::WARN
})
.count();
(ret, n)
};
let _held = world.borrow_mut();
let session_end = WindowMessage {
wparam: WPARAM(1),
..msg(WM_ENDSESSION)
};
assert_eq!(busy_warns(session_end), (None, 1));
assert_eq!(
busy_warns(msg(WM_ENDSESSION)),
(None, 0),
"wParam 偽で警告した"
);
assert_eq!(
busy_warns(msg(WM_ERASEBKGND)),
(None, 0),
"他のメッセージで警告した"
);
}
#[test]
fn closure_routes_representative_message() {
let world = Rc::new(RefCell::new(EcsWorld::new()));
let state = make_state(&world);
let wndproc = make_wndproc();
let boxed = Box::pin(state);
let handled = wndproc(boxed.as_ref(), msg(WM_ERASEBKGND));
assert_eq!(
handled,
Some(LRESULT(1)),
"代表メッセージ WM_ERASEBKGND は Some(LRESULT(1)) を返すべき"
);
let unhandled = wndproc(boxed.as_ref(), msg(WM_USER));
assert!(
unhandled.is_none(),
"非対応メッセージは None(既定手続き委譲)であるべき"
);
}
}