engine: sync editor rendering and ui changes

This commit is contained in:
2026-04-08 16:09:15 +08:00
parent 31756847ab
commit 162f1cc12e
153 changed files with 4454 additions and 2990 deletions

View File

@@ -9,9 +9,12 @@
#include "Core/EditorContext.h"
#include "Core/PlaySessionController.h"
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Math/Quaternion.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <chrono>
#include <filesystem>
@@ -24,6 +27,15 @@ namespace fs = std::filesystem;
namespace XCEngine::Editor {
namespace {
bool DirectoryHasEntries(const fs::path& directoryPath) {
std::error_code ec;
if (!fs::exists(directoryPath, ec) || !fs::is_directory(directoryPath, ec)) {
return false;
}
return fs::directory_iterator(directoryPath) != fs::directory_iterator();
}
class EditorActionRoutingTest : public ::testing::Test {
protected:
void SetUp() override {
@@ -138,6 +150,41 @@ TEST_F(EditorActionRoutingTest, HierarchyRouteExecutesCopyPasteDuplicateDeleteAn
EXPECT_FALSE(m_context.GetSelectionManager().IsSelected(duplicatedEntityId));
}
TEST_F(EditorActionRoutingTest, CreatePrimitiveEntityAddsBuiltinMeshComponentsAndSupportsUndoRedo) {
using XCEngine::Resources::BuiltinPrimitiveType;
using XCEngine::Resources::GetBuiltinDefaultPrimitiveMaterialPath;
using XCEngine::Resources::GetBuiltinPrimitiveMeshPath;
auto* entity = Commands::CreatePrimitiveEntity(m_context, BuiltinPrimitiveType::Cube, nullptr);
ASSERT_NE(entity, nullptr);
EXPECT_EQ(CountHierarchyEntities(m_context.GetSceneManager()), 1u);
auto* meshFilter = entity->GetComponent<::XCEngine::Components::MeshFilterComponent>();
auto* meshRenderer = entity->GetComponent<::XCEngine::Components::MeshRendererComponent>();
ASSERT_NE(meshFilter, nullptr);
ASSERT_NE(meshRenderer, nullptr);
EXPECT_EQ(meshFilter->GetMeshPath(), GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Cube).CStr());
ASSERT_EQ(meshRenderer->GetMaterialCount(), 1u);
EXPECT_EQ(meshRenderer->GetMaterialPath(0), GetBuiltinDefaultPrimitiveMaterialPath().CStr());
EXPECT_TRUE(m_context.GetUndoManager().CanUndo());
m_context.GetUndoManager().Undo();
EXPECT_EQ(CountHierarchyEntities(m_context.GetSceneManager()), 0u);
m_context.GetUndoManager().Redo();
EXPECT_EQ(CountHierarchyEntities(m_context.GetSceneManager()), 1u);
auto* restored = m_context.GetSceneManager().GetScene()->Find("Cube");
ASSERT_NE(restored, nullptr);
auto* restoredMeshFilter = restored->GetComponent<::XCEngine::Components::MeshFilterComponent>();
auto* restoredMeshRenderer = restored->GetComponent<::XCEngine::Components::MeshRendererComponent>();
ASSERT_NE(restoredMeshFilter, nullptr);
ASSERT_NE(restoredMeshRenderer, nullptr);
EXPECT_EQ(restoredMeshFilter->GetMeshPath(), GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Cube).CStr());
ASSERT_EQ(restoredMeshRenderer->GetMaterialCount(), 1u);
EXPECT_EQ(restoredMeshRenderer->GetMaterialPath(0), GetBuiltinDefaultPrimitiveMaterialPath().CStr());
}
TEST_F(EditorActionRoutingTest, ProjectRouteExecutesOpenBackAndDelete) {
const fs::path assetsDir = m_projectRoot / "Assets";
const fs::path folderPath = assetsDir / "RouteFolder";
@@ -419,6 +466,32 @@ TEST_F(EditorActionRoutingTest, HierarchyRouterRenameHelpersPublishAndCommit) {
m_context.GetEventBus().Unsubscribe<EntityRenameRequestedEvent>(renameSubscription);
}
TEST_F(EditorActionRoutingTest, CreateTypedLightCommandsAssignExpectedNamesAndTypes) {
auto* directionalLight = Commands::CreateDirectionalLightEntity(m_context);
auto* pointLight = Commands::CreatePointLightEntity(m_context);
auto* spotLight = Commands::CreateSpotLightEntity(m_context);
ASSERT_NE(directionalLight, nullptr);
ASSERT_NE(pointLight, nullptr);
ASSERT_NE(spotLight, nullptr);
EXPECT_EQ(directionalLight->GetName(), "Directional Light");
EXPECT_EQ(pointLight->GetName(), "Point Light");
EXPECT_EQ(spotLight->GetName(), "Spot Light");
auto* directionalComponent = directionalLight->GetComponent<Components::LightComponent>();
auto* pointComponent = pointLight->GetComponent<Components::LightComponent>();
auto* spotComponent = spotLight->GetComponent<Components::LightComponent>();
ASSERT_NE(directionalComponent, nullptr);
ASSERT_NE(pointComponent, nullptr);
ASSERT_NE(spotComponent, nullptr);
EXPECT_EQ(directionalComponent->GetLightType(), Components::LightType::Directional);
EXPECT_EQ(pointComponent->GetLightType(), Components::LightType::Point);
EXPECT_EQ(spotComponent->GetLightType(), Components::LightType::Spot);
}
TEST_F(EditorActionRoutingTest, HierarchyItemContextRequestSelectsEntityAndStoresPopupTarget) {
auto* entity = Commands::CreateEmptyEntity(m_context, nullptr, "Create Entity", "ContextTarget");
ASSERT_NE(entity, nullptr);
@@ -483,68 +556,6 @@ TEST_F(EditorActionRoutingTest, ProjectCommandsRenameAssetUpdatesSelectionAndPre
EXPECT_EQ(m_context.GetProjectManager().GetSelectedItemPath(), renamedItem->fullPath);
}
TEST_F(EditorActionRoutingTest, ProjectCommandsMigrateSceneAssetReferencesRewritesLegacyScenePayloads) {
using ::XCEngine::Resources::ResourceManager;
const fs::path assetsDir = m_projectRoot / "Assets";
const fs::path scenesDir = assetsDir / "Scenes";
const fs::path materialPath = assetsDir / "runtime.material";
const fs::path scenePath = scenesDir / "LegacyScene.xc";
{
std::ofstream materialFile(materialPath.string(), std::ios::out | std::ios::trunc);
ASSERT_TRUE(materialFile.is_open());
materialFile << "{\n";
materialFile << " \"renderQueue\": \"geometry\",\n";
materialFile << " \"renderState\": {\n";
materialFile << " \"cull\": \"back\"\n";
materialFile << " }\n";
materialFile << "}\n";
}
{
std::ofstream sceneFile(scenePath.string(), std::ios::out | std::ios::trunc);
ASSERT_TRUE(sceneFile.is_open());
sceneFile << "# XCEngine Scene File\n";
sceneFile << "scene=Legacy Scene\n";
sceneFile << "active=1\n\n";
sceneFile << "gameobject_begin\n";
sceneFile << "id=1\n";
sceneFile << "uuid=1\n";
sceneFile << "name=Legacy Object\n";
sceneFile << "active=1\n";
sceneFile << "parent=0\n";
sceneFile << "transform=position=0,0,0;rotation=0,0,0,1;scale=1,1,1;\n";
sceneFile << "component=MeshFilter;mesh=builtin://meshes/cube;meshRef=;\n";
sceneFile << "component=MeshRenderer;materials=Assets/runtime.material;materialRefs=;castShadows=1;receiveShadows=1;renderLayer=0;\n";
sceneFile << "gameobject_end\n";
}
ASSERT_TRUE(Commands::CanMigrateSceneAssetReferences(m_context));
const IProjectManager::SceneAssetReferenceMigrationReport report =
Commands::MigrateSceneAssetReferences(m_context);
EXPECT_EQ(report.scannedSceneCount, 1u);
EXPECT_EQ(report.migratedSceneCount, 1u);
EXPECT_EQ(report.unchangedSceneCount, 0u);
EXPECT_EQ(report.failedSceneCount, 0u);
std::ifstream migratedScene(scenePath.string(), std::ios::in | std::ios::binary);
ASSERT_TRUE(migratedScene.is_open());
std::string migratedText((std::istreambuf_iterator<char>(migratedScene)),
std::istreambuf_iterator<char>());
EXPECT_NE(migratedText.find("meshPath=builtin://meshes/cube;"), std::string::npos);
EXPECT_EQ(migratedText.find("component=MeshFilter;mesh=builtin://meshes/cube;"), std::string::npos);
EXPECT_NE(migratedText.find("materialPaths=;"), std::string::npos);
EXPECT_NE(migratedText.find("materialRefs="), std::string::npos);
EXPECT_EQ(migratedText.find("materialRefs=;"), std::string::npos);
EXPECT_EQ(migratedText.find("component=MeshRenderer;materials="), std::string::npos);
ResourceManager::Get().SetResourceRoot("");
ResourceManager::Get().Shutdown();
}
TEST_F(EditorActionRoutingTest, ProjectItemContextRequestSelectsAssetAndStoresPopupTarget) {
const fs::path assetsDir = m_projectRoot / "Assets";
const fs::path filePath = assetsDir / "ContextAsset.txt";
@@ -618,6 +629,64 @@ TEST_F(EditorActionRoutingTest, ProjectSelectionSurvivesRefreshWhenItemOrderChan
FindCurrentItemIndexByName("Selected.txt"));
}
TEST_F(EditorActionRoutingTest, ProjectCommandsExposeAssetCacheMaintenanceActions) {
using ::XCEngine::Resources::ResourceManager;
const fs::path materialPath = m_projectRoot / "Assets" / "ToolMaterial.mat";
std::ofstream(materialPath.string()) << "{\n \"renderQueue\": \"geometry\"\n}\n";
m_context.GetProjectManager().RefreshCurrentFolder();
ResourceManager& resourceManager = ResourceManager::Get();
resourceManager.Initialize();
resourceManager.SetResourceRoot(m_projectRoot.string().c_str());
const AssetItemPtr materialItem = FindCurrentItemByName("ToolMaterial.mat");
ASSERT_NE(materialItem, nullptr);
m_context.GetProjectManager().SetSelectedItem(materialItem);
EXPECT_TRUE(Commands::CanReimportSelectedAsset(m_context));
EXPECT_TRUE(Commands::CanReimportAllAssets(m_context));
EXPECT_TRUE(Commands::CanClearLibrary(m_context));
m_context.SetRuntimeMode(EditorRuntimeMode::Play);
EXPECT_FALSE(Commands::CanReimportSelectedAsset(m_context));
EXPECT_FALSE(Commands::CanReimportAllAssets(m_context));
EXPECT_FALSE(Commands::CanClearLibrary(m_context));
m_context.SetRuntimeMode(EditorRuntimeMode::Edit);
resourceManager.SetResourceRoot("");
resourceManager.Shutdown();
}
TEST_F(EditorActionRoutingTest, ProjectCommandsReimportSelectedAssetAndClearLibraryDriveAssetCache) {
using ::XCEngine::Resources::ResourceManager;
const fs::path materialPath = m_projectRoot / "Assets" / "ToolMaterial.mat";
std::ofstream(materialPath.string()) << "{\n \"renderQueue\": \"geometry\"\n}\n";
m_context.GetProjectManager().RefreshCurrentFolder();
ResourceManager& resourceManager = ResourceManager::Get();
resourceManager.Initialize();
resourceManager.SetResourceRoot(m_projectRoot.string().c_str());
const AssetItemPtr materialItem = FindCurrentItemByName("ToolMaterial.mat");
ASSERT_NE(materialItem, nullptr);
m_context.GetProjectManager().SetSelectedItem(materialItem);
const fs::path libraryRoot(resourceManager.GetProjectLibraryRoot().CStr());
EXPECT_TRUE(Commands::ReimportSelectedAsset(m_context));
EXPECT_TRUE(DirectoryHasEntries(libraryRoot / "Artifacts"));
EXPECT_TRUE(Commands::ClearLibrary(m_context));
EXPECT_FALSE(DirectoryHasEntries(libraryRoot / "Artifacts"));
EXPECT_TRUE(Commands::ReimportAllAssets(m_context));
EXPECT_TRUE(DirectoryHasEntries(libraryRoot / "Artifacts"));
resourceManager.SetResourceRoot("");
resourceManager.Shutdown();
}
TEST_F(EditorActionRoutingTest, ProjectCommandsRejectMovingFolderIntoItsDescendant) {
const fs::path assetsDir = m_projectRoot / "Assets";
const fs::path parentPath = assetsDir / "Parent";