412 lines
14 KiB
C++
412 lines
14 KiB
C++
#include "Application.h"
|
|
#include "Platform/D3D12WindowRendererImGuiInterop.h"
|
|
#include "Core/EditorLoggingSetup.h"
|
|
#include "Core/ProjectRootResolver.h"
|
|
#include "Core/EditorWindowTitle.h"
|
|
#include "Layers/EditorLayer.h"
|
|
#include "Core/EditorContext.h"
|
|
#include "Core/EditorEvents.h"
|
|
#include "Core/EventBus.h"
|
|
#include "Scripting/EditorScriptAssemblyBuilder.h"
|
|
#include "UI/BuiltInIcons.h"
|
|
#include "Platform/Win32Utf8.h"
|
|
#include "Platform/WindowsProcessDiagnostics.h"
|
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
|
#include <XCEngine/Debug/Logger.h>
|
|
#include <XCEngine/Scripting/ScriptEngine.h>
|
|
#ifdef XCENGINE_ENABLE_MONO_SCRIPTING
|
|
#include <XCEngine/Scripting/Mono/MonoScriptRuntime.h>
|
|
#endif
|
|
#include <chrono>
|
|
#include <filesystem>
|
|
#include <windows.h>
|
|
|
|
namespace XCEngine {
|
|
namespace Editor {
|
|
|
|
Application& Application::Get() {
|
|
static Application instance;
|
|
return instance;
|
|
}
|
|
|
|
void Application::InitializeScriptingRuntime(const std::string& projectPath) {
|
|
ShutdownScriptingRuntime();
|
|
|
|
const std::filesystem::path assemblyDirectoryPath =
|
|
std::filesystem::path(Platform::Utf8ToWide(projectPath)) / L"Library" / L"ScriptAssemblies";
|
|
m_scriptRuntimeStatus.assemblyDirectory = Platform::WideToUtf8(assemblyDirectoryPath.wstring());
|
|
|
|
#ifdef XCENGINE_ENABLE_MONO_SCRIPTING
|
|
namespace fs = std::filesystem;
|
|
|
|
auto& logger = Debug::Logger::Get();
|
|
const fs::path assemblyDirectory = assemblyDirectoryPath;
|
|
m_scriptRuntimeStatus.backendEnabled = true;
|
|
|
|
::XCEngine::Scripting::MonoScriptRuntime::Settings settings;
|
|
settings.assemblyDirectory = assemblyDirectory;
|
|
settings.corlibDirectory = assemblyDirectory;
|
|
settings.coreAssemblyPath = assemblyDirectory / L"XCEngine.ScriptCore.dll";
|
|
settings.appAssemblyPath = assemblyDirectory / L"GameScripts.dll";
|
|
|
|
std::error_code ec;
|
|
const bool hasCoreAssembly = fs::exists(settings.coreAssemblyPath, ec);
|
|
ec.clear();
|
|
const bool hasAppAssembly = fs::exists(settings.appAssemblyPath, ec);
|
|
ec.clear();
|
|
const bool hasCorlibAssembly = fs::exists(assemblyDirectory / L"mscorlib.dll", ec);
|
|
m_scriptRuntimeStatus.assembliesFound = hasCoreAssembly && hasAppAssembly && hasCorlibAssembly;
|
|
|
|
if (!hasCoreAssembly || !hasAppAssembly || !hasCorlibAssembly) {
|
|
m_scriptRuntimeStatus.statusMessage =
|
|
"Script assemblies were not found in " + Platform::WideToUtf8(assemblyDirectory.wstring()) +
|
|
". Script class discovery is disabled until the managed assemblies are built.";
|
|
logger.Warning(Debug::LogCategory::Scripting, m_scriptRuntimeStatus.statusMessage.c_str());
|
|
::XCEngine::Scripting::ScriptEngine::Get().SetRuntime(nullptr);
|
|
return;
|
|
}
|
|
|
|
auto runtime = std::make_unique<::XCEngine::Scripting::MonoScriptRuntime>(settings);
|
|
if (!runtime->Initialize()) {
|
|
m_scriptRuntimeStatus.statusMessage =
|
|
"Failed to initialize editor script runtime: " + runtime->GetLastError();
|
|
logger.Warning(Debug::LogCategory::Scripting, m_scriptRuntimeStatus.statusMessage.c_str());
|
|
::XCEngine::Scripting::ScriptEngine::Get().SetRuntime(nullptr);
|
|
return;
|
|
}
|
|
|
|
::XCEngine::Scripting::ScriptEngine::Get().SetRuntime(runtime.get());
|
|
m_scriptRuntimeStatus.runtimeLoaded = true;
|
|
m_scriptRuntime = std::move(runtime);
|
|
logger.Info(Debug::LogCategory::Scripting, "Editor script runtime initialized.");
|
|
#else
|
|
(void)projectPath;
|
|
m_scriptRuntimeStatus.backendEnabled = false;
|
|
m_scriptRuntimeStatus.statusMessage = "This editor build does not include Mono scripting support.";
|
|
::XCEngine::Scripting::ScriptEngine::Get().SetRuntime(nullptr);
|
|
#endif
|
|
}
|
|
|
|
void Application::ShutdownScriptingRuntime() {
|
|
::XCEngine::Scripting::ScriptEngine::Get().OnRuntimeStop();
|
|
::XCEngine::Scripting::ScriptEngine::Get().SetRuntime(nullptr);
|
|
|
|
#ifdef XCENGINE_ENABLE_MONO_SCRIPTING
|
|
m_scriptRuntime.reset();
|
|
#endif
|
|
|
|
m_scriptRuntimeStatus = {};
|
|
}
|
|
|
|
bool Application::ReloadScriptingRuntime() {
|
|
if (!m_editorContext) {
|
|
return false;
|
|
}
|
|
|
|
const std::string& projectPath = m_editorContext->GetProjectPath();
|
|
if (projectPath.empty()) {
|
|
return false;
|
|
}
|
|
|
|
InitializeScriptingRuntime(projectPath);
|
|
return m_scriptRuntimeStatus.runtimeLoaded;
|
|
}
|
|
|
|
bool Application::RebuildScriptingAssemblies() {
|
|
if (!m_editorContext) {
|
|
return false;
|
|
}
|
|
|
|
const std::string& projectPath = m_editorContext->GetProjectPath();
|
|
if (projectPath.empty()) {
|
|
return false;
|
|
}
|
|
|
|
#ifdef XCENGINE_ENABLE_MONO_SCRIPTING
|
|
auto& logger = Debug::Logger::Get();
|
|
logger.Info(Debug::LogCategory::Scripting, "Rebuilding project script assemblies...");
|
|
|
|
// Release the currently loaded project assembly before invoking the compiler.
|
|
// Otherwise GameScripts.dll can remain locked by the active Mono app domain.
|
|
ShutdownScriptingRuntime();
|
|
|
|
const ::XCEngine::Editor::Scripting::EditorScriptAssemblyBuildResult buildResult =
|
|
::XCEngine::Editor::Scripting::EditorScriptAssemblyBuilder::RebuildProjectAssemblies(projectPath);
|
|
if (!buildResult.succeeded) {
|
|
m_scriptRuntimeStatus.statusMessage = buildResult.message;
|
|
logger.Error(Debug::LogCategory::Scripting, buildResult.message.c_str());
|
|
return false;
|
|
}
|
|
|
|
logger.Info(Debug::LogCategory::Scripting, buildResult.message.c_str());
|
|
return ReloadScriptingRuntime();
|
|
#else
|
|
m_scriptRuntimeStatus.statusMessage = "This editor build does not include Mono scripting support.";
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool Application::CanReimportProjectAsset(const std::string& assetPath) const {
|
|
if (assetPath.empty()) {
|
|
return false;
|
|
}
|
|
|
|
return ::XCEngine::Resources::ResourceManager::Get().CanReimportProjectAsset(assetPath.c_str());
|
|
}
|
|
|
|
bool Application::ReimportProjectAsset(const std::string& assetPath) {
|
|
if (assetPath.empty()) {
|
|
return false;
|
|
}
|
|
|
|
auto& resourceManager = ::XCEngine::Resources::ResourceManager::Get();
|
|
resourceManager.Initialize();
|
|
return resourceManager.ReimportProjectAsset(assetPath.c_str());
|
|
}
|
|
|
|
bool Application::ReimportAllProjectAssets() {
|
|
auto& resourceManager = ::XCEngine::Resources::ResourceManager::Get();
|
|
resourceManager.Initialize();
|
|
return resourceManager.RebuildProjectAssetCache();
|
|
}
|
|
|
|
bool Application::ClearProjectLibrary() {
|
|
auto& resourceManager = ::XCEngine::Resources::ResourceManager::Get();
|
|
resourceManager.Initialize();
|
|
return resourceManager.ClearProjectLibraryCache();
|
|
}
|
|
|
|
std::string Application::GetProjectLibraryRoot() const {
|
|
return ::XCEngine::Resources::ResourceManager::Get().GetProjectLibraryRoot().CStr();
|
|
}
|
|
|
|
bool Application::InitializeWindowRenderer(HWND hwnd) {
|
|
RECT clientRect = {};
|
|
if (!GetClientRect(hwnd, &clientRect)) {
|
|
MessageBoxW(hwnd, L"Failed to query editor client area", L"Error", MB_OK | MB_ICONERROR);
|
|
return false;
|
|
}
|
|
|
|
const int clientWidth = clientRect.right - clientRect.left;
|
|
const int clientHeight = clientRect.bottom - clientRect.top;
|
|
if (clientWidth <= 0 || clientHeight <= 0) {
|
|
MessageBoxW(hwnd, L"Editor client area is invalid", L"Error", MB_OK | MB_ICONERROR);
|
|
return false;
|
|
}
|
|
|
|
if (m_windowRenderer.Initialize(hwnd, clientWidth, clientHeight)) {
|
|
return true;
|
|
}
|
|
|
|
MessageBoxW(hwnd, L"Failed to initialize editor window renderer", L"Error", MB_OK | MB_ICONERROR);
|
|
return false;
|
|
}
|
|
|
|
void Application::InitializeEditorContext(const std::string& projectPath) {
|
|
m_editorContext = std::make_shared<EditorContext>();
|
|
m_editorContext->SetProjectPath(projectPath);
|
|
m_exitRequestedHandlerId = m_editorContext->GetEventBus().Subscribe<EditorExitRequestedEvent>(
|
|
[this](const EditorExitRequestedEvent&) {
|
|
if (m_hwnd) {
|
|
PostMessageW(m_hwnd, WM_CLOSE, 0, 0);
|
|
}
|
|
});
|
|
}
|
|
|
|
void Application::InitializeImGui(HWND hwnd) {
|
|
m_imguiSession.Initialize(
|
|
m_editorContext->GetProjectPath(),
|
|
UI::ImGuiBackendBridge::GetDpiScaleForHwnd(hwnd));
|
|
m_imguiBackend.Initialize(
|
|
hwnd,
|
|
m_windowRenderer.GetDevice(),
|
|
m_windowRenderer.GetCommandQueue(),
|
|
m_windowRenderer.GetSrvHeap(),
|
|
m_windowRenderer.GetSrvDescriptorSize(),
|
|
m_windowRenderer.GetSrvDescriptorCount());
|
|
UI::InitializeBuiltInIcons(
|
|
m_imguiBackend,
|
|
m_windowRenderer.GetDevice(),
|
|
m_windowRenderer.GetCommandQueue());
|
|
|
|
m_viewportHostService.Initialize(m_imguiBackend, m_windowRenderer.GetRHIDevice());
|
|
static_cast<EditorContext*>(m_editorContext.get())->SetViewportHostService(&m_viewportHostService);
|
|
}
|
|
|
|
void Application::AttachEditorLayer() {
|
|
m_editorLayer = new EditorLayer();
|
|
m_editorLayer->SetContext(m_editorContext);
|
|
m_layerStack.pushLayer(std::unique_ptr<Core::Layer>(m_editorLayer));
|
|
m_layerStack.onAttach();
|
|
}
|
|
|
|
void Application::DetachEditorLayer() {
|
|
m_layerStack.onDetach();
|
|
m_editorLayer = nullptr;
|
|
}
|
|
|
|
void Application::ShutdownEditorContext() {
|
|
if (m_editorContext && m_exitRequestedHandlerId) {
|
|
m_editorContext->GetEventBus().Unsubscribe<EditorExitRequestedEvent>(m_exitRequestedHandlerId);
|
|
m_exitRequestedHandlerId = 0;
|
|
}
|
|
|
|
m_editorContext.reset();
|
|
}
|
|
|
|
void Application::RenderEditorFrame() {
|
|
static constexpr float kClearColor[4] = { 0.22f, 0.22f, 0.22f, 1.0f };
|
|
if (!m_windowRenderer.BeginFrame()) {
|
|
return;
|
|
}
|
|
|
|
m_imguiBackend.BeginFrame();
|
|
m_viewportHostService.BeginFrame();
|
|
m_layerStack.onUIRender();
|
|
UpdateWindowTitle();
|
|
ImGui::Render();
|
|
Platform::RenderImGuiFrame(
|
|
m_windowRenderer,
|
|
m_imguiBackend,
|
|
kClearColor,
|
|
[this](const Rendering::RenderContext& renderContext, const Rendering::RenderSurface&) {
|
|
if (m_editorContext) {
|
|
m_viewportHostService.RenderRequestedViewports(*m_editorContext, renderContext);
|
|
}
|
|
});
|
|
}
|
|
|
|
bool Application::Initialize(HWND hwnd) {
|
|
Platform::InstallCrashExceptionFilter();
|
|
Platform::RedirectStderrToExecutableLog();
|
|
|
|
const std::string exeDir = Platform::GetExecutableDirectoryUtf8();
|
|
ConfigureEditorLogging(exeDir);
|
|
|
|
const std::string projectRoot = ResolveEditorProjectRootUtf8();
|
|
const bool workingDirectoryChanged = SetEditorWorkingDirectory(projectRoot);
|
|
|
|
auto& logger = Debug::Logger::Get();
|
|
const std::string projectRootMessage = "Editor project root: " + projectRoot;
|
|
logger.Info(Debug::LogCategory::General, projectRootMessage.c_str());
|
|
|
|
if (!workingDirectoryChanged) {
|
|
const std::string warningMessage = "Failed to switch editor working directory to project root: " + projectRoot;
|
|
logger.Warning(Debug::LogCategory::General, warningMessage.c_str());
|
|
}
|
|
|
|
m_hwnd = hwnd;
|
|
logger.Info(Debug::LogCategory::General, "Initializing editor window renderer...");
|
|
|
|
if (!InitializeWindowRenderer(hwnd)) {
|
|
return false;
|
|
}
|
|
|
|
auto& resourceManager = ::XCEngine::Resources::ResourceManager::Get();
|
|
resourceManager.Initialize();
|
|
resourceManager.SetResourceRoot(projectRoot.c_str());
|
|
m_resourceManagerInitialized = true;
|
|
|
|
logger.Info(Debug::LogCategory::General, "Initializing editor context...");
|
|
InitializeEditorContext(projectRoot);
|
|
InitializeScriptingRuntime(projectRoot);
|
|
logger.Info(Debug::LogCategory::General, "Initializing ImGui backend...");
|
|
InitializeImGui(hwnd);
|
|
logger.Info(Debug::LogCategory::General, "Attaching editor layer...");
|
|
AttachEditorLayer();
|
|
m_hasLastFrameTime = false;
|
|
logger.Info(Debug::LogCategory::General, "Editor initialization completed.");
|
|
m_renderReady = true;
|
|
return true;
|
|
}
|
|
|
|
void Application::Shutdown() {
|
|
m_renderReady = false;
|
|
m_hasLastFrameTime = false;
|
|
DetachEditorLayer();
|
|
if (m_editorContext) {
|
|
static_cast<EditorContext*>(m_editorContext.get())->SetViewportHostService(nullptr);
|
|
}
|
|
m_viewportHostService.Shutdown();
|
|
UI::ShutdownBuiltInIcons();
|
|
m_imguiBackend.Shutdown();
|
|
m_imguiSession.Shutdown();
|
|
ShutdownScriptingRuntime();
|
|
ShutdownEditorContext();
|
|
if (m_resourceManagerInitialized) {
|
|
::XCEngine::Resources::ResourceManager::Get().Shutdown();
|
|
m_resourceManagerInitialized = false;
|
|
}
|
|
m_windowRenderer.Shutdown();
|
|
}
|
|
|
|
void Application::Render() {
|
|
if (!m_renderReady) {
|
|
return;
|
|
}
|
|
|
|
const auto now = std::chrono::steady_clock::now();
|
|
float deltaTime = 0.0f;
|
|
if (m_hasLastFrameTime) {
|
|
deltaTime = std::chrono::duration<float>(now - m_lastFrameTime).count();
|
|
}
|
|
m_lastFrameTime = now;
|
|
m_hasLastFrameTime = true;
|
|
|
|
m_layerStack.onUpdate(deltaTime);
|
|
RenderEditorFrame();
|
|
}
|
|
|
|
void Application::UpdateWindowTitle() {
|
|
if (!m_hwnd || !m_editorContext) {
|
|
return;
|
|
}
|
|
|
|
const std::wstring title = Platform::Utf8ToWide(BuildEditorWindowTitle(*m_editorContext));
|
|
if (title != m_lastWindowTitle) {
|
|
SetWindowTextW(m_hwnd, title.c_str());
|
|
m_lastWindowTitle = title;
|
|
}
|
|
}
|
|
|
|
void Application::OnResize(int width, int height) {
|
|
m_windowRenderer.Resize(width, height);
|
|
}
|
|
|
|
bool Application::SwitchProject(const std::string& projectPath) {
|
|
if (!m_editorContext || projectPath.empty()) {
|
|
return false;
|
|
}
|
|
|
|
const bool hasSavedDockLayout = m_imguiSession.SetProjectPath(projectPath);
|
|
m_editorContext->SetProjectPath(projectPath);
|
|
|
|
auto& logger = Debug::Logger::Get();
|
|
if (!SetEditorWorkingDirectory(projectPath)) {
|
|
const std::string warningMessage = "Failed to switch editor working directory to project root: " + projectPath;
|
|
logger.Warning(Debug::LogCategory::General, warningMessage.c_str());
|
|
}
|
|
|
|
const std::string infoMessage = "Switched editor project root: " + projectPath;
|
|
logger.Info(Debug::LogCategory::General, infoMessage.c_str());
|
|
|
|
::XCEngine::Resources::ResourceManager::Get().SetResourceRoot(projectPath.c_str());
|
|
InitializeScriptingRuntime(projectPath);
|
|
|
|
m_lastWindowTitle.clear();
|
|
UpdateWindowTitle();
|
|
|
|
if (!hasSavedDockLayout) {
|
|
m_editorContext->GetEventBus().Publish(DockLayoutResetRequestedEvent{});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Application::SaveProjectState() {
|
|
m_imguiSession.SaveSettings();
|
|
}
|
|
|
|
}
|
|
}
|