use bevy_ecs::prelude::*;
use tracing::{debug, warn};
use windows::Win32::Foundation::HWND;
use super::zorder_pair_diag::hwnd_field;
pub(crate) const UNKNOWN: &str = "-";
const LINKED_TAG: &str = "[zorder-chain] linked";
const UNLINKED_TAG: &str = "[zorder-chain] unlinked";
const SETTLED_TAG: &str = "[zorder-chain] settled";
const ABSENT_TAG: &str = "[zorder-chain] absent";
const SKIPPED_TAG: &str = "[zorder-chain] skipped";
const LINK_FAILED_TAG: &str = "[zorder-chain] link-failed";
const UNLINK_FAILED_TAG: &str = "[zorder-chain] unlink-failed";
const APPLIED_TAG: &str = "[zorder-group] applied";
const REJECTED_TAG: &str = "[zorder-group] rejected";
#[cfg(test)]
pub(crate) fn chain_record_tags() -> [&'static str; 7] {
[
LINKED_TAG,
UNLINKED_TAG,
SETTLED_TAG,
ABSENT_TAG,
SKIPPED_TAG,
LINK_FAILED_TAG,
UNLINK_FAILED_TAG,
]
}
#[cfg(test)]
pub(crate) fn preserved_group_tags() -> [&'static str; 2] {
[APPLIED_TAG, REJECTED_TAG]
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ChainSegment {
Group(u32),
Tail,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum DetachReason {
Teardown,
Rechain,
Departing,
Diverged,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum ChainSkipReason {
TooFewPresent,
NoChange,
HandleMissing,
}
impl std::fmt::Display for ChainSegment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ChainSegment::Group(id) => write!(f, "g{id}"),
ChainSegment::Tail => write!(f, "tail"),
}
}
}
fn segment_field(segment: Option<ChainSegment>) -> String {
match segment {
Some(s) => s.to_string(),
None => UNKNOWN.to_string(),
}
}
fn tristate_field(value: Option<bool>) -> String {
match value {
Some(v) => v.to_string(),
None => UNKNOWN.to_string(),
}
}
fn hwnd_list_field(hwnds: &[HWND]) -> String {
if hwnds.is_empty() {
return UNKNOWN.to_string();
}
hwnds
.iter()
.map(|h| hwnd_field(Some(*h)))
.collect::<Vec<_>>()
.join(",")
}
fn text_field(value: &str) -> String {
let folded = value.split_whitespace().collect::<Vec<_>>().join("_");
if folded.is_empty() {
return UNKNOWN.to_string();
}
folded
}
pub(crate) fn linked_line(
segment: Option<ChainSegment>,
owned: Entity,
owner: Entity,
owned_hwnd: Option<HWND>,
owner_hwnd: Option<HWND>,
pos: usize,
total: usize,
) -> String {
format!(
"{LINKED_TAG} segment={segment} owned={owned:?} owner={owner:?} \
owned_hwnd={owned_h} owner_hwnd={owner_h} pos={pos}/{total}",
segment = segment_field(segment),
owned_h = hwnd_field(owned_hwnd),
owner_h = hwnd_field(owner_hwnd),
)
}
pub(crate) fn unlinked_line(
segment: Option<ChainSegment>,
owned: Entity,
owned_hwnd: Option<HWND>,
owner_hwnd: Option<HWND>,
reason: DetachReason,
) -> String {
format!(
"{UNLINKED_TAG} segment={segment} owned={owned:?} \
owned_hwnd={owned_h} owner_hwnd={owner_h} reason={reason:?}",
segment = segment_field(segment),
owned_h = hwnd_field(owned_hwnd),
owner_h = hwnd_field(owner_hwnd),
)
}
pub(crate) fn settled_line(
nudged_hwnd: Option<HWND>,
insert_after: Option<HWND>,
declared: &[HWND],
measured: &[HWND],
nudge_ok: Option<bool>,
) -> String {
format!(
"{SETTLED_TAG} nudged_hwnd={nudged} insert_after={after} \
declared={declared} measured={measured} nudge_ok={ok}",
nudged = hwnd_field(nudged_hwnd),
after = hwnd_field(insert_after),
declared = hwnd_list_field(declared),
measured = hwnd_list_field(measured),
ok = tristate_field(nudge_ok),
)
}
pub(crate) fn absent_line(group_id: u32, element: &str) -> String {
format!(
"{ABSENT_TAG} group_id={group_id} element={element}",
element = text_field(element),
)
}
pub(crate) fn skipped_line(reason: ChainSkipReason) -> String {
format!("{SKIPPED_TAG} reason={reason:?}")
}
pub(crate) fn link_failed_line(
segment: Option<ChainSegment>,
owned_hwnd: Option<HWND>,
owner_hwnd: Option<HWND>,
error: &windows::core::Error,
) -> String {
format!(
"{LINK_FAILED_TAG} segment={segment} owned_hwnd={owned_h} owner_hwnd={owner_h} \
error={code:?}",
segment = segment_field(segment),
owned_h = hwnd_field(owned_hwnd),
owner_h = hwnd_field(owner_hwnd),
code = error.code(),
)
}
pub(crate) fn unlink_failed_line(owned_hwnd: Option<HWND>, error: &windows::core::Error) -> String {
format!(
"{UNLINK_FAILED_TAG} owned_hwnd={owned_h} error={code:?}",
owned_h = hwnd_field(owned_hwnd),
code = error.code(),
)
}
pub(crate) fn applied_line(detail: &str) -> String {
format!("{APPLIED_TAG} {detail}")
}
pub(crate) fn rejected_line(reason: &str, tokens: &str) -> String {
format!(
"{REJECTED_TAG} reason={reason} tokens={tokens}",
reason = text_field(reason),
tokens = text_field(tokens),
)
}
pub fn log_group_applied(detail: &str) {
debug!("{}", applied_line(detail));
}
pub fn log_group_rejected(reason: &str, tokens: &str) {
warn!("{}", rejected_line(reason, tokens));
}
#[cfg(test)]
#[path = "zorder_chain_diag_tests.rs"]
mod zorder_chain_diag_tests;