Skip to content

Commit beab14d

Browse files
feat: 支持非阻塞运行脚本
1 parent 7d2bebe commit beab14d

5 files changed

Lines changed: 82 additions & 20 deletions

File tree

‎src/script_chainer/config/script_config.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ class ScriptConfig:
156156
attach_direction: str = AttachDirection.NONE
157157
no_log_timeout_seconds: int = 0
158158
no_log_max_retries: int = 3
159+
block: bool = True
159160

160161
# 不参与序列化的元数据
161162
idx: int = field(default=0, repr=False, compare=False)

‎src/script_chainer/gui/page/script_edit_interface.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ def get_content_widget(self) -> QWidget:
128128
)
129129
content_widget.add_widget(self.run_timeout_seconds_opt)
130130

131+
block_switch_widget, self.block_switch = self._create_switch_option('阻塞')
132+
self.block_opt = MultiPushSettingCard(
133+
icon=FluentIcon.PAUSE,
134+
title='阻塞运行',
135+
content='勾选后启动会等待该脚本运行结束(默认开启);不勾选则在后台非阻塞启动',
136+
btn_list=[block_switch_widget],
137+
)
138+
content_widget.add_widget(self.block_opt)
139+
131140
self.check_done_opt = ComboBoxSettingCard(
132141
icon=FluentIcon.COMPLETED,
133142
title='检查完成方式',
@@ -257,6 +266,7 @@ def init_by_config(self, config: ScriptConfig):
257266
self.kill_script_after_done_switch.setChecked(config.kill_script_after_done)
258267
self.kill_game_after_done_switch.setChecked(config.kill_game_after_done)
259268
self.script_arguments_opt.setValue(config.script_arguments, emit_signal=False)
269+
self.block_switch.setChecked(config.block)
260270
self.notify_start_switch.setChecked(config.notify_start)
261271
self.notify_done_switch.setChecked(config.notify_done)
262272

@@ -351,6 +361,7 @@ def get_config_value(self) -> ScriptConfig:
351361
config.kill_script_after_done = self.kill_script_after_done_switch.isChecked()
352362
config.kill_game_after_done = self.kill_game_after_done_switch.isChecked()
353363
config.script_arguments = self.script_arguments_opt.getValue()
364+
config.block = self.block_switch.isChecked()
354365
config.notify_start = self.notify_start_switch.isChecked()
355366
config.notify_done = self.notify_done_switch.isChecked()
356367

‎src/script_chainer/gui/page/script_setting_cards.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,10 @@ def on_debug_clicked(self) -> None:
162162
command=cmd,
163163
cwd=cwd,
164164
title=f'调试 {display}',
165+
block=self.config.block,
165166
)
166-
show_success(self.window(), '调试运行', f'已在终端启动 {display}')
167+
mode = '阻塞' if self.config.block else '后台非阻塞'
168+
show_success(self.window(), '调试运行', f'已以{mode}模式启动 {display}')
167169
except Exception as e:
168170
show_error(self.window(), '启动失败', str(e))
169171

‎src/script_chainer/gui/page/script_setting_interface.py‎

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from PySide6.QtCore import QThread, Signal
12
from PySide6.QtWidgets import QFileDialog, QHBoxLayout, QVBoxLayout, QWidget
23
from qfluentwidgets import (
34
Action,
@@ -36,9 +37,35 @@
3637
show_warning,
3738
)
3839
from script_chainer.utils.process_utils import launch_in_terminal
39-
from script_chainer.utils.runner_utils import (
40-
build_runner_command,
41-
)
40+
from script_chainer.utils.runner_utils import build_runner_command
41+
42+
43+
class ChainRunner(QThread):
44+
"""后台逐条启动脚本链,每条按自身 block 决定阻塞/非阻塞,结束后经信号汇总结果。"""
45+
46+
chain_finished = Signal(int, list)
47+
48+
def __init__(self, chain_name: str, specs: list[tuple[int, bool, str]], parent=None):
49+
super().__init__(parent)
50+
self.chain_name = chain_name
51+
self.specs = specs
52+
53+
def run(self) -> None:
54+
launched = 0
55+
failed: list[str] = []
56+
for script_index, block, display_name in self.specs:
57+
try:
58+
cmd, cwd = build_runner_command(self.chain_name, script_index)
59+
proc = launch_in_terminal(command=cmd, cwd=cwd, block=block)
60+
except Exception:
61+
failed.append(display_name)
62+
continue
63+
# 阻塞脚本非零退出 → 计入失败;非阻塞 rc 为 wt 的 0,按已启动计
64+
if proc.returncode not in (0, None):
65+
failed.append(display_name)
66+
else:
67+
launched += 1
68+
self.chain_finished.emit(launched, failed)
4269

