diff --git a/data/uking_functions.csv b/data/uking_functions.csv index 5e756f30..024755d0 100644 --- a/data/uking_functions.csv +++ b/data/uking_functions.csv @@ -91875,28 +91875,28 @@ 0x00000071011c0b30,TimerSimple::update,128, 0x00000071011c0bb0,Timer::update,136, 0x00000071011c0c38,Timer::ended,48, -0x00000071011c0c68,sub_71011C0C68,100, -0x00000071011c0ccc,sub_71011C0CCC,108, -0x00000071011c0d38,VFR::createInstance,332, -0x00000071011c0e84,sub_71011C0E84,68, -0x00000071011c0ec8,sub_71011C0EC8,64, -0x00000071011c0f08,VFR::init,468, -0x00000071011c10dc,VFR::__auto1,24, -0x00000071011c10f4,VFR::clearField29,8, -0x00000071011c10fc,sub_71011C10FC,544, -0x00000071011c131c,VFR::x_0,132, -0x00000071011c13a0,sub_71011C13A0,132, -0x00000071011c1424,sub_71011C1424,328, -0x00000071011c156c,VFR::initBeforeStageGenB,316, -0x00000071011c16a8,VFR::isSlowTime,68, +0x00000071011c0c68,sub_71011C0C68,100,_ZN4ksys3VFR18SingletonDisposer_D2Ev +0x00000071011c0ccc,sub_71011C0CCC,108,_ZN4ksys3VFR18SingletonDisposer_D0Ev +0x00000071011c0d38,VFR::createInstance,332,_ZN4ksys3VFR14createInstanceEPN4sead4HeapE +0x00000071011c0e84,sub_71011C0E84,68,_ZN4ksys3VFRD1Ev +0x00000071011c0ec8,sub_71011C0EC8,64,_ZN4ksys3VFRD0Ev +0x00000071011c0f08,VFR::init,468,_ZN4ksys3VFR4initEjiPN4sead4HeapEj +0x00000071011c10dc,VFR::__auto1,24,_ZN4ksys3VFR19setIntervalOverrideEj +0x00000071011c10f4,VFR::clearField29,8,_ZN4ksys3VFR21clearIntervalOverrideEv +0x00000071011c10fc,sub_71011C10FC,544,_ZN4ksys3VFR14updateIntervalEj +0x00000071011c131c,VFR::x_0,132,_ZN4ksys3VFR10useBufferBEv +0x00000071011c13a0,sub_71011C13A0,132,_ZN4ksys3VFR10useBufferAEv +0x00000071011c1424,sub_71011C1424,328,_ZN4ksys3VFR27setDeltaFromTimeMultipliersEPfjj +0x00000071011c156c,VFR::initBeforeStageGenB,316,_ZN4ksys3VFR20resetTimeMultipliersEv +0x00000071011c16a8,VFR::isSlowTime,68,_ZNK4ksys3VFR23hasCustomTimeMultiplierEv 0x00000071011c16ec,VFR::setSlowTime,136, 0x00000071011c1774,VFR::endSlowTime,120, 0x00000071011c17ec,sub_71011C17EC,12, 0x00000071011c17f8,sub_71011C17F8,184, 0x00000071011c18b0,sub_71011C18B0,176, 0x00000071011c1960,sub_71011C1960,200, -0x00000071011c1a28,VFR::isField29Set,8, -0x00000071011c1a30,VFR::x,8, +0x00000071011c1a28,VFR::isField29Set,8,_ZNK4ksys3VFR19hasIntervalOverrideEv +0x00000071011c1a30,VFR::x,8,_ZNK4ksys3VFR19getIntervalOverrideEv 0x00000071011c1a38,sub_71011C1A38,44, 0x00000071011c1a64,sub_71011C1A64,12, 0x00000071011c1a70,sub_71011C1A70,28, diff --git a/lib/sead b/lib/sead index ed983cc4..bb92e59f 160000 --- a/lib/sead +++ b/lib/sead @@ -1 +1 @@ -Subproject commit ed983cc42dee614eb4fbc8d71303acec1c7b2c84 +Subproject commit bb92e59f569bf8e865a9710b03015352c137cc29 diff --git a/src/KingSystem/System/CMakeLists.txt b/src/KingSystem/System/CMakeLists.txt index 42f2dc44..851e291e 100644 --- a/src/KingSystem/System/CMakeLists.txt +++ b/src/KingSystem/System/CMakeLists.txt @@ -17,4 +17,6 @@ target_sources(uking PRIVATE SystemPauseMgr.h Timer.cpp Timer.h + VFR.cpp + VFR.h ) diff --git a/src/KingSystem/System/VFR.cpp b/src/KingSystem/System/VFR.cpp new file mode 100644 index 00000000..71dd2ae6 --- /dev/null +++ b/src/KingSystem/System/VFR.cpp @@ -0,0 +1,178 @@ +#include "KingSystem/System/VFR.h" +#include + +namespace ksys { + +SEAD_SINGLETON_DISPOSER_IMPL(VFR) + +VFR::VFR() { + for (int i = 0; i < NumCores; ++i) { + mPtrs1[i] = &mFloats1a[i]; + mDeltaTimes[i] = &mFloats2a[i]; + mPtrs3[i] = &mFloats3a[i]; + mDeltaFrames[i] = &mFloats4a[i]; + mIntervals[i] = &mIntervalA; + mIntervalRatios[i] = &mIntervalRatioA; + } +} + +VFR::~VFR() { + mTimeSpeedMultipliers.freeBuffer(); +} + +void VFR::setDelta(u32 core, f32 delta) { + delta = sead::Mathf::max(delta, 0.01f); + *mPtrs1[core] = delta; + *mDeltaTimes[core] = delta * *mIntervalRatios[core]; + *mPtrs3[core] = delta * mFrameTime; + *mDeltaFrames[core] = *mDeltaTimes[core] * mFrameTime; +} + +void VFR::setDeltaFromTimeMultipliers(u32 core, const sead::BitFlag32& mask) { + f32 min = 1.0; + for (s32 i = 0; i < mTimeSpeedMultipliers.size(); ++i) { + if (mask.isOnBit(i)) + min = sead::Mathf::min(min, mTimeSpeedMultipliers[i].value); + } + setDelta(core, min); +} + +void VFR::resetTimeMultipliers() { + for (auto& entry : mTimeSpeedMultipliers) { + entry.value = 1.0; + entry.target_value = 1.0; + entry.is_custom = false; + } + x_1(); +} + +void VFR::x_1() { + setDeltaFromTimeMultipliers(0, mMask); + + for (s32 i = 0; i < NumCores; ++i) { + mFloats2a[i] = mFloats2a[0]; + mFloats3a[i] = mFloats3a[0]; + mFloats4a[i] = mFloats4a[0]; + mFloats1a[i] = mFloats1a[0]; + } +} + +void VFR::copyAtoB() { + for (s32 i = 0; i < NumCores; ++i) { + mFloats1b[i] = mFloats1a[i]; + mFloats2b[i] = mFloats2a[i]; + mFloats3b[i] = mFloats3a[i]; + mFloats4b[i] = mFloats4a[i]; + mIntervalB = mIntervalA; + mIntervalRatioB = mIntervalRatioA; + } +} + +void VFR::init(u32 interval, int num_speed_multipliers, sead::Heap* heap, u32 mask) { + mInterval = interval; + mFrameRate = 60 / interval; + mIntervalA = interval; + mIntervalB = interval; + mFrameTime = 1.0f / mFrameRate; + mTimeSpeedMultipliers.allocBufferAssert(num_speed_multipliers, heap); + mMask = mask; + x_1(); + copyAtoB(); +} + +void VFR::setIntervalOverride(u32 interval) { + if (!mHasIntervalOverride) { + mHasIntervalOverride = true; + mIntervalOverride = interval; + } +} + +void VFR::clearIntervalOverride() { + mHasIntervalOverride = false; +} + +bool VFR::hasIntervalOverride() const { + return mHasIntervalOverride; +} + +u32 VFR::getIntervalOverride() const { + return mIntervalOverride; +} + +VFR::TimeSpeedMultiplier::TimeSpeedMultiplier() = default; + +VFR::TimeSpeedMultiplier::~TimeSpeedMultiplier() = default; + +void VFR::TimeSpeedMultiplier::update(f32 multiplier) { + if (target_value == value) + return; + + is_custom = target_value != 1.0; + if (target_value < value) { + value = target_value; + } else { + sead::Mathf::chase(&value, target_value, sead::Mathf::max(value * multiplier, 0.01)); + } +} + +void VFR::updateInterval(u32 new_interval) { + copyAtoB(); + + if (mHasIntervalOverride) + new_interval = mIntervalOverride; + + if (new_interval != 0 && mIntervalA != new_interval) { + mIntervalA = new_interval; + mHasIntervalChanged = true; + mNewIntervalRatio = f32(new_interval) * (1.0f / mInterval); + } else { + mHasIntervalChanged = false; + } + + if (mNewIntervalRatio != mIntervalRatioA) + mIntervalRatioA = mNewIntervalRatio; + + for (s32 i = 0; i < mTimeSpeedMultipliers.size(); ++i) { + mTimeSpeedMultipliers[i].update(mIntervalRatioA); + } + + x_1(); +} + +void VFR::useBufferB() { + const u32 core = sead::CoreInfo::getCurrentCoreId(); + mPtrs1[core] = &mFloats1b[core]; + mDeltaTimes[core] = &mFloats2b[core]; + mPtrs3[core] = &mFloats3b[core]; + mDeltaFrames[core] = &mFloats4b[core]; + mIntervals[core] = &mIntervalB; + mIntervalRatios[core] = &mIntervalRatioB; +} + +void VFR::useBufferA() { + const u32 core = sead::CoreInfo::getCurrentCoreId(); + mPtrs1[core] = &mFloats1a[core]; + mDeltaTimes[core] = &mFloats2a[core]; + mPtrs3[core] = &mFloats3a[core]; + mDeltaFrames[core] = &mFloats4a[core]; + mIntervals[core] = &mIntervalA; + mIntervalRatios[core] = &mIntervalRatioA; +} + +f32 VFR::setDeltaFromTimeMultipliers(f32* value, u32 include_mask, u32 exclude_mask) { + const u32 core = sead::CoreInfo::getCurrentCoreId(); + *value = *mPtrs1[core]; + const f32 delta = *mDeltaTimes[core]; + setDeltaFromTimeMultipliers(core, (mMask.getDirect() | include_mask) & ~exclude_mask); + return delta; +} + +bool VFR::hasCustomTimeMultiplier() const { + for (const auto& entry : mTimeSpeedMultipliers) { + if (entry.value != 1.0) + return true; + } + return false; +} + +} // namespace ksys diff --git a/src/KingSystem/System/VFR.h b/src/KingSystem/System/VFR.h new file mode 100644 index 00000000..f3626f96 --- /dev/null +++ b/src/KingSystem/System/VFR.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "KingSystem/Utils/Types.h" + +namespace ksys { + +class VFR { + SEAD_SINGLETON_DISPOSER(VFR) + VFR(); + virtual ~VFR(); + +public: + struct TimeSpeedMultiplier { + TimeSpeedMultiplier(); + ~TimeSpeedMultiplier(); + TimeSpeedMultiplier(const TimeSpeedMultiplier&) = default; + TimeSpeedMultiplier(TimeSpeedMultiplier&&) = default; + TimeSpeedMultiplier& operator=(const TimeSpeedMultiplier&) = default; + TimeSpeedMultiplier& operator=(TimeSpeedMultiplier&&) = default; + + void update(f32 multiplier); + + bool is_custom = false; + f32 value = 1.0; + f32 target_value = 1.0; + }; + + void init(u32 interval, int num_speed_multipliers, sead::Heap* heap, u32 mask); + + void setIntervalOverride(u32 interval); + void clearIntervalOverride(); + bool hasIntervalOverride() const; + u32 getIntervalOverride() const; + + void updateInterval(u32 new_interval); + + void useBufferB(); + void useBufferA(); + + f32 setDeltaFromTimeMultipliers(f32* value, u32 include_mask, u32 exclude_mask); + void resetTimeMultipliers(); + bool hasCustomTimeMultiplier() const; + // TODO: requires ksys::Sound + void setTimeMultiplier(u32 idx, f32 multiplier); + // TODO: requires ksys::Sound + void resetTimeMultiplier(u32 idx); + +private: + struct TimeSpeedMultipliers : sead::Buffer { + TimeSpeedMultipliers() { + mBuffer = nullptr; + mSize = 0; + } + }; + + void setDelta(u32 core, f32 delta); + void setDeltaFromTimeMultipliers(u32 core, const sead::BitFlag32& mask); + void x_1(); + void copyAtoB(); + + bool mHasIntervalChanged = false; + bool mHasIntervalOverride = false; + u32 mIntervalOverride = 1; + f32 mNewIntervalRatio = 1.0; + u32 mIntervalA = 1; + u32 mIntervalB = 1; + f32 mIntervalRatioA = 1.0; + f32 mIntervalRatioB = 1.0; + + static constexpr int NumCores = 3; + + sead::SafeArray mIntervals; + sead::SafeArray mIntervalRatios; + + TimeSpeedMultipliers mTimeSpeedMultipliers; + sead::BitFlag32 mMask = 0xffffffff; + + sead::SafeArray mFloats1a{}; + sead::SafeArray mFloats2a{}; + sead::SafeArray mFloats3a{}; + sead::SafeArray mFloats4a{}; + + sead::SafeArray mFloats1b{}; + sead::SafeArray mFloats2b{}; + sead::SafeArray mFloats3b{}; + sead::SafeArray mFloats4b{}; + + sead::SafeArray mPtrs1; + sead::SafeArray mDeltaTimes; + sead::SafeArray mPtrs3; + sead::SafeArray mDeltaFrames; + /// Present interval. + u32 mInterval = 1; + /// Frames per second. + u32 mFrameRate = 60; + /// Second per frame. + f32 mFrameTime = 1.0 / 60.0; +}; +KSYS_CHECK_SIZE_NX150(VFR, 0x160); + +} // namespace ksys