From 6a565ea71745aaf4c91a7698bbf56f7d906fcaaa Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sun, 6 Dec 2020 11:52:26 +0200 Subject: Added build option for sleeping while idle It appears at least on macOS, SDL is doing a while lot of stuff while waiting for new events. Perhaps because it has some sort of high-frequency input/sensor processing? Not sure. Now Lagrange will idle by polling events every 15 ms and sleeping in between. This reduces CPU time by an order of magnitude on macOS. Need to still test on other platforms. --- src/app.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- src/app.h | 1 + 2 files changed, 71 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/app.c b/src/app.c index 1e0a847d..ba7d41d2 100644 --- a/src/app.c +++ b/src/app.c @@ -89,6 +89,8 @@ static const char *prefsFileName_App_ = "prefs.cfg"; static const char *stateFileName_App_ = "state.binary"; static const char *downloadDir_App_ = "~/Downloads"; +static const int idleThreshold_App_ = 1000; /* ms */ + struct Impl_App { iCommandLine args; iString * execPath; @@ -100,7 +102,12 @@ struct Impl_App { iSortedArray tickers; uint32_t lastTickerTime; uint32_t elapsedSinceLastTicker; - iBool running; + iBool isRunning; +#if defined (LAGRANGE_IDLE_SLEEP) + iBool isIdling; + uint32_t lastEventTime; + int sleepTimer; +#endif iAtomicInt pendingRefresh; int tabEnum; iStringList *launchCommands; @@ -320,6 +327,16 @@ static void saveState_App_(const iApp *d) { iRelease(f); } +#if defined (LAGRANGE_IDLE_SLEEP) +static uint32_t checkAsleep_App_(uint32_t interval, void *param) { + iApp *d = param; + SDL_Event ev = { .type = SDL_USEREVENT }; + ev.user.code = asleep_UserEventCode; + SDL_PushEvent(&ev); + return interval; +} +#endif + static void init_App_(iApp *d, int argc, char **argv) { const iBool isFirstRun = !fileExistsCStr_FileInfo(cleanedPath_CStr(dataDir_App_)); d->isFinishedLaunching = iFalse; @@ -349,7 +366,7 @@ static void init_App_(iApp *d, int argc, char **argv) { #endif init_Prefs(&d->prefs); setCStr_String(&d->prefs.downloadDir, downloadDir_App_); - d->running = iFalse; + d->isRunning = iFalse; d->window = NULL; set_Atomic(&d->pendingRefresh, iFalse); d->mimehooks = new_MimeHooks(); @@ -358,6 +375,11 @@ static void init_App_(iApp *d, int argc, char **argv) { d->bookmarks = new_Bookmarks(); d->tabEnum = 0; /* generates unique IDs for tab pages */ setThemePalette_Color(d->prefs.theme); +#if defined (LAGRANGE_IDLE_SLEEP) + d->isIdling = iFalse; + d->lastEventTime = 0; + d->sleepTimer = SDL_AddTimer(1000, checkAsleep_App_, d); +#endif #if defined (iPlatformApple) setupApplication_MacOS(); #endif @@ -484,19 +506,25 @@ const iString *debugInfo_App(void) { } iLocalDef iBool isWaitingAllowed_App_(iApp *d) { +#if defined (LAGRANGE_IDLE_SLEEP) + if (d->isIdling) { + return iFalse; + } +#endif return !value_Atomic(&d->pendingRefresh) && isEmpty_SortedArray(&d->tickers); } void processEvents_App(enum iAppEventMode eventMode) { iApp *d = &app_; SDL_Event ev; + iBool gotEvents = iFalse; while ((isWaitingAllowed_App_(d) && eventMode == waitForNewEvents_AppEventMode && SDL_WaitEvent(&ev)) || ((!isWaitingAllowed_App_(d) || eventMode == postedEventsOnly_AppEventMode) && SDL_PollEvent(&ev))) { switch (ev.type) { case SDL_QUIT: - d->running = iFalse; + d->isRunning = iFalse; goto backToMainLoop; case SDL_DROPFILE: { iBool newTab = iFalse; @@ -516,6 +544,25 @@ void processEvents_App(enum iAppEventMode eventMode) { break; } default: { +#if defined (LAGRANGE_IDLE_SLEEP) + if (ev.type == SDL_USEREVENT && ev.user.code == asleep_UserEventCode) { + if (SDL_GetTicks() - d->lastEventTime > idleThreshold_App_) { + if (!d->isIdling) { + printf("[App] idling...\n"); + fflush(stdout); + } + d->isIdling = iTrue; + } + continue; + } + d->lastEventTime = SDL_GetTicks(); + if (d->isIdling) { + printf("[App] ...woke up\n"); + fflush(stdout); + } + d->isIdling = iFalse; +#endif + gotEvents = iTrue; iBool wasUsed = processEvent_Window(d->window, &ev); if (!wasUsed) { /* There may be a key bindings for this. */ @@ -539,6 +586,14 @@ void processEvents_App(enum iAppEventMode eventMode) { } } } +#if defined (LAGRANGE_IDLE_SLEEP) + if (d->isIdling && !gotEvents) { + /* This is where we spend most of our time when idle. 60 Hz still quite a lot but we + can't wait too long after the user tries to interact again with the app. In any + case, on macOS SDL_WaitEvent() seems to use 10x more CPU time than sleeping. */ + SDL_Delay(1000 / 60); + } +#endif backToMainLoop:; } @@ -586,10 +641,10 @@ static int resizeWatcher_(void *user, SDL_Event *event) { static int run_App_(iApp *d) { arrange_Widget(findWidget_App("root")); - d->running = iTrue; + d->isRunning = iTrue; SDL_EventState(SDL_DROPFILE, SDL_ENABLE); /* open files via drag'n'drop */ SDL_AddEventWatch(resizeWatcher_, d); - while (d->running) { + while (d->isRunning) { processEvents_App(waitForNewEvents_AppEventMode); runTickers_App_(d); refresh_App(); @@ -600,6 +655,9 @@ static int run_App_(iApp *d) { void refresh_App(void) { iApp *d = &app_; +#if defined (LAGRANGE_IDLE_SLEEP) + if (d->isIdling) return; +#endif destroyPending_Widget(); draw_Window(d->window); set_Atomic(&d->pendingRefresh, iFalse); @@ -657,6 +715,9 @@ int run_App(int argc, char **argv) { void postRefresh_App(void) { iApp *d = &app_; +#if defined (LAGRANGE_IDLE_SLEEP) + d->isIdling = iFalse; +#endif const iBool wasPending = exchange_Atomic(&d->pendingRefresh, iTrue); if (!wasPending) { SDL_Event ev; @@ -1085,12 +1146,12 @@ iBool handleCommand_App(const char *cmd) { } else if (equal_Command(cmd, "prefs.sideicon.changed")) { d->prefs.sideIcon = arg_Command(cmd) != 0; - refresh_App(); + postRefresh_App(); return iTrue; } else if (equal_Command(cmd, "prefs.hoveroutline.changed")) { d->prefs.hoverOutline = arg_Command(cmd) != 0; - refresh_App(); + postRefresh_App(); return iTrue; } else if (equal_Command(cmd, "saturation.set")) { @@ -1373,13 +1434,13 @@ iBool handleCommand_App(const char *cmd) { } else if (equal_Command(cmd, "feeds.update.started")) { setFlags_Widget(findWidget_App("feeds.progress"), hidden_WidgetFlag, iFalse); - refresh_App(); + postRefresh_App(); return iFalse; } else if (equal_Command(cmd, "feeds.update.finished")) { setFlags_Widget(findWidget_App("feeds.progress"), hidden_WidgetFlag, iTrue); refreshFinished_Feeds(); - refresh_App(); + postRefresh_App(); return iFalse; } else if (equal_Command(cmd, "visited.changed")) { diff --git a/src/app.h b/src/app.h index bc086dfe..743484a5 100644 --- a/src/app.h +++ b/src/app.h @@ -46,6 +46,7 @@ enum iAppEventMode { enum iUserEventCode { command_UserEventCode = 1, refresh_UserEventCode = 2, + asleep_UserEventCode = 3, }; const iString *execPath_App (void); -- cgit v1.2.3