Skip to content

Commit b7f6bd2

Browse files
authored
feat: 适配新 anchor (#161)
## Summary by Sourcery 调整调试器运行时界面和事件处理机制,以支持在识别流水线中基于锚点的下一节点导航元数据。 新功能: - 在运行时下一节点列表中,为源自锚点的条目显示锚点元数据和图标。 - 将锚点标志从框架事件接收端传递到运行时控件,以区分锚点条目和普通节点。 增强改进: - 在列表项和列表上跟踪锚点相关属性,使识别结果既可以通过锚点目标匹配,也可以通过名称匹配。 - 在启动新的下一节点列表时,从流水线节点数据中推导锚点名称和目标节点,并将这些信息附加到对应的列表项上。 <details> <summary>Original summary in English</summary> ## Summary by Sourcery Adapt the debugger runtime UI and event handling to support anchor-based next-node navigation metadata in recognition pipelines. New Features: - Display anchor metadata and icon for anchor-derived items in the runtime next-node list. - Propagate anchor flags from the framework event sink into the runtime control to distinguish anchor entries from normal nodes. Enhancements: - Track anchor-related attributes on list items and lists so recognition results can match by anchor target as well as by name. - Derive anchor names and target nodes from pipeline node data when starting a new next-node list, and attach this information to corresponding list items. </details>
1 parent d816ae8 commit b7f6bd2

2 files changed

Lines changed: 140 additions & 10 deletions

File tree

src/MaaDebugger/maafw/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ def on_node_next_list(
448448
"msg": f"NextList.{msg_suffix}",
449449
"name": detail.name,
450450
"next_list": [attr.name for attr in detail.next_list],
451+
"anchor_flags": [attr.anchor for attr in detail.next_list],
451452
}
452453
if debug_mode:
453454
print(f"[DEBUG EventSink] {msg}")

src/MaaDebugger/webpage/index_page/runtime_control.py

Lines changed: 139 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ class ItemData:
4444
name: str
4545
reco_id: int = 0
4646
status: Status = Status.PENDING
47+
# 是否通过 anchor 解析得到
48+
is_anchor: bool = False
49+
# 锚点名称(如 "锚点"),仅当 is_anchor=True 时有效
50+
anchor_name: str = ""
51+
# 锚点指向的目标节点名称
52+
anchor_target: str = ""
53+
# 锚点设置者节点名称(即定义 anchor 的节点)
54+
anchor_setter: str = ""
4755
# 嵌套子项的容器引用(用于 RecognitionNode 添加子识别项)
4856
nested_container: Optional[Any] = field(default=None, repr=False)
4957
# 嵌套子项列表
@@ -59,6 +67,11 @@ class ListData:
5967
row_len: int
6068
current: str
6169
next_list: List[str]
70+
anchor_flags: List[bool] = field(default_factory=list)
71+
# 每个 next_list 项对应的锚点名称(空字符串表示非锚点)
72+
anchor_names: List[str] = field(default_factory=list)
73+
# 每个 next_list 项对应的锚点目标节点名称(空字符串表示非锚点)
74+
anchor_targets: List[str] = field(default_factory=list)
6275

6376

6477
def main():
@@ -193,8 +206,25 @@ def on_page_change(self, page: int):
193206
for row_len in row_len_list[start_index:end_index]:
194207
self.create_list(self.other_page_row, self.list_data_map[row_len])
195208

196-
def add_item_data(self, index, name, row_len: int):
197-
data = ItemData(row_len, index, name)
209+
def add_item_data(
210+
self,
211+
index,
212+
name,
213+
row_len: int,
214+
is_anchor: bool = False,
215+
anchor_name: str = "",
216+
anchor_target: str = "",
217+
anchor_setter: str = "",
218+
):
219+
data = ItemData(
220+
row_len,
221+
index,
222+
name,
223+
is_anchor=is_anchor,
224+
anchor_name=anchor_name,
225+
anchor_target=anchor_target,
226+
anchor_setter=anchor_setter,
227+
)
198228
self.data[row_len][index] = data
199229

200230
def create_list(self, row: ui.row, data: ListData):
@@ -227,7 +257,17 @@ def create_items(self, index: int, name: str, row_len: int):
227257
StatusIndicator(data, "status")
228258

229259
with ui.item_section():
230-
ui.item_label(name)
260+
if data.is_anchor:
261+
with ui.row(wrap=False).classes("items-center gap-1"):
262+
display_name = data.anchor_target or name
263+
anchor_label = (
264+
f"{display_name} (anchor:{data.anchor_name or '未知'}, "
265+
f"from:{data.anchor_setter or '未知'})"
266+
)
267+
ui.item_label(anchor_label)
268+
ui.badge("⚓", color="blue-grey-4").props("rounded")
269+
else:
270+
ui.item_label(name)
231271

232272
with ui.item_section().props("side"):
233273
ui.item_label().bind_text_from(data, "reco_id").bind_visibility_from(
@@ -296,9 +336,12 @@ async def _handle_message(self, msg: Dict[str, Any]):
296336
if msg_type == "NextList.Starting":
297337
name = msg.get("name", "")
298338
next_names = msg.get("next_list", [])
339+
anchor_flags = msg.get("anchor_flags", [])
299340
if debug_mode:
300-
print(f"[DEBUG] NextList.Starting: name={name}, next_list={next_names}")
301-
self._on_next_list_starting(name, next_names)
341+
print(
342+
f"[DEBUG] NextList.Starting: name={name}, next_list={next_names}, anchor_flags={anchor_flags}"
343+
)
344+
self._on_next_list_starting(name, next_names, anchor_flags)
302345
await maafw.screenshotter.refresh(False)
303346

304347
# RecognitionNode.Starting - 标记进入嵌套识别模式
@@ -407,7 +450,11 @@ def _on_recognized(self, reco_id: int, name: str, hit: bool):
407450
# 如果通过 reco_id 没找到,检查当前 row(非嵌套项)
408451
if not found:
409452
for item in self.data[self.row_len].values():
410-
if item.status == Status.PENDING and item.name == name:
453+
if item.status != Status.PENDING:
454+
continue
455+
name_matched = item.name == name
456+
anchor_matched = item.is_anchor and item.anchor_target == name
457+
if name_matched or anchor_matched:
411458
item.reco_id = reco_id
412459
item.status = Status.SUCCEEDED if hit else Status.FAILED
413460
matched_item = item
@@ -421,7 +468,11 @@ def _on_recognized(self, reco_id: int, name: str, hit: bool):
421468
if not found:
422469
for row_data in self.data.values():
423470
for item in row_data.values():
424-
if item.status == Status.PENDING and item.name == name:
471+
if item.status != Status.PENDING:
472+
continue
473+
name_matched = item.name == name
474+
anchor_matched = item.is_anchor and item.anchor_target == name
475+
if name_matched or anchor_matched:
425476
item.reco_id = reco_id
426477
item.status = Status.SUCCEEDED if hit else Status.FAILED
427478
matched_item = item
@@ -458,11 +509,74 @@ def _on_reco_node_ended(self):
458509
f"[DEBUG] RecognitionNode depth decreased to {self._reco_node_depth}, stack size: {len(self._recognition_stack)}"
459510
)
460511

461-
def _on_next_list_starting(self, current: str, next_list: List[str]):
512+
def _on_next_list_starting(
513+
self,
514+
current: str,
515+
next_list: List[str],
516+
anchor_flags: Optional[List[bool]] = None,
517+
):
462518
"""处理 NextList 开始事件"""
463519
self.row_len += 1
464520

465-
list_data = ListData(self.row_len, current, next_list)
521+
normalized_anchor_flags = anchor_flags or []
522+
523+
# 从 pipeline 的 next 列表中提取 [Anchor] 标记,得到锚点名和目标节点
524+
anchor_names: List[str] = []
525+
anchor_targets: List[str] = []
526+
if any(normalized_anchor_flags):
527+
node_data = maafw.get_node_data(current)
528+
if isinstance(node_data, dict):
529+
node_next = node_data.get("next", [])
530+
anchor_map = node_data.get("anchor", {})
531+
else:
532+
node_next = []
533+
anchor_map = {}
534+
535+
for i, _name in enumerate(next_list):
536+
is_anchor = (
537+
i < len(normalized_anchor_flags) and normalized_anchor_flags[i]
538+
)
539+
if not is_anchor:
540+
anchor_names.append("")
541+
anchor_targets.append("")
542+
continue
543+
544+
anchor_name = ""
545+
anchor_target = ""
546+
547+
if i < len(node_next) and isinstance(node_next[i], str):
548+
raw_next = node_next[i]
549+
if raw_next.startswith("[Anchor]"):
550+
anchor_name = raw_next.replace("[Anchor]", "", 1)
551+
if isinstance(anchor_map, dict):
552+
anchor_target = anchor_map.get(anchor_name, "")
553+
554+
if (
555+
not anchor_name
556+
and isinstance(anchor_map, dict)
557+
and isinstance(_name, str)
558+
):
559+
if _name in anchor_map:
560+
anchor_name = _name
561+
anchor_target = anchor_map.get(_name, "")
562+
563+
if not anchor_target and isinstance(_name, str):
564+
anchor_target = _name
565+
566+
anchor_names.append(anchor_name)
567+
anchor_targets.append(anchor_target)
568+
else:
569+
anchor_names = [""] * len(next_list)
570+
anchor_targets = [""] * len(next_list)
571+
572+
list_data = ListData(
573+
self.row_len,
574+
current,
575+
next_list,
576+
normalized_anchor_flags,
577+
anchor_names,
578+
anchor_targets,
579+
)
466580
self.add_list_data(list_data)
467581

468582
# 299/300 -> page:1 | 300/300 -> page:2
@@ -507,7 +621,22 @@ def add_list_data(self, data: ListData):
507621
self.list_data_map[data.row_len] = data
508622
for index in range(len(data.next_list)):
509623
name = data.next_list[index]
510-
self.add_item_data(index, name, data.row_len)
624+
is_anchor = index < len(data.anchor_flags) and data.anchor_flags[index]
625+
anchor_name = (
626+
data.anchor_names[index] if index < len(data.anchor_names) else ""
627+
)
628+
anchor_target = (
629+
data.anchor_targets[index] if index < len(data.anchor_targets) else ""
630+
)
631+
self.add_item_data(
632+
index,
633+
name,
634+
data.row_len,
635+
is_anchor=is_anchor,
636+
anchor_name=anchor_name,
637+
anchor_target=anchor_target,
638+
anchor_setter=data.current if is_anchor else "",
639+
)
511640

512641
def on_resource_loading(
513642
self,

0 commit comments

Comments
 (0)