SceneFlow 作者指南
SceneFlow 是程式碼內的輕量 Pipeline。它只處理“當前允許識別什麼畫面, 成功後允許到哪裡去, 何時重試或恢復”。OCR、點選、按鍵、路線和長時間業務迴圈仍寫在任務 action 中。
一輪流程如何執行
flow.step(
FishingStep.CAST,
self.is_ready_to_cast,
self._cast,
next=(FishingStep.CAST, FishingStep.WAIT_BITE, FishingStep.RESULT),
policy=StepPolicy(max_attempts=4, interval=2),
on_failure=self._route_cast_failure,
)
其含義是:
- 只有
CAST在當前候選集內, 且is_ready_to_cast()為真時, 才執行_cast()。 _cast()沒有拋異常就視為完成。它的返回值會被忽略。- 完成後只允許識別
CAST、WAIT_BITE、RESULT。next不是立即呼叫下一 action, 而是下一輪的可識別範圍。 CAST寫在next中, 所以準備頁仍然可見時可以再次拋竿。沒有寫入自身的 action 不會被框架重放。- 四次後仍停在 CAST, 則呼叫
_route_cast_failure()決定去補貨還是結束當前輪。
flowchart LR
A["候选: CAST"] --> B{"准备页?"}
B -->|是| C["执行 _cast()"]
C --> D["候选: CAST / WAIT_BITE / RESULT"]
D --> E{"匹配其中一个画面"}
E -->|CAST| C
E -->|WAIT_BITE| F["执行 _wait_bite()"]
E -->|RESULT| G["执行 _collect_result()"]
API
flow.step(key, detector, action, *, next, policy=None, transition=None, on_failure=None)
| 引數 | 含義 |
|---|---|
key |
步驟 Enum。 |
detector |
無副作用的畫面判斷函式。 |
action |
業務動作。返回值不參與流程; 成功不拋異常, 失敗拋 WaitFailedException。 |
next |
action 成功後允許識別的後繼步驟, 必須非空。只有把自身寫入其中才允許再次執行 action。 |
policy |
當前 action 的重複次數和最小間隔。 |
transition |
已知頁面切換期間重複執行的輕量輸入, 例如 Escape。 |
on_failure |
當前步驟已無法繼續時的區域性路由。 |
StepPolicy
StepPolicy(max_attempts=None, interval=0.0)
max_attempts: 同一個 action 最多執行幾次。None表示不限制。interval: 同一個 action 再次執行前的最小間隔。它不替代點選或按鍵自身的節流。
StepPolicy 不管理 action 的執行時長。長動作自己擁有領域 timeout, 例如釣魚控條的 CONTROL_TIMEOUT。Pipeline 在 action 完成後才繼續識別後繼步驟; 節點策略主要提供重複次數、最小重試間隔與錯誤路由, 而非統一接管業務 action timeout。
如果 action 丟擲 WaitFailedException:
- 後繼畫面已經出現時, 直接進入後繼步驟;
- 原畫面仍在且未達到
max_attempts時, 等待interval後重試; - 否則進入
on_failure。
on_failure
def _route_cast_failure(self, failure: StepFailure) -> FishingStep | None:
if self.config[self.CONF_AUTO_BUY_BAIT]:
return FishingStep.OPEN_SELL
self.add_failed("未检测到进入抛竿状态")
return FishingStep.CAST
on_failure 只在框架確認當前步驟不能繼續時呼叫, 例如達到 max_attempts 或 action 丟擲不可重試的 WaitFailedException。
- 返回已註冊的步驟: 立即切換為只識別該步驟;
- 返回
None: 進入全域性recovery(); - 沒有
on_failure: 同樣進入recovery()。
已知切換與未知恢復
兩者都用 interval 節流重複輸入, 但觸發條件不同。
return_to_ready = flow.transition(
lambda: self.send_key("esc"),
interval=2,
timeout=60,
)
flow.step(
FishingStep.RESULT,
self.has_success_overlay,
self._collect_result,
next=(FishingStep.CAST,),
transition=return_to_ready,
)
transition()在 source action 成功完成後立刻執行一次, 並在等待next頁面時按interval重試。它的timeout從 action 返回後開始計算。recovery()只處理候選步驟都不可見, 或失敗路由返回None的未知畫面。它預設先等待 5 秒grace, 之後才按interval執行恢復動作。
flow.recovery(
self._recover_fishing_scene,
grace=5,
interval=2,
max_attempts=180,
timeout=360,
)
釣魚的 _recover_fishing_scene() 可在傳送 Escape 前釋放控條按鍵; 普通 transition 則直接寫 lambda: self.send_key("esc") 即可。
Guard 與中斷
guard()優先於普通 step, 但不修改當前候選集。釣魚的 TEAM guard 重新進入釣魚點後, 會繼續等待原來的補貨或釣魚步驟。interrupt()優先於 guard。月卡中斷在BaseNTETask中統一註冊。- 普通
wait_until()已自動檢查中斷。自定義高頻迴圈呼叫self.scene_flow.safe_point(); 發現中斷會拋SceneReplan並重新分類。 - 被
SceneReplan中斷的 action 不計入max_attempts。
最小檢查表
- detector 是否只讀畫面, 沒有輸入副作用?
- action 是否只完成一次業務提交? 失敗是否拋
WaitFailedException? - 成功後真實可能出現的每個畫面是否都在
next? - 是否真的要再次輸入? 需要才把當前 key 放入
next。 - 長動作是否在 action 內部設定自己的業務 timeout?
- 已知退出頁面是否使用 transition, 未知畫面是否交給 recovery?