Cache and cpu / mem improvements for launcher when idle.
This commit is contained in:
@@ -91,6 +91,12 @@ class AppPaths:
|
|||||||
def launcher_update_lock(self) -> Path:
|
def launcher_update_lock(self) -> Path:
|
||||||
return self.state / "launcher-update.lock"
|
return self.state / "launcher-update.lock"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def launcher_update_check_cache(self) -> Path:
|
||||||
|
"""Display-only cache of the last authenticated update check."""
|
||||||
|
|
||||||
|
return self.cache / "launcher-update-check.json"
|
||||||
|
|
||||||
def launcher_update_archive(self, version: str) -> Path:
|
def launcher_update_archive(self, version: str) -> Path:
|
||||||
return self.downloads / f"pristontale-eu-linux-update-v{version}.tar.gz"
|
return self.downloads / f"pristontale-eu-linux-update-v{version}.tar.gz"
|
||||||
|
|
||||||
@@ -118,6 +124,12 @@ class AppPaths:
|
|||||||
def launcher_operation_lock(self) -> Path:
|
def launcher_operation_lock(self) -> Path:
|
||||||
return self.state / "launcher-operation.lock"
|
return self.state / "launcher-operation.lock"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def game_runtime_marker(self) -> Path:
|
||||||
|
"""Verified Proton/D3D preparation state for the game prefix."""
|
||||||
|
|
||||||
|
return self.state / "game-runtime.json"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def source_game(self) -> Path:
|
def source_game(self) -> Path:
|
||||||
return self.launcher_prefix / GAME_RELATIVE_PATH
|
return self.launcher_prefix / GAME_RELATIVE_PATH
|
||||||
|
|||||||
148
ptlinux/ui.py
148
ptlinux/ui.py
@@ -28,11 +28,18 @@ from .news import (
|
|||||||
fetch_news,
|
fetch_news,
|
||||||
fetch_news_image,
|
fetch_news_image,
|
||||||
fetch_official_logo,
|
fetch_official_logo,
|
||||||
|
read_cached_broadcast,
|
||||||
|
read_cached_news,
|
||||||
)
|
)
|
||||||
from .overlay import RuntimeCompatibility, inspect_runtime_compatibility
|
from .overlay import RuntimeCompatibility, inspect_runtime_compatibility
|
||||||
from .paths import AppPaths
|
from .paths import AppPaths
|
||||||
from .settings import Settings, ensure_settings, save_settings
|
from .settings import Settings, ensure_settings, save_settings
|
||||||
from .self_update import LauncherUpdateCheck, check_for_launcher_update
|
from .self_update import (
|
||||||
|
LauncherUpdateCheck,
|
||||||
|
cache_launcher_update_check,
|
||||||
|
check_for_launcher_update,
|
||||||
|
read_cached_launcher_update_check,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
MAX_NEWS_CARDS = 3
|
MAX_NEWS_CARDS = 3
|
||||||
@@ -43,6 +50,10 @@ NEWS_IMAGE_HEIGHT = 118
|
|||||||
LAUNCH_STAGE_TOTAL = 4
|
LAUNCH_STAGE_TOTAL = 4
|
||||||
EVENT_BATCH_SIZE = 48
|
EVENT_BATCH_SIZE = 48
|
||||||
POLL_INTERVAL_MILLISECONDS = 33
|
POLL_INTERVAL_MILLISECONDS = 33
|
||||||
|
ACTIVE_POLL_INTERVAL_MILLISECONDS = 75
|
||||||
|
IDLE_POLL_INTERVAL_MILLISECONDS = 500
|
||||||
|
MAX_ACTIVITY_LOG_LINES = 5000
|
||||||
|
ACTIVITY_LOG_TRIM_LINES = 500
|
||||||
TRANSFER_RATE_WINDOW_SECONDS = 3.0
|
TRANSFER_RATE_WINDOW_SECONDS = 3.0
|
||||||
TRANSFER_RATE_MINIMUM_INTERVAL_SECONDS = 0.2
|
TRANSFER_RATE_MINIMUM_INTERVAL_SECONDS = 0.2
|
||||||
TRANSFER_RATE_IDLE_SECONDS = 2.0
|
TRANSFER_RATE_IDLE_SECONDS = 2.0
|
||||||
@@ -565,6 +576,8 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
preloader_progress: ttk.Progressbar | None = None
|
preloader_progress: ttk.Progressbar | None = None
|
||||||
settings_window: tk.Toplevel | None = None
|
settings_window: tk.Toplevel | None = None
|
||||||
operation_active = False
|
operation_active = False
|
||||||
|
game_window_active = False
|
||||||
|
activity_log_lines = 0
|
||||||
operation_started_at: float | None = None
|
operation_started_at: float | None = None
|
||||||
operation_stage = 0
|
operation_stage = 0
|
||||||
operation_stage_total = 0
|
operation_stage_total = 0
|
||||||
@@ -586,8 +599,16 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def append_log(value: str) -> None:
|
def append_log(value: str) -> None:
|
||||||
|
nonlocal activity_log_lines
|
||||||
|
rendered = value.rstrip() + "\n"
|
||||||
log.configure(state="normal")
|
log.configure(state="normal")
|
||||||
log.insert("end", value.rstrip() + "\n")
|
log.insert("end", rendered)
|
||||||
|
activity_log_lines += rendered.count("\n")
|
||||||
|
if activity_log_lines > MAX_ACTIVITY_LOG_LINES:
|
||||||
|
retained_lines = MAX_ACTIVITY_LOG_LINES - ACTIVITY_LOG_TRIM_LINES
|
||||||
|
excess = activity_log_lines - retained_lines
|
||||||
|
log.delete("1.0", f"{excess + 1}.0")
|
||||||
|
activity_log_lines -= excess
|
||||||
log.see("end")
|
log.see("end")
|
||||||
log.configure(state="disabled")
|
log.configure(state="disabled")
|
||||||
|
|
||||||
@@ -759,7 +780,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
restart_on_success: bool = False,
|
restart_on_success: bool = False,
|
||||||
game_files_may_change: bool = False,
|
game_files_may_change: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
nonlocal operation_active
|
nonlocal operation_active, game_window_active
|
||||||
nonlocal operation_started_at
|
nonlocal operation_started_at
|
||||||
nonlocal operation_stage
|
nonlocal operation_stage
|
||||||
nonlocal operation_stage_total
|
nonlocal operation_stage_total
|
||||||
@@ -767,6 +788,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
nonlocal operation_refreshes_compatibility
|
nonlocal operation_refreshes_compatibility
|
||||||
nonlocal last_elapsed_second
|
nonlocal last_elapsed_second
|
||||||
operation_active = True
|
operation_active = True
|
||||||
|
game_window_active = False
|
||||||
operation_started_at = time.monotonic()
|
operation_started_at = time.monotonic()
|
||||||
operation_stage = 0
|
operation_stage = 0
|
||||||
operation_stage_total = 0
|
operation_stage_total = 0
|
||||||
@@ -1145,6 +1167,47 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
daemon=True,
|
daemon=True,
|
||||||
).start()
|
).start()
|
||||||
|
|
||||||
|
def show_launcher_update_check(
|
||||||
|
check: LauncherUpdateCheck,
|
||||||
|
*,
|
||||||
|
log_reason: bool = True,
|
||||||
|
) -> None:
|
||||||
|
nonlocal launcher_update_check
|
||||||
|
launcher_update_check = check
|
||||||
|
if check.available and check.supported:
|
||||||
|
launcher_update_button.configure(
|
||||||
|
text=f"UPDATE TO {check.latest_version}",
|
||||||
|
state="normal",
|
||||||
|
)
|
||||||
|
elif check.available:
|
||||||
|
launcher_update_button.configure(
|
||||||
|
text="MANUAL UPDATE NEEDED",
|
||||||
|
state="normal",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
launcher_update_button.configure(
|
||||||
|
text=f"LAUNCHER {__version__}",
|
||||||
|
state="normal",
|
||||||
|
)
|
||||||
|
if log_reason:
|
||||||
|
append_log(check.reason)
|
||||||
|
|
||||||
|
def refresh_launcher_update_check() -> None:
|
||||||
|
def worker() -> None:
|
||||||
|
try:
|
||||||
|
check = check_for_launcher_update()
|
||||||
|
except RuntimeError as update_error:
|
||||||
|
events.put(("launcher_update_error", str(update_error)))
|
||||||
|
else:
|
||||||
|
cache_launcher_update_check(paths.launcher_update_check_cache, check)
|
||||||
|
events.put(("launcher_update_check", check))
|
||||||
|
|
||||||
|
threading.Thread(
|
||||||
|
target=worker,
|
||||||
|
name="ptlinux-self-update",
|
||||||
|
daemon=True,
|
||||||
|
).start()
|
||||||
|
|
||||||
def refresh_remote_content() -> None:
|
def refresh_remote_content() -> None:
|
||||||
def broadcast_worker() -> None:
|
def broadcast_worker() -> None:
|
||||||
events.put(("broadcast", fetch_latest_broadcast(paths.broadcast_cache)))
|
events.put(("broadcast", fetch_latest_broadcast(paths.broadcast_cache)))
|
||||||
@@ -1160,27 +1223,17 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
else:
|
else:
|
||||||
events.put(("version", snapshot))
|
events.put(("version", snapshot))
|
||||||
|
|
||||||
def launcher_update_worker() -> None:
|
|
||||||
try:
|
|
||||||
check = check_for_launcher_update()
|
|
||||||
except RuntimeError as update_error:
|
|
||||||
events.put(("launcher_update_error", str(update_error)))
|
|
||||||
else:
|
|
||||||
events.put(("launcher_update_check", check))
|
|
||||||
|
|
||||||
threading.Thread(target=broadcast_worker, name="ptlinux-broadcast", daemon=True).start()
|
threading.Thread(target=broadcast_worker, name="ptlinux-broadcast", daemon=True).start()
|
||||||
threading.Thread(target=news_worker, name="ptlinux-news", daemon=True).start()
|
threading.Thread(target=news_worker, name="ptlinux-news", daemon=True).start()
|
||||||
threading.Thread(target=version_worker, name="ptlinux-version", daemon=True).start()
|
threading.Thread(target=version_worker, name="ptlinux-version", daemon=True).start()
|
||||||
threading.Thread(
|
refresh_launcher_update_check()
|
||||||
target=launcher_update_worker,
|
|
||||||
name="ptlinux-self-update",
|
|
||||||
daemon=True,
|
|
||||||
).start()
|
|
||||||
|
|
||||||
def finish_operation(returncode: int) -> None:
|
def finish_operation(returncode: int) -> None:
|
||||||
nonlocal operation_active, operation_refreshes_compatibility, operation_started_at
|
nonlocal operation_active, game_window_active
|
||||||
|
nonlocal operation_refreshes_compatibility, operation_started_at
|
||||||
refresh_compatibility = operation_refreshes_compatibility
|
refresh_compatibility = operation_refreshes_compatibility
|
||||||
operation_active = False
|
operation_active = False
|
||||||
|
game_window_active = False
|
||||||
operation_refreshes_compatibility = False
|
operation_refreshes_compatibility = False
|
||||||
operation_started_at = None
|
operation_started_at = None
|
||||||
progress_updates.clear()
|
progress_updates.clear()
|
||||||
@@ -1200,7 +1253,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
progress_text.set("See the activity log for details.")
|
progress_text.set("See the activity log for details.")
|
||||||
|
|
||||||
def poll_events() -> None:
|
def poll_events() -> None:
|
||||||
nonlocal launcher_update_check, runtime_compatibility
|
nonlocal game_window_active, launcher_update_check, runtime_compatibility
|
||||||
processed = 0
|
processed = 0
|
||||||
try:
|
try:
|
||||||
while processed < EVENT_BATCH_SIZE:
|
while processed < EVENT_BATCH_SIZE:
|
||||||
@@ -1236,6 +1289,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
if step > 0 and total > 0 and isinstance(message, str) and message:
|
if step > 0 and total > 0 and isinstance(message, str) and message:
|
||||||
set_operation_stage(step, total, message)
|
set_operation_stage(step, total, message)
|
||||||
elif kind in {"game_started", "game_window_ready"}:
|
elif kind in {"game_started", "game_window_ready"}:
|
||||||
|
game_window_active = True
|
||||||
progress_bar.stop()
|
progress_bar.stop()
|
||||||
status.set("Game client started.")
|
status.set("Game client started.")
|
||||||
close_game_preloader()
|
close_game_preloader()
|
||||||
@@ -1285,26 +1339,13 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
value,
|
value,
|
||||||
LauncherUpdateCheck,
|
LauncherUpdateCheck,
|
||||||
):
|
):
|
||||||
launcher_update_check = value
|
show_launcher_update_check(value)
|
||||||
if value.available and value.supported:
|
|
||||||
launcher_update_button.configure(
|
|
||||||
text=f"UPDATE TO {value.latest_version}",
|
|
||||||
state="normal",
|
|
||||||
)
|
|
||||||
elif value.available:
|
|
||||||
launcher_update_button.configure(
|
|
||||||
text="MANUAL UPDATE NEEDED",
|
|
||||||
state="normal",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
launcher_update_button.configure(
|
|
||||||
text=f"LAUNCHER {__version__}",
|
|
||||||
state="normal",
|
|
||||||
)
|
|
||||||
append_log(value.reason)
|
|
||||||
elif kind == "launcher_update_error" and isinstance(value, str):
|
elif kind == "launcher_update_error" and isinstance(value, str):
|
||||||
launcher_update_check = None
|
if launcher_update_check is None:
|
||||||
launcher_update_button.configure(text="CHECK LAUNCHER", state="normal")
|
launcher_update_button.configure(
|
||||||
|
text="CHECK LAUNCHER",
|
||||||
|
state="normal",
|
||||||
|
)
|
||||||
append_log(f"Launcher update check unavailable: {value}")
|
append_log(f"Launcher update check unavailable: {value}")
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
@@ -1315,7 +1356,14 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
if speed_text.get() != "0 B/s" and transfer_rate.is_idle():
|
if speed_text.get() != "0 B/s" and transfer_rate.is_idle():
|
||||||
speed_text.set("0 B/s")
|
speed_text.set("0 B/s")
|
||||||
update_elapsed_indicator()
|
update_elapsed_indicator()
|
||||||
root.after(POLL_INTERVAL_MILLISECONDS, poll_events)
|
root.after(
|
||||||
|
dashboard_poll_interval(
|
||||||
|
operation_active=operation_active,
|
||||||
|
game_window_active=game_window_active,
|
||||||
|
pending_events=not events.empty(),
|
||||||
|
),
|
||||||
|
poll_events,
|
||||||
|
)
|
||||||
|
|
||||||
def command_for(*arguments: str) -> list[str]:
|
def command_for(*arguments: str) -> list[str]:
|
||||||
return [sys.executable, "-m", "ptlinux", *arguments]
|
return [sys.executable, "-m", "ptlinux", *arguments]
|
||||||
@@ -1327,7 +1375,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
if required_reason is not None:
|
if required_reason is not None:
|
||||||
status.set("Checking for a compatible launcher update…")
|
status.set("Checking for a compatible launcher update…")
|
||||||
progress_text.set("Play remains disabled until this release is supported.")
|
progress_text.set("Play remains disabled until this release is supported.")
|
||||||
refresh_remote_content()
|
refresh_launcher_update_check()
|
||||||
return
|
return
|
||||||
if not check.available:
|
if not check.available:
|
||||||
if required_reason is None:
|
if required_reason is None:
|
||||||
@@ -1729,6 +1777,13 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
|
|
||||||
news_content.bind("<Configure>", update_news_viewport)
|
news_content.bind("<Configure>", update_news_viewport)
|
||||||
news_canvas.bind("<Configure>", resize_news_content)
|
news_canvas.bind("<Configure>", resize_news_content)
|
||||||
|
show_broadcast(read_cached_broadcast(paths.broadcast_cache))
|
||||||
|
show_news(read_cached_news(paths.news_cache))
|
||||||
|
cached_launcher_update = read_cached_launcher_update_check(
|
||||||
|
paths.launcher_update_check_cache
|
||||||
|
)
|
||||||
|
if cached_launcher_update is not None:
|
||||||
|
show_launcher_update_check(cached_launcher_update, log_reason=False)
|
||||||
refresh_primary_action()
|
refresh_primary_action()
|
||||||
refresh_runtime_compatibility()
|
refresh_runtime_compatibility()
|
||||||
poll_events()
|
poll_events()
|
||||||
@@ -1747,6 +1802,21 @@ def _as_int(value: object) -> int:
|
|||||||
return value if isinstance(value, int) and value >= 0 else 0
|
return value if isinstance(value, int) and value >= 0 else 0
|
||||||
|
|
||||||
|
|
||||||
|
def dashboard_poll_interval(
|
||||||
|
*,
|
||||||
|
operation_active: bool,
|
||||||
|
game_window_active: bool,
|
||||||
|
pending_events: bool,
|
||||||
|
) -> int:
|
||||||
|
"""Choose a responsive poll rate while avoiding busy idle/game polling."""
|
||||||
|
|
||||||
|
if pending_events:
|
||||||
|
return POLL_INTERVAL_MILLISECONDS
|
||||||
|
if operation_active and not game_window_active:
|
||||||
|
return ACTIVE_POLL_INTERVAL_MILLISECONDS
|
||||||
|
return IDLE_POLL_INTERVAL_MILLISECONDS
|
||||||
|
|
||||||
|
|
||||||
def _format_bytes(value: int) -> str:
|
def _format_bytes(value: int) -> str:
|
||||||
units = ("B", "KiB", "MiB", "GiB")
|
units = ("B", "KiB", "MiB", "GiB")
|
||||||
amount = float(value)
|
amount = float(value)
|
||||||
|
|||||||
Reference in New Issue
Block a user