From 66a05f804e95f282ac7ed099e26ae18e62b0f460 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sun, 4 Oct 2020 12:27:56 +0200 Subject: [PATCH] report queries back etc --- katrain/__main__.py | 7 ++++--- katrain/core/constants.py | 2 +- katrain/core/engine.py | 31 ++++++++++++++++++++++++------- katrain/core/game.py | 8 +++----- katrain/core/game_node.py | 2 ++ katrain/gui/badukpan.py | 4 ++-- 6 files changed, 36 insertions(+), 18 deletions(-) diff --git a/katrain/__main__.py b/katrain/__main__.py index ba081d9..2b1c17a 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -130,9 +130,10 @@ class KaTrainGui(Screen, KaTrainBase): return self.play_mode.mode def toggle_continuous_analysis(self): - self.idle_analysis = not self.idle_analysis - if not self.idle_analysis: + if self.idle_analysis: + self.engine.terminate_continuous_query() self.controls.set_status("", STATUS_INFO) + self.idle_analysis = not self.idle_analysis self.update_state() def start(self): @@ -208,7 +209,7 @@ class KaTrainGui(Screen, KaTrainBase): ): # cn mismatch stops this if undo fired. avoid message loop here or fires repeatedly. self._do_ai_move(cn) Clock.schedule_once(self.board_gui.play_stone_sound, 0.25) - if len(self.engine.queries) == 0 and self.idle_analysis: + if len(self.engine.queries) == 0 and self.idle_analysis: # TODO on node change, toggle twice? self("analyze-extra", "extra", continuous=True) Clock.schedule_once(lambda _dt: self.update_gui(cn, redraw_board=redraw_board), -1) # trigger? diff --git a/katrain/core/constants.py b/katrain/core/constants.py index 540fde0..b34c05e 100644 --- a/katrain/core/constants.py +++ b/katrain/core/constants.py @@ -92,7 +92,7 @@ AI_OPTION_VALUES = { "max_points_lost": [x / 10 for x in range(51)], "settled_weight": [x / 4 for x in range(1, 17)], "opponent_fac": [x / 10 for x in range(-20, 11)], - "min_visits": range(1,10) + "min_visits": range(1, 10), } AI_KEY_PROPERTIES = {"kyu_rank", "strength", "weaken_fac", "pick_frac", "pick_n", "automatic"} diff --git a/katrain/core/engine.py b/katrain/core/engine.py index 4bff9be..8e0f48d 100644 --- a/katrain/core/engine.py +++ b/katrain/core/engine.py @@ -34,6 +34,7 @@ class KataGoEngine: def __init__(self, katrain, config): self.katrain = katrain self.queries = {} # outstanding query id -> start time and callback + self.continuous_query = None self.config = config self.query_counter = 0 self.katago_process = None @@ -85,10 +86,10 @@ class KataGoEngine: def start(self): try: self.katrain.log(f"Starting KataGo with {self.command}", OUTPUT_DEBUG) - startupinfo = None # stop command box popups on windows/pyinstaller + startupinfo = None if hasattr(subprocess, "STARTUPINFO"): startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW # stop command box popups on win/pyinstaller self.katago_process = subprocess.Popen( self.command, startupinfo=startupinfo, @@ -107,7 +108,10 @@ class KataGoEngine: def on_new_game(self): self.base_priority += 1 + for query_id in self.queries: + self.terminate_query(query_id) self.queries = {} + self.continuous_query = None def restart(self): self.queries = {} @@ -196,14 +200,16 @@ class KataGoEngine: elif "warning" in analysis: self.katrain.log(f"{analysis} received from KataGo", OUTPUT_DEBUG) else: - del self.queries[query_id] + if not analysis.get("isDuringSearch", False): + del self.queries[query_id] time_taken = time.time() - start_time self.katrain.log( f"[{time_taken:.1f}][{query_id}] KataGo Analysis Received: {analysis.keys()}", OUTPUT_DEBUG, ) self.katrain.log(line, OUTPUT_EXTRA_DEBUG) try: - callback(analysis) + if callback and not analysis.get("noResults", False): + callback(analysis) except Exception as e: self.katrain.log(f"Error in engine callback for query {query_id}: {e}", OUTPUT_ERROR) if getattr(self.katrain, "update_state", None): # easier mocking etc @@ -212,12 +218,16 @@ class KataGoEngine: self.katrain.log(f"Unexpected exception {e} while processing KataGo output {line}", OUTPUT_ERROR) traceback.print_exc() - def send_query(self, query, callback, error_callback, next_move=None): + def send_query(self, query, callback, error_callback, next_move=None, report_during_search=False): with self._lock: self.query_counter += 1 if "id" not in query: query["id"] = f"QUERY:{str(self.query_counter)}" self.queries[query["id"]] = (callback, error_callback, time.time(), next_move) + if report_during_search: + query["reportDuringSearchEvery"] = 0.25 + self.terminate_continuous_query() + self.continuous_query = query["id"] if self.katago_process: self.katrain.log(f"Sending query {query['id']}: {json.dumps(query)}", OUTPUT_DEBUG) try: @@ -225,7 +235,13 @@ class KataGoEngine: self.katago_process.stdin.flush() except OSError as e: self.check_alive(os_error=str(e), exception_if_dead=True) - return # do not raise, since there's nothing to catch it + + def terminate_continuous_query(self): + self.terminate_query(self.continuous_query) + + def terminate_query(self, query_id): + if query_id is not None: + self.send_query({"action": "terminate", "terminateId": query_id}, None, None) def request_analysis( self, @@ -240,6 +256,7 @@ class KataGoEngine: ownership: Optional[bool] = None, next_move: Optional[GameNode] = None, extra_settings: Optional[Dict] = None, + report_during_search: bool = False, ): nodes = analysis_node.nodes_from_root moves = [m for node in nodes for m in node.moves] @@ -288,5 +305,5 @@ class KataGoEngine: "moves": [[m.player, m.gtp()] for m in moves], "overrideSettings": {**settings, **(extra_settings or {})}, } - self.send_query(query, callback, error_callback, next_move) + self.send_query(query, callback, error_callback, next_move, report_during_search) analysis_node.analysis_visits_requested = max(analysis_node.analysis_visits_requested, visits) diff --git a/katrain/core/game.py b/katrain/core/game.py index 3a3261f..d127596 100644 --- a/katrain/core/game.py +++ b/katrain/core/game.py @@ -323,13 +323,11 @@ class Game: if mode == "extra": if kwargs.get("continuous", False): - visits = min( - 1_000_000_000, max(engine.config["max_visits"], math.ceil(cn.analysis_visits_requested * 1.25)) - ) + visits = 1_000_000_000 else: visits = cn.analysis_visits_requested + engine.config["max_visits"] - self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits), STATUS_ANALYSIS) - cn.analyze(engine, visits=visits, priority=-1_000, time_limit=False) + self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits), STATUS_ANALYSIS) + cn.analyze(engine, visits=visits, priority=-1_000, time_limit=False, report_during_search=True) return if mode == "game": nodes = self.root.nodes_in_tree diff --git a/katrain/core/game_node.py b/katrain/core/game_node.py index 0a73a3b..62ad6ff 100644 --- a/katrain/core/game_node.py +++ b/katrain/core/game_node.py @@ -77,6 +77,7 @@ class GameNode(SGFNode): refine_move=None, analyze_fast=False, find_alternatives=False, + report_during_search=False, ): engine.request_analysis( self, @@ -87,6 +88,7 @@ class GameNode(SGFNode): time_limit=time_limit, next_move=refine_move, find_alternatives=find_alternatives, + report_during_search=report_during_search, ) def update_move_analysis(self, move_analysis, move_gtp): diff --git a/katrain/gui/badukpan.py b/katrain/gui/badukpan.py index 35cb671..e5d48b4 100644 --- a/katrain/gui/badukpan.py +++ b/katrain/gui/badukpan.py @@ -106,7 +106,7 @@ class BadukPanWidget(Widget): return katrain = self.katrain if self.ghost_stone and ("button" not in touch.profile or touch.button == "left"): - game = self.katrain and self.katrain.game + game = self.katrain and self.katrain.game current_node = game and self.katrain.game.current_node if ( current_node @@ -114,7 +114,7 @@ class BadukPanWidget(Widget): and not self.katrain.next_player_info.ai and not self.katrain.controls.timer.paused and self.katrain.play_analyze_mode == MODE_PLAY - and self.katrain.config("timer/main_time",0) * 60 - game.main_time_used <= 0 + and self.katrain.config("timer/main_time", 0) * 60 - game.main_time_used <= 0 and current_node.time_used < self.katrain.config("timer/minimal_use", 0) ): self.katrain.controls.set_status(