use std::cell::Cell;
use std::time::Instant;
use bevy_ecs::prelude::*;
use windows::Win32::Foundation::{HWND, RECT};
use crate::ecs::world::FrameCount;
pub const TRANSITION_TARGET: &str = "wintf::transition";
pub const RECORD_PREFIX_TAG: &str = "[transition]";
pub const MISSING: &str = "-";
pub const KIND_MONITOR: &str = "monitor";
pub const KIND_WINDPI: &str = "windpi";
pub const KIND_WRITE: &str = "write";
pub const KIND_FLUSH: &str = "flush";
pub const KIND_MSG: &str = "msg";
pub const KIND_ENQUEUE: &str = "enqueue";
pub const KIND_ALL: &[&str] = &[
KIND_MONITOR,
KIND_WINDPI,
KIND_WRITE,
KIND_FLUSH,
KIND_MSG,
KIND_ENQUEUE,
];
pub const STAGE_FLUSH: &str = "flush";
pub const STAGE_SYNC: &str = "sync";
pub const STAGE_BEGIN: &str = "begin";
pub const STAGE_END: &str = "end";
pub const STAGE_ALL: &[&str] = &[STAGE_FLUSH, STAGE_SYNC, STAGE_BEGIN, STAGE_END];
pub const MSG_DPICHANGED: &str = "WM_DPICHANGED";
pub const MSG_WINDOWPOSCHANGED: &str = "WM_WINDOWPOSCHANGED";
pub const MSG_DISPLAYCHANGE: &str = "WM_DISPLAYCHANGE";
pub const MSG_ALL: &[&str] = &[MSG_DPICHANGED, MSG_WINDOWPOSCHANGED, MSG_DISPLAYCHANGE];
pub const ORIGIN_ZORDER_PAIR: &str = "zorder-pair";
pub const ORIGIN_WINDOW_POS: &str = "window-pos";
pub const ORIGIN_DPI_SUGGESTED: &str = "dpi-suggested";
pub const FIELD_FRAME: &str = "frame";
pub const FIELD_T_US: &str = "t_us";
pub const FIELD_KIND: &str = "kind";
pub const FIELD_ENTITY: &str = "entity";
pub const FIELD_OLD_DPI: &str = "old_dpi";
pub const FIELD_NEW_DPI: &str = "new_dpi";
pub const FIELD_OLD_WA: &str = "old_wa";
pub const FIELD_NEW_WA: &str = "new_wa";
pub const FIELD_STAGE: &str = "stage";
pub const FIELD_SEQ: &str = "seq";
pub const FIELD_HWND: &str = "hwnd";
pub const FIELD_ORIGIN: &str = "origin";
pub const FIELD_SCOPE: &str = "scope";
pub const FIELD_WIN_KIND: &str = "win_kind";
pub const FIELD_X: &str = "x";
pub const FIELD_Y: &str = "y";
pub const FIELD_CX: &str = "cx";
pub const FIELD_CY: &str = "cy";
pub const FIELD_FLAGS: &str = "flags";
pub const FIELD_AX: &str = "ax";
pub const FIELD_AY: &str = "ay";
pub const FIELD_AW: &str = "aw";
pub const FIELD_AH: &str = "ah";
pub const FIELD_CALL_US: &str = "call_us";
pub const FIELD_OK: &str = "ok";
pub const FIELD_IN_BATCH: &str = "in_batch";
pub const FIELD_COUNT: &str = "count";
pub const FIELD_SINCE_TICK_US: &str = "since_tick_us";
pub const FIELD_TOTAL_US: &str = "total_us";
pub const FIELD_MSG: &str = "msg";
pub const FIELD_IN_SWP: &str = "in_swp";
pub const FIELD_SINCE_FLUSH_US: &str = "since_flush_us";
pub const FIELD_MERGED_INTO_SEQ: &str = "merged_into_seq";
pub const MONITOR_FIELDS: &[&str] = &[
FIELD_ENTITY,
FIELD_OLD_DPI,
FIELD_NEW_DPI,
FIELD_OLD_WA,
FIELD_NEW_WA,
];
pub const WINDPI_FIELDS: &[&str] = &[FIELD_ENTITY, FIELD_OLD_DPI, FIELD_NEW_DPI];
pub const WRITE_FIELDS: &[&str] = &[
FIELD_STAGE,
FIELD_SEQ,
FIELD_HWND,
FIELD_ORIGIN,
FIELD_SCOPE,
FIELD_WIN_KIND,
FIELD_X,
FIELD_Y,
FIELD_CX,
FIELD_CY,
FIELD_FLAGS,
FIELD_AX,
FIELD_AY,
FIELD_AW,
FIELD_AH,
FIELD_CALL_US,
FIELD_OK,
];
pub const WRITE_OPTIONAL_FIELDS: &[&str] = &[FIELD_IN_BATCH];
pub const FLUSH_FIELDS: &[&str] = &[
FIELD_STAGE,
FIELD_COUNT,
FIELD_SINCE_TICK_US,
FIELD_TOTAL_US,
];
pub const MSG_FIELDS: &[&str] = &[FIELD_MSG, FIELD_HWND, FIELD_IN_SWP, FIELD_SINCE_FLUSH_US];
pub const ENQUEUE_FIELDS: &[&str] = &[
FIELD_HWND,
FIELD_ORIGIN,
FIELD_SCOPE,
FIELD_WIN_KIND,
FIELD_MERGED_INTO_SEQ,
];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Stamp {
pub frame: u32,
pub t_us: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WriteTag {
pub origin: &'static str,
pub scope: Option<u32>,
pub kind: &'static str,
}
impl WriteTag {
pub const UNTAGGED: WriteTag = WriteTag {
origin: MISSING,
scope: None,
kind: MISSING,
};
}
impl Default for WriteTag {
fn default() -> Self {
Self::UNTAGGED
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WriteStage {
Flush,
Sync,
}
impl WriteStage {
pub fn as_str(self) -> &'static str {
match self {
WriteStage::Flush => STAGE_FLUSH,
WriteStage::Sync => STAGE_SYNC,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FlushStage {
Begin,
End,
}
impl FlushStage {
pub fn as_str(self) -> &'static str {
match self {
FlushStage::Begin => STAGE_BEGIN,
FlushStage::End => STAGE_END,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct MonitorRecord {
pub stamp: Stamp,
pub entity: Entity,
pub old_dpi: u32,
pub new_dpi: u32,
pub old_work_area: RECT,
pub new_work_area: RECT,
}
#[derive(Clone, Copy, Debug)]
pub struct WindowDpiRecord {
pub stamp: Stamp,
pub entity: Entity,
pub old_dpi: u32,
pub new_dpi: u32,
}
#[derive(Clone, Copy, Debug)]
pub struct WriteRecord {
pub stamp: Stamp,
pub stage: WriteStage,
pub seq: u32,
pub hwnd: HWND,
pub tag: WriteTag,
pub x: i32,
pub y: i32,
pub cx: i32,
pub cy: i32,
pub flags: u32,
pub after: Option<RECT>,
pub call_us: u64,
pub ok: bool,
pub in_batch: bool,
}
#[derive(Clone, Copy, Debug)]
pub struct FlushRecord {
pub stamp: Stamp,
pub stage: FlushStage,
pub count: usize,
pub since_tick_us: u64,
pub total_us: Option<u64>,
}
#[derive(Clone, Copy, Debug)]
pub struct MsgRecord {
pub stamp: Stamp,
pub msg: &'static str,
pub hwnd: HWND,
pub in_swp: bool,
pub since_flush_us: Option<u64>,
}
#[derive(Clone, Copy, Debug)]
pub struct EnqueueRecord {
pub stamp: Stamp,
pub hwnd: HWND,
pub tag: WriteTag,
pub merged_into_seq: Option<u32>,
}
fn hwnd_field(hwnd: HWND) -> String {
format!("0x{:X}", hwnd.0 as usize)
}
fn rect_field(rect: RECT) -> String {
format!("{},{},{},{}", rect.left, rect.top, rect.right, rect.bottom)
}
fn opt_field<T: std::fmt::Display>(value: Option<T>) -> String {
match value {
Some(v) => v.to_string(),
None => MISSING.to_string(),
}
}
fn tag_fields(tag: WriteTag) -> String {
format!(
"{FIELD_ORIGIN}={origin} {FIELD_SCOPE}={scope} {FIELD_WIN_KIND}={win_kind}",
origin = tag.origin,
scope = opt_field(tag.scope),
win_kind = tag.kind,
)
}
fn after_fields(after: Option<RECT>) -> String {
match after {
Some(r) => format!(
"{FIELD_AX}={x} {FIELD_AY}={y} {FIELD_AW}={w} {FIELD_AH}={h}",
x = r.left,
y = r.top,
w = r.right - r.left,
h = r.bottom - r.top,
),
None => format!(
"{FIELD_AX}={MISSING} {FIELD_AY}={MISSING} {FIELD_AW}={MISSING} {FIELD_AH}={MISSING}"
),
}
}
pub fn record_prefix(stamp: Stamp, kind: &'static str) -> String {
format!(
"{RECORD_PREFIX_TAG} {FIELD_FRAME}={frame} {FIELD_T_US}={t_us} {FIELD_KIND}={kind}",
frame = stamp.frame,
t_us = stamp.t_us,
)
}
pub fn monitor_line(record: &MonitorRecord) -> String {
format!(
"{prefix} {FIELD_ENTITY}={entity:?} {FIELD_OLD_DPI}={old_dpi} {FIELD_NEW_DPI}={new_dpi} \
{FIELD_OLD_WA}={old_wa} {FIELD_NEW_WA}={new_wa}",
prefix = record_prefix(record.stamp, KIND_MONITOR),
entity = record.entity,
old_dpi = record.old_dpi,
new_dpi = record.new_dpi,
old_wa = rect_field(record.old_work_area),
new_wa = rect_field(record.new_work_area),
)
}
pub fn windpi_line(record: &WindowDpiRecord) -> String {
format!(
"{prefix} {FIELD_ENTITY}={entity:?} {FIELD_OLD_DPI}={old_dpi} {FIELD_NEW_DPI}={new_dpi}",
prefix = record_prefix(record.stamp, KIND_WINDPI),
entity = record.entity,
old_dpi = record.old_dpi,
new_dpi = record.new_dpi,
)
}
pub fn write_line(record: &WriteRecord) -> String {
format!(
"{prefix} {FIELD_STAGE}={stage} {FIELD_SEQ}={seq} {FIELD_HWND}={hwnd} {tag} \
{FIELD_X}={x} {FIELD_Y}={y} {FIELD_CX}={cx} {FIELD_CY}={cy} {FIELD_FLAGS}=0x{flags:X} \
{after} {FIELD_CALL_US}={call_us} {FIELD_OK}={ok} {FIELD_IN_BATCH}={in_batch}",
prefix = record_prefix(record.stamp, KIND_WRITE),
stage = record.stage.as_str(),
seq = record.seq,
hwnd = hwnd_field(record.hwnd),
tag = tag_fields(record.tag),
x = record.x,
y = record.y,
cx = record.cx,
cy = record.cy,
flags = record.flags,
after = after_fields(record.after),
call_us = record.call_us,
ok = record.ok,
in_batch = record.in_batch,
)
}
pub fn flush_line(record: &FlushRecord) -> String {
format!(
"{prefix} {FIELD_STAGE}={stage} {FIELD_COUNT}={count} \
{FIELD_SINCE_TICK_US}={since_tick_us} {FIELD_TOTAL_US}={total_us}",
prefix = record_prefix(record.stamp, KIND_FLUSH),
stage = record.stage.as_str(),
count = record.count,
since_tick_us = record.since_tick_us,
total_us = opt_field(record.total_us),
)
}
pub fn msg_line(record: &MsgRecord) -> String {
format!(
"{prefix} {FIELD_MSG}={msg} {FIELD_HWND}={hwnd} {FIELD_IN_SWP}={in_swp} \
{FIELD_SINCE_FLUSH_US}={since_flush_us}",
prefix = record_prefix(record.stamp, KIND_MSG),
msg = record.msg,
hwnd = hwnd_field(record.hwnd),
in_swp = record.in_swp,
since_flush_us = opt_field(record.since_flush_us),
)
}
pub fn enqueue_line(record: &EnqueueRecord) -> String {
format!(
"{prefix} {FIELD_HWND}={hwnd} {tag} {FIELD_MERGED_INTO_SEQ}={merged}",
prefix = record_prefix(record.stamp, KIND_ENQUEUE),
hwnd = hwnd_field(record.hwnd),
tag = tag_fields(record.tag),
merged = opt_field(record.merged_into_seq),
)
}
#[inline]
pub fn is_enabled() -> bool {
tracing::enabled!(target: TRANSITION_TARGET, tracing::Level::DEBUG)
}
#[inline]
pub fn emit_line(line: &str) {
tracing::debug!(target: TRANSITION_TARGET, "{line}");
}
#[derive(Resource, Clone, Copy, Debug)]
pub struct TickStart(pub Instant);
thread_local! {
static TICK_MIRROR: Cell<Option<(u32, Instant)>> = const { Cell::new(None) };
}
pub(crate) fn elapsed_us(at: Instant) -> u64 {
u64::try_from(at.elapsed().as_micros()).unwrap_or(u64::MAX)
}
pub fn begin_tick(frame: u32, start: Instant) {
TICK_MIRROR.with(|mirror| mirror.set(Some((frame, start))));
}
pub fn current_frame() -> u32 {
TICK_MIRROR
.with(|mirror| mirror.get())
.map_or(0, |(frame, _)| frame)
}
pub fn since_tick_start_us() -> u64 {
TICK_MIRROR
.with(|mirror| mirror.get())
.map_or(0, |(_, start)| elapsed_us(start))
}
pub fn stamp() -> Stamp {
Stamp {
frame: current_frame(),
t_us: since_tick_start_us(),
}
}
pub fn stamp_from_world(frame: &FrameCount, start: &TickStart) -> Stamp {
Stamp {
frame: frame.0,
t_us: elapsed_us(start.0),
}
}
thread_local! {
static FLUSH_START: Cell<Option<Instant>> = const { Cell::new(None) };
}
#[must_use = "生存札を落とすと flush 区間が即座に閉じる"]
pub struct FlushEpoch {
prev: Option<Instant>,
}
impl Drop for FlushEpoch {
fn drop(&mut self) {
FLUSH_START.with(|start| start.set(self.prev));
}
}
pub fn begin_flush() -> FlushEpoch {
let prev = FLUSH_START.with(|start| start.replace(Some(Instant::now())));
FlushEpoch { prev }
}
pub fn since_flush_us() -> Option<u64> {
FLUSH_START.with(|start| start.get()).map(elapsed_us)
}
#[doc(hidden)]
pub fn reset_for_test() {
TICK_MIRROR.with(|mirror| mirror.set(None));
FLUSH_START.with(|start| start.set(None));
}
#[cfg(test)]
#[path = "transition_diag_tests.rs"]
mod transition_diag_tests;