Move design notes into docs/used
This commit is contained in:
189
docs/used/D3D12_RHI_Test_Issue.md
Normal file
189
docs/used/D3D12_RHI_Test_Issue.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# D3D12 RHI 测试失败问题报告
|
||||
|
||||
## 问题描述
|
||||
|
||||
5个D3D12测试在调用 `CreateCommandList` 时失败,错误码 `DXGI_ERROR_NOT_CURRENTLY_AVAILABLE` (0x887A0005)
|
||||
|
||||
## 失败的测试
|
||||
|
||||
| 测试名称 | 错误位置 |
|
||||
|---------|---------|
|
||||
| `CommandList_ClearRenderTarget_WithRealView` | `CreateCommandList` 返回 nullptr |
|
||||
| `CommandList_ClearDepthStencil_WithRealView` | `CreateTexture` 返回 nullptr (D24_S8_UInt格式) |
|
||||
| `CommandList_SetRenderTargets_WithRealViews` | `CreateCommandList` 返回 nullptr |
|
||||
| `CommandList_BeginEndRenderPass_Basic` | `CreateCommandList` 返回 nullptr |
|
||||
| `CommandList_BeginEndRenderPass_WithClear` | `CreateCommandList` 返回 nullptr |
|
||||
|
||||
## 错误码分析
|
||||
|
||||
```
|
||||
DXGI_ERROR_NOT_CURRENTLY_AVAILABLE (0x887A0005)
|
||||
```
|
||||
|
||||
根据 Microsoft 文档,此错误表示:
|
||||
> "The resource or operation is not available at the current time. This can be returned when a command is submitted to a queue that is not in a state to process that command."
|
||||
|
||||
## 测试代码(最小复现)
|
||||
|
||||
```cpp
|
||||
TEST_P(RHITestFixture, CommandList_ClearRenderTarget_WithRealView) {
|
||||
// 1. 创建纹理 - 成功
|
||||
TextureDesc texDesc = {};
|
||||
texDesc.width = 256;
|
||||
texDesc.height = 256;
|
||||
texDesc.format = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
|
||||
texDesc.textureType = static_cast<uint32_t>(TextureType::Texture2D);
|
||||
RHITexture* texture = GetDevice()->CreateTexture(texDesc);
|
||||
ASSERT_NE(texture, nullptr); // ✅ 通过
|
||||
|
||||
// 2. 创建RTV - 成功
|
||||
RHIResourceView* rtv = GetDevice()->CreateRenderTargetView(texture, {});
|
||||
ASSERT_NE(rtv, nullptr); // ✅ 通过
|
||||
|
||||
// 3. 创建命令列表 - 失败
|
||||
CommandListDesc cmdDesc = {};
|
||||
cmdDesc.commandListType = static_cast<uint32_t>(CommandQueueType::Direct);
|
||||
RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc);
|
||||
ASSERT_NE(cmdList, nullptr); // ❌ 失败!cmdList == nullptr
|
||||
}
|
||||
```
|
||||
|
||||
## 调试日志
|
||||
|
||||
```
|
||||
CreateTexture: start
|
||||
CreateRenderTargetView: start
|
||||
CreateCommandList: start
|
||||
m_commandQueue=00000180F8F722E0
|
||||
m_device=00000180F22B5B30
|
||||
CreateCommandAllocator hr=887A0005
|
||||
CreateCommandList: allocator init failed
|
||||
```
|
||||
|
||||
## 关键发现
|
||||
|
||||
### 1. 通过的测试(关键差异)
|
||||
|
||||
以下测试**通过**,它们不创建纹理或只创建SRV:
|
||||
|
||||
```cpp
|
||||
// ✅ 通过 - 只创建纹理,不创建RTV
|
||||
TEST_P(RHITestFixture, CommandList_Reset_Close) {
|
||||
CommandListDesc cmdDesc = {};
|
||||
cmdDesc.commandListType = static_cast<uint32_t>(CommandQueueType::Direct);
|
||||
RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc);
|
||||
ASSERT_NE(cmdList, nullptr); // ✅ 通过
|
||||
cmdList->Shutdown();
|
||||
delete cmdList;
|
||||
}
|
||||
|
||||
// ✅ 通过 - 创建纹理+SRV,但不创建RTV
|
||||
TEST_P(RHITestFixture, CommandList_TransitionBarrier_WithRealResource) {
|
||||
RHITexture* texture = GetDevice()->CreateTexture(texDesc); // ✅
|
||||
RHIResourceView* srv = GetDevice()->CreateShaderResourceView(texture, {}); // ✅
|
||||
RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc); // ✅ 通过
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 失败的测试(共同点)
|
||||
|
||||
所有失败的测试都**先创建纹理,再创建RTV,然后创建命令列表**。
|
||||
|
||||
### 3. OpenGL vs D3D12
|
||||
|
||||
- **OpenGL**: 全部117个测试通过 ✅
|
||||
- **D3D12 (RHITestFixture)**: 112/117 通过,5个失败 ❌
|
||||
- **D3D12TestFixture**: 全部53个测试通过 ✅
|
||||
|
||||
## 代码分析
|
||||
|
||||
### CreateCommandList 实现
|
||||
|
||||
```cpp
|
||||
// engine/src/RHI/D3D12/D3D12Device.cpp:409
|
||||
RHICommandList* D3D12Device::CreateCommandList(const CommandListDesc& desc) {
|
||||
auto* allocator = new D3D12CommandAllocator();
|
||||
|
||||
// 失败发生在这里
|
||||
if (!allocator->Initialize(m_device.Get(), static_cast<CommandQueueType>(desc.commandListType))) {
|
||||
delete allocator;
|
||||
return nullptr; // 返回 nullptr
|
||||
}
|
||||
|
||||
auto* cmdList = new D3D12CommandList();
|
||||
if (!cmdList->Initialize(m_device.Get(), ...)) {
|
||||
delete allocator;
|
||||
delete cmdList;
|
||||
return nullptr;
|
||||
}
|
||||
return cmdList;
|
||||
}
|
||||
```
|
||||
|
||||
### CreateCommandAllocator 实现
|
||||
|
||||
```cpp
|
||||
// engine/src/RHI/D3D12/D3D12CommandAllocator.cpp:15
|
||||
bool D3D12CommandAllocator::Initialize(ID3D12Device* device, CommandQueueType type) {
|
||||
m_type = type;
|
||||
HRESULT hResult = device->CreateCommandAllocator(
|
||||
ToD3D12(type), // D3D12_COMMAND_LIST_TYPE_DIRECT
|
||||
IID_PPV_ARGS(&m_commandAllocator));
|
||||
return SUCCEEDED(hResult); // 返回 false,hr=887A0005
|
||||
}
|
||||
```
|
||||
|
||||
### CreateRenderTargetView 实现
|
||||
|
||||
```cpp
|
||||
// engine/src/RHI/D3D12/D3D12Device.cpp:455
|
||||
RHIResourceView* D3D12Device::CreateRenderTargetView(RHITexture* texture, const ResourceViewDesc& desc) {
|
||||
auto* view = new D3D12ResourceView();
|
||||
auto* d3d12Texture = static_cast<D3D12Texture*>(texture);
|
||||
ID3D12Resource* resource = d3d12Texture->GetResource();
|
||||
|
||||
D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
|
||||
rtvDesc.Format = static_cast<DXGI_FORMAT>(desc.format); // desc.format = 0 (Unknown!)
|
||||
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
|
||||
|
||||
// 创建非 shader-visible 的 descriptor heap
|
||||
DescriptorHeapDesc heapDesc = {};
|
||||
heapDesc.descriptorCount = 1;
|
||||
heapDesc.heapType = static_cast<uint32_t>(DescriptorHeapType::RTV);
|
||||
heapDesc.shaderVisible = false; // 非 shader-visible
|
||||
auto* heapPool = CreateDescriptorHeap(heapDesc);
|
||||
|
||||
view->InitializeAsRenderTarget(m_device.Get(), resource, &rtvDesc, heap, 0);
|
||||
return view;
|
||||
}
|
||||
```
|
||||
|
||||
## 已排除的原因
|
||||
|
||||
1. ❌ **调试层问题** - 禁用后仍然失败
|
||||
2. ❌ **命令队列类型不匹配** - 使用相同的 `CommandQueueType::Direct`
|
||||
3. ❌ **静态全局状态污染** - 每个测试创建独立设备
|
||||
4. ❌ **内存泄漏导致descriptor heap耗尽** - 内存问题不会导致此错误码
|
||||
5. ❌ **测试顺序依赖** - 每个测试有独立的 SetUp/TearDown
|
||||
|
||||
## 疑点
|
||||
|
||||
1. 为什么 `CreateCommandAllocator` 在创建 RTV(非shader-visible descriptor heap)之后会失败?
|
||||
2. D3D12TestFixture(使用相同设备创建方式)为什么全部通过?
|
||||
3. 为什么 SRV(shader-visible descriptor heap)不会触发这个问题,只有 RTV 会?
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `engine/src/RHI/D3D12/D3D12Device.cpp` - CreateCommandList, CreateTexture, CreateRenderTargetView
|
||||
- `engine/src/RHI/D3D12/D3D12CommandAllocator.cpp` - CreateCommandAllocator
|
||||
- `engine/src/RHI/D3D12/D3D12DescriptorHeap.cpp` - CreateDescriptorHeap
|
||||
- `engine/src/RHI/D3D12/D3D12ResourceView.cpp` - InitializeAsRenderTarget
|
||||
- `tests/RHI/unit/test_command_list.cpp` - 失败的测试
|
||||
- `tests/RHI/unit/fixtures/RHITestFixture.cpp` - 测试fixture
|
||||
|
||||
## 测试环境
|
||||
|
||||
- 平台: Windows
|
||||
- RHI后端: D3D12
|
||||
- 测试框架: Google Test
|
||||
- 编译配置: Debug
|
||||
590
docs/used/Editor重构计划.md
Normal file
590
docs/used/Editor重构计划.md
Normal file
@@ -0,0 +1,590 @@
|
||||
# Editor 重构计划
|
||||
|
||||
## 一、当前问题总结
|
||||
|
||||
### 1.1 架构问题
|
||||
|
||||
| 问题 | 位置 | 严重程度 |
|
||||
|------|------|----------|
|
||||
| `Panel` 滥用 `Core::Layer` 继承 | `editor/src/panels/Panel.h` | P0 |
|
||||
| 单例模式滥用 | 所有 Manager 类 | P1 |
|
||||
| 直接使用 `GameObject*` 裸指针 | 各 Panel 文件 | P1 |
|
||||
| `EditorSceneManager` 职责过重 | `editor/src/Managers/SceneManager.h` | P1 |
|
||||
| 无统一事件总线 | 整体架构 | P1 |
|
||||
|
||||
### 1.2 功能缺失
|
||||
|
||||
| 功能 | 当前状态 |
|
||||
|------|----------|
|
||||
| Undo/Redo | 完全缺失 |
|
||||
| 场景序列化/反序列化 | 完全缺失 |
|
||||
| 资源引用系统 | 完全缺失 |
|
||||
| Play/Edit 模式切换 | 只有框架 |
|
||||
| Transform Gizmos | 完全缺失 |
|
||||
| Inspector 组件扩展 | 仅支持 TransformComponent |
|
||||
| 场景/游戏视图相机控制 | 完全缺失 |
|
||||
|
||||
### 1.3 代码质量
|
||||
|
||||
- 硬编码路径(崩溃日志路径)
|
||||
- 调试日志残留(InspectorPanel 中大量无意义的 Debug 日志)
|
||||
- 内存泄漏风险(raw new 无智能指针)
|
||||
- 异常处理硬编码
|
||||
|
||||
---
|
||||
|
||||
## 二、重构目标
|
||||
|
||||
### 2.1 短期目标(原型验证)
|
||||
|
||||
- [ ] 解决 P0 级架构问题
|
||||
- [ ] 实现基础 Undo/Redo
|
||||
- [ ] 实现场景序列化
|
||||
- [ ] 分离引擎核心与编辑器状态
|
||||
|
||||
### 2.2 中期目标(功能完善)
|
||||
|
||||
- [ ] 实现 Transform Gizmos
|
||||
- [ ] 实现 Inspector 组件扩展机制
|
||||
- [ ] 实现场景/游戏视图相机控制
|
||||
- [ ] 实现 Play/Edit 模式切换
|
||||
- [ ] 实现资源引用系统
|
||||
|
||||
### 2.3 长期目标(生产可用)
|
||||
|
||||
- [ ] 编辑器配置序列化
|
||||
- [ ] 插件系统
|
||||
- [ ] 多场景编辑
|
||||
- [ ] 性能优化
|
||||
|
||||
---
|
||||
|
||||
## 三、重构方案
|
||||
|
||||
### 3.1 架构重构
|
||||
|
||||
#### 3.1.1 Panel 独立化
|
||||
|
||||
**现状**:
|
||||
```cpp
|
||||
class Panel : public Core::Layer { // ❌ 编辑器面板不是游戏层级
|
||||
```
|
||||
|
||||
**重构**:
|
||||
```cpp
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
class Panel {
|
||||
public:
|
||||
Panel(const std::string& name) : m_name(name), m_isOpen(true) {}
|
||||
virtual ~Panel() = default;
|
||||
|
||||
virtual void OnAttach() {}
|
||||
virtual void OnDetach() {}
|
||||
virtual void OnUpdate(float dt) {}
|
||||
virtual void OnEvent(void* event) {}
|
||||
virtual void Render() = 0;
|
||||
|
||||
bool IsOpen() const { return m_isOpen; }
|
||||
void SetOpen(bool open) { m_isOpen = open; }
|
||||
|
||||
protected:
|
||||
std::string m_name;
|
||||
bool m_isOpen;
|
||||
};
|
||||
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
```
|
||||
|
||||
**理由**:`Core::Layer` 设计用于游戏层级系统(UI/World/Background),编辑器面板只是 UI 控件,不应继承它。
|
||||
|
||||
#### 3.1.2 单例改造为依赖注入
|
||||
|
||||
**现状**:
|
||||
```cpp
|
||||
class SelectionManager {
|
||||
public:
|
||||
static SelectionManager& Get() { ... } // ❌ 单例
|
||||
};
|
||||
```
|
||||
|
||||
**重构**:
|
||||
```cpp
|
||||
class IEditorContext {
|
||||
public:
|
||||
virtual ~IEditorContext() = default;
|
||||
virtual ISelectionManager& GetSelectionManager() = 0;
|
||||
virtual ISceneManager& GetSceneManager() = 0;
|
||||
virtual IProjectManager& GetProjectManager() = 0;
|
||||
virtual IUndoRedoSystem& GetUndoRedoSystem() = 0;
|
||||
};
|
||||
|
||||
class EditorLayer {
|
||||
public:
|
||||
void SetContext(std::shared_ptr<IEditorContext> context) { m_context = context; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<IEditorContext> m_context;
|
||||
};
|
||||
```
|
||||
|
||||
#### 3.1.3 引入事件总线
|
||||
|
||||
**新增 `EditorEventBus`**:
|
||||
```cpp
|
||||
class EditorEventBus {
|
||||
public:
|
||||
template<typename T>
|
||||
void Subscribe(std::function<void(const T&)> handler);
|
||||
template<typename T>
|
||||
void Unsubscribe(uint64_t id);
|
||||
template<typename T>
|
||||
void Publish(const T& event);
|
||||
|
||||
private:
|
||||
std::unordered_map<size_t, std::vector<std::function<void(void*)>>> m_handlers;
|
||||
std::unordered_map<size_t, uint64_t> m_nextId;
|
||||
};
|
||||
```
|
||||
|
||||
**定义事件类型**:
|
||||
```cpp
|
||||
struct SelectionChangedEvent {
|
||||
std::vector<GameObjectID> selectedObjects;
|
||||
};
|
||||
|
||||
struct EntityCreatedEvent {
|
||||
GameObjectID entityId;
|
||||
};
|
||||
|
||||
struct EntityDestroyedEvent {
|
||||
GameObjectID entityId;
|
||||
};
|
||||
```
|
||||
|
||||
#### 3.1.4 GameObject 生命周期管理
|
||||
|
||||
**现状**:
|
||||
```cpp
|
||||
::XCEngine::Components::GameObject* m_selectedGameObject = nullptr; // ❌ 裸指针
|
||||
```
|
||||
|
||||
**重构**:
|
||||
```cpp
|
||||
using GameObjectHandle = ResourceHandle<GameObject>;
|
||||
|
||||
struct SelectionState {
|
||||
std::vector<GameObjectHandle> selectedObjects;
|
||||
GameObjectHandle primarySelection; // 最后选中的
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.2 功能重构
|
||||
|
||||
#### 3.2.1 Undo/Redo 系统
|
||||
|
||||
**Command Pattern 实现**:
|
||||
```cpp
|
||||
class ICommand {
|
||||
public:
|
||||
virtual ~ICommand() = default;
|
||||
virtual void Execute() = 0;
|
||||
virtual void Undo() = 0;
|
||||
virtual std::string GetName() const = 0;
|
||||
};
|
||||
|
||||
class CreateEntityCommand : public ICommand {
|
||||
public:
|
||||
CreateEntityCommand(ISceneManager& scene, GameObjectHandle parent, std::string name);
|
||||
void Execute() override;
|
||||
void Undo() override;
|
||||
std::string GetName() const override { return "Create " + m_name; }
|
||||
|
||||
private:
|
||||
ISceneManager& m_scene;
|
||||
GameObjectHandle m_parent;
|
||||
std::string m_name;
|
||||
GameObjectHandle m_createdEntity;
|
||||
};
|
||||
|
||||
class DeleteEntityCommand : public ICommand {
|
||||
public:
|
||||
DeleteEntityCommand(ISceneManager& scene, GameObjectHandle entity);
|
||||
void Execute() override;
|
||||
void Undo() override;
|
||||
std::string GetName() const override { return "Delete " + m_entityName; }
|
||||
|
||||
private:
|
||||
ISceneManager& m_scene;
|
||||
GameObjectHandle m_entity;
|
||||
std::string m_entityName;
|
||||
std::vector<GameObjectHandle> m_children;
|
||||
GameObjectHandle m_parent;
|
||||
// 存储删除前的完整状态用于恢复
|
||||
};
|
||||
|
||||
class TransformCommand : public ICommand {
|
||||
public:
|
||||
TransformCommand(GameObjectHandle entity, const Transform& oldState, const Transform& newState);
|
||||
void Execute() override { Apply(m_newState); }
|
||||
void Undo() override { Apply(m_oldState); }
|
||||
|
||||
private:
|
||||
void Apply(const Transform& t);
|
||||
GameObjectHandle m_entity;
|
||||
Transform m_oldState;
|
||||
Transform m_newState;
|
||||
};
|
||||
```
|
||||
|
||||
**Undo/Redo Manager**:
|
||||
```cpp
|
||||
class UndoRedoSystem {
|
||||
public:
|
||||
void Execute(std::unique_ptr<ICommand> command);
|
||||
void Undo();
|
||||
void Redo();
|
||||
bool CanUndo() const { return m_undoStack.size() > m_historyIndex + 1; }
|
||||
bool CanRedo() const { return m_historyIndex >= 0; }
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<ICommand>> m_undoStack;
|
||||
int m_historyIndex = -1;
|
||||
static constexpr size_t MAX_HISTORY = 100;
|
||||
};
|
||||
```
|
||||
|
||||
#### 3.2.2 场景序列化
|
||||
|
||||
**Scene 序列化接口**:
|
||||
```cpp
|
||||
class ISceneSerializer {
|
||||
public:
|
||||
virtual ~ISceneSerializer() = default;
|
||||
virtual bool Serialize(IScene* scene, const std::string& path) = 0;
|
||||
virtual bool Deserialize(IScene* scene, const std::string& path) = 0;
|
||||
};
|
||||
|
||||
class JsonSceneSerializer : public ISceneSerializer {
|
||||
public:
|
||||
bool Serialize(IScene* scene, const std::string& path) override;
|
||||
bool Deserialize(IScene* scene, const std::string& path) override;
|
||||
|
||||
private:
|
||||
nlohmann::json GameObjectToJson(GameObject* go);
|
||||
GameObject* JsonToGameObject(const nlohmann::json& j, GameObject* parent);
|
||||
};
|
||||
```
|
||||
|
||||
**序列化格式(JSON)**:
|
||||
```json
|
||||
{
|
||||
"version": "1.0",
|
||||
"name": "MainScene",
|
||||
"entities": [
|
||||
{
|
||||
"id": "1234567890",
|
||||
"name": "Player",
|
||||
"components": [
|
||||
{
|
||||
"type": "TransformComponent",
|
||||
"position": [0, 0, 0],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1]
|
||||
},
|
||||
{
|
||||
"type": "MeshRendererComponent",
|
||||
"mesh": "Assets/Meshes/cube.mesh",
|
||||
"material": "Assets/Materials/default.mat"
|
||||
}
|
||||
],
|
||||
"children": [
|
||||
{
|
||||
"id": "9876543210",
|
||||
"name": "Weapon",
|
||||
"components": [...]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2.3 Inspector 组件扩展机制
|
||||
|
||||
**抽象 Component 编辑器**:
|
||||
```cpp
|
||||
class IComponentEditor {
|
||||
public:
|
||||
virtual ~IComponentEditor() = default;
|
||||
virtual std::string GetComponentTypeName() const = 0;
|
||||
virtual bool CanEdit(Component* component) const = 0;
|
||||
virtual void Render(Component* component) = 0;
|
||||
};
|
||||
|
||||
class TransformComponentEditor : public IComponentEditor {
|
||||
public:
|
||||
std::string GetComponentTypeName() const override { return "TransformComponent"; }
|
||||
bool CanEdit(Component* component) const override {
|
||||
return dynamic_cast<TransformComponent*>(component) != nullptr;
|
||||
}
|
||||
void Render(Component* component) override;
|
||||
};
|
||||
|
||||
class InspectorPanel {
|
||||
public:
|
||||
void RegisterComponentEditor(std::unique_ptr<IComponentEditor> editor);
|
||||
void RenderGameObject(GameObject* gameObject);
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<IComponentEditor>> m_editors;
|
||||
std::unique_ptr<IComponentEditor> GetEditorFor(Component* component);
|
||||
};
|
||||
```
|
||||
|
||||
**注册默认编辑器**:
|
||||
```cpp
|
||||
void InspectorPanel::Initialize() {
|
||||
RegisterComponentEditor(std::make_unique<TransformComponentEditor>());
|
||||
// 后续添加更多编辑器
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2.4 资源引用系统
|
||||
|
||||
**AssetHandle**:
|
||||
```cpp
|
||||
template<typename T>
|
||||
class AssetHandle {
|
||||
public:
|
||||
AssetHandle() = default;
|
||||
explicit AssetHandle(AssetID id, std::shared_ptr<T> asset = nullptr);
|
||||
|
||||
AssetID GetID() const { return m_id; }
|
||||
T* Get() const { return m_asset.get(); }
|
||||
T* operator->() const { return m_asset.get(); }
|
||||
explicit operator bool() const { return m_asset != nullptr; }
|
||||
|
||||
private:
|
||||
AssetID m_id;
|
||||
std::shared_ptr<T> m_asset;
|
||||
};
|
||||
|
||||
class MeshRendererComponent {
|
||||
public:
|
||||
AssetHandle<Mesh> GetMesh() const { return m_mesh; }
|
||||
void SetMesh(AssetHandle<Mesh> mesh) { m_mesh = mesh; }
|
||||
|
||||
private:
|
||||
AssetHandle<Mesh> m_mesh;
|
||||
AssetHandle<Material> m_material;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.3 界面重构
|
||||
|
||||
#### 3.3.1 SceneView 实现
|
||||
|
||||
**相机控制器**:
|
||||
```cpp
|
||||
class SceneViewCameraController {
|
||||
public:
|
||||
enum class Mode { Pan, Orbit, Zoom };
|
||||
|
||||
void OnUpdate(float dt);
|
||||
void OnMouseMove(int x, int y);
|
||||
void OnMouseWheel(int delta);
|
||||
|
||||
Matrix4 GetViewMatrix() const;
|
||||
Matrix4 GetProjectionMatrix() const;
|
||||
|
||||
private:
|
||||
Mode m_mode = Mode::Orbit;
|
||||
Vector3 m_target = Vector3::Zero();
|
||||
float m_distance = 10.0f;
|
||||
float m_pitch = 0.0f;
|
||||
float m_yaw = 0.0f;
|
||||
|
||||
bool m_isDragging = false;
|
||||
ImVec2 m_lastMousePos;
|
||||
};
|
||||
```
|
||||
|
||||
**Gizmos 渲染**:
|
||||
```cpp
|
||||
class GizmosRenderer {
|
||||
public:
|
||||
void DrawTranslateGizmo(GameObjectHandle target);
|
||||
void DrawRotateGizmo(GameObjectHandle target);
|
||||
void DrawScaleGizmo(GameObjectHandle target);
|
||||
|
||||
enum class Axis { X, Y, Z, XY, XZ, YZ, XYZ };
|
||||
std::optional<Axis> HandleInput(const Ray& ray);
|
||||
};
|
||||
```
|
||||
|
||||
#### 3.3.2 Editor 模式状态机
|
||||
|
||||
```cpp
|
||||
enum class EditorMode { Edit, Play, Paused };
|
||||
|
||||
class EditorStateMachine {
|
||||
public:
|
||||
void SetMode(EditorMode mode);
|
||||
EditorMode GetMode() const { return m_mode; }
|
||||
|
||||
void Update(float dt);
|
||||
|
||||
private:
|
||||
EditorMode m_mode = EditorMode::Edit;
|
||||
|
||||
void EnterEditMode();
|
||||
void ExitEditMode();
|
||||
void EnterPlayMode();
|
||||
void ExitPlayMode();
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、重构实施计划
|
||||
|
||||
### 阶段一:架构重构(第 1-2 周)
|
||||
|
||||
| 任务 | 依赖 | 状态 |
|
||||
|------|------|------|
|
||||
| Panel 类独立化 | - | ✅ 完成 |
|
||||
| 引入事件总线 | - | ✅ 完成 |
|
||||
| EditorContext 接口定义 | Panel 独立化 | ✅ 完成 |
|
||||
| SelectionManager 改造 | 事件总线 | ✅ 完成 |
|
||||
| EditorLayer 重构 | 上述全部 | ✅ 完成 |
|
||||
|
||||
**已完成的文件**:
|
||||
- `editor/src/panels/Panel.h/cpp` - 独立的 Panel 基类
|
||||
- `editor/src/Core/EventBus.h` - 类型安全事件总线
|
||||
- `editor/src/Core/EditorEvents.h` - 编辑器事件类型定义
|
||||
- `editor/src/Core/ISelectionManager.h` - 选择管理器接口
|
||||
- `editor/src/Core/SelectionManager.h` - 选择管理器实现
|
||||
- `editor/src/Core/IEditorContext.h` - 编辑器上下文接口
|
||||
- `editor/src/Core/EditorContext.h` - 编辑器上下文实现
|
||||
- `editor/src/Core/EditorConsoleSink.h/cpp` - Editor 专用日志 Sink
|
||||
|
||||
**2026-03-25 更新**:
|
||||
- `editor/src/panels/HierarchyPanel.h/cpp` - 已迁移至 IEditorContext
|
||||
- `editor/src/panels/InspectorPanel.h/cpp` - 已迁移至 IEditorContext + EventBus
|
||||
- 移除了所有 Panel 中的 `SelectionManager::Get()` 和 `EditorSceneManager::Get()` 单例调用
|
||||
- SceneManager::RenameEntity 实现已添加
|
||||
- **日志系统统一**:删除了独立的 LogSystem 单例,Editor 和 Engine 共用 Debug::Logger
|
||||
- ConsolePanel 改用 EditorConsoleSink 从 Logger 读取日志
|
||||
- 删除了 `editor/src/Managers/LogSystem.h/cpp` 和 `editor/src/Core/LogEntry.h`
|
||||
- **命名规范修复**:SelectionManagerImpl → SelectionManager,EditorContextImpl → EditorContext
|
||||
- 删除了未使用的 SceneManagerImpl 和 ISceneManager
|
||||
- **ProjectManager 单例移除**:创建了 IProjectManager 接口,ProjectManager 实现该接口,EditorContext 拥有实例
|
||||
- **ISceneManager 接口**:创建了 ISceneManager 接口,SceneManager 实现该接口
|
||||
- **IEditorContext::GetSceneManager() 返回类型**:从 `void*` 改为 `ISceneManager&`
|
||||
- **HierarchyPanel EventBus 订阅**:HierarchyPanel 现在订阅 SelectionChangedEvent
|
||||
|
||||
### 阶段二:核心功能(第 3-4 周)
|
||||
|
||||
| 任务 | 依赖 | 状态 |
|
||||
|------|------|------|
|
||||
| Undo/Redo 系统 | 事件总线 | TODO |
|
||||
| Command 基类 | - | TODO |
|
||||
| 常用 Command 实现 | Undo/Redo 系统 | TODO |
|
||||
| 场景序列化 | Command 基类 | TODO |
|
||||
| SceneManager 重构 | 事件总线、Command | TODO |
|
||||
|
||||
### 阶段三:Inspector 改进(第 5-6 周)
|
||||
|
||||
| 任务 | 依赖 | 状态 |
|
||||
|------|------|------|
|
||||
| ComponentEditor 接口 | 阶段一完成 | TODO |
|
||||
| TransformComponentEditor | ComponentEditor 接口 | TODO |
|
||||
| MeshRendererComponentEditor | AssetHandle 系统 | TODO |
|
||||
| 动态添加组件 UI | ComponentEditor 接口 | TODO |
|
||||
|
||||
### 阶段四:SceneView 实现(第 7-8 周)
|
||||
|
||||
| 任务 | 依赖 | 状态 |
|
||||
|------|------|------|
|
||||
| CameraController | - | TODO |
|
||||
| GizmosRenderer | CameraController | TODO |
|
||||
| Selection Outline | GizmosRenderer | TODO |
|
||||
| Grid Renderer | - | TODO |
|
||||
|
||||
### 阶段五:Play/Edit 模式(第 9-10 周)
|
||||
|
||||
| 任务 | 依赖 | 状态 |
|
||||
|------|------|------|
|
||||
| EditorStateMachine | 阶段一完成 | TODO |
|
||||
| 场景状态保存/恢复 | 序列化 | TODO |
|
||||
| GameView 集成 | StateMachine | TODO |
|
||||
| 工具栏 UI | GizmosRenderer | TODO |
|
||||
|
||||
---
|
||||
|
||||
## 五、注意事项
|
||||
|
||||
### 5.1 向后兼容
|
||||
|
||||
- 保持 `GameObject`、`Component` 等核心类接口不变
|
||||
- 新增接口使用 `virtual` 或默认实现
|
||||
- 序列化格式预留 `version` 字段
|
||||
|
||||
### 5.2 性能考虑
|
||||
|
||||
- Undo/Redo 栈限制最大容量
|
||||
- 事件总线使用 `std::function` 的 `std::any` 存储避免类型擦除开销
|
||||
- Inspector 使用 `std::unordered_map` 加速组件编辑器查找
|
||||
|
||||
### 5.3 测试计划
|
||||
|
||||
- 单元测试:Command、Serializer、EventBus
|
||||
- 集成测试:Undo/Redo 完整流程
|
||||
- UI 测试:手动测试为主
|
||||
|
||||
---
|
||||
|
||||
## 六、代码规范
|
||||
|
||||
### 6.1 命名规范
|
||||
|
||||
- Editor 相关类放在 `XCEngine::Editor` 命名空间
|
||||
- 接口类前缀 `I`(如 `ISelectionManager`)
|
||||
- Editor 内部实现后缀 `Impl`(如 `SelectionManagerImpl`)
|
||||
|
||||
### 6.2 文件组织
|
||||
|
||||
```
|
||||
editor/src/
|
||||
├── Core/
|
||||
│ ├── EditorContext.h/cpp
|
||||
│ ├── EventBus.h/cpp
|
||||
│ └── UndoRedo.h/cpp
|
||||
├── Commands/
|
||||
│ ├── Command.h
|
||||
│ ├── CreateEntityCommand.h/cpp
|
||||
│ ├── DeleteEntityCommand.h/cpp
|
||||
│ └── TransformCommand.h/cpp
|
||||
├── Panels/
|
||||
│ ├── Panel.h # 基础类
|
||||
│ ├── HierarchyPanel.h/cpp
|
||||
│ ├── InspectorPanel.h/cpp
|
||||
│ └── ...
|
||||
└── UI/
|
||||
├── Gizmos.h/cpp
|
||||
└── CameraController.h/cpp
|
||||
```
|
||||
|
||||
### 6.3 禁止事项
|
||||
|
||||
- ❌ 禁止在 Editor 代码中使用 `static` 单例模式
|
||||
- ❌ 禁止直接持有 `GameObject*` 裸指针
|
||||
- ❌ 禁止在 Release 版本保留 `Debug::Logger` 调用
|
||||
- ❌ 禁止硬编码任何路径
|
||||
Reference in New Issue
Block a user