[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
This commit is contained in:
Gonen
2025-04-03 23:17:15 +03:00
parent 95bf012438
commit 4ce91cad3d
13 changed files with 55 additions and 55 deletions

View File

@@ -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:

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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")

View File

@@ -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)

View File

@@ -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:

View File

@@ -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()

View File

@@ -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")

View File

@@ -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

View File

@@ -93,9 +93,7 @@ class SysPatchDisplayFrame(wx.Frame):
frame.ShowWindowModal()
while thread.is_alive():
wx.Yield()
gui_support.wait_for_thread(thread)
frame.Close()

View File

@@ -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()

View File

@@ -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: