Files
ImmortalSect/doc/模块联动.md
T

159 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 模块联动规范
本文档定义模块间联动逻辑的处理方案:谁调用谁、变更怎么传播、如何保证一致性。联动场景包括「建筑需要弟子才有产出」「炼丹需输入药材→产出丹药」「消耗药材→库存减少」等跨系统交互。
## 1. 方案组合结论
| 层 | 选择 | 理由 |
| --- | --- | --- |
| 主动操作联动(炼丹/交货/种植) | 直接调用 + 依赖注入 | 逻辑显式、可断点调试、单测零成本 |
| 回合结算顺序 | 固定管线(MainGame 依次调用) | 消除时间耦合,顺序可预期 |
| 状态变更通知(UI/统计/音效) | 信号广播(观察者,单向) | 不引入依赖,加监听者零改动 |
| 全局事件总线 / 规则引擎 | 不做,数据形状预留 | P1~P3 联动类型仅个位数,通用引擎成本大于收益 |
**明确不用的方案**
- 不用事件总线处理权威状态变更(如扣库存):请求-响应型联动需要双向信号,代码量反超直接调用,且结算顺序难追踪。事件广播只用于通知,不用于变更。
- 不用规则引擎全量执行联动:为 4 类联动(种植收获/炼丹/交货/修炼)写通用执行器是净亏损。P4 功法/术法批量进场(联动类型持续增长)时再评估抽成引擎。
## 2. 联动场景的本质
| 场景 | 本质 | 参与方 |
| --- | --- | --- |
| 建筑需要弟子才有产出 | 前置条件检查(人手占用) | 生产系统 ↔ 弟子系统 |
| 炼丹需输入药材→产出丹药 | 资源转换(事务) | 炼丹系统 ↔ 库存系统 |
| 消耗药材→库存减少 | 库存变更 + 广播 | 调用方 → 库存 → UI/统计 |
统一模式:**A 向 B 提需求(检查)→ B 执行变更 → 变更通知所有关心者**。
## 3. 依赖管理铁律
### 3.1 依赖图无环(DAG
库存、经济是叶子节点,谁也不依赖;生产类依赖库存/弟子;订单依赖库存+经济。**任何 Manager 不得依赖其上层**,出现环即设计错误。
```
OrderManager ──→ InventoryManager(叶子)
└──→ EconomyManager(叶子)
AlchemyManager ─→ InventoryManager
FieldManager ───→ InventoryManager
DiscipleManager ─→ EconomyManager(修炼扣灵石)
FoodManager ─────→ InventoryManager
└─→ DiscipleManager
```
**已知隐患**P2 启动前必须复查):
- 忠诚/抽成:弟子抽成 → 经济结算方向仍是 弟子→经济,不构成环;但若经济系统反过来查询弟子(按忠诚发分红)会成环,届时改走信号通知。
- 收徒拜师礼:一次性收入走 EconomyManager,同向,无环。
### 3.2 注入点唯一
所有 `setup()` 集中在 MainGame 一处,Manager 自身零 autoload 引用(TimeSystem/SaveSystem 除外按需)。这是依赖可管理的根源:
```gdscript
# MainGame._ready() 中:
economy_manager.setup()
inventory_manager.setup()
field_manager.setup(inventory_manager)
alchemy_manager.setup(inventory_manager)
food_manager.setup(disciple_manager, inventory_manager)
order_manager.setup(inventory_manager, economy_manager)
disciple_manager.setup(economy_manager)
```
好处:测试场景只注入 Mock 依赖即可单独运行;依赖关系读一遍 MainGame 全貌可见。
### 3.3 结算顺序管线化
`month_passed` 固定按序调用各 Manager 的 `settle_month()`,消除时间耦合:
```
灵田(收获入库存) → 炼丹(自动生产) → 弟子(修炼结算)
→ 口粮(凡人弟子消耗) → 订单(过期检查) → 经济(固定收入)
```
顺序依据:口粮必须在收获后扣(吃的是新粮);经济最后(汇总本月收支)。新增系统只插入管线,不改变既有顺序。
## 4. 原子事务约定(check-take-give
联动变更必须原子:先检查所有前置条件与材料,全部通过后再执行扣除与产出,**不允许扣一半失败**。
### 4.1 收敛到库存层
「先检查后扣除」的手工纪律收敛为库存层的两个方法,调用方不可能写错:
```gdscript
# InventoryManager
func try_take(items: Dictionary) -> bool # 全量检查,够则返回 true
func take(items: Dictionary) -> void # 实际扣除(try_take 通过后调用)
func add_item(item_id: String, count: int) -> void
func has_items(items: Dictionary) -> bool # 只查不扣(供 UI 显示可用性)
```
### 4.2 调用方模板
```gdscript
# AlchemyManager.refine() —— 主动操作联动标准写法
func refine(recipe_id: String, disciple: Disciple) -> bool:
var r: Dictionary = ContentDB.get_recipe(recipe_id)
if disciple.alchemy_skill < r["skill_req"]:
return false
if not inventory.try_take(r["inputs"]):
return false
inventory.take(r["inputs"])
inventory.add_item(r["output_id"], r["output_count"])
pill_refined.emit(r["output_id"])
return true
```
## 5. 单一写入口
每种数据只允许其归属 Manager 变更:
| 数据 | 唯一写入口 | 外部只能 |
| --- | --- | --- |
| 库存物品 | `InventoryManager.take/add_item` | 调用这两个方法 |
| 灵石余额 | `EconomyManager.spend/earn` | 调用这两个方法 |
| 弟子属性 | `DiscipleManager.set_attr` | 调用该方法 |
| 弟子增删 | `DiscipleManager.add/remove_disciple` | 调用该方法 |
违反此规则的直接后果:数据被谁改的不可追踪,信号漏发,UI 不同步。
## 6. 信号分工
| 信号类型 | 用途 | 方向 |
| --- | --- | --- |
| Manager 发出的领域信号(`inventory_changed`/`pill_refined`…) | UI 刷新、统计、音效 | 后端 → 前端/其他监听者,单向 |
| TimeSystem 的 `month_passed` 等 | 回合结算管线入口 | 已定,不改 |
| UI 调用 Manager 公开方法 | 玩家操作 | 前端 → 后端,直接调用不走信号 |
原则:**信号只通知,不携带状态变更职责**。UI 收到信号后从后端读最新值,不依赖信号参数里的数据(参数仅作提示)。
## 7. 规则引擎的预留(P4 退路)
P1 定义配方数据时,形状直接写成 `requires / consumes / produces` 三段(逻辑在代码手写):
```gdscript
{
"id": "refine_peiyuan",
"requires": [{"type": "disciple_skill", "skill": "alchemy", "min": 3}],
"consumes": [{"item": "herb_lingzhi", "count": 2}],
"produces": [{"item": "pill_peiyuan", "count": 1}],
}
```
将来联动类型膨胀(P4 功法/术法、炼丹失败率、双产出等)时,把各 Manager 手写逻辑抽成通用执行器(检查 requires → 扣 consumes → 给 produces,天然原子),**数据文件一行不用改**。
评估时机:新增联动类型需要改多个 Manager 的代码时,即考虑抽取。当前(P1)不抽取。
## 8. 违背规范的常见症状
| 症状 | 根因 | 对应规范 |
| --- | --- | --- |
| 库存数量莫名变化 | 绕过 InventoryManager 直接改 | §5 单一写入口 |
| 扣了一半材料失败 | 未先 try_take 全量检查 | §4 原子事务 |
| 结算结果依赖按钮点击顺序 | 未走固定管线 | §3.3 结算顺序 |
| Manager 互相调用成环 | 依赖方向失控 | §3.1 DAG |
| 换 UI 后数据不刷新 | 变更未发信号 | §6 信号分工 |