Skip to content

Add option to only tick once per frame to Smooth Patch Precise#49

Draft
LazyDuchess wants to merge 7 commits intosims3fiend:mainfrom
LazyDuchess:feat/variable-tps
Draft

Add option to only tick once per frame to Smooth Patch Precise#49
LazyDuchess wants to merge 7 commits intosims3fiend:mainfrom
LazyDuchess:feat/variable-tps

Conversation

@LazyDuchess
Copy link

hi

this adds a checkbox to smooth patch precise that makes the simulate idle busy wait for the next frame after sleeping. has a fallback where it'll simulate anyways if it takes longer than 1 sec so we don't lock up.

i'm leaving it as a draft rn bc i'm not sure how beneficial it is, and there might be a lot of improvements or it might just be useless. tests haven't been super conclusive.

Copy link
Contributor

@just-harry just-harry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a performance optimisation, I'd recommend using std::memory_order_acq_rel for the atomic operations. Though it doesn't really matter in the grand scheme of things.

Anyway, feel free to ignore my comments, shapes is the one in charge.


// I don't know what the boolean at 0xa60 is, but the game's code skips sleeping if it's zero,
// so if it's zero we'll avoid sleeping.
if ((idealTime == 0) | !*reinterpret_cast<const uint8_t*>((reinterpret_cast<uintptr_t>(this) + 0xa60))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't a bug :(
I intentionally used a bitwise-or here instead of a logical-or so that the branch-predictor would have only one branch to deal with instead of two

extern "C" __declspec(dllimport) NTSTATUS __stdcall NtQueryTimerResolution(ULONG* MaximumTime, ULONG* MinimumTime, ULONG* CurrentTime);

static std::atomic<bool> frameSimulate{false};
static std::atomic<bool> frameSimulate{true};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be on the safe side, it might be worthwhile to set this to true also in the Install method?
In case the user disables tickOnce while frameSimulate is false, and then re-enables it.

Comment on lines +79 to +80
double elapsed = (double)(now - beginWaitTime) / (double)performanceFrequency;
if (elapsed >= 1.0) { break; }
Copy link
Contributor

@just-harry just-harry Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm reading this correctly, this code bails out of the wait if a second-or-more has elapsed? If so, the floating-point calculations could be elided like so:

uint64_t elapsed = now - beginWaitTime;
if (elapsed >= performanceFrequency) { break; }

now = beginWaitTime;
while (frameSimulate.exchange(false) == false)
{
if (*reinterpret_cast<const uint32_t*>((reinterpret_cast<uintptr_t>(scriptHost) + 0xc08)) != 1) break;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's at 0xc08?

.supportedVersions = VERSION_ALL,
.technicalDetails = {
"Credit goes to LazyDuchess for the original Smooth Patch.",
"This patch was authored by \"Just Harry\".",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should note your contribution here—don't let me steal the credit for your effort! :)

uint64_t idealTimeForThisCycle = previousSimulationCycleTime + idealTime;
if ((idealTime == 0) || !*reinterpret_cast<const uint8_t*>((reinterpret_cast<uintptr_t>(this) + 0xa60))) {
if (!tickOnce) return previousSimulationCycleTime;
return BusyWaitForFrame(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If BusyWaitForFrame was defined as an instance method of ScriptHostBase, this could be a tail-call; a very minor optimisation, albeit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants