v1.18.0 (#815)
Co-authored-by: Artur Tamazian <artur-tamazian> Co-authored-by: Javier Gonzalez <javierggt@yahoo.com>
This commit is contained in:
34 files changed
+2286
-465
No files matched your search
+62
-27
@@ -24,6 +24,7 @@ from kivymd.uix.selectioncontrol import MDCheckbox
|
||||
from kivymd.uix.textfield import MDTextField
|
||||
|
||||
from katrain.core.ai import ai_rank_estimation, game_report
|
||||
from katrain.core.engine import resolve_engine_backend
|
||||
from katrain.core.constants import (
|
||||
AI_CONFIG_DEFAULT,
|
||||
AI_DEFAULT,
|
||||
@@ -39,7 +40,6 @@ from katrain.core.constants import (
|
||||
PLAYER_HUMAN,
|
||||
ADDITIONAL_MOVE_ORDER,
|
||||
)
|
||||
from katrain.core.engine import KataGoEngine
|
||||
from katrain.core.lang import i18n, rank_label
|
||||
from katrain.core.sgf_parser import Move
|
||||
from katrain.core.utils import PATHS, find_package_resource, evaluation_class
|
||||
@@ -455,11 +455,36 @@ class ConfigAIPopup(QuickConfigGui):
|
||||
class EngineRecoveryPopup(QuickConfigGui):
|
||||
error_message = StringProperty("")
|
||||
code = ObjectProperty(None)
|
||||
engine_type = StringProperty("local")
|
||||
recovery_message = StringProperty("")
|
||||
|
||||
def __init__(self, katrain, error_message, code):
|
||||
def __init__(self, katrain, error_message, code, engine_type="local"):
|
||||
super().__init__(katrain)
|
||||
self.error_message = str(error_message)
|
||||
self.code = code
|
||||
self.engine_type = engine_type or "local"
|
||||
self.recovery_message = self._build_message()
|
||||
|
||||
def _build_message(self):
|
||||
settings_link = "[color=#CCCC11][u][ref=engine_settings]" + i18n._("menu:settings") + "[/ref][/u][/color]"
|
||||
help_link = "[color=#CCCC11][u][ref=engine_help]" + i18n._("link_here") + "[/ref][/u][/color]"
|
||||
if self.engine_type == "remote":
|
||||
opening_key = "remote engine disconnected popup opening message"
|
||||
suggestion = i18n._("remote engine check url suggestion").format(link=settings_link)
|
||||
else:
|
||||
opening_key = "engine died popup opening message"
|
||||
suggestion = i18n._("change engine suggestion").format(link=settings_link)
|
||||
opening = i18n._(opening_key).format(code=self.code, error_message=self.error_message)
|
||||
help_text = i18n._("go to engine help page").format(link=help_link)
|
||||
return opening + "\n\n" + suggestion + "\n\n" + help_text
|
||||
|
||||
def retry(self):
|
||||
"""Rebuild the engine from current config and re-analyze. For a
|
||||
remote engine this reconnects; for a local one it respawns the
|
||||
subprocess. Recovers a transient failure without changing settings."""
|
||||
if self.popup:
|
||||
self.popup.dismiss()
|
||||
Clock.schedule_once(lambda _dt: self.katrain.restart_engine(), 0)
|
||||
|
||||
|
||||
class BaseConfigPopup(QuickConfigGui):
|
||||
@@ -481,23 +506,28 @@ class BaseConfigPopup(QuickConfigGui):
|
||||
|
||||
KATAGOS = {
|
||||
"win": {
|
||||
"OpenCL v1.16.0": "https://github.com/lightvector/KataGo/releases/download/v1.16.0/katago-v1.16.0-opencl-windows-x64.zip",
|
||||
"Eigen AVX2 (Modern CPUs) v1.16.0": "https://github.com/lightvector/KataGo/releases/download/v1.16.0/katago-v1.16.0-eigenavx2-windows-x64.zip",
|
||||
"Eigen (CPU, Non-optimized) v1.16.0": "https://github.com/lightvector/KataGo/releases/download/v1.16.0/katago-v1.16.0-eigen-windows-x64.zip",
|
||||
"OpenCL v1.16.0 (bigger boards)": "https://github.com/lightvector/KataGo/releases/download/v1.16.0/katago-v1.16.0-opencl-windows-x64+bs50.zip",
|
||||
"OpenCL v1.16.5": "https://github.com/lightvector/KataGo/releases/download/v1.16.5/katago-v1.16.5-opencl-windows-x64.zip",
|
||||
"Eigen AVX2 (Modern CPUs) v1.16.5": "https://github.com/lightvector/KataGo/releases/download/v1.16.5/katago-v1.16.5-eigenavx2-windows-x64.zip",
|
||||
"Eigen (CPU, Non-optimized) v1.16.5": "https://github.com/lightvector/KataGo/releases/download/v1.16.5/katago-v1.16.5-eigen-windows-x64.zip",
|
||||
"OpenCL v1.16.5 (bigger boards)": "https://github.com/lightvector/KataGo/releases/download/v1.16.5/katago-v1.16.5-opencl-windows-x64+bs50.zip",
|
||||
},
|
||||
"linux": {
|
||||
"OpenCL v1.16.0": "https://github.com/lightvector/KataGo/releases/download/v1.16.0/katago-v1.16.0-opencl-linux-x64.zip",
|
||||
"Eigen AVX2 (Modern CPUs) v1.16.0": "https://github.com/lightvector/KataGo/releases/download/v1.16.0/katago-v1.16.0-eigenavx2-linux-x64.zip",
|
||||
"Eigen (CPU, Non-optimized) v1.16.0": "https://github.com/lightvector/KataGo/releases/download/v1.16.0/katago-v1.16.0-eigen-linux-x64.zip",
|
||||
"OpenCL v1.16.0 (bigger boards)": "https://github.com/lightvector/KataGo/releases/download/v1.16.0/katago-v1.16.0-opencl-linux-x64+bs50.zip",
|
||||
"OpenCL v1.16.5": "https://github.com/lightvector/KataGo/releases/download/v1.16.5/katago-v1.16.5-opencl-linux-x64.zip",
|
||||
"Eigen AVX2 (Modern CPUs) v1.16.5": "https://github.com/lightvector/KataGo/releases/download/v1.16.5/katago-v1.16.5-eigenavx2-linux-x64.zip",
|
||||
"Eigen (CPU, Non-optimized) v1.16.5": "https://github.com/lightvector/KataGo/releases/download/v1.16.5/katago-v1.16.5-eigen-linux-x64.zip",
|
||||
"OpenCL v1.16.5 (bigger boards)": "https://github.com/lightvector/KataGo/releases/download/v1.16.5/katago-v1.16.5-opencl-linux-x64+bs50.zip",
|
||||
},
|
||||
"just-descriptions": {},
|
||||
}
|
||||
|
||||
def __init__(self, katrain):
|
||||
super().__init__(katrain)
|
||||
self.paths = [self.katrain.config("engine/model"), self.katrain.config("engine/humanlike_model"), "katrain/models", DATA_FOLDER]
|
||||
self.paths = [
|
||||
self.katrain.config("engine/model"),
|
||||
self.katrain.config("engine/humanlike_model"),
|
||||
"katrain/models",
|
||||
DATA_FOLDER,
|
||||
]
|
||||
self.katago_paths = [self.katrain.config("engine/katago"), DATA_FOLDER]
|
||||
self.last_clicked_download_models = 0
|
||||
|
||||
@@ -561,7 +591,9 @@ class BaseConfigPopup(QuickConfigGui):
|
||||
key=lambda descpath: ("Recommended" not in descpath[0], " - " not in descpath[0], descpath[0]),
|
||||
)
|
||||
humanlike_models_available_msg = i18n._("models available").format(num=len(humanlike_model_files))
|
||||
self.humanlike_model_files.values = [humanlike_models_available_msg] + [desc for desc, path in humanlike_model_files]
|
||||
self.humanlike_model_files.values = [humanlike_models_available_msg] + [
|
||||
desc for desc, path in humanlike_model_files
|
||||
]
|
||||
self.humanlike_model_files.value_keys = [""] + [path for desc, path in humanlike_model_files]
|
||||
self.humanlike_model_files.text = humanlike_models_available_msg
|
||||
|
||||
@@ -650,7 +682,9 @@ class BaseConfigPopup(QuickConfigGui):
|
||||
|
||||
for name, url in {**self.MODELS, **dist_models}.items():
|
||||
filename = os.path.split(url)[1]
|
||||
if not any(os.path.split(f)[1] == filename for f in self.model_files.values + self.humanlike_model_files.values):
|
||||
if not any(
|
||||
os.path.split(f)[1] == filename for f in self.model_files.values + self.humanlike_model_files.values
|
||||
):
|
||||
savepath = os.path.expanduser(os.path.join(DATA_FOLDER, filename))
|
||||
savepath_tmp = savepath + ".part"
|
||||
self.katrain.log(f"Downloading {name} from {url} to {savepath_tmp}", OUTPUT_INFO)
|
||||
@@ -759,14 +793,28 @@ class BaseConfigPopup(QuickConfigGui):
|
||||
|
||||
|
||||
class ConfigPopup(BaseConfigPopup):
|
||||
ENGINE_TAB_BUTTONS = {"local": "local_tab_button", "remote": "remote_tab_button", "custom": "custom_tab_button"}
|
||||
|
||||
def __init__(self, katrain):
|
||||
super().__init__(katrain)
|
||||
Clock.schedule_once(self.check_katas)
|
||||
Clock.schedule_once(self.select_engine_tab)
|
||||
MDApp.get_running_app().bind(language=self.check_models)
|
||||
MDApp.get_running_app().bind(language=self.check_katas)
|
||||
|
||||
def select_engine_tab(self, *_args):
|
||||
# The active tab is authoritative for which engine is used; pick it based on the current config.
|
||||
backend = resolve_engine_backend(self.katrain.config("engine"))
|
||||
self.engine_sm.current = backend
|
||||
getattr(self, self.ENGINE_TAB_BUTTONS[backend]).state = "down"
|
||||
|
||||
def update_config(self, save_to_file=True, close_popup=True):
|
||||
old_backend = self.katrain.config("engine/backend", "")
|
||||
backend = self.engine_sm.current
|
||||
self.katrain._config["engine"]["backend"] = backend
|
||||
updated = super().update_config(save_to_file=save_to_file, close_popup=close_popup)
|
||||
if backend != old_backend:
|
||||
updated.add("engine/backend")
|
||||
self.katrain.debug_level = self.katrain.config("general/debug_level", OUTPUT_INFO)
|
||||
|
||||
ignore = {"max_visits", "fast_visits", "max_time", "enable_ownership", "wide_root_noise"}
|
||||
@@ -774,21 +822,8 @@ class ConfigPopup(BaseConfigPopup):
|
||||
if detected_restart:
|
||||
|
||||
def restart_engine(_dt):
|
||||
self.katrain.controls.set_status("", STATUS_INFO)
|
||||
self.katrain.log(f"Restarting Engine after {detected_restart} settings change")
|
||||
self.katrain.controls.set_status(i18n._("restarting engine"), STATUS_INFO)
|
||||
|
||||
old_engine = self.katrain.engine # type: KataGoEngine
|
||||
old_proc = old_engine.katago_process
|
||||
if old_proc:
|
||||
old_engine.shutdown(finish=False)
|
||||
new_engine = KataGoEngine(self.katrain, self.katrain.config("engine"))
|
||||
self.katrain.engine = new_engine
|
||||
self.katrain.game.engines = {"B": new_engine, "W": new_engine}
|
||||
self.katrain.game.analyze_all_nodes(
|
||||
analyze_fast=True
|
||||
) # old engine was possibly broken, so make sure we redo any failures
|
||||
self.katrain.update_state()
|
||||
self.katrain.restart_engine()
|
||||
|
||||
Clock.schedule_once(restart_engine, 0)
|
||||
|
||||
|
||||
+45
-11
@@ -1,9 +1,9 @@
|
||||
from kivy.clock import Clock
|
||||
from kivymd.app import MDApp
|
||||
from kivy.core.audio import SoundLoader
|
||||
from kivy.utils import platform
|
||||
|
||||
cached_sounds = {}
|
||||
_audio_available = False
|
||||
|
||||
# prefer ffpyplayer on linux, then others, avoid gst and avoid or ffpyplayer on windows
|
||||
ranking = [("ffplay", 98 if platform in ["win", "macosx"] else -2), ("sdl", -1), ("gst", 99), ("", 0)]
|
||||
@@ -13,22 +13,56 @@ except Exception as e:
|
||||
print("Exception sorting sound loaders: ", e) # private vars, so could break with versions etc
|
||||
|
||||
|
||||
def play_sound(file, volume=1, cache=True):
|
||||
def _play(sound):
|
||||
if sound:
|
||||
sound.play()
|
||||
sound.seek(0)
|
||||
def preload_sounds(sound_dir):
|
||||
"""Preload all sounds before the SDL2 window is created.
|
||||
|
||||
SoundLoader.load() deadlocks when no audio output device is available,
|
||||
so we test audio in a subprocess first.
|
||||
"""
|
||||
global _audio_available
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
audio_files = [
|
||||
os.path.join(sound_dir, fn) for fn in os.listdir(sound_dir)
|
||||
if fn.endswith((".wav", ".ogg", ".mp3"))
|
||||
]
|
||||
if not audio_files:
|
||||
return
|
||||
|
||||
# Test if audio subsystem works by loading one sound in a subprocess
|
||||
try:
|
||||
subprocess.run(
|
||||
[sys.executable, "-c",
|
||||
f"from kivy.core.audio import SoundLoader; SoundLoader.load({audio_files[0]!r})"],
|
||||
timeout=3, capture_output=True,
|
||||
)
|
||||
except subprocess.TimeoutExpired as e:
|
||||
print(f"Warning: Audio unavailable ({e}). Sounds disabled.", file=sys.stderr)
|
||||
return
|
||||
|
||||
# Audio works, preload all sounds
|
||||
for path in audio_files:
|
||||
cached_sounds[os.path.basename(path)] = SoundLoader.load(path)
|
||||
_audio_available = True
|
||||
|
||||
|
||||
def play_sound(file, volume=1, cache=True):
|
||||
if not _audio_available:
|
||||
return
|
||||
|
||||
# the following import causes the creation of an SDL2 window, and if there
|
||||
# is no sound device, it deadlocks. We delay the import until we know audio
|
||||
# is available
|
||||
from kivymd.app import MDApp
|
||||
app = MDApp.get_running_app()
|
||||
if app and app.gui and app.gui.config("timer/sound"):
|
||||
sound = cached_sounds.get(file)
|
||||
if sound is None:
|
||||
sound = SoundLoader.load(file)
|
||||
if cache:
|
||||
cached_sounds[file] = sound
|
||||
if sound is not None:
|
||||
sound.volume = volume
|
||||
Clock.schedule_once(lambda _dt: _play(sound), 0)
|
||||
sound.play()
|
||||
sound.seek(0)
|
||||
|
||||
|
||||
def stop_sound(file):
|
||||
|
||||
Reference in new issue
Block a user