From 02eafc2bac9e4e0ba23b1d8426ed18444741674b Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Tue, 28 Apr 2026 15:24:47 +0800 Subject: [PATCH] Refine project panel runtime ownership --- editor/AGENTS.md | 2 + editor/app/Features/Project/ProjectPanel.cpp | 99 +++++++------------ editor/app/Features/Project/ProjectPanel.h | 5 - .../Pipelines/BuiltinForwardPipeline.h | 1 + tests/UI/Editor/unit/test_project_panel.cpp | 20 +++- 5 files changed, 56 insertions(+), 71 deletions(-) diff --git a/editor/AGENTS.md b/editor/AGENTS.md index b73c39ed..9c0fb01f 100644 --- a/editor/AGENTS.md +++ b/editor/AGENTS.md @@ -29,6 +29,7 @@ - `EditorWorkspacePanelRuntimeSet` 托管产品面板生命周期。新增 panel 时先改 `EditorProductManifest.*`,再按需要调整 `BuildEditorWorkspaceModel()` 的默认布局和测试;不要再手工在 registry / menu / runtime set / viewport host 多处分别补定义。 - `EditorSelectionService` 是 hierarchy/project/inspector/scene viewport 之间的选择同步核心。不要在单个 panel 内维护另一套长期选择真相。 - `EditorProjectRuntime` 包装 `ProjectBrowserModel`,负责 project tree/grid、选择、文件操作、scene asset open request。文件系统改动后要刷新并 revalidate selection。 +- `ProjectPanel` 只消费由 `EditorContext` / `EditorPanelServices` 提供的 `EditorProjectRuntime`,不再拥有或初始化自己的 project runtime。测试也应显式创建 `EditorProjectRuntime` 并通过 `SetProjectRuntime()` 注入,不要把 `projectRoot` 传给 panel。 - `EditorSceneRuntime` 负责 startup scene、editor scene camera、hierarchy selection、component list、transform edit history 和 scene tool state。 当前目录地图: @@ -118,6 +119,7 @@ ctest --test-dir build -C Debug -R "editor|xceditor" --output-on-failure - 已把 `game` panel 明确标成 placeholder viewport,而不是隐式共享 scene renderer 或假装 Game runtime 已完成。 - 已新增 manifest validation 测试,确保产品 manifest 能声明 panel runtime owner 和 viewport renderer owner,并覆盖 `game` placeholder 的预期状态。 - 已更新根 `AGENT.md` 和本文件,去掉旧 `--project` 说法,记录当前 XCUI editor、`XCEditorCore` 分层和 product manifest 规则。 +- 已移除 `ProjectPanel` 自持 `EditorProjectRuntime` 的 fallback 路径;project runtime 事实源现在只来自 `EditorContext`,面板测试改为显式 runtime 注入。 - 本次改动验证过: - `cmake --build build --config Debug --target XCEditor` - `cmake --build build --config Debug --target editor_app_core_tests` diff --git a/editor/app/Features/Project/ProjectPanel.cpp b/editor/app/Features/Project/ProjectPanel.cpp index 7ecb302b..f5f11004 100644 --- a/editor/app/Features/Project/ProjectPanel.cpp +++ b/editor/app/Features/Project/ProjectPanel.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include namespace XCEngine::UI::Editor::App { @@ -342,28 +341,16 @@ Widgets::UIEditorMenuPopupState PreserveContextMenuWidgetState( } // namespace -EditorProjectRuntime* ProjectPanel::ResolveProjectRuntime() { - return m_projectRuntime != nullptr - ? m_projectRuntime - : m_ownedProjectRuntime.get(); -} - -const EditorProjectRuntime* ProjectPanel::ResolveProjectRuntime() const { - return m_projectRuntime != nullptr - ? m_projectRuntime - : m_ownedProjectRuntime.get(); -} - bool ProjectPanel::HasProjectRuntime() const { - return ResolveProjectRuntime() != nullptr; + return m_projectRuntime != nullptr; } ProjectPanel::BrowserModel& ProjectPanel::GetBrowserModel() { - return ResolveProjectRuntime()->GetBrowserModel(); + return m_projectRuntime->GetBrowserModel(); } const ProjectPanel::BrowserModel& ProjectPanel::GetBrowserModel() const { - return ResolveProjectRuntime()->GetBrowserModel(); + return m_projectRuntime->GetBrowserModel(); } void ProjectPanel::RebuildWindowTreeItems() { @@ -400,12 +387,6 @@ ProjectPanel::GetPresentedWindowTreeExpansionModel() const { return ResolveUIEditorFilterableTreeHostExpansionModel(m_treeFilterHostFrame); } -void ProjectPanel::Initialize(const std::filesystem::path& projectRoot) { - m_ownedProjectRuntime = std::make_unique(); - m_ownedProjectRuntime->Initialize(projectRoot); - SyncSelectionsFromRuntime(); -} - void ProjectPanel::SetProjectRuntime(EditorProjectRuntime* projectRuntime) { if (m_projectRuntime == projectRuntime) { return; @@ -478,16 +459,14 @@ const std::vector& ProjectPanel::GetFrameEvents() const { } const ProjectPanel::FolderEntry* ProjectPanel::FindFolderEntry(std::string_view itemId) const { - const EditorProjectRuntime* runtime = ResolveProjectRuntime(); - return runtime != nullptr - ? runtime->FindFolderEntry(itemId) + return m_projectRuntime != nullptr + ? m_projectRuntime->FindFolderEntry(itemId) : nullptr; } const ProjectPanel::AssetEntry* ProjectPanel::FindAssetEntry(std::string_view itemId) const { - const EditorProjectRuntime* runtime = ResolveProjectRuntime(); - return runtime != nullptr - ? runtime->FindAssetEntry(itemId) + return m_projectRuntime != nullptr + ? m_projectRuntime->FindAssetEntry(itemId) : nullptr; } @@ -498,15 +477,14 @@ ProjectPanel::AssetCommandTarget ProjectPanel::ResolveAssetCommandTarget( return {}; } - return ResolveProjectRuntime()->ResolveAssetCommandTarget( + return m_projectRuntime->ResolveAssetCommandTarget( explicitItemId, forceCurrentFolder); } const ProjectPanel::AssetEntry* ProjectPanel::GetSelectedAssetEntry() const { - const EditorProjectRuntime* runtime = ResolveProjectRuntime(); - return runtime != nullptr && runtime->HasSelection() - ? runtime->FindAssetEntry(runtime->GetSelection().itemId) + return m_projectRuntime != nullptr && m_projectRuntime->HasSelection() + ? m_projectRuntime->FindAssetEntry(m_projectRuntime->GetSelection().itemId) : nullptr; } @@ -666,7 +644,7 @@ void ProjectPanel::UpdateRenameSession( std::string renamedItemId = {}; if (m_renameFrame.result.valueChanged && - !ResolveProjectRuntime()->RenameItem( + !m_projectRuntime->RenameItem( m_renameFrame.result.itemId, m_renameFrame.result.valueAfter, &renamedItemId)) { @@ -704,7 +682,7 @@ std::optional ProjectPanel::ResolveEditCommandT return std::nullopt; } - return ResolveProjectRuntime()->ResolveEditCommandTarget( + return m_projectRuntime->ResolveEditCommandTarget( explicitItemId, forceCurrentFolder); } @@ -737,14 +715,13 @@ void ProjectPanel::SyncSelectionsFromRuntime() { } void ProjectPanel::SyncAssetSelectionFromRuntime() { - const EditorProjectRuntime* runtime = ResolveProjectRuntime(); - if (runtime == nullptr || !runtime->HasSelection()) { + if (m_projectRuntime == nullptr || !m_projectRuntime->HasSelection()) { m_assetSelection.ClearSelection(); return; } - if (FindAssetEntry(runtime->GetSelection().itemId) != nullptr) { - m_assetSelection.SetSelection(runtime->GetSelection().itemId); + if (FindAssetEntry(m_projectRuntime->GetSelection().itemId) != nullptr) { + m_assetSelection.SetSelection(m_projectRuntime->GetSelection().itemId); return; } @@ -862,7 +839,7 @@ void ProjectPanel::RebuildBrowserScrollLayout() { } bool ProjectPanel::NavigateToFolder(std::string_view itemId, EventSource source) { - if (!ResolveProjectRuntime()->NavigateToFolder(itemId)) { + if (!m_projectRuntime->NavigateToFolder(itemId)) { return false; } @@ -883,7 +860,7 @@ bool ProjectPanel::OpenProjectItem(std::string_view itemId, EventSource source) } if (asset->directory) { - const bool navigated = ResolveProjectRuntime()->OpenItem(asset->itemId); + const bool navigated = m_projectRuntime->OpenItem(asset->itemId); if (navigated && HasValidBounds(m_layout.bounds)) { SyncSelectionsFromRuntime(); RebuildPanelLayout(m_layout.bounds); @@ -901,7 +878,7 @@ bool ProjectPanel::OpenProjectItem(std::string_view itemId, EventSource source) return navigated; } - if (!ResolveProjectRuntime()->OpenItem(asset->itemId)) { + if (!m_projectRuntime->OpenItem(asset->itemId)) { return false; } @@ -1365,7 +1342,7 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand( m_hoveredAssetItemId.clear(); m_lastPrimaryClickedAssetId = std::string(createdItemId); m_lastPrimaryClickTime = {}; - ResolveProjectRuntime()->SetSelection(createdItemId); + m_projectRuntime->SetSelection(createdItemId); SyncSelectionsFromRuntime(); const AssetEntry* createdAsset = FindAssetEntry(createdItemId); @@ -1388,7 +1365,7 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand( } std::string createdFolderId = {}; - if (!ResolveProjectRuntime()->CreateFolder( + if (!m_projectRuntime->CreateFolder( target.containerFolder->itemId, "New Folder", &createdFolderId)) { @@ -1425,7 +1402,7 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand( } std::string createdItemId = {}; - if (!ResolveProjectRuntime()->CreateMaterial( + if (!m_projectRuntime->CreateMaterial( target.containerFolder->itemId, "New Material", &createdItemId)) { @@ -1588,8 +1565,8 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchEditCommand( if (commandId == "edit.delete") { const std::string previousCurrentFolderId = GetBrowserModel().GetCurrentFolderId(); - const bool hadAssetSelection = ResolveProjectRuntime()->HasSelection(); - if (!ResolveProjectRuntime()->DeleteItem(target->itemId)) { + const bool hadAssetSelection = m_projectRuntime->HasSelection(); + if (!m_projectRuntime->DeleteItem(target->itemId)) { return BuildDispatchResult(false, "Failed to delete the selected project item."); } @@ -1606,7 +1583,7 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchEditCommand( m_browserScrollFrame.layout.contentRect, m_browserVerticalOffset); } - if (hadAssetSelection && !ResolveProjectRuntime()->HasSelection()) { + if (hadAssetSelection && !m_projectRuntime->HasSelection()) { EmitSelectionClearedEvent(EventSource::GridPrimary); } if (previousCurrentFolderId != GetBrowserModel().GetCurrentFolderId()) { @@ -1669,7 +1646,7 @@ void ProjectPanel::Update( } if (GetBrowserModel().GetFolderEntries().empty()) { - ResolveProjectRuntime()->Refresh(); + m_projectRuntime->Refresh(); SyncSelectionsFromRuntime(); } @@ -1837,7 +1814,7 @@ void ProjectPanel::Update( } return true; } - } treeDragCallbacks{ m_folderSelection, m_folderExpansion, *ResolveProjectRuntime() }; + } treeDragCallbacks{ m_folderSelection, m_folderExpansion, *m_projectRuntime }; const TreeDrag::ProcessResult treeDragResult = TreeDrag::ProcessInputEvents( m_treeDragState, @@ -1849,13 +1826,13 @@ void ProjectPanel::Update( TreeDrag::kDefaultDragThreshold, m_splitterDragging || m_assetDragState.dragging); if (treeDragResult.dropCommitted) { - const bool hadAssetSelection = ResolveProjectRuntime()->HasSelection(); + const bool hadAssetSelection = m_projectRuntime->HasSelection(); CloseContextMenu(); - ResolveProjectRuntime()->ClearSelection(); + m_projectRuntime->ClearSelection(); SyncSelectionsFromRuntime(); m_hoveredAssetItemId.clear(); m_lastPrimaryClickedAssetId.clear(); - if (hadAssetSelection && !ResolveProjectRuntime()->HasSelection()) { + if (hadAssetSelection && !m_projectRuntime->HasSelection()) { EmitSelectionClearedEvent(EventSource::Tree); } RebuildPanelLayout(dispatchEntry.bounds); @@ -1928,7 +1905,7 @@ void ProjectPanel::Update( } assetDragCallbacks{ m_assetSelection, m_folderExpansion, - *ResolveProjectRuntime(), + *m_projectRuntime, m_layout, GetBrowserModel().GetAssetEntries(), [this](const UIPoint& point, DropTargetSurface* surface) { @@ -1954,7 +1931,7 @@ void ProjectPanel::Update( } } if (assetDragResult.dropCommitted) { - const bool hadAssetSelection = ResolveProjectRuntime()->HasSelection(); + const bool hadAssetSelection = m_projectRuntime->HasSelection(); CloseContextMenu(); ClearRenameState(); m_hoveredAssetItemId.clear(); @@ -1966,13 +1943,13 @@ void ProjectPanel::Update( : assetDragCallbacks.movedItemId; if (const AssetEntry* movedAsset = FindAssetEntry(movedItemId); movedAsset != nullptr) { - ResolveProjectRuntime()->SetSelection(movedItemId); + m_projectRuntime->SetSelection(movedItemId); SyncSelectionsFromRuntime(); EmitEvent(EventKind::AssetSelected, EventSource::GridDrag, movedAsset); } else { - ResolveProjectRuntime()->ClearSelection(); + m_projectRuntime->ClearSelection(); SyncSelectionsFromRuntime(); - if (hadAssetSelection && !ResolveProjectRuntime()->HasSelection()) { + if (hadAssetSelection && !m_projectRuntime->HasSelection()) { EmitSelectionClearedEvent(EventSource::GridDrag); } } @@ -2069,8 +2046,8 @@ void ProjectPanel::Update( const auto& assetEntries = GetBrowserModel().GetAssetEntries(); const std::size_t hitIndex = HitTestAssetTile(event.position); if (hitIndex >= assetEntries.size()) { - if (ResolveProjectRuntime()->HasSelection()) { - ResolveProjectRuntime()->ClearSelection(); + if (m_projectRuntime->HasSelection()) { + m_projectRuntime->ClearSelection(); SyncAssetSelectionFromRuntime(); EmitSelectionClearedEvent(EventSource::Background); } @@ -2079,7 +2056,7 @@ void ProjectPanel::Update( const AssetEntry& assetEntry = assetEntries[hitIndex]; const bool alreadySelected = m_assetSelection.IsSelected(assetEntry.itemId); - const bool selectionChanged = ResolveProjectRuntime()->SetSelection(assetEntry.itemId); + const bool selectionChanged = m_projectRuntime->SetSelection(assetEntry.itemId); SyncAssetSelectionFromRuntime(); if (selectionChanged) { EmitEvent(EventKind::AssetSelected, EventSource::GridPrimary, &assetEntry); @@ -2117,7 +2094,7 @@ void ProjectPanel::Update( const AssetEntry& assetEntry = assetEntries[hitIndex]; if (!m_assetSelection.IsSelected(assetEntry.itemId)) { - ResolveProjectRuntime()->SetSelection(assetEntry.itemId); + m_projectRuntime->SetSelection(assetEntry.itemId); SyncAssetSelectionFromRuntime(); EmitEvent(EventKind::AssetSelected, EventSource::GridSecondary, &assetEntry); } diff --git a/editor/app/Features/Project/ProjectPanel.h b/editor/app/Features/Project/ProjectPanel.h index 1b47ae8f..32d173b9 100644 --- a/editor/app/Features/Project/ProjectPanel.h +++ b/editor/app/Features/Project/ProjectPanel.h @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -82,7 +81,6 @@ public: bool directory = false; }; - void Initialize(const std::filesystem::path& projectRoot); void SetProjectRuntime(EditorProjectRuntime* projectRuntime); void SetCommandFocusService(EditorCommandFocusService* commandFocusService); void SetSystemInteractionHost(System::SystemInteractionService* systemInteractionHost); @@ -163,8 +161,6 @@ private: std::vector assetTiles = {}; }; - EditorProjectRuntime* ResolveProjectRuntime(); - const EditorProjectRuntime* ResolveProjectRuntime() const; bool HasProjectRuntime() const; BrowserModel& GetBrowserModel(); const BrowserModel& GetBrowserModel() const; @@ -250,7 +246,6 @@ private: std::string_view explicitItemId, bool forceCurrentFolder); - std::unique_ptr m_ownedProjectRuntime = {}; EditorProjectRuntime* m_projectRuntime = nullptr; EditorCommandFocusService* m_commandFocusService = nullptr; System::SystemInteractionService* m_systemInteractionHost = nullptr; diff --git a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h index 5108fa64..d2fa2af7 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h +++ b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/UI/Editor/unit/test_project_panel.cpp b/tests/UI/Editor/unit/test_project_panel.cpp index 4bfe2593..2585670b 100644 --- a/tests/UI/Editor/unit/test_project_panel.cpp +++ b/tests/UI/Editor/unit/test_project_panel.cpp @@ -143,8 +143,10 @@ UIEditorHostedPanelDispatchEntry MakeProjectDispatchEntry() { TEST(ProjectPanelTests, CreateFolderCommandCreatesDirectoryAndQueuesRename) { TemporaryRepo repo = {}; + EditorProjectRuntime runtime = {}; + ASSERT_TRUE(runtime.Initialize(repo.Root() / "project")); ProjectPanel panel = {}; - panel.Initialize(repo.Root() / "project"); + panel.SetProjectRuntime(&runtime); const UIEditorHostCommandEvaluationResult evaluation = panel.EvaluateAssetCommand("assets.create_folder"); @@ -168,8 +170,10 @@ TEST(ProjectPanelTests, CreateFolderCommandCreatesDirectoryAndQueuesRename) { TEST(ProjectPanelTests, CreateMaterialCommandCreatesFileAndQueuesRename) { TemporaryRepo repo = {}; + EditorProjectRuntime runtime = {}; + ASSERT_TRUE(runtime.Initialize(repo.Root() / "project")); ProjectPanel panel = {}; - panel.Initialize(repo.Root() / "project"); + panel.SetProjectRuntime(&runtime); const UIEditorHostCommandEvaluationResult evaluation = panel.EvaluateAssetCommand("assets.create_material"); @@ -191,8 +195,10 @@ TEST(ProjectPanelTests, CreateMaterialCommandCreatesFileAndQueuesRename) { TEST(ProjectPanelTests, BackgroundContextMenuCreateFolderUsesCurrentFolder) { TemporaryRepo repo = {}; + EditorProjectRuntime runtime = {}; + ASSERT_TRUE(runtime.Initialize(repo.Root() / "project")); ProjectPanel panel = {}; - panel.Initialize(repo.Root() / "project"); + panel.SetProjectRuntime(&runtime); const UIEditorHostedPanelDispatchEntry dispatchEntry = MakeProjectDispatchEntry(); panel.Update( @@ -218,8 +224,10 @@ TEST(ProjectPanelTests, FolderContextMenuCreateFolderUsesFolderTarget) { TemporaryRepo repo = {}; ASSERT_TRUE(std::filesystem::create_directories(repo.Root() / "project/Assets/FolderA")); + EditorProjectRuntime runtime = {}; + ASSERT_TRUE(runtime.Initialize(repo.Root() / "project")); ProjectPanel panel = {}; - panel.Initialize(repo.Root() / "project"); + panel.SetProjectRuntime(&runtime); const UIEditorHostedPanelDispatchEntry dispatchEntry = MakeProjectDispatchEntry(); panel.Update( @@ -283,7 +291,9 @@ TEST(ProjectPanelTests, IconServiceCanBeConfiguredBeforeRuntimeInitialization) { FakeIconService icons = {}; panel.SetIconService(&icons); - panel.Initialize(repo.Root() / "project"); + EditorProjectRuntime runtime = {}; + ASSERT_TRUE(runtime.Initialize(repo.Root() / "project")); + panel.SetProjectRuntime(&runtime); const UIEditorHostCommandEvaluationResult evaluation = panel.EvaluateAssetCommand("assets.create_folder");