Skip to content

Commit

Permalink
add option to run single threaded
Browse files Browse the repository at this point in the history
  • Loading branch information
qimiko committed May 19, 2024
1 parent 053b0b3 commit b1f7c36
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 65 deletions.
9 changes: 8 additions & 1 deletion mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"settings": {
"disable-controller": {
"name": "Disable Controller Support",
"description": "Runs a different input loop that waits for messages instead of polling. This has greater input precision and may perform better, but will globally disable the use of controllers.",
"description": "Runs a different input loop that waits for messages instead of polling. This has greater input precision and may perform better, but will globally disable the use of controllers. Has no effect in single-threaded mode.",
"type": "bool",
"default": false,
"platforms": ["windows"]
Expand All @@ -28,6 +28,13 @@
"slider": false
},
"platforms": ["windows"]
},
"single-threaded": {
"name": "Run Single Threaded (req. restart)",
"description": "Runs the game on a single thread, interweaving input and rendering. This has significantly reduced performance, but may be more stable in some cases. Input polling rate will be limited to the display refresh rate if this option and VSync are enabled. Most similar to the traditional TPS bypass/draw divide.",
"type": "bool",
"default": false,
"platforms": ["windows"]
}
},
"id": "zmx.cbf-lite",
Expand Down
174 changes: 110 additions & 64 deletions src/windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ LARGE_INTEGER* s_nTimeElapsed = nullptr;
int* s_iFrameCounter = nullptr;
float* s_fFrameTime = nullptr;
std::atomic_bool g_waitForMessages{};
bool g_runSingleThreaded = false;
bool g_singleThreadedInInputPoll = false;

