From 4ce91cad3d3201bbbe9b3fca27aab298bc6c4414 Mon Sep 17 00:00:00 2001 From: Gonen <830775+goneng@users.noreply.github.com> Date: Thu, 3 Apr 2025 23:17:15 +0300 Subject: [PATCH] [WIP] GUI: Reduce CPU usage by adding sleep intervals to UI wait loops Fix issue where the GUI main thread was consuming excessive CPU while waiting for background tasks to complete. By adding a configurable sleep interval between UI updates, CPU usage is significantly reduced while maintaining responsiveness. - Add thread_sleep_interval constant to global Constants class - Implement wait_for_thread utility function in gui_support - Update all wxPython event loops to use the new helper - Test shows approximately 60% CPU usage reduction during waits --- CHANGELOG.md | 3 +++ opencore_legacy_patcher/application_entry.py | 2 +- opencore_legacy_patcher/constants.py | 2 ++ opencore_legacy_patcher/wx_gui/gui_build.py | 5 ++-- .../wx_gui/gui_cache_os_update.py | 15 ++++-------- .../wx_gui/gui_download.py | 2 ++ .../wx_gui/gui_install_oc.py | 7 ++---- .../wx_gui/gui_macos_installer_download.py | 6 ++--- .../wx_gui/gui_macos_installer_flash.py | 23 +++++++++---------- opencore_legacy_patcher/wx_gui/gui_support.py | 13 +++++++++++ .../wx_gui/gui_sys_patch_display.py | 4 +--- .../wx_gui/gui_sys_patch_start.py | 16 +++++-------- opencore_legacy_patcher/wx_gui/gui_update.py | 12 ++++------ 13 files changed, 55 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b32f273f..a576e36f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # OpenCore Legacy Patcher changelog +## 2.4.0 +- Reduce CPU usage on main UI thread + ## 2.3.2 - Resolve erroring in Passwords app and Safari Autofill on T1 Macs running 15.4 or later - Increment binaries: diff --git a/opencore_legacy_patcher/application_entry.py b/opencore_legacy_patcher/application_entry.py index 8617f5b2d..c1b7d454b 100644 --- a/opencore_legacy_patcher/application_entry.py +++ b/opencore_legacy_patcher/application_entry.py @@ -130,7 +130,7 @@ class OpenCoreLegacyPatcher: if not any(x in sys.argv for x in ignore_args): while self.constants.unpack_thread.is_alive(): - time.sleep(0.1) + time.sleep(self.constants.thread_sleep_interval) arguments.arguments(self.constants) diff --git a/opencore_legacy_patcher/constants.py b/opencore_legacy_patcher/constants.py index 550856877..eeaf9e909 100644 --- a/opencore_legacy_patcher/constants.py +++ b/opencore_legacy_patcher/constants.py @@ -155,6 +155,8 @@ class Constants: self.unpack_thread = None # Determine if unpack thread finished (threading.Thread) self.update_stage: int = 0 # Determine update stage (see gui_support.py) self.log_filepath: Path = None # Path to log file + self.thread_sleep_interval: float = 0.1 # Sleep interval between UI updates (seconds) - reduce refresh-rate to reduce CPU-usage + self.thread_nap_interval: float = 0.01 # Short Sleep interval between UI updates (seconds) - for faster UI updates of the progress bar self.commit_info: tuple = (None, None, None) # Commit info (Branch, Commit Date, Commit URL) diff --git a/opencore_legacy_patcher/wx_gui/gui_build.py b/opencore_legacy_patcher/wx_gui/gui_build.py index f673a2b80..796ba8c0b 100644 --- a/opencore_legacy_patcher/wx_gui/gui_build.py +++ b/opencore_legacy_patcher/wx_gui/gui_build.py @@ -6,6 +6,7 @@ import wx import logging import threading import traceback +import time from .. import constants @@ -101,12 +102,12 @@ class BuildFrame(wx.Frame): """ while gui_support.PayloadMount(self.constants, self).is_unpack_finished() is False: wx.Yield() + time.sleep(self.constants.thread_sleep_interval) thread = threading.Thread(target=self._build) thread.start() - while thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(thread) self.return_button.Enable() diff --git a/opencore_legacy_patcher/wx_gui/gui_cache_os_update.py b/opencore_legacy_patcher/wx_gui/gui_cache_os_update.py index 5f6db193c..e3dfd0113 100644 --- a/opencore_legacy_patcher/wx_gui/gui_cache_os_update.py +++ b/opencore_legacy_patcher/wx_gui/gui_cache_os_update.py @@ -76,13 +76,11 @@ class OSUpdateFrame(wx.Frame): if results[HardwarePatchsetSettings.KERNEL_DEBUG_KIT_REQUIRED] is True: kdk_thread = threading.Thread(target=_kdk_thread_spawn) kdk_thread.start() - while kdk_thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(kdk_thread) if results[HardwarePatchsetSettings.METALLIB_SUPPORT_PKG_REQUIRED] is True: metallib_thread = threading.Thread(target=_metallib_thread_spawn) metallib_thread.start() - while metallib_thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(metallib_thread) download_objects = { @@ -149,8 +147,7 @@ class OSUpdateFrame(wx.Frame): kdk_checksum_thread = threading.Thread(target=_validate_kdk_checksum_thread) kdk_checksum_thread.start() - while kdk_checksum_thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(kdk_checksum_thread) if self.kdk_checksum_result is False: logging.error("KDK checksum validation failed") @@ -172,8 +169,7 @@ class OSUpdateFrame(wx.Frame): kdk_install_thread = threading.Thread(target=_install_kdk_thread) kdk_install_thread.start() - while kdk_install_thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(kdk_install_thread) if self.kdk_install_result is False: logging.info("Failed to install KDK") @@ -194,8 +190,7 @@ class OSUpdateFrame(wx.Frame): metallib_install_thread = threading.Thread(target=_install_metallib_thread) metallib_install_thread.start() - while metallib_install_thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(metallib_install_thread) if self.metallib_install_result is False: logging.info("Failed to install Metallib") diff --git a/opencore_legacy_patcher/wx_gui/gui_download.py b/opencore_legacy_patcher/wx_gui/gui_download.py index aca36e9aa..6b34e578b 100644 --- a/opencore_legacy_patcher/wx_gui/gui_download.py +++ b/opencore_legacy_patcher/wx_gui/gui_download.py @@ -4,6 +4,7 @@ gui_download.py: Generate UI for downloading files import wx import logging +import time from .. import constants @@ -86,6 +87,7 @@ class DownloadFrame(wx.Frame): label_amount.Centre(wx.HORIZONTAL) wx.Yield() + time.sleep(self.constants.thread_sleep_interval) if self.download_obj.download_complete is False and self.user_cancelled is False: wx.MessageBox(f"Download failed: \n{self.download_obj.error_msg}", "Error", wx.OK | wx.ICON_ERROR) diff --git a/opencore_legacy_patcher/wx_gui/gui_install_oc.py b/opencore_legacy_patcher/wx_gui/gui_install_oc.py index d5375d6d5..ef4ef4c8f 100644 --- a/opencore_legacy_patcher/wx_gui/gui_install_oc.py +++ b/opencore_legacy_patcher/wx_gui/gui_install_oc.py @@ -103,9 +103,7 @@ class InstallOCFrame(wx.Frame): thread = threading.Thread(target=self._fetch_disks) thread.start() - while thread.is_alive(): - wx.Yield() - continue + gui_support.wait_for_thread(thread) self.progress_bar_animation.stop_pulse() self.progress_bar.Hide() @@ -281,8 +279,7 @@ class InstallOCFrame(wx.Frame): thread = threading.Thread(target=self._install_oc, args=(partition,)) thread.start() - while thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(thread) if self.result is True: if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE and self.constants.detected_os >= os_data.os_data.big_sur: diff --git a/opencore_legacy_patcher/wx_gui/gui_macos_installer_download.py b/opencore_legacy_patcher/wx_gui/gui_macos_installer_download.py index 27da9501f..a05a271e2 100644 --- a/opencore_legacy_patcher/wx_gui/gui_macos_installer_download.py +++ b/opencore_legacy_patcher/wx_gui/gui_macos_installer_download.py @@ -149,8 +149,7 @@ class macOSInstallerDownloadFrame(wx.Frame): thread = threading.Thread(target=_fetch_installers) thread.start() - while thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(thread) progress_bar_animation.stop_pulse() progress_bar.Hide() @@ -412,8 +411,7 @@ class macOSInstallerDownloadFrame(wx.Frame): self.Show() # Wait for thread to finish - while thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(thread) progress_bar_animation.stop_pulse() progress_bar.Hide() diff --git a/opencore_legacy_patcher/wx_gui/gui_macos_installer_flash.py b/opencore_legacy_patcher/wx_gui/gui_macos_installer_flash.py index e25923c05..80913b548 100644 --- a/opencore_legacy_patcher/wx_gui/gui_macos_installer_flash.py +++ b/opencore_legacy_patcher/wx_gui/gui_macos_installer_flash.py @@ -88,8 +88,7 @@ class macOSInstallerFlashFrame(wx.Frame): thread = threading.Thread(target=fetch_installers) thread.start() - while thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(thread) frame_modal = wx.Dialog(self, title=self.title, size=(350, 200)) @@ -180,8 +179,7 @@ class macOSInstallerFlashFrame(wx.Frame): thread = threading.Thread(target=_fetch_disks) thread.start() - while thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(thread) self.frame_modal = wx.Dialog(self, title=self.title, size=(350, 200)) @@ -317,7 +315,9 @@ class macOSInstallerFlashFrame(wx.Frame): except: bytes_written = 0 wx.CallAfter(progress_bar.SetValue, bytes_written) + wx.Yield() + time.sleep(self.constants.thread_sleep_interval) if self.result is False: logging.error("Failed to flash installer, cannot continue.") @@ -370,8 +370,7 @@ class macOSInstallerFlashFrame(wx.Frame): thread = threading.Thread(target=prepare_script, args=(self, installer_path, disk, self.constants)) thread.start() - while thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(thread) return self.prepare_result @@ -399,10 +398,11 @@ class macOSInstallerFlashFrame(wx.Frame): return False logging.info("Successfully created macOS installer") - while thread.is_alive(): - # wait for download_thread to finish - # though highly unlikely this thread is still alive (flashing an Installer will take a while) - time.sleep(0.1) + + # wait for download_thread to finish + # though highly unlikely this thread is still alive (flashing an Installer will take a while) + gui_support.wait_for_thread(thread) + logging.info("Installing Root Patcher to drive") self._install_installer_pkg(disk) @@ -617,8 +617,7 @@ class macOSInstallerFlashFrame(wx.Frame): thread = threading.Thread(target=_integrity_check) thread.start() - while thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(thread) if error_message == "": logging.info("Installer pkg validated") diff --git a/opencore_legacy_patcher/wx_gui/gui_support.py b/opencore_legacy_patcher/wx_gui/gui_support.py index 2cac53fa3..5882833d3 100644 --- a/opencore_legacy_patcher/wx_gui/gui_support.py +++ b/opencore_legacy_patcher/wx_gui/gui_support.py @@ -263,6 +263,19 @@ class ThreadHandler(logging.Handler): wx.CallAfter(self.text_box.AppendText, self.format(record) + '\n') +def wait_for_thread(thread: threading.Thread, sleep_interval=None): + """ + Waits for a thread to finish while processing UI events at regular intervals + to prevent UI freezing and excessive CPU usage. + """ + # Use the passed sleep_interval, or get from global_constants + interval = sleep_interval if sleep_interval is not None else constants.Constants().thread_sleep_interval + + while thread.is_alive(): + wx.Yield() + time.sleep(interval) + + class RestartHost: """ Restarts the host machine diff --git a/opencore_legacy_patcher/wx_gui/gui_sys_patch_display.py b/opencore_legacy_patcher/wx_gui/gui_sys_patch_display.py index 747e5f60b..082f21ec8 100644 --- a/opencore_legacy_patcher/wx_gui/gui_sys_patch_display.py +++ b/opencore_legacy_patcher/wx_gui/gui_sys_patch_display.py @@ -93,9 +93,7 @@ class SysPatchDisplayFrame(wx.Frame): frame.ShowWindowModal() - while thread.is_alive(): - wx.Yield() - + gui_support.wait_for_thread(thread) frame.Close() diff --git a/opencore_legacy_patcher/wx_gui/gui_sys_patch_start.py b/opencore_legacy_patcher/wx_gui/gui_sys_patch_start.py index 75d1695ea..7f0a71c74 100644 --- a/opencore_legacy_patcher/wx_gui/gui_sys_patch_start.py +++ b/opencore_legacy_patcher/wx_gui/gui_sys_patch_start.py @@ -88,8 +88,7 @@ class SysPatchStartFrame(wx.Frame): kdk_thread = threading.Thread(target=_kdk_thread_spawn) kdk_thread.start() - while kdk_thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(kdk_thread) if self.kdk_obj.success is False: progress_bar_animation.stop_pulse() @@ -170,8 +169,7 @@ class SysPatchStartFrame(wx.Frame): metallib_thread = threading.Thread(target=_metallib_thread_spawn) metallib_thread.start() - while metallib_thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(metallib_thread) if self.metallib_obj.success is False: progress_bar_animation.stop_pulse() @@ -209,8 +207,7 @@ class SysPatchStartFrame(wx.Frame): install_thread = threading.Thread(target=_install_metallib) install_thread.start() - while install_thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(install_thread) if self.result is False: progress_bar_animation.stop_pulse() @@ -314,6 +311,7 @@ class SysPatchStartFrame(wx.Frame): while gui_support.PayloadMount(self.constants, self).is_unpack_finished() is False: wx.Yield() + time.sleep(self.constants.thread_sleep_interval) if self.patches[HardwarePatchsetSettings.KERNEL_DEBUG_KIT_REQUIRED] is True: if self._kdk_download(self) is False: @@ -329,8 +327,7 @@ class SysPatchStartFrame(wx.Frame): thread = threading.Thread(target=self._start_root_patching, args=(self.patches,)) thread.start() - while thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(thread) self._post_patch() self.return_button.Enable() @@ -356,8 +353,7 @@ class SysPatchStartFrame(wx.Frame): thread = threading.Thread(target=self._revert_root_patching, args=(self.patches,)) thread.start() - while thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(thread) self._post_patch() self.return_button.Enable() diff --git a/opencore_legacy_patcher/wx_gui/gui_update.py b/opencore_legacy_patcher/wx_gui/gui_update.py index 14a6a3841..d1902f94a 100644 --- a/opencore_legacy_patcher/wx_gui/gui_update.py +++ b/opencore_legacy_patcher/wx_gui/gui_update.py @@ -102,8 +102,7 @@ class UpdateFrame(wx.Frame): thread = threading.Thread(target=_fetch_update) thread.start() - while thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(thread) gui_download.DownloadFrame( self.frame, @@ -128,8 +127,7 @@ class UpdateFrame(wx.Frame): thread = threading.Thread(target=self._extract_update) thread.start() - while thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(thread) # Title: Installing update title_label.SetLabel("Installing update...") @@ -138,8 +136,7 @@ class UpdateFrame(wx.Frame): thread = threading.Thread(target=self._install_update) thread.start() - while thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(thread) # Title: Update complete title_label.SetLabel("Update complete!") @@ -170,8 +167,7 @@ class UpdateFrame(wx.Frame): thread = threading.Thread(target=self._launch_update) thread.start() - while thread.is_alive(): - wx.Yield() + gui_support.wait_for_thread(thread) timer = 5 while True: