Use explicit workspace mutation requests for window sync

This commit is contained in:
2026-04-26 00:25:49 +08:00
parent 5b6c46d382
commit c62a7fec0a
10 changed files with 130 additions and 23 deletions

View File

@@ -183,7 +183,9 @@ EditorWindow* EditorWindowHostRuntime::CreateWorkspaceWindow(
UIEditorWorkspaceController workspaceController,
const CreateParams& params) {
return CreateEditorWindow(
m_contentFactory.CreateWorkspaceContentController(std::move(workspaceController)),
m_contentFactory.CreateWorkspaceContentController(
params.windowId,
std::move(workspaceController)),
params);
}

View File

@@ -169,21 +169,15 @@ UIEditorWindowWorkspaceState EditorWindowWorkspaceCoordinator::BuildWindowStateF
return state;
}
bool EditorWindowWorkspaceCoordinator::CommitLiveWindowMutation(EditorWindow& window) {
if (!window.IsWorkspaceWindow() ||
window.GetHwnd() == nullptr ||
window.TryGetWorkspaceController() == nullptr) {
return true;
}
bool EditorWindowWorkspaceCoordinator::CommitLiveWindowMutation(
const EditorWindowWorkspaceMutationRequest& request) {
const std::wstring_view primaryWindowTitle =
m_hostRuntime.GetHostConfig().primaryWindowTitle != nullptr
? std::wstring_view(m_hostRuntime.GetHostConfig().primaryWindowTitle)
: std::wstring_view{};
std::string error = {};
EditorWindowSynchronizationPlan plan = m_windowSystem.BuildPlanForLiveWindowMutation(
window.GetWindowId(),
window.GetWorkspaceController(),
EditorWindowSynchronizationPlan plan = m_windowSystem.BuildPlanForWorkspaceMutationRequest(
request,
CaptureHostSnapshots(),
primaryWindowTitle,
error);
@@ -191,7 +185,7 @@ bool EditorWindowWorkspaceCoordinator::CommitLiveWindowMutation(EditorWindow& wi
LogRuntimeTrace(
"window",
"workspace live mutation rejected for window '" +
std::string(window.GetWindowId()) + "': " + error);
request.windowState.windowId + "': " + error);
return false;
}
@@ -825,7 +819,7 @@ void EditorWindowWorkspaceCoordinator::HandleWindowFrameTransferRequests(
EditorWindow& sourceWindow,
const EditorWindowFrameTransferRequests& transferRequests) {
if (transferRequests.workspace.workspaceMutation.has_value()) {
if (!CommitLiveWindowMutation(sourceWindow)) {
if (!CommitLiveWindowMutation(*transferRequests.workspace.workspaceMutation)) {
LogRuntimeTrace(
"window",
"failed to commit live workspace mutation for window '" +

View File

@@ -74,7 +74,7 @@ private:
LONG preferredWidth = 0,
LONG preferredHeight = 0);
UIEditorWindowWorkspaceState BuildWindowStateForWindow(const EditorWindow& window) const;
bool CommitLiveWindowMutation(EditorWindow& window);
bool CommitLiveWindowMutation(const EditorWindowWorkspaceMutationRequest& request);
void RefreshWindowTitle(EditorWindow& window) const;
void BeginGlobalTabDragSession(
std::string_view panelWindowId,

View File

@@ -12,8 +12,11 @@ namespace {
class DefaultEditorWindowContentFactory final : public EditorWindowContentFactory {
public:
std::unique_ptr<EditorWindowContentController> CreateWorkspaceContentController(
std::string_view windowId,
UIEditorWorkspaceController workspaceController) const override {
return CreateEditorWorkspaceWindowContentController(std::move(workspaceController));
return CreateEditorWorkspaceWindowContentController(
windowId,
std::move(workspaceController));
}
std::unique_ptr<EditorWindowContentController> CreateUtilityContentController(

View File

@@ -18,6 +18,7 @@ public:
virtual ~EditorWindowContentFactory() = default;
virtual std::unique_ptr<EditorWindowContentController> CreateWorkspaceContentController(
std::string_view windowId,
UIEditorWorkspaceController workspaceController) const = 0;
virtual std::unique_ptr<EditorWindowContentController> CreateUtilityContentController(
const EditorUtilityWindowDescriptor& descriptor) const = 0;

View File

@@ -33,8 +33,10 @@ EditorWindowContentCursorKind ToContentCursorKind(
} // namespace
EditorWorkspaceWindowContentController::EditorWorkspaceWindowContentController(
std::string windowId,
UIEditorWorkspaceController workspaceController)
: m_workspaceController(std::move(workspaceController)) {}
: m_windowId(std::move(windowId)),
m_workspaceController(std::move(workspaceController)) {}
EditorWorkspaceWindowContentController::~EditorWorkspaceWindowContentController() = default;
@@ -131,6 +133,7 @@ EditorWindowFrameTransferRequests EditorWorkspaceWindowContentController::Update
transferRequests.workspace.workspaceMutation = EditorWindowWorkspaceMutationRequest{
.windowState =
UIEditorWindowWorkspaceState{
.windowId = m_windowId,
.workspace = m_workspaceController.GetWorkspace(),
.session = m_workspaceController.GetSession(),
},
@@ -220,8 +223,10 @@ std::string EditorWorkspaceWindowContentController::ResolveDetachedWindowTitleTe
}
std::unique_ptr<EditorWindowContentController> CreateEditorWorkspaceWindowContentController(
std::string_view windowId,
UIEditorWorkspaceController workspaceController) {
return std::make_unique<EditorWorkspaceWindowContentController>(
std::string(windowId),
std::move(workspaceController));
}

View File

@@ -17,7 +17,9 @@ class EditorWorkspaceWindowContentController final
, public EditorWindowInputFeedbackBinding
, public EditorWindowTitleBarBinding {
public:
explicit EditorWorkspaceWindowContentController(UIEditorWorkspaceController workspaceController);
EditorWorkspaceWindowContentController(
std::string windowId,
UIEditorWorkspaceController workspaceController);
~EditorWorkspaceWindowContentController() override;
EditorWindowContentCapabilities GetCapabilities() const override;
@@ -69,12 +71,14 @@ public:
std::string_view fallbackWindowTitle) const override;
private:
std::string m_windowId = {};
UIEditorWorkspaceController m_workspaceController = {};
EditorShellRuntime m_shellRuntime = {};
EditorWindowFrameOrchestrator m_frameOrchestrator = {};
};
std::unique_ptr<EditorWindowContentController> CreateEditorWorkspaceWindowContentController(
std::string_view windowId,
UIEditorWorkspaceController workspaceController);
} // namespace XCEngine::UI::Editor::App

View File

@@ -78,21 +78,39 @@ EditorWindowSynchronizationPlan EditorWindowSystem::BuildPlanForLiveWindowMutati
const std::vector<EditorWindowHostSnapshot>& hostWindows,
std::wstring_view primaryWindowTitle,
std::string& outError) const {
if (windowId.empty()) {
outError = "live window mutation missing window id";
EditorWindowWorkspaceMutationRequest request = {};
request.windowState.windowId = std::string(windowId);
request.windowState.workspace = workspaceController.GetWorkspace();
request.windowState.session = workspaceController.GetSession();
return BuildPlanForWorkspaceMutationRequest(
request,
hostWindows,
primaryWindowTitle,
outError);
}
EditorWindowSynchronizationPlan EditorWindowSystem::BuildPlanForWorkspaceMutationRequest(
const EditorWindowWorkspaceMutationRequest& request,
const std::vector<EditorWindowHostSnapshot>& hostWindows,
std::wstring_view primaryWindowTitle,
std::string& outError) const {
if (!request.IsValid()) {
outError = "live window mutation request missing window id";
return {};
}
UIEditorWindowWorkspaceSet nextWindowSet = GetWindowSet();
UIEditorWindowWorkspaceState* existingState =
FindMutableUIEditorWindowWorkspaceState(nextWindowSet, windowId);
FindMutableUIEditorWindowWorkspaceState(nextWindowSet, request.windowState.windowId);
if (existingState == nullptr) {
outError = "live window mutation references unknown window '" + std::string(windowId) + "'";
outError =
"live window mutation references unknown window '" +
request.windowState.windowId + "'";
return {};
}
existingState->workspace = workspaceController.GetWorkspace();
existingState->session = workspaceController.GetSession();
existingState->workspace = request.windowState.workspace;
existingState->session = request.windowState.session;
return BuildPlanForWindowSet(
nextWindowSet,
hostWindows,

View File

@@ -1,6 +1,7 @@
#pragma once
#include "Composition/EditorWindowWorkspaceStore.h"
#include "Windowing/Frame/EditorWindowTransferRequests.h"
#include "Windowing/System/EditorWindowSynchronizationPlan.h"
#include <XCEditor/Workspace/UIEditorWorkspaceController.h>
@@ -41,6 +42,11 @@ public:
const std::vector<EditorWindowHostSnapshot>& hostWindows,
std::wstring_view primaryWindowTitle,
std::string& outError) const;
EditorWindowSynchronizationPlan BuildPlanForWorkspaceMutationRequest(
const EditorWindowWorkspaceMutationRequest& request,
const std::vector<EditorWindowHostSnapshot>& hostWindows,
std::wstring_view primaryWindowTitle,
std::string& outError) const;
EditorWindowSynchronizationPlan BuildPlanForDestroyedWindow(
std::string_view windowId,
const std::vector<EditorWindowHostSnapshot>& hostWindows,

View File

@@ -1,5 +1,6 @@
#include <gtest/gtest.h>
#include "Windowing/Frame/EditorWindowTransferRequests.h"
#include "Windowing/System/EditorWindowSystem.h"
#include <XCEditor/Workspace/UIEditorWindowWorkspaceController.h>
@@ -13,6 +14,7 @@ using XCEngine::UI::Editor::App::EditorWindowHostSnapshot;
using XCEngine::UI::Editor::App::EditorWindowSynchronizationActionKind;
using XCEngine::UI::Editor::App::EditorWindowSynchronizationPlannerInput;
using XCEngine::UI::Editor::App::EditorWindowSystem;
using XCEngine::UI::Editor::App::EditorWindowWorkspaceMutationRequest;
using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceController;
using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel;
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSingleTabStack;
@@ -319,6 +321,78 @@ TEST(EditorWindowSynchronizationPlannerTest, LiveWindowMutationBuildsCommitPlanW
EXPECT_EQ(system.GetWindowSet().windows.front().workspace.activePanelId, "doc-b");
}
TEST(EditorWindowSynchronizationPlannerTest, WorkspaceMutationRequestBuildsCommitPlanFromExplicitPayload) {
EditorWindowSystem system = BuildSystem();
const auto workspaceController =
BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());
std::string error = {};
ASSERT_TRUE(system.BootstrapPrimaryWindow("main", workspaceController, error)) << error;
auto liveController = workspaceController;
const auto commandResult = liveController.Dispatch(
UIEditorWorkspaceCommand{
.kind = UIEditorWorkspaceCommandKind::ActivatePanel,
.panelId = "doc-b",
});
ASSERT_EQ(commandResult.status, UIEditorWorkspaceCommandStatus::Changed);
EditorWindowWorkspaceMutationRequest request = {};
request.windowState.windowId = "main";
request.windowState.workspace = liveController.GetWorkspace();
request.windowState.session = liveController.GetSession();
auto plan = system.BuildPlanForWorkspaceMutationRequest(
request,
{
BuildWorkspaceSnapshot(
"main",
true,
workspaceController,
L"Main Scene - XCEngine Editor"),
},
L"Main Scene - XCEngine Editor",
error);
ASSERT_TRUE(plan.valid) << error;
ASSERT_EQ(plan.actions.size(), 1u);
EXPECT_EQ(plan.actions[0].kind, EditorWindowSynchronizationActionKind::UpdateWorkspaceWindow);
EXPECT_EQ(plan.actions[0].update.windowState.windowId, "main");
const auto* mutatedState = FindUIEditorWindowWorkspaceState(plan.targetWindowSet, "main");
ASSERT_NE(mutatedState, nullptr);
EXPECT_EQ(mutatedState->workspace.activePanelId, "doc-b");
ASSERT_TRUE(system.CommitSynchronizationPlan(plan, error)) << error;
EXPECT_EQ(system.GetWindowSet().windows.front().workspace.activePanelId, "doc-b");
}
TEST(EditorWindowSynchronizationPlannerTest, WorkspaceMutationRequestRejectsUnknownWindowId) {
EditorWindowSystem system = BuildSystem();
const auto workspaceController =
BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());
std::string error = {};
ASSERT_TRUE(system.BootstrapPrimaryWindow("main", workspaceController, error)) << error;
EditorWindowWorkspaceMutationRequest request = {};
request.windowState.windowId = "missing-window";
request.windowState.workspace = workspaceController.GetWorkspace();
request.windowState.session = workspaceController.GetSession();
auto plan = system.BuildPlanForWorkspaceMutationRequest(
request,
{
BuildWorkspaceSnapshot(
"main",
true,
workspaceController,
L"Main Scene - XCEngine Editor"),
},
L"Main Scene - XCEngine Editor",
error);
EXPECT_FALSE(plan.valid);
EXPECT_NE(error.find("missing-window"), std::string::npos);
}
TEST(EditorWindowSynchronizationPlannerTest, DestroyedPrimaryWindowProducesCloseActionsForRemainingDetachedWindows) {
EditorWindowSystem system = BuildSystem();
const auto workspaceController =