#![allow(non_snake_case)]
use std::cell::RefCell;
use std::rc::Rc;
use bevy_ecs::prelude::Entity;
use tracing::{debug, info, warn};
use windows::Win32::Foundation::*;
use crate::ecs::window::transition_diag::{self, MSG_DISPLAYCHANGE, MsgRecord};
use crate::ecs::window::{OnCloseRequest, OnSessionEnd};
use crate::ecs::world::EcsWorld;
type HandlerResult = Option<LRESULT>;
pub(crate) const DESPAWNED_SKIP_TAG: &str = "[despawn-skip]";
#[inline]
pub(super) fn WM_ERASEBKGND(
_world: &Rc<RefCell<EcsWorld>>,
_entity: Entity,
_hwnd: HWND,
_wparam: WPARAM,
_lparam: LPARAM,
) -> HandlerResult {
Some(LRESULT(1)) }
#[inline]
pub(super) fn WM_PAINT(
_world: &Rc<RefCell<EcsWorld>>,
_entity: Entity,
_hwnd: HWND,
_wparam: WPARAM,
_lparam: LPARAM,
) -> HandlerResult {
None
}
#[inline]
pub(super) fn WM_CLOSE(
world: &Rc<RefCell<EcsWorld>>,
entity: Entity,
_hwnd: HWND,
_wparam: WPARAM,
_lparam: LPARAM,
) -> HandlerResult {
if let Ok(mut w) = world.try_borrow_mut() {
if w.world().get_entity(entity).is_err() {
debug!(
entity = ?entity,
"{DESPAWNED_SKIP_TAG} WM_CLOSE: 対象 entity は既に破棄済み(despawn)→ \
除去要求を正常系として打ち切り"
);
} else if let Some(cb) = w.world().get::<OnCloseRequest>(entity).copied() {
info!(
event = "os_close_request",
entity = ?entity,
"[WM_CLOSE] 閉鎖要求を利用側の関数へ渡す"
);
(cb.0)(w.world_mut(), entity);
} else {
w.world_mut().despawn(entity);
}
}
Some(LRESULT(0))
}
#[inline]
pub(super) fn WM_ENDSESSION(
world: &Rc<RefCell<EcsWorld>>,
entity: Entity,
_hwnd: HWND,
wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
if wparam.0 == 0 {
debug!(
event = "os_session_end_cancelled",
entity = ?entity,
"[WM_ENDSESSION] セッションの終了は取りやめられた → 何もしない"
);
return Some(LRESULT(0));
}
let Ok(mut w) = world.try_borrow_mut() else {
warn!(
event = "os_session_end_world_busy",
entity = ?entity,
"[WM_ENDSESSION] World が借用中でセッションの終了の受け手を呼べない → \
後始末をせずに終わるので起動中の印が残り、次の起動は前回落ちた扱い(Ref6/7 付き)になる"
);
return Some(LRESULT(0));
};
if w.world().get_entity(entity).is_err() {
debug!(
entity = ?entity,
"{DESPAWNED_SKIP_TAG} WM_ENDSESSION: 対象 entity は既に破棄済み(despawn)→ \
セッションの終了は先の受け手で済んだものとして打ち切り"
);
} else if let Some(cb) = w.world().get::<OnSessionEnd>(entity).copied() {
info!(
event = "os_session_end",
entity = ?entity,
lparam = lparam.0,
"[WM_ENDSESSION] セッションの終了を利用側の関数へ渡す"
);
(cb.0)(w.world_mut(), entity);
} else {
debug!(
entity = ?entity,
"[WM_ENDSESSION] セッションの終了の関数を持たない窓 → 何もしない"
);
}
Some(LRESULT(0))
}
#[inline]
pub(super) fn WM_DISPLAYCHANGE(
world: &Rc<RefCell<EcsWorld>>,
_entity: Entity,
hwnd: HWND,
_wparam: WPARAM,
_lparam: LPARAM,
) -> HandlerResult {
if transition_diag::is_enabled() {
transition_diag::emit_line(&transition_diag::msg_line(&MsgRecord {
stamp: transition_diag::stamp(),
msg: MSG_DISPLAYCHANGE,
hwnd,
in_swp: crate::ecs::window::is_self_initiated(),
since_flush_us: transition_diag::since_flush_us(),
}));
}
if let Ok(mut world_borrow) = world.try_borrow_mut() {
if let Some(mut app) = world_borrow
.world_mut()
.get_resource_mut::<crate::ecs::App>()
{
app.mark_display_change();
}
}
None }
#[cfg(test)]
mod tests {
use super::*;
use windows::Win32::Foundation::HWND;
fn spawn_window_entity(world: &Rc<RefCell<EcsWorld>>) -> Entity {
world
.borrow_mut()
.world_mut()
.spawn(crate::ecs::window::Window::default())
.id()
}
#[test]
fn wm_close_despawns_target_entity() {
let world = Rc::new(RefCell::new(EcsWorld::new()));
let entity = spawn_window_entity(&world);
assert!(
world.borrow().world().get_entity(entity).is_ok(),
"spawn 直後は Entity が生存しているべき"
);
let ret = WM_CLOSE(
&world,
entity,
HWND(std::ptr::null_mut()),
WPARAM(0),
LPARAM(0),
);
assert_eq!(
ret,
Some(LRESULT(0)),
"WM_CLOSE は既定破棄抑止の Some(LRESULT(0)) を返すべき"
);
assert!(
world.borrow().world().get_entity(entity).is_err(),
"WM_CLOSE 後は対象 Entity が despawn されているべき(除去要求)"
);
}
#[test]
fn wm_close_safe_skips_when_world_already_borrowed() {
let world = Rc::new(RefCell::new(EcsWorld::new()));
let entity = spawn_window_entity(&world);
let guard = world.borrow_mut();
let ret = WM_CLOSE(
&world,
entity,
HWND(std::ptr::null_mut()),
WPARAM(0),
LPARAM(0),
);
drop(guard);
assert_eq!(
ret,
Some(LRESULT(0)),
"再入時も Some(LRESULT(0)) を返す(safe-skip)"
);
assert!(
world.borrow().world().get_entity(entity).is_ok(),
"借用中の再入では despawn されず Entity が残存するべき"
);
}
const CAPTURE_DIRECTIVES: &str = "info,wintf::ecs::window_proc=debug";
#[test]
fn wm_close_skips_stale_entity_as_normal_teardown() {
use crate::ecs::test_support::capture_under_filter;
{
let world = Rc::new(RefCell::new(EcsWorld::new()));
let entity = spawn_window_entity(&world);
assert!(
world.borrow_mut().world_mut().despawn(entity),
"探針前提: 生存 entity への 1 回目の despawn は成功する"
);
assert!(
!world.borrow_mut().world_mut().despawn(entity),
"対照アーム: 陳腐化 id への despawn は false を返す\
(=`Could not despawn entity` の警告 1 件)。\
ここが true なら本檻は恒真の空虚檻である"
);
}
{
let probe = capture_under_filter(CAPTURE_DIRECTIVES, || {
tracing::warn!("[harness-selfcheck] 捕捉可能性の自己証明");
});
assert!(
probe.contains("WARN") && probe.contains("[harness-selfcheck]"),
"捕捉ハーネスが tracing の WARN を見られない。\
後段の陰性主張が空虚になるため檻として成立しない: {probe}"
);
}
let world = Rc::new(RefCell::new(EcsWorld::new()));
let entity = spawn_window_entity(&world);
assert!(
world.borrow_mut().world_mut().despawn(entity),
"探針前提: 先行 despawn は成功する(以後この id は陳腐化している)"
);
let mut ret = None;
let out = capture_under_filter(CAPTURE_DIRECTIVES, || {
ret = WM_CLOSE(
&world,
entity,
HWND(std::ptr::null_mut()),
WPARAM(0),
LPARAM(0),
);
});
assert_eq!(
ret,
Some(LRESULT(0)),
"陳腐化 id でも既定破棄抑止の Some(LRESULT(0)) を返す(投函契約は不変)"
);
let hits: Vec<&str> = out
.lines()
.filter(|l| l.contains(DESPAWNED_SKIP_TAG))
.collect();
assert_eq!(
hits.len(),
1,
"陳腐化 id に対する打ち切り行 `{DESPAWNED_SKIP_TAG}` がちょうど 1 行でない\
(0 行なら存在確認が無く bevy の警告が出る経路のまま): {out}"
);
assert!(
hits[0].contains("DEBUG"),
"破棄済みの打ち切りは正常終了系ゆえ DEBUG 水準(Req 6.2): {}",
hits[0]
);
assert!(
hits[0].contains("WM_CLOSE"),
"打ち切り行が自分の相(WM_CLOSE)を名乗っていない: {}",
hits[0]
);
assert!(
!out.contains("WARN") && !out.contains("ERROR"),
"破棄済み entity に対して警告以上のログが出ている(Req 6.2 違反): {out}"
);
}
#[test]
fn wm_close_on_live_entity_despawns_without_skip_line() {
use crate::ecs::test_support::capture_under_filter;
let world = Rc::new(RefCell::new(EcsWorld::new()));
let entity = spawn_window_entity(&world);
let out = capture_under_filter(CAPTURE_DIRECTIVES, || {
WM_CLOSE(
&world,
entity,
HWND(std::ptr::null_mut()),
WPARAM(0),
LPARAM(0),
);
});
assert!(
world.borrow().world().get_entity(entity).is_err(),
"生存 entity は従来どおり despawn される(正準シグナルを弱めてはならない)"
);
assert!(
!out.contains(DESPAWNED_SKIP_TAG),
"生存経路で打ち切り行 `{DESPAWNED_SKIP_TAG}` が出ている\
(正常系の窓破棄が「破棄済み」と誤報される): {out}"
);
}
}
#[cfg(test)]
#[path = "lifecycle_tests.rs"]
mod lifecycle_tests;