Various updates:

- Simplify Layers logic with hecl bugfix
- Show About window with error message on launch with no game
- Use high_resolution_clock for FPS & load logic (increased resolution on Windows)
This commit is contained in:
Luke Street 2021-05-30 15:03:35 -04:00
parent 052c1888cb
commit 78bcba85e2
8 changed files with 121 additions and 105 deletions

View File

@ -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<nanotime_t>(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<nanotime_t, 4> m_overheadTimes{};
std::array<duration_t, 4> 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<std::chrono::nanoseconds>(delta_clock::now() - start).count();
duration_t TimeSince(delta_clock::time_point start) {
return std::chrono::duration_cast<duration_t>(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<boo::ITextureR> 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<hecl::Database::Project>(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<hecl::Database::Project>(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::nanoseconds>(std::chrono::seconds{1}) / 60;
}
};

View File

@ -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::milliseconds>(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::nanoseconds>(std::chrono::steady_clock::now() - startTime) <
target);
return true;
}
void CResFactory::CancelBuild(const SObjectTag& tag) {

View File

@ -48,7 +48,7 @@ public:
std::unique_ptr<IObj> Build(const SObjectTag&, const CVParamTransfer&, CObjectReference* selfRef) override;
void BuildAsync(const SObjectTag&, const CVParamTransfer&, std::unique_ptr<IObj>*,
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); }

View File

@ -35,7 +35,7 @@ public:
EnumerateNamedResources(const std::function<bool(std::string_view, const SObjectTag&)>& 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;

View File

@ -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<u32> 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();
}

View File

@ -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();

View File

@ -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<boo::ITextureR>& spareTex)
: m_booSetter(gfxFactory, cmdQ, spareTex)
, xe4_gameplayResult(EGameplayResult::Playing)
, x128_globalObjects(std::make_unique<CGameGlobalObjects>(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<std::string>& args) { Warp(console, args); },
hecl::SConsoleCommand::ECommandFlags::Normal);
m_imGuiConsole = std::make_unique<ImGuiConsole>(*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);
}

View File

@ -248,7 +248,6 @@ private:
hecl::CVarManager* m_cvarMgr = nullptr;
std::unique_ptr<hecl::CVarCommons> m_cvarCommons;
std::unique_ptr<hecl::Console> m_console;
std::unique_ptr<ImGuiConsole> m_imGuiConsole;
// Warmup state
std::vector<SObjectTag> m_warmupTags;
std::vector<SObjectTag>::iterator m_warmupIt;