Lagrange [work/v1.10]

Added build option for sleeping while idle

=> 6a565ea71745aaf4c91a7698bbf56f7d906fcaaa

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 82d55590..0edfbed2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -30,6 +30,7 @@ option (ENABLE_X11_SWRENDER     "Use software rendering under X11" OFF)
 option (ENABLE_KERNING          "Enable kerning in font renderer (slower)" ON)
 option (ENABLE_RESOURCE_EMBED   "Embed resources inside the executable" OFF)
 option (ENABLE_WINDOWPOS_FIX    "Set position after showing window (workaround for SDL bug)" OFF)
+option (ENABLE_IDLE_SLEEP       "While idle, sleep in the main thread instead of waiting for events" ON)
 
 include (BuildType.cmake)
 include (res/Embed.cmake)
@@ -219,6 +220,9 @@ if (ENABLE_MPG123 AND MPG123_FOUND)
     target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_MPG123=1)
     target_link_libraries (app PUBLIC PkgConfig::MPG123)
 endif ()
+if (ENABLE_IDLE_SLEEP)
+    target_compile_definitions (app PUBLIC LAGRANGE_IDLE_SLEEP=1)
+endif ()
 target_link_libraries (app PUBLIC the_Foundation::the_Foundation)
 target_link_libraries (app PUBLIC ${SDL2_LDFLAGS})
 if (APPLE)
diff --git a/res/about/version.gmi b/res/about/version.gmi
index af758486..5944932f 100644
--- a/res/about/version.gmi
+++ b/res/about/version.gmi
@@ -10,6 +10,7 @@
 * 'text/*' content falls back to plain text.
 * Reduced visual artifacts in Unicode box-drawing characters (overlapping/gaps).
 * Fixed truncated tab titles when opening tabs in background.
+* macOS: Fixed excessive CPU usage while idling.
 
 ## 0.12
 * Added MIME hooks: pipe Gemini responses through external programs for arbitrary processing. (See "about:help" for usage.)
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);
Proxy Information
Original URL
gemini://git.skyjake.fi/lagrange/work%2Fv1.10/cdiff/6a565ea71745aaf4c91a7698bbf56f7d906fcaaa
Status Code
Success (20)
Meta
text/gemini; charset=utf-8
Capsule Response Time
55.295988 milliseconds
Gemini-to-HTML Time
0.36479 milliseconds

This content has been proxied by September (ba2dc).