use bevy_ecs::hierarchy::ChildOf;
use bevy_ecs::prelude::*;
use std::cell::{Cell, RefCell};
use std::time::Instant;
use tracing::{debug, trace, warn};
use windows::Win32::Foundation::*;
use windows::Win32::UI::WindowsAndMessaging::*;
use super::components::Window;
use super::transition_diag::{
self, EnqueueRecord, FlushRecord, FlushStage, WriteRecord, WriteStage, WriteTag,
};
thread_local! {
static SELF_INITIATED_DEPTH: Cell<i32> = const { Cell::new(0) };
}
pub fn is_self_initiated() -> bool {
SELF_INITIATED_DEPTH.with(|depth| depth.get()) > 0
}
struct SetWindowPosGuard;
impl SetWindowPosGuard {
fn new() -> Self {
SELF_INITIATED_DEPTH.with(|depth| depth.set(depth.get() + 1));
Self
}
}
impl Drop for SetWindowPosGuard {
fn drop(&mut self) {
let depth = SELF_INITIATED_DEPTH.with(|cell| {
let next = cell.get() - 1;
cell.set(next);
next
});
trace!(depth, "SELF_INITIATED_DEPTH decremented by guard");
}
}
pub unsafe fn guarded_set_window_pos(
hwnd: HWND,
hwnd_insert_after: Option<HWND>,
x: i32,
y: i32,
cx: i32,
cy: i32,
flags: SET_WINDOW_POS_FLAGS,
) -> windows::core::Result<()> {
let _guard = SetWindowPosGuard::new();
log_window_write(
&WindowPosArgs {
hwnd,
hwnd_insert_after,
x,
y,
cx,
cy,
flags,
},
VIA_SET_WINDOW_POS,
);
unsafe { SetWindowPos(hwnd, hwnd_insert_after, x, y, cx, cy, flags) }?;
Ok(())
}
const WINDOW_WRITE_LOG_MESSAGE: &str = "[guarded_set_window_pos] Calling SetWindowPos";
const VIA_SET_WINDOW_POS: &str = "SetWindowPos";
const VIA_DEFER_WINDOW_POS: &str = "DeferWindowPos";
fn log_window_write(args: &WindowPosArgs, via: &'static str) {
debug!(
hwnd = format!("0x{:X}", args.hwnd.0 as usize),
x = args.x, y = args.y, cx = args.cx, cy = args.cy,
flags = ?args.flags,
via = via,
"{WINDOW_WRITE_LOG_MESSAGE}"
);
}
#[derive(Debug, Clone)]
pub struct SetWindowPosCommand {
pub hwnd: HWND,
pub x: i32,
pub y: i32,
pub width: i32,
pub height: i32,
pub flags: SET_WINDOW_POS_FLAGS,
pub hwnd_insert_after: Option<HWND>,
pub tag: WriteTag,
}
thread_local! {
static WINDOW_POS_COMMANDS: RefCell<Vec<SetWindowPosCommand>> = const { RefCell::new(Vec::new()) };
}
pub(crate) fn read_back_window_rect(hwnd: HWND) -> Option<RECT> {
let mut rect = RECT::default();
if unsafe { GetWindowRect(hwnd, &mut rect) }.is_ok() {
Some(rect)
} else {
None
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct WindowPosArgs {
pub hwnd: HWND,
pub hwnd_insert_after: Option<HWND>,
pub x: i32,
pub y: i32,
pub cx: i32,
pub cy: i32,
pub flags: SET_WINDOW_POS_FLAGS,
}
pub(crate) fn window_pos_args(cmd: &SetWindowPosCommand) -> WindowPosArgs {
WindowPosArgs {
hwnd: cmd.hwnd,
hwnd_insert_after: cmd.hwnd_insert_after,
x: cmd.x,
y: cmd.y,
cx: cmd.width,
cy: cmd.height,
flags: cmd.flags,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum BatchDegrade {
BeginFailed,
DeferFailed {
seq: u32,
},
EndFailed,
}
impl BatchDegrade {
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::BeginFailed => "BeginDeferWindowPos-failed",
Self::DeferFailed { .. } => "DeferWindowPos-failed",
Self::EndFailed => "EndDeferWindowPos-failed",
}
}
}
#[cfg(test)]
thread_local! {
static FORCE_BATCH_BEGIN_FAILURE: RefCell<bool> = const { RefCell::new(false) };
}
#[cfg(test)]
pub(crate) fn with_forced_batch_begin_failure<R>(body: impl FnOnce() -> R) -> R {
struct Restore;
impl Drop for Restore {
fn drop(&mut self) {
FORCE_BATCH_BEGIN_FAILURE.with(|cell| *cell.borrow_mut() = false);
}
}
FORCE_BATCH_BEGIN_FAILURE.with(|cell| *cell.borrow_mut() = true);
let _restore = Restore;
body()
}
fn begin_batch(count: usize) -> Option<HDWP> {
#[cfg(test)]
if FORCE_BATCH_BEGIN_FAILURE.with(|cell| *cell.borrow()) {
return None;
}
unsafe { BeginDeferWindowPos(i32::try_from(count).unwrap_or(i32::MAX)) }.ok()
}
unsafe fn apply_as_batch(
commands: &[SetWindowPosCommand],
observe: bool,
call_us: &mut [u64],
) -> Result<(), BatchDegrade> {
let mut batch = begin_batch(commands.len()).ok_or(BatchDegrade::BeginFailed)?;
let _guard = SetWindowPosGuard::new();
for (index, cmd) in commands.iter().enumerate() {
let args = window_pos_args(cmd);
let started = observe.then(Instant::now);
let next = unsafe {
DeferWindowPos(
batch,
args.hwnd,
args.hwnd_insert_after,
args.x,
args.y,
args.cx,
args.cy,
args.flags,
)
};
if observe && let Some(slot) = call_us.get_mut(index) {
*slot = started.map_or(0, transition_diag::elapsed_us);
}
match next {
Ok(handle) => batch = handle,
Err(error) => {
warn!(
seq = index,
hwnd = ?args.hwnd,
error = ?error,
"DeferWindowPos failed; the whole batch is discarded"
);
return Err(BatchDegrade::DeferFailed {
seq: u32::try_from(index).unwrap_or(u32::MAX),
});
}
}
}
if let Err(error) = unsafe { EndDeferWindowPos(batch) } {
warn!(error = ?error, "EndDeferWindowPos failed");
return Err(BatchDegrade::EndFailed);
}
for cmd in commands {
log_window_write(&window_pos_args(cmd), VIA_DEFER_WINDOW_POS);
}
Ok(())
}
unsafe fn apply_sequentially(
commands: &[SetWindowPosCommand],
observe: bool,
call_us: &mut [u64],
ok: &mut [bool],
) {
for (index, cmd) in commands.iter().enumerate() {
let args = window_pos_args(cmd);
let started = observe.then(Instant::now);
let result = unsafe {
guarded_set_window_pos(
args.hwnd,
args.hwnd_insert_after,
args.x,
args.y,
args.cx,
args.cy,
args.flags,
)
};
if observe && let Some(slot) = call_us.get_mut(index) {
*slot = started.map_or(0, transition_diag::elapsed_us);
}
if let Some(slot) = ok.get_mut(index) {
*slot = result.is_ok();
}
if let Err(e) = result {
warn!(
hwnd = ?args.hwnd,
error = ?e,
"SetWindowPos failed"
);
} else {
trace!(hwnd = ?args.hwnd, "SetWindowPos succeeded");
}
}
}
const COALESCIBLE_FLAGS: u32 = SWP_NOZORDER.0 | SWP_NOACTIVATE.0 | SWP_NOMOVE.0 | SWP_NOSIZE.0;
const REQUIRED_FOR_COALESCE: u32 = SWP_NOZORDER.0 | SWP_NOACTIVATE.0;
fn is_coalescible(cmd: &SetWindowPosCommand) -> bool {
cmd.hwnd_insert_after.is_none()
&& (cmd.flags.0 & REQUIRED_FOR_COALESCE) == REQUIRED_FOR_COALESCE
&& (cmd.flags.0 & !COALESCIBLE_FLAGS) == 0
}
fn find_merge_target(queue: &[SetWindowPosCommand], hwnd: HWND) -> Option<usize> {
let mut first = None;
for (index, existing) in queue.iter().enumerate() {
if existing.hwnd != hwnd {
continue;
}
if is_coalescible(existing) {
if first.is_none() {
first = Some(index);
}
} else {
first = None;
}
}
first
}
fn merge_into(base: &mut SetWindowPosCommand, later: &SetWindowPosCommand) {
let base_nomove = (base.flags.0 & SWP_NOMOVE.0) != 0;
let base_nosize = (base.flags.0 & SWP_NOSIZE.0) != 0;
let later_nomove = (later.flags.0 & SWP_NOMOVE.0) != 0;
let later_nosize = (later.flags.0 & SWP_NOSIZE.0) != 0;
if !later_nomove {
base.x = later.x;
base.y = later.y;
}
if !later_nosize {
base.width = later.width;
base.height = later.height;
}
let mut flags = SWP_NOZORDER | SWP_NOACTIVATE;
if base_nomove && later_nomove {
flags |= SWP_NOMOVE;
}
if base_nosize && later_nosize {
flags |= SWP_NOSIZE;
}
base.flags = flags;
}
pub(crate) fn coalesce_geometry(
queue: &mut Vec<SetWindowPosCommand>,
cmd: SetWindowPosCommand,
) -> Option<u32> {
let target = if is_coalescible(&cmd) {
find_merge_target(queue, cmd.hwnd)
} else {
None
};
match target {
Some(index) => {
merge_into(&mut queue[index], &cmd);
Some(u32::try_from(index).unwrap_or(u32::MAX))
}
None => {
queue.push(cmd);
None
}
}
}
impl SetWindowPosCommand {
pub fn new(
hwnd: HWND,
x: i32,
y: i32,
width: i32,
height: i32,
flags: SET_WINDOW_POS_FLAGS,
hwnd_insert_after: Option<HWND>,
) -> Self {
Self {
hwnd,
x,
y,
width,
height,
flags,
hwnd_insert_after,
tag: WriteTag::UNTAGGED,
}
}
pub fn with_tag(mut self, tag: WriteTag) -> Self {
self.tag = tag;
self
}
pub fn enqueue(cmd: SetWindowPosCommand) {
crate::ecs::world::tick_wake::mark(crate::ecs::world::tick_wake::WINDOW_CMD);
trace!(
hwnd = ?cmd.hwnd,
x = cmd.x,
y = cmd.y,
width = cmd.width,
height = cmd.height,
"SetWindowPosCommand::enqueue"
);
let hwnd = cmd.hwnd;
let tag = cmd.tag;
let merged_into_seq =
WINDOW_POS_COMMANDS.with(|cell| coalesce_geometry(&mut cell.borrow_mut(), cmd));
if transition_diag::is_enabled() {
transition_diag::emit_line(&transition_diag::enqueue_line(&EnqueueRecord {
stamp: transition_diag::stamp(),
hwnd,
tag,
merged_into_seq,
}));
}
}
pub fn flush() {
let observe = transition_diag::is_enabled();
let _flush_epoch = observe.then(transition_diag::begin_flush);
WINDOW_POS_COMMANDS.with(|cell| {
let commands: Vec<_> = cell.borrow_mut().drain(..).collect();
if commands.is_empty() {
return;
}
let count = commands.len();
trace!(count = count, "SetWindowPosCommand::flush processing");
let flush_started = observe.then(Instant::now);
if observe {
transition_diag::emit_line(&transition_diag::flush_line(&FlushRecord {
stamp: transition_diag::stamp(),
stage: FlushStage::Begin,
count,
since_tick_us: transition_diag::since_tick_start_us(),
total_us: None,
}));
}
let mut call_us = vec![0u64; if observe { count } else { 0 }];
let mut ok = vec![false; if observe { count } else { 0 }];
let batched = unsafe { apply_as_batch(&commands, observe, &mut call_us) };
let in_batch = match batched {
Ok(()) => {
ok.fill(true);
true
}
Err(reason) => {
warn!(
count = count,
reason = reason.as_str(),
"DeferWindowPos batch unavailable; falling back to one SetWindowPos per command"
);
unsafe { apply_sequentially(&commands, observe, &mut call_us, &mut ok) };
false
}
};
if observe {
for (index, cmd) in commands.iter().enumerate() {
transition_diag::emit_line(&transition_diag::write_line(&WriteRecord {
stamp: transition_diag::stamp(),
stage: WriteStage::Flush,
seq: u32::try_from(index).unwrap_or(u32::MAX),
hwnd: cmd.hwnd,
tag: cmd.tag,
x: cmd.x,
y: cmd.y,
cx: cmd.width,
cy: cmd.height,
flags: cmd.flags.0,
after: read_back_window_rect(cmd.hwnd),
call_us: call_us.get(index).copied().unwrap_or(0),
ok: ok.get(index).copied().unwrap_or(false),
in_batch,
}));
}
}
if observe {
transition_diag::emit_line(&transition_diag::flush_line(&FlushRecord {
stamp: transition_diag::stamp(),
stage: FlushStage::End,
count,
since_tick_us: transition_diag::since_tick_start_us(),
total_us: Some(flush_started.map_or(0, transition_diag::elapsed_us)),
}));
}
});
}
}
pub fn flush_window_pos_commands() {
SetWindowPosCommand::flush();
}
#[doc(hidden)]
pub fn drain_window_pos_commands() -> Vec<SetWindowPosCommand> {
WINDOW_POS_COMMANDS.with(|cell| cell.borrow_mut().drain(..).collect())
}
pub fn find_owner_window(world: &World, entity: Entity) -> Option<Entity> {
if world.get::<Window>(entity).is_some() {
return Some(entity);
}
let mut current = entity;
while let Some(child_of) = world.get::<ChildOf>(current) {
let parent = child_of.parent();
if world.get::<Window>(parent).is_some() {
return Some(parent);
}
current = parent;
}
None
}
#[cfg(test)]
#[path = "command_transition_tests.rs"]
mod command_transition_tests;
#[cfg(test)]
#[path = "command_coalesce_tests.rs"]
mod command_coalesce_tests;
#[cfg(test)]
#[path = "command_batch_tests.rs"]
mod command_batch_tests;
#[cfg(test)]
#[path = "command_threadlocal_tests.rs"]
mod command_threadlocal_tests;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_window_pos_command_new_stores_all_fields() {
let hwnd = HWND(0x1000 as *mut _);
let after = HWND(0x2000 as *mut _);
let cmd = SetWindowPosCommand::new(hwnd, 10, 20, 300, 400, SWP_NOACTIVATE, Some(after));
assert_eq!(cmd.hwnd, hwnd);
assert_eq!(cmd.x, 10);
assert_eq!(cmd.y, 20);
assert_eq!(cmd.width, 300);
assert_eq!(cmd.height, 400);
assert_eq!(cmd.flags, SWP_NOACTIVATE);
assert_eq!(cmd.hwnd_insert_after, Some(after));
}
#[test]
fn test_set_window_pos_command_new_allows_none_insert_after() {
let cmd = SetWindowPosCommand::new(
HWND(0x1 as *mut _),
0,
0,
0,
0,
SET_WINDOW_POS_FLAGS(0),
None,
);
assert_eq!(cmd.hwnd_insert_after, None);
}
#[test]
fn test_is_self_initiated_false_at_rest() {
assert!(!is_self_initiated());
}
#[test]
fn test_flush_empty_queue_is_noop() {
SetWindowPosCommand::flush();
flush_window_pos_commands();
assert!(!is_self_initiated());
}
}