4370

4471
class ScriptSettingRootInterface(VerticalScrollInterface):
@@ -201,26 +228,32 @@ def on_import_python_script_clicked(self) -> None:
201228
self.update_chain_display()
202229

203230
def on_run_chain_clicked(self) -> None:
204-
"""拉起独立 runner 运行当前脚本链。"""
231+
"""按配置顺序逐条启动当前脚本链,每条按自身 block 决定阻塞/非阻塞。"""
205232
if self.chosen_config is None or self._runner_launch_in_progress:
206233
return
207234

235+
specs = [
236+
(i, cfg.block, cfg.display_name)
237+
for i, cfg in enumerate(self.chosen_config.script_list)
238+
if cfg.enabled
239+
]
240+
if not specs:
241+
show_warning(self.window(), '无法运行', '没有已启用的脚本')
242+
return
243+
208244
self._runner_launch_in_progress = True
209245
self.run_chain_btn.setEnabled(False)
210-
chain_name = self.chosen_config.module_name
211-
try:
212-
cmd, cwd = build_runner_command(chain_name)
213-
launch_in_terminal(
214-
command=cmd,
215-
cwd=cwd,
216-
title=f'运行脚本链 {chain_name}',
217-
)
218-
show_success(self.window(), '运行全部', f'已在终端启动脚本链 {chain_name}')
219-
except Exception as e:
220-
show_error(self.window(), '启动失败', str(e))
221-
finally:
222-
self._runner_launch_in_progress = False
223-
self.run_chain_btn.setEnabled(True)
246+
self._chain_runner = ChainRunner(self.chosen_config.module_name, specs, parent=self)
247+
self._chain_runner.chain_finished.connect(self._on_chain_run_finished)
248+
self._chain_runner.start()
249+
250+
def _on_chain_run_finished(self, launched: int, failed: list) -> None:
251+
self._runner_launch_in_progress = False
252+
self.run_chain_btn.setEnabled(True)
253+
if failed:
254+
show_error(self.window(), '运行异常', f'{len(failed)} 个脚本启动失败:{", ".join(failed)}')
255+
else:
256+
show_success(self.window(), '运行全部', f'已启动 {launched} 个脚本')
224257

225258
def update_chain_display(self) -> None:
226259
"""更新脚本链的显示"""

‎src/script_chainer/utils/process_utils.py‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def launch_in_terminal(
4141
command: list[str],
4242
cwd: str | None = None,
4343
title: str | None = None,
44+
block: bool = False,
4445
) -> subprocess.Popen:
4546
"""在终端窗口中启动命令。
4647
@@ -49,11 +50,25 @@ def launch_in_terminal(
4950
Args:
5051
command: 命令及参数列表。
5152
cwd: 工作目录。
52-
title: 终端窗口标题。
53+
title: 终端窗口标题(仅非阻塞的 wt 分支使用)。
54+
block: 是否阻塞等待命令真正结束。
5355
5456
Returns:
5557
启动的 Popen 对象。
58+
59+
说明:
60+
- ``block=False``(默认):用 wt 开新标签页/窗口后**立即返回**(非阻塞)。
61+
- ``block=True``:Windows Terminal 开完标签页即退出,无法等待真正的子进程,
62+
故绕过 wt、改用新建控制台窗口(``CREATE_NEW_CONSOLE``)并 ``.wait()``——
63+
既有可见窗口,又能正确阻塞到脚本真正结束。
5664
"""
65+
if block:
66+
# 阻塞:不能用 wt(开完标签页即退、等不到真进程),改用新控制台窗口并等待。
67+
flags = subprocess.CREATE_NEW_CONSOLE if sys.platform == 'win32' else 0
68+
proc = subprocess.Popen(command, cwd=cwd, creationflags=flags)
69+
proc.wait()
70+
return proc
71+
5772
wt_path = _find_windows_terminal()
5873
if wt_path is not None:
5974
wt_cmd = [wt_path]

0 commit comments

Comments
 (0)