Files
zdwm/docs/module-layering.org

117 lines
8.2 KiB
Org Mode
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.
#+title: ZDWM 模块分层与依赖方向
#+OPTIONS: toc:2
* 动机
随着模块增加,跨模块的"契约"和"共享数据结构"开始散落:有的进了 =core/=(和窗口管理逻辑混在一起),有的不得不靠依赖倒置塞进消费方目录(如 tray 契约放 =bar/= 导致 backend 反向依赖 bar。结果是 core 职责不清、=bar ↔ backend= 出现交叉依赖、依赖方向不再单向。
本文档定义一套分层约定,把"跨模块共享的东西"从具体实现里抽出来,集中到两个目录,使依赖方向回到干净的单向。
* 核心标准:按"谁直接消费类型"归类
判断一个跨模块的东西进哪个目录,不看它"属于哪个模块",也不只看"是声明还是实现",而是看**它的类型被哪些层直接消费**
- 类型被**两个或以上对等的实现层**直接消费 → 类型放 =interface/=(跨模块声明)
- 只有**操作函数**被多个实现层共用、且这些层不该互相依赖 → 操作放 =common/=
- 类型 + 操作都只被一个实现层(及其依赖者)用 → 整个 ADT 放 =common/=,或就近放该实现层
关键推论:**只需消费"类型"的层,不该被迫背上"操作函数"的依赖**。这是把类型和操作分到两个目录的根本理由。
* 两个目录的定位
** =src/interface/= :跨模块共享的【声明】,零 .c
只放类型定义、多态接口的函数声明。绝对不放假装、零实现代码。被所有实现层依赖,自身不依赖任何实现层。
** =src/common/= :被多实现层共用的【操作函数】,.h + .c
放自包含的 ADT 操作(构造/清理/查询)。操作只依赖 =interface/= 里的类型(和 =base/= 工具),不依赖任何实现层的内部状态。被"需要这些操作的层"依赖。
注意:=common/= 不是"什么都往里塞的杂物间"。只有当操作被**多个不该互相依赖的实现层**共用时,才进 =common/=;只被单一实现层用的操作,就近放该层。
* 依赖方向
#+begin_example
interface/ ← 零依赖(纯声明),所有实现层都依赖它
common/ ← 依赖 interface用类型+ base/(工具),被需要操作的层依赖
core/ runtime/ (依赖 common + interface)
backend/ bar/ (只依赖 interface —— 不碰 common 的操作)
#+end_example
不变量:
- =interface/= 不依赖任何实现层。
- =common/= 只依赖 =interface/==base/=,不依赖任何实现层。
- 实现层之间core/backend/bar/runtime不直接相互依赖跨模块契约都经由 =interface/= 或 =common/=
- 因此 =bar ↔ backend= 这类对等层之间不再有交叉依赖:都通过 =interface/tray.h= 通信。
* 判定示例effect / plan / event
用上面的标准,逐个判定现有跨模块类型:
** =effect_t= → interface类型无 .c
消费方corepolicy/plan 产生 =effect_t=+ backend执行 =effect_t=)。两个对等层直接消费类型,所以类型进 =interface/effect.h==effect_t= 是纯数据,构造它的活(=plan_push_*=)属于 plan 的操作,所以 effect 在 interface 里只有类型、没有 .c。
#+begin_src c
/* backend 只消费展开后的 effect 数组,从不碰 plan_t */
bool backend_apply_effect(backend_t *backend, const effect_t *effects, size_t effect_count);
#+end_src
** =plan_t= → common完整 ADT类型 + 操作)
消费方:只 corepolicy 产生、收集 effect和 runtime提交后 reset/cleanup。backend 完全不消费 =plan_t=——runtime 提交时把 plan 拆成 =effect_t= 数组喂给 backend。所以 =plan_t= 非跨模块,类型和操作整个 ADT 都落 =common/plan.h= + =common/plan.c=。
** =event_t= → interface类型+ common操作
=event_t= 类型被 backend产生/填充)和 core/runtime路由/清理)直接消费 → 类型进 =interface/event.h=
=event_reset= / =event_cleanup= 这些操作被 backend 和 runtime 共用,而两者对等、不该互相依赖,操作不能归属任一层 → 操作落 =common/event.h= + =common/event.c=。
这是"类型和操作分两个目录"的典型:类型跨模块必须在 interfacebackend 要产 =event_t=),操作对等共用必须在 common。
* 汇总表
| 东西 | 类型在哪 | 操作在哪 | 判定理由 |
|------------------------------+-------------------------+-------------------------------------+--------------------------------------------------------------------------|
| =effect_t= | =interface/effect.h= | (无,构造归 plan 操作) | 类型跨模块core 产 + backend 执行),纯数据 |
| =plan_t= | =common/plan.h= | =common/plan.c= | 非跨模块,只 core/runtime 用,整个 ADT 在 common |
| =event_t= | =interface/event.h= | =common/event.h= + =common/event.c= | 类型跨模块;操作对等共用 |
| 后端接口(=backend_t= 等) | =interface/backend.h= | =backend/x11/*.c= | 多态接口:声明在 interface实现在具体后端 |
| tray 契约(=tray_t= 等) | =interface/tray.h= | =backend/x11/tray.c= | 多态接口bar 用、backend 实现,声明在 interface 消除 =bar↔backend= 交叉 |
| 通知协议listeners | =interface/listeners.h= | =core/listeners.c= | 多态接口core 发布、bar 订阅,声明在 interface |
| 基础类型(=window_id_t= 等) | =interface/types.h= | (按需) | 所有层共用 |
* 多态接口 vs 共享 ADT.c 归属的不同
=interface/= 里有两类声明,它们的 .c 归属不同,务必区分:
- **多态接口**=backend.h==tray.h==listeners.h=):声明一组"由谁来实现"的函数实现依赖具体实现层xcb / 未来 wayland。这类 .c **必须在实现层**=backend/x11/==core/=),不能在 interface——否则 interface 就"知道"了具体实现。
- **共享 ADT 的操作**plan、event 的操作):实现自包含、不依赖任何实现层。这类 .c 进 =common/=
换句话说:=interface/= 永远零 .c。多态接口的 .c 在实现层,共享操作的 .c 在 =common/=
* 和 include/zdwm/ 的区别
- =include/zdwm/= 是**对外公共 API**(给用户配置、插件使用)。
- =src/interface/= 是**内部跨模块契约**(给各子系统互相通信)。
两者职责不同,不合并。像 =backend.h==event.h==effect.h= 是内部的,不该进 =include/zdwm/= 暴露给外部。
* 迁移指引
把现有代码按本分层整理时,建议顺序:
1.=src/interface/==src/common/= 目录,加入 CMakeLists。
2. 先迁移"纯类型、零依赖"的声明进 =interface/==types.h==effect.h=(从 =core/plan.h= 抽出 =effect_t=)、=event.h= 的类型部分。
3. 多态接口声明迁移:=backend.h==listeners.h==interface/==.c= 留原处)。
4. 共享 ADT 迁移:=plan.h= + =plan.c= 整体进 =common/==event.h= 操作部分 + =event.c==common/=
5. 更新全项目 include 路径(="core/plan.h"=="common/plan.h"= 等)。
6. 每步后编译验证,确保依赖方向单向、无循环。
注意边界:遇到"看起来该进 interface、但其类型依赖还在 core"的情况(如某个 event 子结构引用了 =core/window.h= 的类型),要么把那个被引用的类型也提到 =interface/=,要么承认它暂不进 interface——**进 interface 的前提是它的所有依赖都在 interface**,不能硬塞。
* tray 的落点
tray 的契约(=tray_t= / =tray_api_t=)是 bar 与 backend 之间的多态接口,按本分层落 =interface/tray.h=(声明),实现在 =backend/x11/tray.c=。这样 bar 和 backend 都只依赖 =interface/tray.h=,彻底消除 =bar ↔ backend= 交叉依赖——这正是引入这套分层的最初动机之一。详见 [[file:tray-design.org][tray-design.org]]。