new_editor: stabilize resize lifecycle groundwork

This commit is contained in:
2026-04-23 00:36:28 +08:00
parent c10367a42e
commit 03e0b362f7
19 changed files with 439 additions and 161 deletions

View File

@@ -16,7 +16,7 @@ namespace {
bool IsLiveWindow(const EditorWindow* window) {
return window != nullptr &&
window->GetHwnd() != nullptr &&
!window->IsClosing();
window->GetLifecycleState() == EditorWindowLifecycleState::Running;
}
LONG ResolveOuterDimension(float value, LONG fallback) {
@@ -54,10 +54,10 @@ void EditorUtilityWindowCoordinator::HandleWindowFrameTransferRequests(
bool EditorUtilityWindowCoordinator::TryProcessOpenUtilityWindowRequest(
EditorWindow& sourceWindow,
const EditorWindowOpenUtilityWindowRequest& request) {
if (sourceWindow.IsClosing()) {
if (!IsLiveWindow(&sourceWindow)) {
LogRuntimeTrace(
"utility",
"open utility window request rejected: source window is closing");
"open utility window request rejected: source window is not running");
return false;
}
@@ -69,7 +69,7 @@ bool EditorUtilityWindowCoordinator::TryProcessOpenUtilityWindowRequest(
}
if (EditorWindow* existingWindow = m_hostRuntime.FindWindow(descriptor->windowId);
existingWindow != nullptr && existingWindow->GetHwnd() == nullptr &&
existingWindow != nullptr && existingWindow->IsDestroyed() &&
m_lifecycleCoordinator != nullptr) {
m_lifecycleCoordinator->ReapDestroyedWindows();
}
@@ -87,7 +87,7 @@ bool EditorUtilityWindowCoordinator::TryProcessOpenUtilityWindowRequest(
existingWindow != nullptr) {
LogRuntimeTrace(
"utility",
"open utility window request rejected: existing utility window is still closing");
"open utility window request rejected: existing utility window is not reusable");
return false;
}

View File

@@ -45,7 +45,7 @@ std::string DescribeHostWindows(
stream << window->GetWindowId()
<< "{hwnd=" << DescribeHwnd(window->GetHwnd())
<< ",primary=" << (window->IsPrimary() ? '1' : '0')
<< ",closing=" << (window->IsClosing() ? '1' : '0')
<< ",state=" << GetEditorWindowLifecycleStateName(window->GetLifecycleState())
<< '}';
}
stream << ']';
@@ -159,7 +159,10 @@ void EditorWindowHostRuntime::BindLifecycleCoordinator(
}
void EditorWindowHostRuntime::HandlePendingNativeWindowCreated(HWND hwnd) {
if (m_pendingCreateWindow != nullptr && !m_pendingCreateWindow->HasHwnd()) {
if (m_pendingCreateWindow != nullptr &&
m_pendingCreateWindow->GetLifecycleState() ==
EditorWindowLifecycleState::PendingNativeCreate &&
!m_pendingCreateWindow->HasHwnd()) {
m_pendingCreateWindow->AttachHwnd(hwnd);
}
}
@@ -183,7 +186,7 @@ void EditorWindowHostRuntime::RenderAllWindows(
for (const std::unique_ptr<EditorWindow>& window : m_windows) {
if (window == nullptr ||
window->GetHwnd() == nullptr ||
window->IsClosing()) {
window->GetLifecycleState() != EditorWindowLifecycleState::Running) {
continue;
}
@@ -213,7 +216,7 @@ void EditorWindowHostRuntime::RenderAllWindows(
for (WindowFrameTransferBatch& batch : transferBatches) {
if (batch.sourceWindow == nullptr ||
batch.sourceWindow->GetHwnd() == nullptr ||
batch.sourceWindow->IsClosing()) {
batch.sourceWindow->GetLifecycleState() != EditorWindowLifecycleState::Running) {
continue;
}

View File

@@ -38,7 +38,7 @@ std::string DescribeHostWindows(
stream << window->GetWindowId()
<< "{hwnd=" << DescribeHwnd(window->GetHwnd())
<< ",primary=" << (window->IsPrimary() ? '1' : '0')
<< ",closing=" << (window->IsClosing() ? '1' : '0')
<< ",state=" << GetEditorWindowLifecycleStateName(window->GetLifecycleState())
<< '}';
}
stream << ']';
@@ -56,6 +56,10 @@ EditorWindowLifecycleCoordinator::EditorWindowLifecycleCoordinator(
EditorWindowLifecycleCoordinator::~EditorWindowLifecycleCoordinator() = default;
void EditorWindowLifecycleCoordinator::PostCloseRequest(EditorWindow& window) {
if (window.IsDestroyed()) {
return;
}
const HWND hwnd = window.GetHwnd();
if (!window.IsClosing()) {
window.MarkClosing();
@@ -77,13 +81,18 @@ void EditorWindowLifecycleCoordinator::PostCloseRequest(EditorWindow& window) {
}
void EditorWindowLifecycleCoordinator::ExecuteCloseRequest(EditorWindow& window) {
if (window.IsDestroyed()) {
return;
}
const HWND hwnd = window.GetHwnd();
LogRuntimeTrace(
"window-close",
"ExecuteCloseRequest begin windowId='" + std::string(window.GetWindowId()) +
"' hwnd=" + DescribeHwnd(hwnd) +
" primary=" + (window.IsPrimary() ? "1" : "0") +
" closingBefore=" + (window.IsClosing() ? "1" : "0") +
" lifecycleBefore=" +
std::string(GetEditorWindowLifecycleStateName(window.GetLifecycleState())) +
" workspace=" + m_workspaceCoordinator.DescribeWindowSet() +
" host=" + DescribeHostWindows(m_hostRuntime.GetWindows()));
@@ -106,6 +115,10 @@ void EditorWindowLifecycleCoordinator::ExecuteCloseRequest(EditorWindow& window)
}
void EditorWindowLifecycleCoordinator::HandleNativeWindowDestroyed(EditorWindow& window) {
if (window.IsDestroyed()) {
return;
}
const bool destroyedPrimary =
m_workspaceCoordinator.IsPrimaryWindowId(window.GetWindowId());
LogRuntimeTrace(
@@ -165,11 +178,12 @@ void EditorWindowLifecycleCoordinator::AbortUnregisteredWindow(EditorWindow& win
}
ShutdownRuntimeIfNeeded(window);
window.MarkDestroyed();
if (hwnd != nullptr && IsWindow(hwnd)) {
DestroyWindow(hwnd);
} else {
window.MarkDestroyed();
}
EraseWindow(window);
ReapDestroyedWindows();
LogRuntimeTrace(
"window-close",
@@ -199,7 +213,7 @@ void EditorWindowLifecycleCoordinator::ReapDestroyedWindows() {
auto& windows = m_hostRuntime.GetWindows();
for (auto it = windows.begin(); it != windows.end();) {
EditorWindow* const window = it->get();
if (window == nullptr || window->GetHwnd() != nullptr) {
if (window == nullptr || !window->IsDestroyed()) {
++it;
continue;
}
@@ -225,25 +239,6 @@ void EditorWindowLifecycleCoordinator::ShutdownRuntimeIfNeeded(EditorWindow& win
}
}
void EditorWindowLifecycleCoordinator::EraseWindow(EditorWindow& window) {
auto& windows = m_hostRuntime.GetWindows();
const auto it = std::find_if(
windows.begin(),
windows.end(),
[&window](const std::unique_ptr<EditorWindow>& candidate) {
return candidate.get() == &window;
});
if (it == windows.end()) {
return;
}
if (m_hostRuntime.m_pendingCreateWindow == &window) {
m_hostRuntime.m_pendingCreateWindow = nullptr;
}
windows.erase(it);
}
void EditorWindowLifecycleCoordinator::LogRuntimeTrace(
std::string_view channel,
std::string_view message) const {

View File

@@ -24,7 +24,6 @@ public:
private:
void ShutdownRuntimeIfNeeded(EditorWindow& window);
void EraseWindow(EditorWindow& window);
void LogRuntimeTrace(std::string_view channel, std::string_view message) const;
EditorWindowHostRuntime& m_hostRuntime;

View File

@@ -45,7 +45,7 @@ std::string DescribeHostWindows(const EditorWindowHostRuntime& hostRuntime) {
stream << window->GetWindowId()
<< "{hwnd=" << DescribeHwnd(window->GetHwnd())
<< ",primary=" << (window->IsPrimary() ? '1' : '0')
<< ",closing=" << (window->IsClosing() ? '1' : '0')
<< ",state=" << GetEditorWindowLifecycleStateName(window->GetLifecycleState())
<< '}';
}
stream << ']';
@@ -74,14 +74,36 @@ void EditorWindowMessageDispatcher::DispatchWindowFrameTransferRequests(
transferRequests);
}
void EditorWindowMessageDispatcher::RenderAndHandleWindowFrame(const DispatchContext& context) {
EditorWindowFrameTransferRequests transferRequests = EditorWindowFrameDriver::DriveImmediateFrame(
context.window,
context.hostRuntime.GetEditorContext(),
context.workspaceCoordinator.IsGlobalTabDragActive());
if (transferRequests.HasPendingRequests()) {
DispatchWindowFrameTransferRequests(context, transferRequests);
void EditorWindowMessageDispatcher::FinalizeImmediateFrame(
const DispatchContext& context,
const EditorWindowFrameTransferRequests& transferRequests) {
context.workspaceCoordinator.RefreshWindowPresentation(context.window);
context.utilityCoordinator.RefreshWindowPresentation(context.window);
if (!transferRequests.HasPendingRequests()) {
return;
}
DispatchWindowFrameTransferRequests(context, transferRequests);
}
void EditorWindowMessageDispatcher::FlushQueuedCompletedImmediateFrame(
const DispatchContext& context) {
if (!context.window.HasQueuedCompletedImmediateFrame()) {
return;
}
FinalizeImmediateFrame(
context,
context.window.ConsumeQueuedCompletedImmediateFrameTransferRequests());
}
void EditorWindowMessageDispatcher::RenderAndHandleWindowFrame(const DispatchContext& context) {
FinalizeImmediateFrame(
context,
EditorWindowFrameDriver::DriveImmediateFrame(
context.window,
context.hostRuntime.GetEditorContext(),
context.workspaceCoordinator.IsGlobalTabDragActive()));
}
bool EditorWindowMessageDispatcher::EnsureTrackingMouseLeave(const DispatchContext& context) {
@@ -435,12 +457,11 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowLifecycleMessage(
outResult = 0;
return true;
case WM_PAINT:
if (EditorWindowFrameTransferRequests transferRequests = context.window.OnPaintMessage(
FinalizeImmediateFrame(
context,
context.window.OnPaintMessage(
context.hostRuntime.GetEditorContext(),
context.workspaceCoordinator.IsGlobalTabDragActive());
transferRequests.HasPendingRequests()) {
DispatchWindowFrameTransferRequests(context, transferRequests);
}
context.workspaceCoordinator.IsGlobalTabDragActive()));
outResult = 0;
return true;
case WM_ERASEBKGND:
@@ -535,9 +556,20 @@ bool EditorWindowMessageDispatcher::TryDispatch(
.window = window,
};
return TryDispatchWindowChromeMessage(context, message, wParam, lParam, outResult) ||
TryDispatchWindowLifecycleMessage(context, message, wParam, lParam, outResult) ||
TryDispatchWindowInputMessage(context, message, wParam, lParam, outResult);
bool handled = false;
if (TryDispatchWindowChromeMessage(context, message, wParam, lParam, outResult)) {
handled = true;
} else if (TryDispatchWindowLifecycleMessage(context, message, wParam, lParam, outResult)) {
handled = true;
} else if (TryDispatchWindowInputMessage(context, message, wParam, lParam, outResult)) {
handled = true;
}
if (handled) {
FlushQueuedCompletedImmediateFrame(context);
}
return handled;
}
} // namespace XCEngine::UI::Editor::App

View File

@@ -36,6 +36,10 @@ public:
private:
struct DispatchContext;
static void FinalizeImmediateFrame(
const DispatchContext& context,
const EditorWindowFrameTransferRequests& transferRequests);
static void FlushQueuedCompletedImmediateFrame(const DispatchContext& context);
static void RenderAndHandleWindowFrame(const DispatchContext& context);
static void DispatchWindowFrameTransferRequests(
const DispatchContext& context,

View File

@@ -61,7 +61,7 @@ bool CanStartGlobalTabDragFromWindow(
bool IsLiveInteractiveWindow(const EditorWindow* window) {
return window != nullptr &&
window->GetHwnd() != nullptr &&
!window->IsClosing();
window->GetLifecycleState() == EditorWindowLifecycleState::Running;
}
std::string DescribeWindowSetState(const UIEditorWindowWorkspaceSet& windowSet) {
@@ -92,7 +92,8 @@ UIEditorWindowWorkspaceSet BuildLiveWindowWorkspaceSet(
if (window == nullptr ||
window->GetHwnd() == nullptr ||
window->GetWindowId() == excludedWindowId ||
(!includeClosingWindows && window->IsClosing())) {
(!includeClosingWindows &&
window->GetLifecycleState() != EditorWindowLifecycleState::Running)) {
continue;
}
@@ -262,9 +263,27 @@ bool EditorWindowWorkspaceCoordinator::SynchronizeWindowsFromWindowSet(
for (const UIEditorWindowWorkspaceState& entry : windowSet.windows) {
windowIdsInSet.push_back(entry.windowId);
const bool isPrimaryWindow = entry.windowId == windowSet.primaryWindowId;
if (EditorWindow* existingWindow = m_hostRuntime.FindWindow(entry.windowId);
existingWindow != nullptr) {
existingWindow->ClearClosing();
EditorWindow* existingWindow = m_hostRuntime.FindWindow(entry.windowId);
if (existingWindow != nullptr &&
existingWindow->IsDestroyed() &&
m_lifecycleCoordinator != nullptr) {
m_lifecycleCoordinator->ReapDestroyedWindows();
existingWindow = m_hostRuntime.FindWindow(entry.windowId);
}
if (existingWindow != nullptr) {
if (existingWindow->GetLifecycleState() != EditorWindowLifecycleState::Running) {
LogRuntimeTrace(
"window",
"workspace synchronization rejected: window '" + entry.windowId +
"' is in lifecycle state '" +
std::string(
GetEditorWindowLifecycleStateName(
existingWindow->GetLifecycleState())) +
"'");
return false;
}
existingWindowSnapshots.push_back(ExistingWindowSnapshot{
existingWindow,
existingWindow->GetWorkspaceController(),
@@ -328,7 +347,8 @@ bool EditorWindowWorkspaceCoordinator::SynchronizeWindowsFromWindowSet(
for (const std::unique_ptr<EditorWindow>& window : m_hostRuntime.GetWindows()) {
if (window == nullptr ||
window->GetHwnd() == nullptr ||
window->IsPrimary()) {
window->IsPrimary() ||
window->GetLifecycleState() != EditorWindowLifecycleState::Running) {
continue;
}
@@ -503,7 +523,7 @@ void EditorWindowWorkspaceCoordinator::EndGlobalTabDragSession() {
if (EditorWindow* ownerWindow = m_hostRuntime.FindWindow(m_globalTabDragSession.panelWindowId);
ownerWindow != nullptr) {
ownerWindow->ReleasePointerCapture(EditorWindowPointerCaptureOwner::GlobalTabDrag);
if (!ownerWindow->IsClosing()) {
if (IsLiveInteractiveWindow(ownerWindow)) {
ownerWindow->ResetInteractionState();
}
}
@@ -598,9 +618,7 @@ bool EditorWindowWorkspaceCoordinator::HandleGlobalTabDragPointerButtonUp(HWND h
}
if (EditorWindow* updatedTargetWindow = m_hostRuntime.FindWindow(targetWindow->GetWindowId());
updatedTargetWindow != nullptr &&
updatedTargetWindow->GetHwnd() != nullptr &&
!updatedTargetWindow->IsClosing()) {
IsLiveInteractiveWindow(updatedTargetWindow)) {
SetForegroundWindow(updatedTargetWindow->GetHwnd());
}
LogRuntimeTrace(
@@ -613,7 +631,7 @@ bool EditorWindowWorkspaceCoordinator::HandleGlobalTabDragPointerButtonUp(HWND h
bool EditorWindowWorkspaceCoordinator::TryStartGlobalTabDrag(
EditorWindow& sourceWindow,
const EditorWindowPanelTransferRequest& request) {
if (sourceWindow.IsClosing()) {
if (!IsLiveInteractiveWindow(&sourceWindow)) {
LogRuntimeTrace("drag", "failed to start global tab drag: source window is closing");
return false;
}
@@ -712,7 +730,7 @@ bool EditorWindowWorkspaceCoordinator::TryStartGlobalTabDrag(
bool EditorWindowWorkspaceCoordinator::TryProcessDetachRequest(
EditorWindow& sourceWindow,
const EditorWindowPanelTransferRequest& request) {
if (sourceWindow.IsClosing()) {
if (!IsLiveInteractiveWindow(&sourceWindow)) {
LogRuntimeTrace("detach", "detach request rejected: source window is closing");
return false;
}
@@ -739,9 +757,7 @@ bool EditorWindowWorkspaceCoordinator::TryProcessDetachRequest(
}
if (EditorWindow* detachedWindow = m_hostRuntime.FindWindow(result.targetWindowId);
detachedWindow != nullptr &&
detachedWindow->GetHwnd() != nullptr &&
!detachedWindow->IsClosing()) {
IsLiveInteractiveWindow(detachedWindow)) {
SetForegroundWindow(detachedWindow->GetHwnd());
}
@@ -774,8 +790,7 @@ EditorWindow* EditorWindowWorkspaceCoordinator::FindTopmostWindowAtScreenPoint(
if (const HWND hitWindow = WindowFromPoint(screenPoint); hitWindow != nullptr) {
const HWND rootWindow = GetAncestor(hitWindow, GA_ROOT);
if (EditorWindow* window = m_hostRuntime.FindWindow(rootWindow);
window != nullptr &&
!window->IsClosing() &&
IsLiveInteractiveWindow(window) &&
window->GetWindowId() != excludedWindowId) {
return window;
}
@@ -784,8 +799,7 @@ EditorWindow* EditorWindowWorkspaceCoordinator::FindTopmostWindowAtScreenPoint(
for (auto it = m_hostRuntime.GetWindows().rbegin(); it != m_hostRuntime.GetWindows().rend(); ++it) {
EditorWindow* const window = it->get();
if (window == nullptr ||
window->GetHwnd() == nullptr ||
window->IsClosing() ||
!IsLiveInteractiveWindow(window) ||
window->GetWindowId() == excludedWindowId) {
continue;
}