@@ -63,17 +63,19 @@ Meta-Minesweeper 采用 **ZMQ 多进程插件架构**:
6363├── plugin_manager.exe # 插件管理器
6464├── plugins/ # 👈 用户插件放这里!
6565│ ├── my_hello.py # 你的插件(单文件)
66- │ └── my_complex/ # 或包形式插件
67- │ ├── __init__.py
68- │ └── utils.py
69- ├── shared_types/ # 共享类型定义
70- │ ├── events.py # 事件类型
71- │ ├── commands.py # 指令类型
66+ │ ├── my_complex/ # 或包形式插件
67+ │ │ ├── __init__.py
68+ │ │ └── utils.py
7269│ └── services/ # 👈 服务接口定义
7370│ └── history.py # HistoryService 接口
74- ├── plugin_manager / # 插件管理器模块
71+ ├── plugin_sdk / # 插件开发 SDK
7572│ ├── plugin_base.py # 👈 BasePlugin 基类
76- │ └── service_registry.py # 服务注册表
73+ │ ├── service_registry.py # 服务注册表
74+ │ └── config_types/ # 配置类型
75+ ├── shared_types/ # 共享类型定义
76+ │ ├── events.py # 事件类型
77+ │ └── commands.py # 指令类型
78+ ├── plugin_manager/ # 插件管理器内部模块
7779├── user_plugins/ # 备用用户插件目录
7880├── data/
7981│ ├── logs/ # 日志输出(自动创建)
@@ -144,6 +146,7 @@ plugins/
144146### 3.3 自动发现规则
145147
146148- 文件/目录名以 ` _ ` 开头的会被跳过(如 ` _template.py ` )
149+ - ` services ` 目录会被跳过(它是服务接口定义,不是插件)
147150- 单个 ` .py ` 文件中可以定义多个继承 ` BasePlugin ` 的类,都会被加载
148151- 包形式插件中,只有 ` __init__.py ` 中导出的 ` BasePlugin ` 子类会被发现
149152
@@ -167,7 +170,7 @@ from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QTextEdit
167170from PyQt5.QtCore import Qt, pyqtSignal
168171
169172# 导入插件基类和辅助类型
170- from plugin_manager import BasePlugin, PluginInfo, make_plugin_icon, WindowMode
173+ from plugin_sdk import BasePlugin, PluginInfo, make_plugin_icon, WindowMode
171174
172175# 导入可用的事件类型
173176from shared_types.events import VideoSaveEvent
@@ -466,7 +469,19 @@ if self.has_service(MyService):
466469 pass
467470
468471# ════════════════════════════════════════
469- # 3. 获取服务代理(推荐)
472+ # 3. 等待服务就绪(推荐)
473+ # ════════════════════════════════════════
474+ # 如果服务提供者可能在消费者之后初始化,使用 wait_for_service
475+ service = self .wait_for_service(MyService, timeout = 10.0 )
476+ if service:
477+ # 服务可用
478+ data = service.get_data(123 )
479+ else :
480+ # 服务未就绪
481+ self .logger.warning(" MyService 未就绪" )
482+
483+ # ════════════════════════════════════════
484+ # 4. 获取服务代理(已知服务存在时)
470485# ════════════════════════════════════════
471486service = self .get_service_proxy(MyService)
472487
@@ -475,7 +490,7 @@ data = service.get_data(123) # 同步调用,阻塞等待结果
475490all_data = service.list_data(100 ) # 超时默认 10 秒
476491
477492# ════════════════════════════════════════
478- # 4 . 异步调用(非阻塞)
493+ # 5 . 异步调用(非阻塞)
479494# ════════════════════════════════════════
480495future = self .call_service_async(MyService, " get_data" , 123 )
481496# 做其他事情...
@@ -488,7 +503,8 @@ result = future.result(timeout=5.0) # 阻塞等待结果
488503| ------| ------|
489504| ` register_service(self, protocol=MyService) ` | 注册服务(在 ` on_initialized ` 中调用) |
490505| ` has_service(MyService) ` | 检查服务是否可用 |
491- | ` get_service_proxy(MyService) ` | 获取服务代理对象(推荐) |
506+ | ` wait_for_service(MyService, timeout=10.0) ` | 等待服务就绪并获取代理(推荐) |
507+ | ` get_service_proxy(MyService) ` | 获取服务代理对象(已知存在时) |
492508| ` call_service_async(MyService, "method", *args) ` | 异步调用,返回 Future |
493509
494510** 注意事项:**
@@ -566,7 +582,7 @@ class PluginInfo:
566582继承 ` OtherInfoBase ` 并声明配置字段:
567583
568584``` python
569- from plugin_manager.config_types import (
585+ from plugin_sdk import (
570586 OtherInfoBase, BoolConfig, IntConfig, FloatConfig,
571587 ChoiceConfig, TextConfig, ColorConfig, FileConfig,
572588 PathConfig, LongTextConfig, RangeConfig,
@@ -742,9 +758,9 @@ data/plugin_data/<plugin_name>/config.json
742758如果预定义的配置类型不满足需求,可以继承 ` BaseConfig ` 创建自定义类型:
743759
744760``` python
745- from plugin_manager .config_types.base_config import BaseConfig
746- from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QDial
747- from PyQt5.QtCore import Qt
761+ from plugin_sdk .config_types import BaseConfig, ConfigWidgetBase, ConfigWidgetWrapper
762+ from PyQt5.QtWidgets import QDial
763+ from typing import Any
748764
749765class DialConfig (BaseConfig[int ]):
750766 """ 旋钮配置 → QDial 控件"""
@@ -762,8 +778,8 @@ class DialConfig(BaseConfig[int]):
762778 self .min_value = min_value
763779 self .max_value = max_value
764780
765- def create_widget (self ):
766- """ 创建自定义 UI 控件,返回 (控件, getter, setter, 信号) """
781+ def create_widget (self ) -> ConfigWidgetBase :
782+ """ 创建自定义 UI 控件,返回 ConfigWidgetBase 实例 """
767783 widget = QDial()
768784 widget.setRange(self .min_value, self .max_value)
769785 widget.setValue(int (self .default))
@@ -772,14 +788,23 @@ class DialConfig(BaseConfig[int]):
772788 if self .description:
773789 widget.setToolTip(self .description)
774790
775- # 返回控件、getter、setter、以及 valueChanged 信号
776- return widget, widget.value, widget.setValue, widget.valueChanged
791+ # 使用 ConfigWidgetWrapper 包装控件
792+ # 参数:控件, getter, setter, 信号
793+ return ConfigWidgetWrapper(
794+ widget,
795+ widget.value, # getter
796+ widget.setValue, # setter
797+ widget.valueChanged # 信号
798+ )
777799
778800 def to_storage (self , value : int ) -> int :
779801 return int (value)
780802
781- def from_storage (self , data ) -> int :
782- return int (data)
803+ def from_storage (self , data : Any) -> int :
804+ try :
805+ return int (data)
806+ except (ValueError , TypeError ):
807+ return int (self .default)
783808
784809# 使用自定义配置类型
785810class MyConfig (OtherInfoBase ):
@@ -792,29 +817,53 @@ class MyConfig(OtherInfoBase):
792817| 方法/属性 | 说明 |
793818| -----------| ------|
794819| ` widget_type ` | 控件类型标识 |
795- | ` create_widget() ` | 返回 ` (控件, getter, setter, 信号) ` 四元组 |
820+ | ` create_widget() ` | 返回 ` ConfigWidgetBase ` 实例 |
796821| ` to_storage(value) ` | 将值转换为 JSON 可序列化格式 |
797822| ` from_storage(data) ` | 从 JSON 数据恢复值 |
798823
799- ** 信号对象说明:**
824+ ** ConfigWidgetBase 接口:**
825+
826+ ` create_widget() ` 必须返回一个实现了以下接口的对象:
827+
828+ | 方法/信号 | 说明 |
829+ | -----------| ------|
830+ | ` get_value() -> Any ` | 获取当前值 |
831+ | ` set_value(value: Any) ` | 设置当前值 |
832+ | ` value_change ` | ` pyqtSignal(object) ` 值变化信号 |
800833
801- 信号对象可以是:
802- - Qt 控件的内置信号(如 ` widget.valueChanged ` 、 ` widget.textChanged ` )
803- - 自定义 ` pyqtSignal ` (需要通过 QObject 子类定义)
834+ ** 使用 ConfigWidgetWrapper: **
835+
836+ 对于简单的包装需求,可以使用 ` ConfigWidgetWrapper ` :
804837
805838``` python
806- # 方式一:使用控件的内置信号
807- return widget, widget.value, widget.setValue, widget.valueChanged
839+ return ConfigWidgetWrapper(widget, getter, setter, signal)
840+ ```
841+
842+ ** 创建自定义 ConfigWidgetBase 子类:**
808843
809- # 方式二:自定义信号(复杂控件)
810- from PyQt5.QtCore import QObject, pyqtSignal
844+ 对于复杂控件,可以创建 ` ConfigWidgetBase ` 的子类:
811845
812- class MySignal (QObject ):
813- changed = pyqtSignal()
846+ ``` python
847+ from plugin_sdk.config_types import ConfigWidgetBase
848+ from PyQt5.QtCore import pyqtSignal
814849
815- signal_emitter = MySignal(parent = container) # parent 防止垃圾回收
816- # ... 控件变化时调用 signal_emitter.changed.emit()
817- return container, get_value, set_value, signal_emitter.changed
850+ class MyCustomWidget (ConfigWidgetBase ):
851+ """ 自定义配置控件"""
852+
853+ # 子类会继承 value_change = pyqtSignal(object) 信号
854+
855+ def __init__ (self , parent = None ):
856+ super ().__init__ (parent)
857+ # 创建内部控件...
858+
859+ def get_value (self ) -> Any:
860+ """ 获取当前值"""
861+ return self ._internal_widget.value()
862+
863+ def set_value (self , value : Any) -> None :
864+ """ 设置当前值"""
865+ self ._internal_widget.setValue(value)
866+ self .value_change.emit(value) # 发射信号
818867```
819868
820869---
@@ -842,7 +891,7 @@ from PyQt5.QtWidgets import (
842891from PyQt5.QtCore import Qt, pyqtSignal, QTimer
843892from PyQt5.QtGui import QFont
844893
845- from plugin_manager import BasePlugin, PluginInfo, make_plugin_icon, WindowMode
894+ from plugin_sdk import BasePlugin, PluginInfo, make_plugin_icon, WindowMode
846895from shared_types.events import VideoSaveEvent, BoardUpdateEvent
847896
848897
@@ -1103,10 +1152,10 @@ def on_initialized(self):
11031152
11041153#### 1. 定义服务接口
11051154
1106- 在 ` shared_types /services/` 目录下创建接口定义文件:
1155+ 在 ` plugins /services/` 目录下创建接口定义文件:
11071156
11081157``` python
1109- # shared_types /services/my_service.py
1158+ # plugins /services/my_service.py
11101159from typing import Protocol, runtime_checkable
11111160from dataclasses import dataclass
11121161
@@ -1126,6 +1175,8 @@ class MyService(Protocol):
11261175#### 2. 服务提供者
11271176
11281177``` python
1178+ from plugins.services.my_service import MyService, MyData
1179+
11291180class ProviderPlugin (BasePlugin ):
11301181 def on_initialized (self ):
11311182 # 注册服务(显式指定 protocol)
@@ -1142,16 +1193,36 @@ class ProviderPlugin(BasePlugin):
11421193#### 3. 服务使用者
11431194
11441195``` python
1196+ from plugins.services.my_service import MyService, MyData
1197+
11451198class ConsumerPlugin (BasePlugin ):
11461199 def on_initialized (self ):
1147- if self .has_service(MyService):
1148- # 获取服务代理(推荐)
1149- self ._service = self .get_service_proxy(MyService)
1200+ # 方式一:等待服务就绪(推荐)
1201+ self ._service = self .wait_for_service(MyService, timeout = 10.0 )
1202+ if self ._service is None :
1203+ self .logger.warning(" MyService 未就绪" )
11501204
11511205 def _do_something (self ):
1152- # 调用服务方法(IDE 完整补全,在服务提供者线程执行)
1153- data = self ._service.get_data(123 )
1154- all_data = self ._service.list_data(100 )
1206+ if self ._service:
1207+ # 调用服务方法(IDE 完整补全,在服务提供者线程执行)
1208+ data = self ._service.get_data(123 )
1209+ all_data = self ._service.list_data(100 )
1210+ ```
1211+
1212+ #### 4. 等待服务就绪
1213+
1214+ 如果服务提供者可能在消费者之后初始化,可以使用 ` wait_for_service ` :
1215+
1216+ ``` python
1217+ def on_initialized (self ):
1218+ # 等待服务就绪,最多 10 秒
1219+ service = self .wait_for_service(MyService, timeout = 10.0 )
1220+ if service:
1221+ # 服务可用
1222+ data = service.get_data(123 )
1223+ else :
1224+ # 服务未就绪,可以稍后重试或降级处理
1225+ self .logger.warning(" MyService 未就绪" )
11551226```
11561227
11571228#### 服务调用方式
@@ -1221,10 +1292,10 @@ class ConsumerPlugin(BasePlugin):
12211292``` python
12221293# ═══ 最小可行插件模板 ═══
12231294
1224- from plugin_manager import BasePlugin, PluginInfo, make_plugin_icon, WindowMode
1225- from plugin_manager.config_types import OtherInfoBase, BoolConfig, IntConfig # 可选
1295+ from plugin_sdk import BasePlugin, PluginInfo, make_plugin_icon, WindowMode
1296+ from plugin_sdk import OtherInfoBase, BoolConfig, IntConfig # 配置类型(可选)
12261297from shared_types.events import VideoSaveEvent # 按需导入
1227- from shared_types .services.my_service import MyService # 服务接口(可选)
1298+ from plugins .services.my_service import MyService # 服务接口(可选)
12281299
12291300# ═══ 配置类定义(可选) ═══
12301301class MyConfig (OtherInfoBase ):
0 commit comments