diff --git a/Runtime/CMain.cpp b/Runtime/CMain.cpp index 1b8fecd91..48700214c 100644 --- a/Runtime/CMain.cpp +++ b/Runtime/CMain.cpp @@ -26,46 +26,47 @@ static void AthenaExc(athena::error::Level level, const char* file, const char*, } class Limiter { - using delta_clock = std::chrono::steady_clock; - using nanotime_t = std::chrono::nanoseconds::rep; + using delta_clock = std::chrono::high_resolution_clock; + using duration_t = std::chrono::nanoseconds; public: - void Sleep(nanotime_t targetFrameTime) { - if (targetFrameTime == 0) { + void Reset() { m_oldTime = delta_clock::now(); } + + void Sleep(duration_t targetFrameTime) { + if (targetFrameTime.count() == 0) { return; } auto start = delta_clock::now(); - nanotime_t adjustedSleepTime = ShouldSleep(targetFrameTime); - if (adjustedSleepTime > 0) { - std::this_thread::sleep_for(std::chrono::nanoseconds(adjustedSleepTime)); - nanotime_t overslept = TimeSince(start) - adjustedSleepTime; - if (overslept < targetFrameTime) { + duration_t adjustedSleepTime = SleepTime(targetFrameTime); + if (adjustedSleepTime.count() > 0) { + std::this_thread::sleep_for(adjustedSleepTime); + duration_t overslept = TimeSince(start) - adjustedSleepTime; + if (overslept < duration_t{targetFrameTime}) { m_overheadTimes[m_overheadTimeIdx] = overslept; m_overheadTimeIdx = (m_overheadTimeIdx + 1) % m_overheadTimes.size(); } } - m_oldTime = delta_clock::now(); + Reset(); } - nanotime_t ShouldSleep(nanotime_t targetFrameTime) { - nanotime_t sleepTime = targetFrameTime - TimeSince(m_oldTime); - m_overhead = std::accumulate(m_overheadTimes.begin(), m_overheadTimes.end(), nanotime_t{}) / - static_cast(m_overheadTimes.size()); + duration_t SleepTime(duration_t targetFrameTime) { + const auto sleepTime = duration_t{targetFrameTime} - TimeSince(m_oldTime); + m_overhead = std::accumulate(m_overheadTimes.begin(), m_overheadTimes.end(), duration_t{}) / m_overheadTimes.size(); if (sleepTime > m_overhead) { return sleepTime - m_overhead; } - return 0; + return duration_t{0}; } private: delta_clock::time_point m_oldTime; - std::array m_overheadTimes{}; + std::array m_overheadTimes{}; size_t m_overheadTimeIdx = 0; - nanotime_t m_overhead = 0; + duration_t m_overhead = duration_t{0}; - nanotime_t TimeSince(delta_clock::time_point start) { - return std::chrono::duration_cast(delta_clock::now() - start).count(); + duration_t TimeSince(delta_clock::time_point start) { + return std::chrono::duration_cast(delta_clock::now() - start); } }; @@ -217,6 +218,8 @@ private: hecl::Runtime::FileStoreManager& m_fileMgr; hecl::CVarManager& m_cvarManager; hecl::CVarCommons& m_cvarCommons; + ImGuiConsole m_imGuiConsole; + std::string m_errorString; boo::ObjToken m_renderTex; hecl::SystemString m_deferredProject; @@ -236,7 +239,7 @@ private: public: Application(hecl::Runtime::FileStoreManager& fileMgr, hecl::CVarManager& cvarMgr, hecl::CVarCommons& cvarCmns) - : m_fileMgr(fileMgr), m_cvarManager(cvarMgr), m_cvarCommons(cvarCmns) {} + : m_fileMgr(fileMgr), m_cvarManager(cvarMgr), m_cvarCommons(cvarCmns), m_imGuiConsole(cvarMgr, cvarCmns) {} int appMain(boo::IApplication* app) override { initialize(app); @@ -297,6 +300,7 @@ public: } if (m_imGuiInitialized) { + m_imGuiConsole.Shutdown(); ImGuiEngine::Shutdown(); } if (g_mainMP1) { @@ -334,20 +338,20 @@ public: if (!m_deferredProject.empty()) { hecl::SystemString subPath; hecl::ProjectRootPath projPath = hecl::SearchForProject(m_deferredProject, subPath); - if (!projPath) { - Log.report(logvisor::Error, FMT_STRING(_SYS_STR("project doesn't exist at '{}'")), m_deferredProject); - m_running.store(false); - return; + if (projPath) { + m_proj = std::make_unique(projPath); + m_deferredProject.clear(); + hecl::ProjectPath projectPath{m_proj->getProjectWorkingPath(), _SYS_STR("out/files/MP1")}; + CDvdFile::Initialize(projectPath); + } else { + Log.report(logvisor::Error, FMT_STRING(_SYS_STR("Project doesn't exist at '{}'")), m_deferredProject); + hecl::SystemUTF8Conv conv{m_deferredProject}; + m_errorString = fmt::format(FMT_STRING("Project not found at '{}'"), conv.str()); + m_deferredProject.clear(); } - m_proj = std::make_unique(projPath); - m_deferredProject.clear(); - hecl::ProjectPath projectPath{m_proj->getProjectWorkingPath(), _SYS_STR("out/files/MP1")}; - CDvdFile::Initialize(projectPath); } - if (!m_proj) { - Log.report(logvisor::Error, FMT_STRING(_SYS_STR("Project directory not specified"))); - m_running.store(false); - return; + if (!m_proj && m_errorString.empty()) { + m_errorString = "Project directory not specified"s; } m_cvarManager.proc(); @@ -377,12 +381,14 @@ public: boo::IGraphicsDataFactory* gfxF = m_window->getMainContextDataFactory(); float scale = std::floor(m_window->getVirtualPixelFactor() * 4.f) / 4.f; - if (!g_mainMP1) { + if (!g_mainMP1 && m_proj) { g_mainMP1.emplace(nullptr, nullptr, gfxF, gfxQ, m_renderTex.get()); g_mainMP1->Init(m_fileMgr, &m_cvarManager, m_window.get(), m_voiceEngine.get(), *m_amuseAllocWrapper); if (!m_noShaderWarmup) { g_mainMP1->WarmupShaders(); } + } + if (!m_imGuiInitialized) { ImGuiEngine::Initialize(gfxF, m_window.get(), scale); m_imGuiInitialized = true; } @@ -403,20 +409,29 @@ public: ImGuiEngine::Begin(realDt, scale); - if (g_mainMP1->Proc(dt)) { - m_running.store(false); - return; + if (g_mainMP1) { + m_imGuiConsole.PreUpdate(); + if (g_mainMP1->Proc(dt)) { + m_running.store(false); + return; + } + m_imGuiConsole.PostUpdate(); + } else { + m_imGuiConsole.ShowAboutWindow(false, m_errorString); } { OPTICK_EVENT("Draw"); gfxQ->setRenderTarget(m_renderTex); + gfxQ->clearTarget(); gfxQ->setViewport(rect); gfxQ->setScissor(rect); if (g_Renderer != nullptr) { g_Renderer->BeginScene(); } - g_mainMP1->Draw(); + if (g_mainMP1) { + g_mainMP1->Draw(); + } if (g_Renderer != nullptr) { g_Renderer->EndScene(); } @@ -436,11 +451,14 @@ public: gfxQ->resolveDisplay(m_renderTex); if (g_ResFactory != nullptr) { - int64_t targetFrameTime = getTargetFrameTime(); - do { - g_ResFactory->AsyncIdle(); - } while (m_limiter.ShouldSleep(targetFrameTime) != 0); - m_limiter.Sleep(targetFrameTime); + const auto targetFrameTime = getTargetFrameTime(); + const auto idleTime = m_limiter.SleepTime(targetFrameTime); + if (g_ResFactory->AsyncIdle(idleTime)) { + m_limiter.Reset(); + } else { + // No more to load; sleep + m_limiter.Sleep(targetFrameTime); + } } if (m_voiceEngine) { @@ -461,8 +479,11 @@ public: [[nodiscard]] bool getDeepColor() const { return m_cvarCommons.getDeepColor(); } - [[nodiscard]] int64_t getTargetFrameTime() const { - return m_cvarCommons.getVariableFrameTime() ? 0 : 1000000000L / 60; + [[nodiscard]] std::chrono::nanoseconds getTargetFrameTime() const { + if (m_cvarCommons.getVariableFrameTime()) { + return std::chrono::nanoseconds{0}; + } + return std::chrono::duration_cast(std::chrono::seconds{1}) / 60; } }; diff --git a/Runtime/CResFactory.cpp b/Runtime/CResFactory.cpp index 22ec21111..2d59cac72 100644 --- a/Runtime/CResFactory.cpp +++ b/Runtime/CResFactory.cpp @@ -75,21 +75,24 @@ void CResFactory::BuildAsync(const SObjectTag& tag, const CVParamTransfer& xfer, } } -void CResFactory::AsyncIdle() { +bool CResFactory::AsyncIdle(std::chrono::nanoseconds target) { OPTICK_EVENT(); - if (m_loadList.empty()) - return; - auto startTime = std::chrono::steady_clock::now(); - while (std::chrono::duration_cast(std::chrono::steady_clock::now() - startTime).count() < - 5) { + if (m_loadList.empty()) { + return false; + } + auto startTime = std::chrono::high_resolution_clock::now(); + do { auto& task = m_loadList.front(); if (PumpResource(task)) { m_loadMap.erase(task.x0_tag); m_loadList.pop_front(); - if (m_loadList.empty()) - return; + if (m_loadList.empty()) { + return false; + } } - } + } while (std::chrono::duration_cast(std::chrono::steady_clock::now() - startTime) < + target); + return true; } void CResFactory::CancelBuild(const SObjectTag& tag) { diff --git a/Runtime/CResFactory.hpp b/Runtime/CResFactory.hpp index b43731240..c27d9c225 100644 --- a/Runtime/CResFactory.hpp +++ b/Runtime/CResFactory.hpp @@ -48,7 +48,7 @@ public: std::unique_ptr Build(const SObjectTag&, const CVParamTransfer&, CObjectReference* selfRef) override; void BuildAsync(const SObjectTag&, const CVParamTransfer&, std::unique_ptr*, CObjectReference* selfRef) override; - void AsyncIdle() override; + bool AsyncIdle(std::chrono::nanoseconds target) override; void CancelBuild(const SObjectTag&) override; bool CanBuild(const SObjectTag& tag) override { return x4_loader.ResourceExists(tag); } diff --git a/Runtime/IFactory.hpp b/Runtime/IFactory.hpp index 72d1f86b3..3039f4816 100644 --- a/Runtime/IFactory.hpp +++ b/Runtime/IFactory.hpp @@ -35,7 +35,7 @@ public: EnumerateNamedResources(const std::function& lambda) const = 0; virtual CResLoader* GetResLoader() { return nullptr; } virtual CFactoryMgr* GetFactoryMgr() { return nullptr; } - virtual void AsyncIdle() {} + virtual bool AsyncIdle(std::chrono::nanoseconds target) { return false; } /* Non-factory versions, replaces CResLoader */ virtual u32 ResourceSize(const metaforce::SObjectTag& tag) = 0; diff --git a/Runtime/ImGuiConsole.cpp b/Runtime/ImGuiConsole.cpp index 3ece98197..fcf49cdae 100644 --- a/Runtime/ImGuiConsole.cpp +++ b/Runtime/ImGuiConsole.cpp @@ -350,7 +350,7 @@ bool ImGuiConsole::ShowEntityInfoWindow(TUniqueId uid) { return open; } -void ImGuiConsole::ShowAboutWindow() { +void ImGuiConsole::ShowAboutWindow(bool canClose, std::string_view errorString) { // Center window ImVec2 center = ImGui::GetMainViewport()->GetCenter(); ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); @@ -359,9 +359,15 @@ void ImGuiConsole::ShowAboutWindow() { ImGui::PushStyleColor(ImGuiCol_TitleBg, windowBg); ImGui::PushStyleColor(ImGuiCol_TitleBgActive, windowBg); - if (ImGui::Begin("About", &m_showAboutWindow, - ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoNav | - ImGuiWindowFlags_NoSavedSettings)) { + bool* open = nullptr; + ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoSavedSettings; + if (canClose) { + open = &m_showAboutWindow; + } else { + flags |= ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove; + } + if (ImGui::Begin("About", open, flags)) { float iconSize = 128.f * ImGui::GetIO().DisplayFramebufferScale.x; ImGui::SameLine(ImGui::GetWindowSize().x / 2 - iconSize + (iconSize / 2)); ImGui::Image(ImGuiUserTextureID_MetaforceIcon, ImVec2{iconSize, iconSize}); @@ -371,6 +377,12 @@ void ImGuiConsole::ShowAboutWindow() { ImGuiTextCenter(METAFORCE_WC_DESCRIBE); const ImVec2& padding = ImGui::GetStyle().WindowPadding; ImGui::Dummy(padding); + if (!errorString.empty()) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{0.77f, 0.12f, 0.23f, 1.f}); + ImGuiTextCenter(errorString); + ImGui::PopStyleColor(); + ImGui::Dummy(padding); + } ImGuiTextCenter("2015-2021"); ImGuiTextCenter("Phillip Stephens (Antidote)"); ImGuiTextCenter("Jack Andersen (jackoalan)"); @@ -414,12 +426,14 @@ void ImGuiConsole::ShowAboutWindow() { if (ImGui::TableNextColumn()) { ImGuiStringViewText(METAFORCE_BUILD_TYPE); } - ImGui::TableNextRow(); - if (ImGui::TableNextColumn()) { - ImGuiStringViewText("Game"); - } - if (ImGui::TableNextColumn()) { - ImGuiStringViewText(g_Main->GetVersionString()); + if (g_Main != nullptr) { + ImGui::TableNextRow(); + if (ImGui::TableNextColumn()) { + ImGuiStringViewText("Game"); + } + if (ImGui::TableNextColumn()) { + ImGuiStringViewText(g_Main->GetVersionString()); + } } ImGui::EndTable(); } @@ -573,8 +587,6 @@ void ImGuiConsole::ShowDebugOverlay() { if (hasPrevious) { ImGui::Separator(); } -// ImGui::NewLine(); - ImDrawList* dl = ImGui::GetWindowDrawList(); zeus::CVector2f p = ImGui::GetCursorScreenPos(); @@ -633,11 +645,11 @@ void ImGuiConsole::ShowDebugOverlay() { // dpad { float halfWidth = dpadWidth / 2; - dl->AddRectFilled(dpadCenter + zeus::CVector2f(-halfWidth, -dpadRadius), dpadCenter + zeus::CVector2f(halfWidth, dpadRadius), - stickGray); + dl->AddRectFilled(dpadCenter + zeus::CVector2f(-halfWidth, -dpadRadius), + dpadCenter + zeus::CVector2f(halfWidth, dpadRadius), stickGray); - dl->AddRectFilled(dpadCenter + zeus::CVector2f(-dpadRadius, -halfWidth), dpadCenter + zeus::CVector2f(dpadRadius, halfWidth), - stickGray); + dl->AddRectFilled(dpadCenter + zeus::CVector2f(-dpadRadius, -halfWidth), + dpadCenter + zeus::CVector2f(dpadRadius, halfWidth), stickGray); if (input.DDPUp()) { dl->AddRectFilled(dpadCenter + zeus::CVector2f(-halfWidth, -dpadRadius), @@ -822,7 +834,7 @@ void ImGuiConsole::PreUpdate() { ShowLayersWindow(); } if (m_showAboutWindow) { - ShowAboutWindow(); + ShowAboutWindow(true); } if (m_showDemoWindow) { ImGui::ShowDemoWindow(&m_showDemoWindow); @@ -853,7 +865,7 @@ void ImGuiConsole::PostUpdate() { } } -ImGuiConsole::~ImGuiConsole() { +void ImGuiConsole::Shutdown() { dummyWorlds.clear(); stringTables.clear(); } @@ -1029,33 +1041,19 @@ void ImGuiConsole::ShowLayersWindow() { auto worldLayerState = g_GameState->StateForWorld(world.second).GetLayerState(); auto areas = ListAreas(world.second); - // TODO: m_startNameIdx have incorrect values in the data due to a Metaforce bug - // so when filtering we need to keep track of these here, this can be simplified when fixed - std::vector layerStartIdx; - layerStartIdx.reserve(areas.size()); - u32 startNameIdx = 0; auto iter = areas.begin(); while (iter != areas.end()) { - u32 layerCount = worldLayerState->GetAreaLayerCount(iter->second); - if (layerCount == 0) { - continue; - } if (!search.empty() && !ContainsCaseInsensitive(iter->first, search)) { iter = areas.erase(iter); } else { - layerStartIdx.push_back(startNameIdx); iter++; } - if (iter != areas.end()) { - startNameIdx += layerCount; - } } if (areas.empty()) { continue; } if (ImGui::TreeNodeEx(world.first.c_str(), search.empty() ? 0 : ImGuiTreeNodeFlags_DefaultOpen)) { - auto startNameIdxIter = layerStartIdx.begin(); for (const auto& area : areas) { u32 layerCount = worldLayerState->GetAreaLayerCount(area.second); if (layerCount == 0) { @@ -1065,18 +1063,19 @@ void ImGuiConsole::ShowLayersWindow() { if (ImGui::Button("Warp here")) { Warp(world.second, area.second); } - // TODO see above comment - // u32 startNameIdx = layers->m_areas[area.second].m_startNameIdx; - - for (u32 layer = 0; layer < layerCount; ++layer) { - bool active = worldLayerState->IsLayerActive(area.second, layer); - if (ImGui::Checkbox(layers->m_names[*startNameIdxIter + layer].c_str(), &active)) { - worldLayerState->SetLayerActive(area.second, layer, active); + u32 startNameIdx = layers->m_areas[area.second].m_startNameIdx; + if (startNameIdx + layerCount > layers->m_names.size()) { + ImGui::Text("Broken layer data, please re-package"); + } else { + for (u32 layer = 0; layer < layerCount; ++layer) { + bool active = worldLayerState->IsLayerActive(area.second, layer); + if (ImGui::Checkbox(layers->m_names[startNameIdx + layer].c_str(), &active)) { + worldLayerState->SetLayerActive(area.second, layer, active); + } } } ImGui::TreePop(); } - startNameIdxIter++; } ImGui::TreePop(); } diff --git a/Runtime/ImGuiConsole.hpp b/Runtime/ImGuiConsole.hpp index e8852b6b9..0aa829f79 100644 --- a/Runtime/ImGuiConsole.hpp +++ b/Runtime/ImGuiConsole.hpp @@ -36,9 +36,10 @@ public: ImGuiConsole(hecl::CVarManager& cvarMgr, hecl::CVarCommons& cvarCommons) : m_cvarMgr(cvarMgr), m_cvarCommons(cvarCommons) {} - ~ImGuiConsole(); void PreUpdate(); void PostUpdate(); + void Shutdown(); + void ShowAboutWindow(bool canClose, std::string_view errorString = ""sv); static void BeginEntityRow(const ImGuiEntityEntry& entry); static void EndEntityRow(const ImGuiEntityEntry& entry); @@ -84,7 +85,6 @@ private: void ShowInspectWindow(bool* isOpen); void LerpDebugColor(CActor* act); void UpdateEntityEntries(); - void ShowAboutWindow(); void ShowDebugOverlay(); void ShowItemsWindow(); void ShowLayersWindow(); diff --git a/Runtime/MP1/MP1.cpp b/Runtime/MP1/MP1.cpp index 5d5634d8d..bc9e2f83d 100644 --- a/Runtime/MP1/MP1.cpp +++ b/Runtime/MP1/MP1.cpp @@ -238,8 +238,8 @@ void CGameArchitectureSupport::specialKeyUp(boo::ESpecialKey key, boo::EModifier CMain::CMain(IFactory* resFactory, CSimplePool* resStore, boo::IGraphicsDataFactory* gfxFactory, boo::IGraphicsCommandQueue* cmdQ, const boo::ObjToken& spareTex) : m_booSetter(gfxFactory, cmdQ, spareTex) +, xe4_gameplayResult(EGameplayResult::Playing) , x128_globalObjects(std::make_unique(resFactory, resStore)) { - xe4_gameplayResult = EGameplayResult::Playing; g_Main = this; } @@ -769,7 +769,6 @@ void CMain::Init(const hecl::Runtime::FileStoreManager& storeMgr, hecl::CVarMana "Warp"sv, "Warps to a given area and world"sv, "[worldname] areaId"sv, [this](hecl::Console* console, const std::vector& args) { Warp(console, args); }, hecl::SConsoleCommand::ECommandFlags::Normal); - m_imGuiConsole = std::make_unique(*m_cvarMgr, *m_cvarCommons); bool loadedVersion = false; if (CDvdFile::FileExists("version.yaml")) { @@ -908,8 +907,6 @@ bool CMain::Proc(float dt) { m_loadedPersistentResources = true; } - m_imGuiConsole->PreUpdate(); - if (!m_paused) { CGBASupport::GlobalPoll(); x164_archSupport->UpdateTicks(dt); @@ -918,8 +915,6 @@ bool CMain::Proc(float dt) { CStreamAudioManager::Update(dt); } - m_imGuiConsole->PostUpdate(); - if (x164_archSupport->GetIOWinManager().IsEmpty() || CheckReset()) { CStreamAudioManager::StopAll(); /* @@ -965,7 +960,6 @@ void CMain::Draw() { return; } - CGraphics::g_BooMainCommandQueue->clearTarget(true, true); x164_archSupport->Draw(); m_console->draw(CGraphics::g_BooMainCommandQueue); } diff --git a/Runtime/MP1/MP1.hpp b/Runtime/MP1/MP1.hpp index 0bb5215c9..b813d0aea 100644 --- a/Runtime/MP1/MP1.hpp +++ b/Runtime/MP1/MP1.hpp @@ -248,7 +248,6 @@ private: hecl::CVarManager* m_cvarMgr = nullptr; std::unique_ptr m_cvarCommons; std::unique_ptr m_console; - std::unique_ptr m_imGuiConsole; // Warmup state std::vector m_warmupTags; std::vector::iterator m_warmupIt;