Fix Game monitor not restart game issue
This commit is contained in:
parent
94e3b55136
commit
96f53ecdfc
@ -54,9 +54,23 @@ Wolf Chat 是一個基於 MCP (Modular Capability Provider) 框架的聊天機
|
||||
- 持續監控遊戲視窗 (`config.WINDOW_TITLE`)。
|
||||
- 確保視窗維持在設定檔 (`config.py`) 中指定的位置 (`GAME_WINDOW_X`, `GAME_WINDOW_Y`) 和大小 (`GAME_WINDOW_WIDTH`, `GAME_WINDOW_HEIGHT`)。
|
||||
- 確保視窗維持在最上層 (Always on Top)。
|
||||
- **作為獨立進程運行**:由 `main.py` 使用 `subprocess.Popen` 啟動,以隔離執行環境,確保縮放行為一致。
|
||||
- **定時遊戲重啟** (如果 `config.ENABLE_SCHEDULED_RESTART` 為 True):
|
||||
- 根據 `config.RESTART_INTERVAL_MINUTES` 設定的間隔執行。
|
||||
- **簡化流程 (2025-04-25)**:
|
||||
1. 通過 `stdout` 向 `main.py` 發送 JSON 訊號 (`{'action': 'pause_ui'}`),請求暫停 UI 監控。
|
||||
2. 等待固定時間(30 秒)。
|
||||
3. 調用 `restart_game_process` 函數,**嘗試**終止 (`terminate`/`kill`) `LastWar.exe` 進程(**無驗證**)。
|
||||
4. 等待固定時間(2 秒)。
|
||||
5. **嘗試**使用 `os.startfile` 啟動 `config.GAME_EXECUTABLE_PATH`(**無驗證**)。
|
||||
6. 等待固定時間(30 秒)。
|
||||
7. 使用 `try...finally` 結構確保**總是**執行下一步。
|
||||
8. 通過 `stdout` 向 `main.py` 發送 JSON 訊號 (`{'action': 'resume_ui'}`),請求恢復 UI 監控。
|
||||
- **視窗調整**:遊戲視窗的位置/大小/置頂狀態的調整完全由 `monitor_game_window` 的主循環持續負責,重啟流程不再進行立即調整。
|
||||
- **作為獨立進程運行**:由 `main.py` 使用 `subprocess.Popen` 啟動,捕獲其 `stdout` (用於 JSON 訊號) 和 `stderr` (用於日誌)。
|
||||
- **進程間通信**:
|
||||
- `game_monitor.py` -> `main.py`:通過 `stdout` 發送 JSON 格式的 `pause_ui` 和 `resume_ui` 訊號。
|
||||
- **日誌處理**:`game_monitor.py` 的日誌被配置為輸出到 `stderr`,以保持 `stdout` 清潔,確保訊號傳遞可靠性。`main.py` 會讀取 `stderr` 並可能顯示這些日誌。
|
||||
- **生命週期管理**:由 `main.py` 在啟動時創建,並在 `shutdown` 過程中嘗試終止 (`terminate`)。
|
||||
- 僅在進行調整時打印訊息。
|
||||
|
||||
### 資料流程
|
||||
|
||||
@ -381,6 +395,28 @@ Wolf Chat 是一個基於 MCP (Modular Capability Provider) 框架的聊天機
|
||||
4. **工具優先級**:明確定義了內部工具使用的優先順序:`read_note` > `search_notes` > `recent_activity`。
|
||||
- **效果**:預期 LLM 在回應前會更穩定地執行記憶體檢索步驟,特別是強制性的用戶 Profile 檢查,從而提高回應的上下文一致性和角色扮演的準確性。
|
||||
|
||||
### 遊戲監控與定時重啟穩定性改進 (2025-04-25)
|
||||
|
||||
- **目的**:解決 `game_monitor.py` 在執行定時重啟時,可能出現遊戲未成功關閉/重啟,且 UI 監控未恢復的問題。
|
||||
- **`game_monitor.py` (第一階段修改)**:
|
||||
- **日誌重定向**:將所有 `logging` 輸出重定向到 `stderr`,確保 `stdout` 只用於傳輸 JSON 訊號 (`pause_ui`, `resume_ui`) 給 `main.py`,避免訊號被日誌干擾。
|
||||
- **終止驗證**:在 `restart_game_process` 中,嘗試終止遊戲進程後,加入循環檢查(最多 10 秒),使用 `psutil.pid_exists` 確認進程確實已結束。
|
||||
- **啟動驗證**:在 `restart_game_process` 中,嘗試啟動遊戲後,使用循環檢查(最多 90 秒),調用 `find_game_window` 確認遊戲視窗已出現,取代固定的等待時間。
|
||||
- **立即調整嘗試**:在 `perform_scheduled_restart` 中,於成功驗證遊戲啟動後,立即嘗試調整一次視窗位置/大小/置頂。
|
||||
- **保證恢復訊號**:在 `perform_scheduled_restart` 中,使用 `try...finally` 結構包裹遊戲重啟邏輯,確保無論重啟成功與否,都會嘗試通過 `stdout` 發送 `resume_ui` 訊號給 `main.py`。
|
||||
- **`game_monitor.py` (第二階段修改 - 簡化)**:
|
||||
- **移除驗證與立即調整**:根據使用者回饋,移除了終止驗證、啟動驗證以及立即調整視窗的邏輯。
|
||||
- **恢復固定等待**:重啟流程恢復使用固定的 `time.sleep()` 等待時間。
|
||||
- **發送重啟完成訊號**:在重啟流程結束後,發送 `{'action': 'restart_complete'}` JSON 訊號給 `main.py`。
|
||||
- **`main.py`**:
|
||||
- **轉發重啟完成訊號**:`read_monitor_output` 線程接收到 `game_monitor.py` 的 `{'action': 'restart_complete'}` 訊號後,將 `{'action': 'handle_restart_complete'}` 命令放入 `command_queue`。
|
||||
- **`ui_interaction.py`**:
|
||||
- **內部處理重啟完成**:`run_ui_monitoring_loop` 接收到 `{'action': 'handle_restart_complete'}` 命令後,在 UI 線程內部執行:
|
||||
1. 暫停 UI 監控。
|
||||
2. 等待固定時間(30 秒),讓遊戲啟動並穩定。
|
||||
3. 恢復 UI 監控並重置狀態(清除 `recent_texts` 和 `last_processed_bubble_info`)。
|
||||
- **效果**:將暫停/恢復 UI 監控的時序控制權移至 `ui_interaction.py` 內部,減少了模塊間的直接依賴和潛在干擾,依賴持續監控來確保最終視窗狀態。
|
||||
|
||||
## 開發建議
|
||||
|
||||
### 優化方向
|
||||
|
||||
46
config.py
46
config.py
@ -37,30 +37,30 @@ exa_config_arg_string_single_dump = json.dumps(exa_config_dict) # Use this one
|
||||
|
||||
# --- MCP Server Configuration ---
|
||||
MCP_SERVERS = {
|
||||
"exa": { # Temporarily commented out to prevent blocking startup
|
||||
"command": "cmd",
|
||||
"args": [
|
||||
"/c",
|
||||
"npx",
|
||||
"-y",
|
||||
"@smithery/cli@latest",
|
||||
"run",
|
||||
"exa",
|
||||
"--config",
|
||||
# Pass the dynamically created config string with the environment variable key
|
||||
exa_config_arg_string_single_dump # Use the single dump variable
|
||||
],
|
||||
},
|
||||
#"exa": {
|
||||
# "command": "npx",
|
||||
# "args": [
|
||||
# "Z:/mcp/Server/exa-mcp-server/build/index.js",
|
||||
# "--tools=web_search,research_paper_search,twitter_search,company_research,crawling,competitor_finder"
|
||||
# ],
|
||||
# "env": {
|
||||
# "EXA_API_KEY": EXA_API_KEY
|
||||
# }
|
||||
#"exa": { # Temporarily commented out to prevent blocking startup
|
||||
## "command": "cmd",
|
||||
# "args": [
|
||||
# "/c",
|
||||
# "npx",
|
||||
# "-y",
|
||||
# "@smithery/cli@latest",
|
||||
# "run",
|
||||
# "exa",
|
||||
# "--config",
|
||||
# # Pass the dynamically created config string with the environment variable key
|
||||
# exa_config_arg_string_single_dump # Use the single dump variable
|
||||
# ],
|
||||
#},
|
||||
"exa": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"C:/Users/Bigspring/AppData/Roaming/npm/exa-mcp-server",
|
||||
"--tools=web_search,research_paper_search,twitter_search,company_research,crawling,competitor_finder"
|
||||
],
|
||||
"env": {
|
||||
"EXA_API_KEY": EXA_API_KEY
|
||||
}
|
||||
},
|
||||
#"github.com/modelcontextprotocol/servers/tree/main/src/memory": {
|
||||
# "command": "npx",
|
||||
# "args": [
|
||||
|
||||
@ -23,8 +23,15 @@ import logging
|
||||
|
||||
# --- Setup Logging ---
|
||||
monitor_logger = logging.getLogger('GameMonitor')
|
||||
# Basic config for direct run, main.py might configure differently
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
monitor_logger.setLevel(logging.INFO) # Set level for the logger
|
||||
log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
# Create handler for stderr
|
||||
stderr_handler = logging.StreamHandler(sys.stderr) # Explicitly use stderr
|
||||
stderr_handler.setFormatter(log_formatter)
|
||||
# Add handler to the logger
|
||||
if not monitor_logger.hasHandlers(): # Avoid adding multiple handlers if run multiple times
|
||||
monitor_logger.addHandler(stderr_handler)
|
||||
monitor_logger.propagate = False # Prevent propagation to root logger if basicConfig was called elsewhere
|
||||
|
||||
# --- Helper Functions ---
|
||||
|
||||
@ -64,6 +71,7 @@ def restart_game_process():
|
||||
except Exception as wait_kill_err:
|
||||
monitor_logger.error(f"等待進程 {proc_info['pid']} 強制結束時出錯: {wait_kill_err}", exc_info=False)
|
||||
|
||||
# Removed Termination Verification - Rely on main loop for eventual state correction
|
||||
monitor_logger.info(f"已處理匹配的進程 PID: {proc_info['pid']},停止搜索。(Processed matching process PID: {proc_info['pid']}, stopping search.)")
|
||||
break # Exit the loop once a process is handled
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
||||
@ -97,43 +105,43 @@ def restart_game_process():
|
||||
monitor_logger.error(f"啟動錯誤 (OSError): {ose} - 檢查路徑和權限。(Launch Error (OSError): {ose} - Check path and permissions.)", exc_info=True)
|
||||
except Exception as e:
|
||||
monitor_logger.error(f"啟動遊戲時發生未預期錯誤: {e}", exc_info=True)
|
||||
# Don't return False here, let the process continue to send resume signal
|
||||
# Removed Startup Verification - Rely on main loop for eventual state correction
|
||||
# Always return True (or nothing) to indicate the attempt was made
|
||||
return # Or return True, doesn't matter much now
|
||||
|
||||
def perform_scheduled_restart():
|
||||
"""Handles the sequence of pausing UI, restarting game, resuming UI."""
|
||||
monitor_logger.info("開始執行定時重啟流程。(Starting scheduled restart sequence.)")
|
||||
|
||||
# 1. Signal main process to pause UI monitoring via stdout
|
||||
pause_signal_data = {'action': 'pause_ui'}
|
||||
# Removed pause_ui signal - UI will handle its own pause/resume based on restart_complete
|
||||
|
||||
try:
|
||||
json_signal = json.dumps(pause_signal_data)
|
||||
monitor_logger.info(f"準備發送暫停訊號: {json_signal} (Preparing to send pause signal)") # Log before print
|
||||
print(json_signal, flush=True)
|
||||
monitor_logger.info("已發送暫停 UI 監控訊號。(Sent pause UI monitoring signal.)")
|
||||
except Exception as e:
|
||||
monitor_logger.error(f"發送暫停訊號 '{json_signal}' 失敗: {e}", exc_info=True) # Log signal data on error
|
||||
# 1. Attempt to restart the game (no verification)
|
||||
monitor_logger.info("嘗試執行遊戲重啟。(Attempting game restart process.)")
|
||||
restart_game_process() # Fire and forget restart attempt
|
||||
monitor_logger.info("遊戲重啟嘗試已執行。(Game restart attempt executed.)")
|
||||
|
||||
# 2. Wait 1 minute
|
||||
monitor_logger.info("等待 60 秒以暫停 UI。(Waiting 60 seconds for UI pause...)")
|
||||
time.sleep(30)
|
||||
# 2. Wait fixed time after restart attempt
|
||||
monitor_logger.info("等待 30 秒讓遊戲啟動(無驗證)。(Waiting 30 seconds for game to launch (no verification)...)")
|
||||
time.sleep(30) # Fixed wait
|
||||
|
||||
# 3. Restart the game
|
||||
restart_game_process()
|
||||
except Exception as restart_err:
|
||||
monitor_logger.error(f"執行 restart_game_process 時發生未預期錯誤: {restart_err}", exc_info=True)
|
||||
# Continue to finally block even on error
|
||||
|
||||
# 4. Wait 1 minute
|
||||
monitor_logger.info("等待 60 秒讓遊戲啟動。(Waiting 60 seconds for game to launch...)")
|
||||
time.sleep(30)
|
||||
finally:
|
||||
# 3. Signal main process that restart attempt is complete via stdout
|
||||
monitor_logger.info("發送重啟完成訊號。(Sending restart complete signal.)")
|
||||
restart_complete_signal_data = {'action': 'restart_complete'}
|
||||
try:
|
||||
json_signal = json.dumps(restart_complete_signal_data)
|
||||
print(json_signal, flush=True)
|
||||
monitor_logger.info("已發送重啟完成訊號。(Sent restart complete signal.)")
|
||||
except Exception as e:
|
||||
monitor_logger.error(f"發送重啟完成訊號 '{json_signal}' 失敗: {e}", exc_info=True) # Log signal data on error
|
||||
|
||||
# 5. Signal main process to resume UI monitoring via stdout
|
||||
resume_signal_data = {'action': 'resume_ui'}
|
||||
try:
|
||||
json_signal = json.dumps(resume_signal_data)
|
||||
monitor_logger.info(f"準備發送恢復訊號: {json_signal} (Preparing to send resume signal)") # Log before print
|
||||
print(json_signal, flush=True)
|
||||
monitor_logger.info("已發送恢復 UI 監控訊號。(Sent resume UI monitoring signal.)")
|
||||
except Exception as e:
|
||||
monitor_logger.error(f"發送恢復訊號 '{json_signal}' 失敗: {e}", exc_info=True) # Log signal data on error
|
||||
|
||||
monitor_logger.info("定時重啟流程完成。(Scheduled restart sequence complete.)")
|
||||
monitor_logger.info("定時重啟流程(包括 finally 塊)執行完畢。(Scheduled restart sequence (including finally block) finished.)")
|
||||
# Configure logger (basic example, adjust as needed)
|
||||
# (Logging setup moved earlier)
|
||||
|
||||
@ -229,19 +237,18 @@ def monitor_game_window():
|
||||
pass # Keep silent
|
||||
|
||||
except gw.PyGetWindowException as e:
|
||||
# monitor_logger.warning(f"無法訪問視窗屬性 (可能已關閉): {e} (Could not access window properties (may be closed): {e})")
|
||||
pass # Window might have closed between find and access
|
||||
# Log PyGetWindowException specifically, might indicate window closed during check
|
||||
monitor_logger.warning(f"監控循環中無法訪問視窗屬性 (可能已關閉): {e} (Could not access window properties in monitor loop (may be closed): {e})")
|
||||
except Exception as e:
|
||||
# Log other exceptions during monitoring
|
||||
monitor_logger.error(f"監控遊戲視窗時發生未預期錯誤: {e} (Unexpected error during game window monitoring: {e})", exc_info=True)
|
||||
|
||||
# Print adjustment message only if an adjustment was made and it's different from the last one
|
||||
# Log adjustment message only if an adjustment was made and it's different from the last one
|
||||
# This should NOT print JSON signals
|
||||
if adjustment_made and current_message and current_message != last_adjustment_message:
|
||||
# monitor_logger.info(f"遊戲視窗狀態已調整: {current_message.strip()}") # Log instead of print
|
||||
# Keep print for now for visibility of adjustments, but ensure ONLY JSON goes for signals
|
||||
# print(f"[GameMonitor] {current_message.strip()}") # REMOVED to prevent non-JSON output
|
||||
monitor_logger.info(f"[GameMonitor] {current_message.strip()}") # Log instead
|
||||
last_adjustment_message = current_message
|
||||
# Log the adjustment message instead of printing to stdout
|
||||
monitor_logger.info(f"[GameMonitor] {current_message.strip()}")
|
||||
last_adjustment_message = current_message
|
||||
elif not window:
|
||||
# Reset last message if window disappears
|
||||
last_adjustment_message = ""
|
||||
|
||||
16
main.py
16
main.py
@ -168,14 +168,22 @@ def read_monitor_output(process: subprocess.Popen, queue: ThreadSafeQueue, loop:
|
||||
loop.call_soon_threadsafe(queue.put_nowait, command)
|
||||
print("[Monitor Reader Thread] 暫停命令已放入隊列。(Pause command queued.)") # Log after queueing
|
||||
elif action == 'resume_ui':
|
||||
command = {'action': 'resume'}
|
||||
print(f"[Monitor Reader Thread] 準備將命令放入隊列: {command} (Preparing to queue command)") # Log before queueing
|
||||
loop.call_soon_threadsafe(queue.put_nowait, command)
|
||||
print("[Monitor Reader Thread] 恢復命令已放入隊列。(Resume command queued.)") # Log after queueing
|
||||
# Removed direct resume_ui handling - ui_interaction will handle pause/resume based on restart_complete
|
||||
print("[Monitor Reader Thread] 收到舊的 'resume_ui' 訊號,忽略。(Received old 'resume_ui' signal, ignoring.)")
|
||||
elif action == 'restart_complete':
|
||||
command = {'action': 'handle_restart_complete'}
|
||||
print(f"[Monitor Reader Thread] 收到 'restart_complete' 訊號,準備將命令放入隊列: {command} (Received 'restart_complete' signal, preparing to queue command)")
|
||||
try:
|
||||
loop.call_soon_threadsafe(queue.put_nowait, command)
|
||||
print("[Monitor Reader Thread] 'handle_restart_complete' 命令已放入隊列。(handle_restart_complete command queued.)")
|
||||
except Exception as q_err:
|
||||
print(f"[Monitor Reader Thread] 將 'handle_restart_complete' 命令放入隊列時出錯: {q_err} (Error putting 'handle_restart_complete' command in queue: {q_err})")
|
||||
else:
|
||||
print(f"[Monitor Reader Thread] 從監控器收到未知動作: {action} (Received unknown action from monitor: {action})")
|
||||
except json.JSONDecodeError:
|
||||
print(f"[Monitor Reader Thread] ERROR: 無法解析來自監控器的 JSON: '{line}' (Could not decode JSON from monitor: '{line}')")
|
||||
# Log the raw line that failed to parse
|
||||
# print(f"[Monitor Reader Thread] Raw line that failed JSON decode: '{line}'") # Already logged raw line earlier
|
||||
except Exception as e:
|
||||
print(f"[Monitor Reader Thread] 處理監控器輸出時出錯: {e} (Error processing monitor output: {e})")
|
||||
# No sleep needed here as readline() is blocking
|
||||
|
||||
@ -121,7 +121,7 @@ CHAT_INPUT_CENTER_X = 400
|
||||
CHAT_INPUT_CENTER_Y = 1280
|
||||
SCREENSHOT_REGION = (70, 50, 800, 1365) # Updated region
|
||||
CONFIDENCE_THRESHOLD = 0.9 # Increased threshold for corner matching
|
||||
STATE_CONFIDENCE_THRESHOLD = 0.7
|
||||
STATE_CONFIDENCE_THRESHOLD = 0.9
|
||||
AVATAR_OFFSET_X = -45 # Original offset, used for non-reply interactions like position removal
|
||||
# AVATAR_OFFSET_X_RELOCATED = -50 # Replaced by specific reply offsets
|
||||
AVATAR_OFFSET_X_REPLY = -45 # Horizontal offset for avatar click after re-location (for reply context)
|
||||
@ -1163,7 +1163,26 @@ def run_ui_monitoring_loop(trigger_queue: queue.Queue, command_queue: queue.Queu
|
||||
if monitoring_paused_flag[0]: # Avoid redundant prints if already running
|
||||
print("UI Thread: Processing resume command. Resuming monitoring.")
|
||||
monitoring_paused_flag[0] = False
|
||||
# No continue needed here
|
||||
# No state reset here, reset_state command handles that
|
||||
|
||||
elif action == 'handle_restart_complete': # Added for game monitor restart signal
|
||||
print("UI Thread: Received 'handle_restart_complete' command. Initiating internal pause/wait/resume sequence.")
|
||||
# --- Internal Pause/Wait/Resume Sequence ---
|
||||
if not monitoring_paused_flag[0]: # Only pause if not already paused
|
||||
print("UI Thread: Pausing monitoring internally for restart.")
|
||||
monitoring_paused_flag[0] = True
|
||||
# No need to send command back to main loop, just update flag
|
||||
|
||||
print("UI Thread: Waiting 30 seconds for game to stabilize after restart.")
|
||||
time.sleep(30) # Wait for game to launch and stabilize
|
||||
|
||||
print("UI Thread: Resuming monitoring internally after restart wait.")
|
||||
monitoring_paused_flag[0] = False
|
||||
# Clear state to ensure fresh detection after restart
|
||||
recent_texts.clear()
|
||||
last_processed_bubble_info = None
|
||||
print("UI Thread: Monitoring resumed and state reset after restart.")
|
||||
# --- End Internal Sequence ---
|
||||
|
||||
elif action == 'clear_history': # Added for F7
|
||||
print("UI Thread: Processing clear_history command.")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user