class GameEvent {
protected:
Expand Down Expand Up @@ -149,6 +151,10 @@ struct CustomCCEGLView : geode::Modify<CustomCCEGLView, cocos2d::CCEGLView> {
// - unset the main thread's context (again)
// - set the rendering thread's context back

if (g_runSingleThreaded) {
return CCEGLView::toggleFullScreen(fullscreen, borderless);
}

if (GetCurrentThreadId() != g_mainThreadId) {
std::unique_lock fsLock(g_fullscreenMutex);
g_needsSwitchFullscreen = true;
Expand Down Expand Up @@ -394,7 +400,7 @@ struct AsyncCCKeyboardDispatcher : geode::Modify<AsyncCCKeyboardDispatcher, coco
}

bool dispatchKeyboardMSG(cocos2d::enumKeyCodes key, bool isDown, bool isRepeat) {
if (GetCurrentThreadId() != CustomCCEGLView::g_renderingThreadId) {
if (g_singleThreadedInInputPoll || GetCurrentThreadId() != CustomCCEGLView::g_renderingThreadId) {
// queue our event
auto event = new ManualKeyboardEvent(key, isDown, isRepeat);
CustomCCEGLView::g_self->queueEvent(event);
Expand All @@ -413,16 +419,7 @@ struct CustomCCApplication : geode::Modify<CustomCCApplication, cocos2d::CCAppli
(void)self.setHookPriority("cocos2d::CCApplication::run", 5000);
}

// while this function is unused, i thought it'd be good to keep it for reference purposes
int runSingleThreaded() {
// setup hidden function pointers
PVRFrameEnableControlWindow = reinterpret_cast<decltype(PVRFrameEnableControlWindow)>(geode::base::getCocos() + 0xc4190);
updateControllerState = reinterpret_cast<decltype(updateControllerState)>(geode::base::getCocos() + 0xcb960);

s_nTimeElapsed = reinterpret_cast<LARGE_INTEGER*>(geode::base::getCocos() + 0x1a5a80);
s_iFrameCounter = reinterpret_cast<int*>(geode::base::getCocos() + 0x1a5a7c);
s_fFrameTime = reinterpret_cast<float*>(geode::base::getCocos() + 0x1a5a78);

PVRFrameEnableControlWindow(false);
this->setupGLView();

Expand All @@ -433,16 +430,33 @@ struct CustomCCApplication : geode::Modify<CustomCCApplication, cocos2d::CCAppli
this->setupVerticalSync();
this->updateVerticalSync();

auto customGlView = static_cast<CustomCCEGLView*>(glView);
CustomCCEGLView::g_mainThreadId = GetCurrentThreadId();
CustomCCEGLView::g_renderingThreadId = GetCurrentThreadId();

if (!this->applicationDidFinishLaunching()) {
return 0;
}

auto isFullscreen = glView->m_bIsFullscreen;
this->m_bFullscreen = isFullscreen;

LARGE_INTEGER lastTime;
QueryPerformanceCounter(&lastTime);
*s_nTimeElapsed = lastTime;
auto freq = query_performance_frequency();
double dFreq = freq;

std::int64_t currentInputPollingRate = g_pollingRate;
double pollUpdateRate = 1.0 / static_cast<double>(currentInputPollingRate);

auto pollInterval = static_cast<std::uint64_t>(freq * pollUpdateRate);

auto inputFrames = 0u;
auto inputTime = 0.0f;

LARGE_INTEGER lastFrameTime;
LARGE_INTEGER lastInputTime;
QueryPerformanceCounter(&lastFrameTime);
lastInputTime.QuadPart = lastFrameTime.QuadPart;
*s_nTimeElapsed = lastFrameTime;

while (true) {
if (glView->windowShouldClose()) {
Expand Down Expand Up @@ -473,73 +487,100 @@ struct CustomCCApplication : geode::Modify<CustomCCApplication, cocos2d::CCAppli
}
isFullscreen = this->m_bFullscreen;

auto updateRate = m_bVerticalSyncEnabled ? m_fVsyncInterval : m_fAnimationInterval;
auto interval = m_bVerticalSyncEnabled ? m_nVsyncInterval : m_nAnimationInterval;

auto useFrameCount = !isFullscreen && !this->m_bForceTimer;

LARGE_INTEGER currentTime;
QueryPerformanceCounter(&currentTime);
*s_nTimeElapsed = currentTime;

if (!useFrameCount && currentTime.QuadPart - lastTime.QuadPart < interval.QuadPart) {
continue;
}
if (currentTime.QuadPart - lastInputTime.QuadPart >= pollInterval) {
g_singleThreadedInInputPoll = true;

if (this->m_bUpdateController) {
updateControllerState(this->m_pControllerHandler);
updateControllerState(this->m_pController2Handler);
this->m_bUpdateController = false;
}
if (this->m_bUpdateController) {
updateControllerState(this->m_pControllerHandler);
updateControllerState(this->m_pController2Handler);
this->m_bUpdateController = false;
}

auto player1Controller = this->m_pControllerHandler;
auto player2Controller = this->m_pController2Handler;
auto player1Controller = this->m_pControllerHandler;
auto player2Controller = this->m_pController2Handler;

this->m_bControllerConnected = player1Controller->m_controllerConnected || player2Controller->m_controllerConnected;
this->m_bControllerConnected = player1Controller->m_controllerConnected || player2Controller->m_controllerConnected;

if (player1Controller->m_controllerConnected) {
this->updateControllerKeys(player1Controller, 1);
}
if (player1Controller->m_controllerConnected) {
this->updateControllerKeys(player1Controller, 1);
}

if (player2Controller->m_controllerConnected) {
this->updateControllerKeys(player2Controller, 2);
}
if (player2Controller->m_controllerConnected) {
this->updateControllerKeys(player2Controller, 2);
}

auto dCurrentTime = static_cast<double>(currentTime.QuadPart);
auto dLastTime = static_cast<double>(lastTime.QuadPart);
auto dInterval = static_cast<double>(interval.QuadPart);
auto dUpdateRate = static_cast<double>(updateRate);
auto dt = static_cast<double>(currentTime.QuadPart - lastInputTime.QuadPart) / dFreq;

auto dt = ((dCurrentTime - dLastTime) / dInterval) * dUpdateRate;
lastTime.QuadPart = currentTime.QuadPart;
inputFrames++;
inputTime += dt;

if (useFrameCount) {
auto currentFrameTime = *s_fFrameTime + dt;
(*s_iFrameCounter)++;
*s_fFrameTime = currentFrameTime;
if (0.5 < currentFrameTime) {
auto frameInterval = static_cast<float>(*s_iFrameCounter);
*s_iFrameCounter = 0;
*s_fFrameTime = 0.0f;
if (inputTime > 0.5f) {
g_inputTps = std::round(inputFrames / inputTime);
inputFrames = 0;
inputTime = 0.0f;

auto targetFps = 1.0 / updateRate;
auto currentFps = frameInterval / currentFrameTime;
if ((targetFps * 1.2) < currentFps) {
this->m_bForceTimer = true;
// recalculate interval here if necessary
if (currentInputPollingRate != g_pollingRate) {
currentInputPollingRate = g_pollingRate;
pollUpdateRate = 1.0 / currentInputPollingRate;
pollInterval = static_cast<std::uint64_t>(freq * pollUpdateRate);
}
}
}

// i haven't actually figured out what happens here
auto actualDeltaTime = dt;
if (m_bSmoothFix && director->getSmoothFixCheck() && std::abs(dt - updateRate) <= updateRate * 0.1f) {
actualDeltaTime = updateRate;
glView->pollEvents();

g_singleThreadedInInputPoll = false;
lastInputTime.QuadPart = currentTime.QuadPart;
}

glView->pollEvents();
director->setDeltaTime(dt);
director->setActualDeltaTime(actualDeltaTime);
reinterpret_cast<cocos2d::CCDisplayLinkDirector*>(director)->mainLoop();
auto updateRate = m_bVerticalSyncEnabled ? m_fVsyncInterval : m_fAnimationInterval;
auto interval = m_bVerticalSyncEnabled ? m_nVsyncInterval : m_nAnimationInterval;

auto useFrameCount = !isFullscreen && !this->m_bForceTimer;

if (useFrameCount || currentTime.QuadPart - lastFrameTime.QuadPart >= interval.QuadPart) {
// run update
auto dCurrentTime = static_cast<double>(currentTime.QuadPart);
auto dLastTime = static_cast<double>(lastFrameTime.QuadPart);
auto dInterval = static_cast<double>(interval.QuadPart);
auto dUpdateRate = static_cast<double>(updateRate);

auto dt = ((dCurrentTime - dLastTime) / dInterval) * dUpdateRate;
lastFrameTime.QuadPart = currentTime.QuadPart;

if (useFrameCount) {
auto currentFrameTime = *s_fFrameTime + dt;
(*s_iFrameCounter)++;
*s_fFrameTime = currentFrameTime;
if (0.5 < currentFrameTime) {
auto frameInterval = static_cast<float>(*s_iFrameCounter);
*s_iFrameCounter = 0;
*s_fFrameTime = 0.0f;

auto targetFps = 1.0 / updateRate;
auto currentFps = frameInterval / currentFrameTime;
if ((targetFps * 1.2) < currentFps) {
this->m_bForceTimer = true;
}
}
}

// i haven't actually figured out what happens here
auto adjustedDeltaTime = dt;
if (m_bSmoothFix && director->getSmoothFixCheck() && std::abs(dt - updateRate) <= updateRate * 0.1f) {
adjustedDeltaTime = updateRate;
}

customGlView->dumpEventQueue();

director->setDeltaTime(adjustedDeltaTime);
director->setActualDeltaTime(dt);
reinterpret_cast<cocos2d::CCDisplayLinkDirector*>(director)->mainLoop();
}
}
}

Expand All @@ -555,6 +596,10 @@ struct CustomCCApplication : geode::Modify<CustomCCApplication, cocos2d::CCAppli
s_iFrameCounter = reinterpret_cast<int*>(geode::base::getCocos() + 0x1a5a7c);
s_fFrameTime = reinterpret_cast<float*>(geode::base::getCocos() + 0x1a5a78);

if (g_runSingleThreaded) {
return runSingleThreaded();
}

PVRFrameEnableControlWindow(false);
this->setupGLView();

Expand All @@ -571,7 +616,7 @@ struct CustomCCApplication : geode::Modify<CustomCCApplication, cocos2d::CCAppli
QueryPerformanceCounter(&lastTime);

std::int64_t currentPollingRate = g_pollingRate;
double updateRate = 1.0 / currentPollingRate;
double updateRate = 1.0 / static_cast<double>(currentPollingRate);

auto freq = query_performance_frequency();
double dFreq = freq;
Expand Down Expand Up @@ -793,5 +838,6 @@ struct CustomCCDirector : geode::Modify<CustomCCDirector, cocos2d::CCDirector> {
listenForSettingChanges("input-rate", +[](std::int64_t value) {
g_pollingRate = value;
});
}

g_runSingleThreaded = Mod::get()->getSettingValue<bool>("single-threaded");
}

0 comments on commit b1f7c36

Please sign in to comment.