mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-15 04:02:50 -04:00
Time revamp (#2304)
* Don't Use OSCalendarTime for Speedrun Timing - Shouldn't rely on OSTicksToCalendarTime since it will be changed to handle time zone conversion, and that doesn't make sense on elapsed time * Use OSGetSystemTime Extension - Fixes desyncing issues with save file time and a couple other odd instances, particularly on mobile platforms that suspend apps * Time revamp * Update aurora * Split IGT/RTA calculations * Shift-Turbo to slow down --------- Co-authored-by: SuperDude88 <82904174+SuperDude88@users.noreply.github.com>
This commit is contained in:
Vendored
+1
-1
Submodule extern/aurora updated: 8005d336ab...b2d7b78767
@@ -38,7 +38,7 @@ void JFWDisplay::ctor_subroutine(bool enableAlpha) {
|
||||
mTickRate = 0;
|
||||
mCombinationRatio = 0.0f;
|
||||
field_0x30 = 0;
|
||||
field_0x2c = OSGetTick();
|
||||
field_0x2c = DUSK_IF_ELSE(static_cast<OSTick>(OSGetNativeTime()), OSGetTick());
|
||||
field_0x34 = 0;
|
||||
field_0x48 = 0;
|
||||
field_0x4a = 0;
|
||||
@@ -258,7 +258,7 @@ void JFWDisplay::beginRender() {
|
||||
waitForTick(mTickRate, mFrameRate);
|
||||
JUTVideo::getManager()->waitRetraceIfNeed();
|
||||
|
||||
OSTick tick = OSGetTick();
|
||||
OSTick tick = DUSK_IF_ELSE(static_cast<OSTick>(OSGetNativeTime()), OSGetTick());
|
||||
field_0x30 = tick - field_0x2c;
|
||||
field_0x2c = tick;
|
||||
field_0x34 = field_0x2c - JUTVideo::getVideoLastTick();
|
||||
@@ -371,7 +371,7 @@ constexpr auto FRAME_PERIOD = std::chrono::duration_cast<std::chrono::nanosecond
|
||||
constexpr auto RETRACE_PERIOD = FRAME_PERIOD / 2;
|
||||
|
||||
static void waitPrecise(Limiter& limiter, Limiter::duration_t targetNs) {
|
||||
const auto sleepTime = limiter.Sleep(targetNs);
|
||||
const auto sleepTime = limiter.Sleep(targetNs);
|
||||
dusk::frameUsagePct =
|
||||
100.0f * (1.0f - static_cast<float>(sleepTime) / static_cast<float>(targetNs));
|
||||
}
|
||||
@@ -381,15 +381,12 @@ static void waitForTick(u32 p1, u16 p2) {
|
||||
#if TARGET_PC
|
||||
static Limiter limiter;
|
||||
|
||||
if (dusk::frame_interp::is_enabled() && !dusk::getTransientSettings().skipFrameRateLimit) {
|
||||
dusk::frameUsagePct = 0.f;
|
||||
return;
|
||||
if (dusk::frame_interp::is_enabled() || dusk::getTransientSettings().turboMode) {
|
||||
limiter.Reset();
|
||||
dusk::frameUsagePct = 0.f;
|
||||
return;
|
||||
}
|
||||
|
||||
if (dusk::getTransientSettings().skipFrameRateLimit) {
|
||||
p1 = OS_TIMER_CLOCK / 120;
|
||||
}
|
||||
|
||||
if (fopOvlpM_IsPeek() && dusk::getTransientSettings().stateShareLoadActive) {
|
||||
return;
|
||||
}
|
||||
@@ -399,7 +396,6 @@ static void waitForTick(u32 p1, u16 p2) {
|
||||
|
||||
if (p1 != 0) {
|
||||
#if TARGET_PC
|
||||
static Limiter limiter;
|
||||
waitPrecise(limiter, static_cast<Uint64>(OSTicksToMicroseconds(p1)) * 1000ULL);
|
||||
#else
|
||||
static OSTime nextTick = OSGetTime();
|
||||
@@ -413,7 +409,6 @@ static void waitForTick(u32 p1, u16 p2) {
|
||||
} else {
|
||||
u32 uVar1 = (p2 == 0) ? 1 : p2;
|
||||
#if TARGET_PC
|
||||
static Limiter limiter;
|
||||
waitPrecise(limiter, static_cast<Uint64>((RETRACE_PERIOD * uVar1).count()));
|
||||
#else
|
||||
static u32 nextCount = VIGetRetraceCount();
|
||||
|
||||
@@ -42,7 +42,7 @@ JUTVideo::JUTVideo(GXRenderModeObj const* param_0) {
|
||||
mRetraceCount = VIGetRetraceCount();
|
||||
field_0x10 = 1;
|
||||
field_0x18 = 0;
|
||||
sVideoLastTick = OSGetTick();
|
||||
sVideoLastTick = DUSK_IF_ELSE(static_cast<OSTick>(OSGetNativeTime()), OSGetTick());
|
||||
sVideoInterval = 670000;
|
||||
mPreRetraceCallback = VISetPreRetraceCallback(preRetraceProc);
|
||||
mPostRetraceCallback = VISetPostRetraceCallback(postRetraceProc);
|
||||
@@ -68,7 +68,7 @@ void JUTVideo::preRetraceProc(u32 retrace_count) {
|
||||
(*sManager->mPreCallback)(retrace_count);
|
||||
}
|
||||
|
||||
OSTick tick = OSGetTick();
|
||||
OSTick tick = DUSK_IF_ELSE(static_cast<OSTick>(OSGetNativeTime()), OSGetTick());
|
||||
sVideoInterval = tick - sVideoLastTick;
|
||||
sVideoLastTick = tick;
|
||||
|
||||
|
||||
+1
-1
@@ -2346,7 +2346,7 @@ void dScnKy_env_light_c::setLight() {
|
||||
u8 next_pal_end_id;
|
||||
#if TARGET_PC
|
||||
const f32 deltaTime = dusk::game_clock::consume_interval(this);
|
||||
timeScale = deltaTime / dusk::game_clock::period_for_original_frames(1.0f);
|
||||
timeScale = deltaTime / dusk::game_clock::kSimPeriod;
|
||||
#endif
|
||||
setLight_palno_get(&g_env_light.PrevCol, &g_env_light.UseCol, &g_env_light.wether_pat0,
|
||||
&g_env_light.wether_pat1, &prev_pal_start_id, &prev_pal_end_id,
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@ static int dKyeff_Create(kankyo_class* i_this) {
|
||||
|
||||
if (strcmp(dComIfGp_getStartStageName(), "Name") == 0) {
|
||||
camera_process_class* camera = dComIfGp_getCamera(0);
|
||||
OSTime time = OSGetTime();
|
||||
OSTime time = DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime());
|
||||
OSTicksToCalendarTime(time, &calendar);
|
||||
|
||||
g_env_light.global_wind_influence.vec.x = 1.0f;
|
||||
|
||||
@@ -1798,7 +1798,7 @@ void dMenu_Ring_c::drawSelectItem() {
|
||||
if (field_0x674[i] != 0) {
|
||||
#if TARGET_PC
|
||||
mSelectItemSlideElapsed[i] += dusk::game_clock::consume_interval(this);
|
||||
const f32 u = std::min(mSelectItemSlideElapsed[i] / dusk::game_clock::period_for_original_frames(10.0f), 1.0f);
|
||||
const f32 u = std::min(mSelectItemSlideElapsed[i] / (dusk::game_clock::kSimPeriod * 10.0f), 1.0f);
|
||||
if (u >= 1.0f) {
|
||||
setSelectItemForce(i);
|
||||
} else {
|
||||
|
||||
+2
-2
@@ -1828,7 +1828,7 @@ int dSv_info_c::memory_to_card(char* card_ptr, int dataNum) {
|
||||
savedata->getPlayer().getPlayerInfo().setTotalTime(play_time);
|
||||
}
|
||||
|
||||
savedata->getPlayer().getPlayerStatusB().setDateIpl(OSGetTime());
|
||||
savedata->getPlayer().getPlayerStatusB().setDateIpl(DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime()));
|
||||
|
||||
memcpy(card_ptr, savedata, sizeof(dSv_save_c));
|
||||
card_ptr += 0x958;
|
||||
@@ -2035,7 +2035,7 @@ void flagFile_c::listenPropertyEvent(const JORPropertyEvent* i_event) {
|
||||
}
|
||||
case 102: {
|
||||
OSCalendarTime time;
|
||||
OSTicksToCalendarTime(OSGetTime(), &time);
|
||||
OSTicksToCalendarTime(DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime()), &time);
|
||||
|
||||
const char* start_stage_name = dComIfGp_getStartStageName();
|
||||
char filename[64];
|
||||
|
||||
@@ -41,6 +41,15 @@ bool isActionBound(ActionBinds action, u32 port) {
|
||||
return getActionBindButton(action, port) != PAD_NATIVE_BUTTON_INVALID;
|
||||
}
|
||||
|
||||
bool isActionBoundAnyPort(ActionBinds action) {
|
||||
for (u32 port = 0; port < PAD_CHANMAX; ++port) {
|
||||
if (isActionBound(action, port)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void updateActionBindings() {
|
||||
for (u32 port = 0; port < PAD_CHANMAX; ++port) {
|
||||
// Move the current press to the previous frame
|
||||
|
||||
@@ -31,6 +31,7 @@ using ActionBindsMap = std::unordered_map<ActionBinds, ActionBindData>;
|
||||
ActionBindsMap& getActionBinds();
|
||||
|
||||
bool isActionBound(ActionBinds action, u32 port);
|
||||
bool isActionBoundAnyPort(ActionBinds action);
|
||||
|
||||
void updateActionBindings();
|
||||
|
||||
|
||||
@@ -147,6 +147,10 @@ void begin_frame(FrameInterpMode mode, bool is_sim_frame, float step) {
|
||||
g_enabled = mode != FrameInterpMode::Off;
|
||||
g_is_sim_frame = is_sim_frame;
|
||||
g_step = std::clamp(step, 0.0f, 1.0f);
|
||||
if (!g_enabled) {
|
||||
g_interpolating = false;
|
||||
clear_replacements();
|
||||
}
|
||||
}
|
||||
|
||||
bool is_enabled() {
|
||||
|
||||
+86
-55
@@ -1,104 +1,135 @@
|
||||
#include "dusk/game_clock.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <aurora/time.hpp>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
#include <dusk/frame_interpolation.h>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace dusk::game_clock {
|
||||
|
||||
using clock = std::chrono::steady_clock;
|
||||
using native_clock = aurora::time::native_clock;
|
||||
using game_clock = aurora::time::game_clock;
|
||||
|
||||
FrameTiming g_frameTiming;
|
||||
|
||||
namespace {
|
||||
bool s_initialized = false;
|
||||
clock::time_point s_previous_sample{};
|
||||
clock::time_point s_current_snapshot_time{};
|
||||
bool s_fixedStepActive = false;
|
||||
bool s_simTickActive = false;
|
||||
native_clock::time_point s_previousNativeSample{};
|
||||
game_clock::time_point s_latestGameSample{};
|
||||
game_clock::time_point s_currentSnapshotTime{};
|
||||
game_clock::time_point s_pendingSimTime{};
|
||||
|
||||
std::unordered_map<uintptr_t, clock::time_point> s_interval_last_sample;
|
||||
std::unordered_map<uintptr_t, game_clock::time_point> s_intervalLastSample;
|
||||
|
||||
constexpr clock::duration kSimPeriodDuration =
|
||||
std::chrono::duration_cast<clock::duration>(std::chrono::duration<float>(sim_pace()));
|
||||
constexpr clock::duration kAbnormalGapResetThreshold = std::chrono::milliseconds(250);
|
||||
constexpr int kMaxSimTicksPerFrame = 2;
|
||||
constexpr game_clock::duration kSimPeriodDuration =
|
||||
std::chrono::duration_cast<game_clock::duration>(std::chrono::duration<float>(kSimPeriod));
|
||||
constexpr native_clock::duration kAbnormalGapResetThreshold = std::chrono::milliseconds(250);
|
||||
constexpr int kMaxSimTicksPerFrame = static_cast<int>(aurora::time::kMaximumTimeScale) * 4;
|
||||
} // namespace
|
||||
|
||||
void ensure_initialized() {
|
||||
void initialize() {
|
||||
if (s_initialized) {
|
||||
return;
|
||||
}
|
||||
s_previous_sample = clock::now();
|
||||
s_current_snapshot_time = s_previous_sample;
|
||||
s_previousNativeSample = native_clock::now();
|
||||
s_latestGameSample = game_clock::now();
|
||||
s_currentSnapshotTime = s_latestGameSample;
|
||||
s_pendingSimTime = s_latestGameSample;
|
||||
s_initialized = true;
|
||||
}
|
||||
|
||||
void reset_frame_timer() {
|
||||
s_previous_sample = clock::now();
|
||||
s_current_snapshot_time = s_previous_sample - kSimPeriodDuration;
|
||||
void reset() {
|
||||
s_previousNativeSample = native_clock::now();
|
||||
s_latestGameSample = game_clock::now();
|
||||
s_currentSnapshotTime = s_latestGameSample - kSimPeriodDuration;
|
||||
s_pendingSimTime = s_currentSnapshotTime;
|
||||
s_simTickActive = false;
|
||||
}
|
||||
|
||||
MainLoopPacer advance_main_loop() {
|
||||
ensure_initialized();
|
||||
const FrameTiming& advance() {
|
||||
const auto nativeNow = native_clock::now();
|
||||
const auto gameNow = game_clock::now();
|
||||
const auto nativeFrameGap = nativeNow - s_previousNativeSample;
|
||||
s_previousNativeSample = nativeNow;
|
||||
s_latestGameSample = gameNow;
|
||||
|
||||
const clock::time_point now = clock::now();
|
||||
const clock::duration frame_gap = now - s_previous_sample;
|
||||
const float presentation_dt = std::chrono::duration<float>(frame_gap).count();
|
||||
s_previous_sample = now;
|
||||
auto& out = g_frameTiming;
|
||||
out = {.dt = std::chrono::duration<float>().count()};
|
||||
|
||||
MainLoopPacer out{};
|
||||
out.presentation_dt_seconds = presentation_dt;
|
||||
const float timeScale = aurora::time::scale();
|
||||
const bool interpolating =
|
||||
getSettings().game.enableFrameInterpolation.getValue() != FrameInterpMode::Off;
|
||||
const bool separatePresentation = interpolating || timeScale != 1.0f;
|
||||
out.interpolating = interpolating;
|
||||
out.separatePresentation = separatePresentation;
|
||||
s_fixedStepActive = separatePresentation;
|
||||
|
||||
const bool should_interpolate = dusk::getSettings().game.enableFrameInterpolation.getValue() !=
|
||||
dusk::FrameInterpMode::Off &&
|
||||
!dusk::getTransientSettings().skipFrameRateLimit;
|
||||
out.is_interpolating = should_interpolate;
|
||||
out.sim_pace = sim_pace();
|
||||
|
||||
if (!should_interpolate) {
|
||||
s_current_snapshot_time = now;
|
||||
out.sim_ticks_to_run = 1;
|
||||
if (!separatePresentation) {
|
||||
s_currentSnapshotTime = gameNow;
|
||||
out.numSimTicks = 1;
|
||||
return out;
|
||||
}
|
||||
|
||||
if (frame_gap > kAbnormalGapResetThreshold) {
|
||||
s_current_snapshot_time = now - kSimPeriodDuration;
|
||||
out.sim_ticks_to_run = 0;
|
||||
const auto simulationTarget = interpolating ? gameNow - kSimPeriodDuration : gameNow;
|
||||
if (timeScale == 0.f || nativeFrameGap > kAbnormalGapResetThreshold) {
|
||||
s_currentSnapshotTime = simulationTarget;
|
||||
out.numSimTicks = 0;
|
||||
return out;
|
||||
}
|
||||
|
||||
int sim_ticks_to_run = 0;
|
||||
clock::time_point projected_snapshot_time = s_current_snapshot_time;
|
||||
const clock::time_point render_time = now - kSimPeriodDuration;
|
||||
while (sim_ticks_to_run < kMaxSimTicksPerFrame && projected_snapshot_time < render_time) {
|
||||
projected_snapshot_time += kSimPeriodDuration;
|
||||
sim_ticks_to_run++;
|
||||
int numSimTicks = 0;
|
||||
auto projectedSnapshotTime = s_currentSnapshotTime;
|
||||
while (numSimTicks < kMaxSimTicksPerFrame) {
|
||||
const bool tickDue = interpolating ?
|
||||
projectedSnapshotTime < simulationTarget :
|
||||
projectedSnapshotTime + kSimPeriodDuration <= simulationTarget;
|
||||
if (!tickDue) {
|
||||
break;
|
||||
}
|
||||
projectedSnapshotTime += kSimPeriodDuration;
|
||||
numSimTicks++;
|
||||
}
|
||||
out.sim_ticks_to_run = sim_ticks_to_run;
|
||||
out.numSimTicks = numSimTicks;
|
||||
return out;
|
||||
}
|
||||
|
||||
void begin_sim_tick() {
|
||||
s_pendingSimTime =
|
||||
s_fixedStepActive ? s_currentSnapshotTime + kSimPeriodDuration : s_latestGameSample;
|
||||
s_simTickActive = true;
|
||||
}
|
||||
|
||||
void commit_sim_tick() {
|
||||
ensure_initialized();
|
||||
s_current_snapshot_time += kSimPeriodDuration;
|
||||
if (s_simTickActive) {
|
||||
s_currentSnapshotTime = s_pendingSimTime;
|
||||
s_simTickActive = false;
|
||||
} else {
|
||||
s_currentSnapshotTime += kSimPeriodDuration;
|
||||
}
|
||||
}
|
||||
|
||||
float sample_interpolation_step() {
|
||||
ensure_initialized();
|
||||
const float step =
|
||||
std::chrono::duration<float>(clock::now() - s_current_snapshot_time).count() / sim_pace();
|
||||
std::chrono::duration<float>(game_clock::now() - s_currentSnapshotTime).count() /
|
||||
kSimPeriod;
|
||||
return std::clamp(step, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
float consume_interval(const void* consumer) {
|
||||
ensure_initialized();
|
||||
const uintptr_t key = reinterpret_cast<uintptr_t>(consumer);
|
||||
const clock::time_point now = clock::now();
|
||||
|
||||
float dt = ui_initial_dt();
|
||||
const auto it = s_interval_last_sample.find(key);
|
||||
if (it != s_interval_last_sample.end()) {
|
||||
const auto key = reinterpret_cast<uintptr_t>(consumer);
|
||||
const auto now = s_simTickActive ? s_pendingSimTime : game_clock::now();
|
||||
const float timeScale = aurora::time::scale();
|
||||
float dt = kUiInitialDt * timeScale;
|
||||
if (const auto it = s_intervalLastSample.find(key); it != s_intervalLastSample.end()) {
|
||||
dt = std::chrono::duration<float>(now - it->second).count();
|
||||
dt = std::min(dt, ui_maximum_dt());
|
||||
const float maximumDt = std::max(kUiMaximumDt * timeScale, kSimPeriod);
|
||||
dt = std::min(dt, maximumDt);
|
||||
}
|
||||
s_interval_last_sample[key] = now;
|
||||
s_intervalLastSample[key] = now;
|
||||
return dt;
|
||||
}
|
||||
|
||||
|
||||
+18
-13
@@ -2,22 +2,27 @@
|
||||
|
||||
namespace dusk::game_clock {
|
||||
|
||||
void ensure_initialized();
|
||||
void reset_frame_timer();
|
||||
// Amount of time that a simulation tick advances
|
||||
constexpr float kSimPeriod = 1.0f / 30.0f;
|
||||
constexpr float kUiMaximumDt = 0.05f;
|
||||
constexpr float kUiInitialDt = 1.0f / 60.0f;
|
||||
|
||||
constexpr float sim_pace() { return 1.0f / 30.0f; }
|
||||
constexpr float period_for_original_frames(float frame_count) { return frame_count * sim_pace(); }
|
||||
constexpr float ui_maximum_dt() { return 0.05f; }
|
||||
constexpr float ui_initial_dt() { return 1.0f / 60.0f; }
|
||||
|
||||
struct MainLoopPacer {
|
||||
float presentation_dt_seconds;
|
||||
bool is_interpolating;
|
||||
int sim_ticks_to_run;
|
||||
float sim_pace;
|
||||
struct FrameTiming {
|
||||
// Amount of time elapsed in seconds since the last advance
|
||||
float dt;
|
||||
// Whether interpolation is active
|
||||
bool interpolating;
|
||||
// Run simulation and presentation separately (for interpolation or time scaling)
|
||||
bool separatePresentation;
|
||||
// Number of simulation ticks to run
|
||||
int numSimTicks;
|
||||
};
|
||||
extern FrameTiming g_frameTiming;
|
||||
|
||||
MainLoopPacer advance_main_loop();
|
||||
void initialize();
|
||||
void reset();
|
||||
const FrameTiming& advance();
|
||||
void begin_sim_tick();
|
||||
void commit_sim_tick();
|
||||
float sample_interpolation_step();
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <aurora/aurora.h>
|
||||
#include <chrono>
|
||||
#include <numeric>
|
||||
#include <string_view>
|
||||
#include <chrono>
|
||||
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#include "imgui.h"
|
||||
@@ -20,6 +21,7 @@
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/presentation.hpp"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/ui/ui.hpp"
|
||||
#include "f_pc/f_pc_manager.h"
|
||||
@@ -37,6 +39,8 @@ using namespace std::string_literals;
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
namespace {
|
||||
constexpr float kTurboTimeScale = 4.f;
|
||||
|
||||
ImGuiWindow* FindDragScrollWindow(ImGuiWindow* window) {
|
||||
while (window != nullptr) {
|
||||
const bool canScrollX = window->ScrollMax.x > 0.0f;
|
||||
@@ -235,10 +239,32 @@ namespace dusk {
|
||||
}
|
||||
|
||||
void ImGuiConsole::UpdateSettings() {
|
||||
getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind &&
|
||||
(ImGui::IsKeyDown(ImGuiKey_Tab) || getActionBindHoldAnyPort(ActionBinds::TURBO_SPEED_BUTTON));
|
||||
static bool previousTurboActive = false;
|
||||
static bool previousSlowActive = false;
|
||||
static float previousTimeScale = 1.0f;
|
||||
|
||||
if (dusk::frame_interp::get_ui_tick_pending() && mDoMain::developmentMode == 1 && (mDoCPd_c::getHold(PAD_1) & (PAD_TRIGGER_R | PAD_TRIGGER_L)) == (PAD_TRIGGER_R | PAD_TRIGGER_L) && mDoCPd_c::getTrigY(PAD_1)) {
|
||||
const bool turboBound = isActionBoundAnyPort(ActionBinds::TURBO_SPEED_BUTTON);
|
||||
const bool turboActive =
|
||||
getSettings().game.enableTurboKeybind &&
|
||||
(turboBound ? getActionBindHoldAnyPort(ActionBinds::TURBO_SPEED_BUTTON) :
|
||||
ImGui::IsKeyDown(ImGuiKey_Tab));
|
||||
const bool slowDown = turboActive && ImGui::GetIO().KeyShift;
|
||||
if (turboActive != previousTurboActive) {
|
||||
getTransientSettings().turboMode = turboActive;
|
||||
presentation::update_frame_rate_preference();
|
||||
if (turboActive) {
|
||||
previousTimeScale = aurora_get_timescale();
|
||||
aurora_set_timescale(slowDown ? 1.f / kTurboTimeScale : kTurboTimeScale);
|
||||
} else {
|
||||
aurora_set_timescale(previousTimeScale);
|
||||
}
|
||||
} else if (turboActive && slowDown != previousSlowActive) {
|
||||
aurora_set_timescale(slowDown ? 1.f / kTurboTimeScale : kTurboTimeScale);
|
||||
}
|
||||
previousTurboActive = turboActive;
|
||||
previousSlowActive = slowDown;
|
||||
|
||||
if (frame_interp::get_ui_tick_pending() && mDoMain::developmentMode == 1 && (mDoCPd_c::getHold(PAD_1) & (PAD_TRIGGER_R | PAD_TRIGGER_L)) == (PAD_TRIGGER_R | PAD_TRIGGER_L) && mDoCPd_c::getTrigY(PAD_1)) {
|
||||
getTransientSettings().moveLinkActive = !getTransientSettings().moveLinkActive;
|
||||
}
|
||||
if (mDoMain::developmentMode != 1) {
|
||||
@@ -270,7 +296,7 @@ namespace dusk {
|
||||
m_isHidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool showMenu = !m_isHidden;
|
||||
|
||||
// The menu bar renders with ImGuiCol_WindowBg behind it. We just want ImGuiCol_MenuBarBg,
|
||||
|
||||
@@ -25,10 +25,6 @@
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
namespace aurora::gx {
|
||||
extern bool enableLodBias;
|
||||
}
|
||||
|
||||
namespace dusk {
|
||||
ImGuiMenuTools::ImGuiMenuTools() {}
|
||||
|
||||
@@ -76,7 +72,6 @@ namespace dusk {
|
||||
getSettings().game.disableWaterRefraction.setValue(disableWaterRefraction);
|
||||
config::save();
|
||||
}
|
||||
ImGui::Checkbox("Enable LOD Bias", &aurora::gx::enableLodBias);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ namespace dusk::presentation {
|
||||
namespace {
|
||||
|
||||
float preferred_frame_rate() {
|
||||
if (getTransientSettings().turboMode) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
switch (getSettings().game.enableFrameInterpolation.getValue()) {
|
||||
case FrameInterpMode::Off:
|
||||
return 30.0f;
|
||||
|
||||
@@ -396,7 +396,7 @@ static TransientSettings g_transientSettings = {
|
||||
.colliderViewOpacity = 50.0f,
|
||||
.drawRange = 100.0f,
|
||||
},
|
||||
.skipFrameRateLimit = false,
|
||||
.turboMode = false,
|
||||
};
|
||||
|
||||
TransientSettings& getTransientSettings() {
|
||||
|
||||
+1
-1
@@ -327,7 +327,7 @@ struct CollisionViewSettings {
|
||||
|
||||
struct TransientSettings {
|
||||
CollisionViewSettings collisionView;
|
||||
bool skipFrameRateLimit;
|
||||
bool turboMode;
|
||||
bool moveLinkActive;
|
||||
bool stateShareLoadActive;
|
||||
};
|
||||
|
||||
+12
-6
@@ -6,18 +6,23 @@ namespace dusk {
|
||||
struct SpeedrunInfo {
|
||||
void startRun() {
|
||||
m_isRunStarted = true;
|
||||
m_startTimestamp = OSGetTime();
|
||||
m_rtaStartTimestamp = OSGetNativeTime();
|
||||
m_igtStartTimestamp = OSGetTime();
|
||||
}
|
||||
|
||||
void stopRun() {
|
||||
m_isRunStarted = false;
|
||||
m_endTimestamp = OSGetTime() - m_startTimestamp;
|
||||
m_rtaTimer = OSGetNativeTime() - m_rtaStartTimestamp;
|
||||
if (!m_isPauseIGT) {
|
||||
m_igtTimer = OSGetTime() - m_igtStartTimestamp - m_totalLoadTime;
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
m_isRunStarted = false;
|
||||
m_startTimestamp = 0;
|
||||
m_endTimestamp = 0;
|
||||
m_rtaStartTimestamp = 0;
|
||||
m_rtaTimer = 0;
|
||||
m_igtStartTimestamp = 0;
|
||||
m_isPauseIGT = false;
|
||||
m_loadStartTimestamp = 0;
|
||||
m_totalLoadTime = 0;
|
||||
@@ -25,8 +30,9 @@ struct SpeedrunInfo {
|
||||
}
|
||||
|
||||
bool m_isRunStarted = false;
|
||||
OSTime m_startTimestamp = 0;
|
||||
OSTime m_endTimestamp = 0;
|
||||
OSTime m_rtaStartTimestamp = 0;
|
||||
OSTime m_rtaTimer = 0;
|
||||
OSTime m_igtStartTimestamp = 0;
|
||||
|
||||
bool m_isPauseIGT = false;
|
||||
OSTime m_loadStartTimestamp = 0;
|
||||
|
||||
+23
-14
@@ -203,10 +203,19 @@ void remove_element(Rml::Element*& elem) noexcept {
|
||||
|
||||
} // namespace
|
||||
|
||||
static std::string FormatTime(OSTime ticks) {
|
||||
OSCalendarTime t;
|
||||
OSTicksToCalendarTime(ticks, &t);
|
||||
return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", t.hour, t.min, t.sec, t.msec);
|
||||
static std::string FormatElapsedTime(OSTime ticksElapsed) {
|
||||
using namespace std::chrono;
|
||||
|
||||
milliseconds ms{OSTicksToMilliseconds(ticksElapsed)};
|
||||
|
||||
const hours hr = duration_cast<hours>(ms);
|
||||
ms -= hr;
|
||||
const minutes min = duration_cast<minutes>(ms);
|
||||
ms -= min;
|
||||
const seconds sec = duration_cast<seconds>(ms);
|
||||
ms -= sec;
|
||||
|
||||
return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", hr.count(), min.count(), sec.count(), ms.count());
|
||||
}
|
||||
|
||||
Overlay::Overlay() : Document(kDocumentSource, true, DocumentScope::Overlay) {
|
||||
@@ -317,33 +326,33 @@ void Overlay::update() {
|
||||
mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigY(PAD_1))
|
||||
{
|
||||
if (m_speedrunInfo.m_isRunStarted) {
|
||||
m_speedrunInfo.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp;
|
||||
m_speedrunInfo.m_isRunStarted = false;
|
||||
m_speedrunInfo.stopRun();
|
||||
}
|
||||
}
|
||||
|
||||
OSTime elapsedTime = 0;
|
||||
OSTime rtaElapsedTime = 0;
|
||||
if (m_speedrunInfo.m_isRunStarted) {
|
||||
elapsedTime = OSGetTime() - m_speedrunInfo.m_startTimestamp;
|
||||
} else if (m_speedrunInfo.m_endTimestamp != 0) {
|
||||
elapsedTime = m_speedrunInfo.m_endTimestamp;
|
||||
rtaElapsedTime = OSGetNativeTime() - m_speedrunInfo.m_rtaStartTimestamp;
|
||||
} else if (m_speedrunInfo.m_rtaTimer != 0) {
|
||||
rtaElapsedTime = m_speedrunInfo.m_rtaTimer;
|
||||
}
|
||||
|
||||
if (!m_speedrunInfo.m_isPauseIGT) {
|
||||
m_speedrunInfo.m_igtTimer = elapsedTime - m_speedrunInfo.m_totalLoadTime;
|
||||
if (m_speedrunInfo.m_isRunStarted && !m_speedrunInfo.m_isPauseIGT) {
|
||||
m_speedrunInfo.m_igtTimer = OSGetTime() - m_speedrunInfo.m_igtStartTimestamp -
|
||||
m_speedrunInfo.m_totalLoadTime;
|
||||
}
|
||||
|
||||
mSpeedrunTimer->SetAttribute("open", "");
|
||||
|
||||
if (getSettings().game.showSpeedrunRTATimer) {
|
||||
mSpeedrunRta->SetAttribute("open", "");
|
||||
mSpeedrunRta->SetInnerRML(escape(fmt::format("RTA {}", FormatTime(elapsedTime))));
|
||||
mSpeedrunRta->SetInnerRML(escape(fmt::format("RTA {}", FormatElapsedTime(rtaElapsedTime))));
|
||||
} else {
|
||||
mSpeedrunRta->RemoveAttribute("open");
|
||||
}
|
||||
|
||||
mSpeedrunIgt->SetInnerRML(
|
||||
escape(fmt::format("IGT {}", FormatTime(m_speedrunInfo.m_igtTimer))));
|
||||
escape(fmt::format("IGT {}", FormatElapsedTime(m_speedrunInfo.m_igtTimer))));
|
||||
} else {
|
||||
mSpeedrunTimer->RemoveAttribute("open");
|
||||
}
|
||||
|
||||
@@ -19,15 +19,16 @@ static int fopOvlpReq_phase_Done(overlap_request_class* i_overlapReq) {
|
||||
i_overlapReq->field_0x8 = 0;
|
||||
i_overlapReq->field_0xc = 0;
|
||||
|
||||
#if TARGET_PC
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.speedrunMode) {
|
||||
if (dusk::m_speedrunInfo.m_isRunStarted) {
|
||||
dusk::m_speedrunInfo.m_isPauseIGT = false;
|
||||
dusk::m_speedrunInfo.m_totalLoadTime += OSGetTime() - dusk::m_speedrunInfo.m_loadStartTimestamp;
|
||||
dusk::m_speedrunInfo.m_totalLoadTime +=
|
||||
OSGetTime() - dusk::m_speedrunInfo.m_loadStartTimestamp;
|
||||
dusk::m_speedrunInfo.m_loadStartTimestamp = OSGetTime();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
return cPhs_NEXT_e;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "m_Do/m_Do_controller_pad.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/game_clock.h"
|
||||
#endif
|
||||
|
||||
#include "tracy/Tracy.hpp"
|
||||
@@ -68,8 +68,8 @@ void fpcM_Management(fpcM_ManagementFunc i_preExecuteFn, fpcM_ManagementFunc i_p
|
||||
}
|
||||
|
||||
#ifdef TARGET_PC
|
||||
// FRAME INTERP NOTE: Called in m_Do_main when interp is enabled
|
||||
if (!dusk::frame_interp::is_enabled())
|
||||
// The main loop manages painting when simulation and presentation are separated.
|
||||
if (!dusk::game_clock::g_frameTiming.separatePresentation)
|
||||
#endif
|
||||
{
|
||||
cAPIGph_Painter();
|
||||
|
||||
@@ -320,7 +320,7 @@ static void mDoMemCdRWm_BuildHeader(mDoMemCdRWm_HeaderData* header) {
|
||||
#endif
|
||||
|
||||
OSCalendarTime time;
|
||||
OSTicksToCalendarTime(OSGetTime(), &time);
|
||||
OSTicksToCalendarTime(DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime()), &time);
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::version::isRegionPal()) {
|
||||
|
||||
@@ -527,7 +527,7 @@ void myExceptionCallback(u16, OSContext*, u32, u32) {
|
||||
u32 btnHold;
|
||||
u32 btnTrig;
|
||||
|
||||
mDoMain::sHungUpTime = OSGetTime();
|
||||
mDoMain::sHungUpTime = DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime());
|
||||
OSReportEnable();
|
||||
cAPICPad_recalibrate();
|
||||
// "Vibration stopping & resetting to default\n"
|
||||
|
||||
+34
-20
@@ -233,7 +233,7 @@ void main01(void) {
|
||||
|
||||
OSReport("Entering Main Loop (main01)...\n");
|
||||
|
||||
dusk::game_clock::ensure_initialized();
|
||||
dusk::game_clock::initialize();
|
||||
|
||||
do {
|
||||
// 1. Update Window Events
|
||||
@@ -248,7 +248,7 @@ void main01(void) {
|
||||
break;
|
||||
case AURORA_UNPAUSED:
|
||||
dusk::audio::SetPaused(false);
|
||||
dusk::game_clock::reset_frame_timer();
|
||||
dusk::game_clock::reset();
|
||||
dusk::mouse::on_focus_gained();
|
||||
break;
|
||||
case AURORA_SDL_EVENT:
|
||||
@@ -290,46 +290,56 @@ void main01(void) {
|
||||
|
||||
dusk::ui::update();
|
||||
|
||||
const auto pacing = dusk::game_clock::advance_main_loop();
|
||||
if (pacing.is_interpolating) {
|
||||
if (pacing.sim_ticks_to_run > 0) {
|
||||
dusk::frame_interp::begin_frame(dusk::getSettings().game.enableFrameInterpolation, true, 0.0f);
|
||||
const auto timing = dusk::game_clock::advance();
|
||||
const auto interpolationMode = dusk::getSettings().game.enableFrameInterpolation.getValue();
|
||||
if (timing.separatePresentation) {
|
||||
if (timing.numSimTicks > 0) {
|
||||
dusk::frame_interp::begin_frame(interpolationMode, true, 0.0f);
|
||||
dusk::frame_interp::set_ui_tick_pending(true);
|
||||
|
||||
for (int sim_tick = 0; sim_tick < pacing.sim_ticks_to_run; ++sim_tick) {
|
||||
dusk::frame_interp::begin_sim_tick();
|
||||
for (int i = 0; i < timing.numSimTicks; ++i) {
|
||||
if (timing.interpolating) {
|
||||
dusk::frame_interp::begin_sim_tick();
|
||||
}
|
||||
dusk::game_clock::begin_sim_tick();
|
||||
mDoCPd_c::read();
|
||||
dusk::mouse::read();
|
||||
dusk::gyro::read(pacing.sim_pace);
|
||||
dusk::gyro::read(dusk::game_clock::kSimPeriod);
|
||||
fapGm_Execute();
|
||||
mDoAud_Execute();
|
||||
dusk::game_clock::commit_sim_tick();
|
||||
}
|
||||
}
|
||||
|
||||
dusk::frame_interp::begin_frame(dusk::getSettings().game.enableFrameInterpolation, false,
|
||||
dusk::game_clock::sample_interpolation_step());
|
||||
dusk::frame_interp::interpolate();
|
||||
dusk::frame_interp::begin_presentation_camera();
|
||||
// run draw functions for anything specially marked to handle interp
|
||||
const float interpolationStep =
|
||||
timing.interpolating ? dusk::game_clock::sample_interpolation_step() : 1.0f;
|
||||
dusk::frame_interp::begin_frame(interpolationMode, false, interpolationStep);
|
||||
if (timing.interpolating) {
|
||||
dusk::frame_interp::interpolate();
|
||||
dusk::frame_interp::begin_presentation_camera();
|
||||
}
|
||||
|
||||
fpcM_DrawIterater((fpcM_DrawIteraterFunc)fpcM_Draw);
|
||||
cAPIGph_Painter();
|
||||
dusk::frame_interp::end_presentation_camera();
|
||||
if (timing.interpolating) {
|
||||
dusk::frame_interp::end_presentation_camera();
|
||||
}
|
||||
dusk::frame_interp::set_ui_tick_pending(false);
|
||||
} else {
|
||||
dusk::frame_interp::begin_frame(dusk::FrameInterpMode::Off, true, 0.0f);
|
||||
dusk::frame_interp::set_ui_tick_pending(true);
|
||||
dusk::game_clock::begin_sim_tick();
|
||||
|
||||
// Game Inputs
|
||||
mDoCPd_c::read();
|
||||
dusk::mouse::read();
|
||||
dusk::gyro::read(pacing.presentation_dt_seconds);
|
||||
dusk::gyro::read(timing.dt);
|
||||
|
||||
// EXECUTE GAME LOGIC & RENDER
|
||||
// This calls mDoGph_Painter -> JFWDisplay -> GX Functions
|
||||
fapGm_Execute();
|
||||
|
||||
mDoAud_Execute();
|
||||
dusk::game_clock::commit_sim_tick();
|
||||
}
|
||||
|
||||
aurora_end_frame();
|
||||
@@ -345,7 +355,10 @@ void main01(void) {
|
||||
static double last_fps_setting = 0.0;
|
||||
static Limiter::duration_t target_ns = 0;
|
||||
|
||||
if (dusk::getSettings().game.enableFrameInterpolation.getValue() == dusk::FrameInterpMode::Capped && !dusk::getTransientSettings().skipFrameRateLimit) {
|
||||
if (dusk::getSettings().game.enableFrameInterpolation.getValue() ==
|
||||
dusk::FrameInterpMode::Capped &&
|
||||
!dusk::getTransientSettings().turboMode)
|
||||
{
|
||||
ZoneScopedN("Frame limiter");
|
||||
double current_fps = dusk::getSettings().video.maxFrameRate.getValue();
|
||||
if (current_fps != last_fps_setting) {
|
||||
@@ -354,7 +367,8 @@ void main01(void) {
|
||||
}
|
||||
|
||||
Limiter::duration_t sleepTime = main_loop_limiter.Sleep(target_ns);
|
||||
dusk::frameUsagePct = 100.0f * (1.0f - static_cast<float>(sleepTime) / static_cast<float>(target_ns));
|
||||
dusk::frameUsagePct =
|
||||
100.0f * (1.0f - static_cast<float>(sleepTime) / static_cast<float>(target_ns));
|
||||
} else {
|
||||
main_loop_limiter.Reset();
|
||||
}
|
||||
@@ -845,7 +859,7 @@ int game_main(int argc, char* argv[]) {
|
||||
|
||||
OSInit();
|
||||
|
||||
mDoMain::sPowerOnTime = OSGetTime();
|
||||
mDoMain::sPowerOnTime = DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime());
|
||||
|
||||
// Reset Data
|
||||
static mDoRstData sResetData = {0};
|
||||
|
||||
Reference in New Issue
Block a user