mirror of
https://github.com/motajs/template.git
synced 2026-05-14 04:41:10 +08:00
Compare commits
3 Commits
54606f22db
...
bc809ed35b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc809ed35b | ||
| 4c48b63e7e | |||
| a01caba0c8 |
126
dev.md
126
dev.md
@ -2,12 +2,12 @@
|
||||
|
||||
## 项目结构
|
||||
|
||||
`public`: mota-js 样板所在目录。
|
||||
`packages`: 核心引擎代码 monorepo。
|
||||
`packages-user`: 用户代码 monorepo。
|
||||
`src`: 游戏入口代码。
|
||||
- `public`: mota-js 样板所在目录
|
||||
- `packages`: 核心引擎代码 monorepo
|
||||
- `packages-user`: 用户代码 monorepo
|
||||
- `src`: 游戏入口代码
|
||||
|
||||
`packages` `packages-user` 可以单独打包为库模式,`src` 单向引用 `packages-user`,`packages-user` 单向引用 `packages`,`src` 为游戏的入口代码。
|
||||
依赖关系为单向:`src` → `packages-user` → `packages`。`packages` 与 `packages-user` 均可独立打包为库模式,`src` 为游戏的入口代码。
|
||||
|
||||
## 开发环境
|
||||
|
||||
@ -15,66 +15,88 @@
|
||||
- `pnpm >= 10.0.0`
|
||||
- 任意支持 `ESNext` 特性的浏览器
|
||||
|
||||
**建议使用 `vscode`,搭配 `prettier` `eslint` 插件**
|
||||
**建议使用 `vscode`,搭配 `prettier` `eslint` 插件。**
|
||||
|
||||
## 开发说明
|
||||
|
||||
1. 将项目拉取到本地。
|
||||
2. 运行 `pnpm i` 安装所有依赖,如有要求运行 `pnpm approve-builds`,请允许全部。
|
||||
2. 运行 `pnpm i` 安装所有依赖(如提示运行 `pnpm approve-builds`,请允许全部)。
|
||||
3. 运行 `pnpm dev` 进入开发环境。
|
||||
|
||||
## 构建说明
|
||||
|
||||
- `pnpm build:packages`: 构建所有 `packages` 文件夹下的内容,使用库模式。
|
||||
- `pnpm build:game`: 构建为可以直接部署的构建包。
|
||||
- `pnpm build:lib`: 构建所有 `packages` `packages-user` 文件夹下的内容,使用库模式。
|
||||
- `pnpm type`: 对仓库执行类型检查
|
||||
- `pnpm check:circular`: 对仓库执行循环引用检查
|
||||
| 命令 | 说明 |
|
||||
| --------------------- | ------------------------------------------------------- |
|
||||
| `pnpm build:packages` | 以库模式构建 `packages` 下的所有内容 |
|
||||
| `pnpm build:game` | 构建为可直接部署的游戏包 |
|
||||
| `pnpm build:lib` | 以库模式构建 `packages` 与 `packages-user` 下的所有内容 |
|
||||
| `pnpm type` | 对仓库执行类型检查 |
|
||||
| `pnpm check:circular` | 对仓库执行循环引用检查 |
|
||||
|
||||
## 开发原则
|
||||
|
||||
- 模块原则:
|
||||
- 无副作用原则:所有模块不包含副作用内容,全部由函数、类、常量的声明组成,不出现导出的变量声明、代码执行内容,允许但不建议编写类的静态块。
|
||||
- 如果需要模块初始化,编写一个 `createXxx` 函数,然后在 `index.ts` 中整合,再逐级向上传递,直至遇到包含 `create` 函数的 `index.ts`,所有初始化将会统一在顶层模块中执行。
|
||||
- 不允许一个文件导出不属于当前 `monorepo` 或当前文件夹的内容。
|
||||
- 不允许出现循环引用,如果不得不进行循环引用,应当首先考虑接口设计是否有问题。
|
||||
- 命名规则:
|
||||
- 变量、成员、一般常量、方法、函数使用小驼峰。
|
||||
- 类、接口、类型别名、命名空间、泛型、枚举、组件使用大驼峰。
|
||||
- 不变常量使用全大写命名法,单词之间使用下划线连接。
|
||||
- 专有名词缩写如 `HTTP`, `URI` 全部大写。
|
||||
- 会被 `implements` 的接口使用大写 `I` 开头。
|
||||
- `id`, `class` 等 `HTML/CSS` 内容使用连字符命名法。
|
||||
- 不使用下划线命名法。
|
||||
- 注释:
|
||||
- 常用属性成员、方法、接口、类型必须添加 `jsDoc` 注释。
|
||||
- 长文件可使用 `#region` 分段,可以写上 `#endretion` 允许折叠。
|
||||
- TODO 使用 `// TODO:` 或 `// todo:` 格式。
|
||||
- 单行注释的双斜杠与注释内容之间添加一个空格,多行注释只允许出现 `jsDoc` 注释,如果需要多行非 `jsDoc` 注释,使用多个单行注释。
|
||||
- 注释进行合理换行,考虑到中文字符较宽,建议 40-60 个字符进行换行。不允许在句中换行,必须在标点符号后换行。参数注释换行后保持对齐。
|
||||
- 单行注释结尾不添加句号,对于多行长注释,可以在结尾添加句号。
|
||||
- 类型:
|
||||
- 不允许出现非必要的 `any` 类型。
|
||||
- 所有类的成员必须显式声明类型。
|
||||
- 如果有无法避免出现类型错误的地方,使用 `// @ts-expect-error` 标记,并填写原因。
|
||||
- 没用到的变量、方法使用下划线开头。
|
||||
- 合理运用 `readonly` `protected` `private` 关键字。
|
||||
- 函数不建议使用过多可选参数,如果可选参数过多,可以考虑换用对象。
|
||||
- 尽量少地使用 `as` 关键字进行类型断言,一般情况下不建议进行任何 `as` 类型断言,除非必要。
|
||||
- 其他要求:
|
||||
- 严格遵循 `eslint` 配置,不允许出现 `eslint` 报错。
|
||||
- 尽量不使用 `?.` 运算符,一般建议仅在副作用函数调用(如 `this.obj?.func()`,`this.obj.func?.()`),或对象 `Required` 化(如 `{ value: obj?.value ?? 0 }`)中使用 `?.` 运算符。
|
||||
- 只进行必要的非空判断,不必要的非空判断直接使用非空断言 `!` 实现。
|
||||
- 除非参数要求传入函数等情况,不建议在函数内写任何局部函数。
|
||||
- 语句尽量不换行,除非必要,尤其注意三元运算符与 `private readonly` 类成员。
|
||||
### 模块原则
|
||||
|
||||
- **无副作用**:所有模块只包含函数、类、常量的声明,不允许出现导出的变量声明或顶层代码执行,允许但不建议编写类的静态块。
|
||||
- **模块初始化**:如需初始化,编写一个 `createXxx` 函数,在 `index.ts` 中整合后逐级向上传递,直至顶层模块统一执行。
|
||||
- **不转发导出**:不允许一个文件导出不属于当前 monorepo 或当前文件夹的内容。
|
||||
- **无循环引用**:不允许出现循环引用。若遇到不得不循环引用的情况,应首先反思接口设计是否存在问题。
|
||||
|
||||
### 命名规则
|
||||
|
||||
| 命名对象 | 规范 |
|
||||
| ---------------------------------------------- | ------------------------------------------ |
|
||||
| 变量、成员、一般常量、方法、函数 | 小驼峰 |
|
||||
| 类、接口、类型别名、命名空间、泛型、枚举、组件 | 大驼峰 |
|
||||
| 不变常量 | 全大写,单词间下划线分隔(如 `MAX_COUNT`) |
|
||||
| 专有名词缩写(如 `HTTP`、`URI`) | 全大写 |
|
||||
| 需被 `implements` 的接口 | 大写 `I` 开头 |
|
||||
| HTML/CSS 中的 `id`、`class` 等 | 连字符命名法 |
|
||||
|
||||
不使用下划线命名法。
|
||||
|
||||
### 注释规范
|
||||
|
||||
- 常用属性成员、方法、接口、类型必须添加 `jsDoc` 注释。
|
||||
- 长文件可使用 `#region` / `#endregion` 分段以支持折叠。
|
||||
- TODO 使用 `// TODO:` 或 `// todo:` 格式。
|
||||
- 单行注释的 `//` 与注释内容之间留一个空格;不允许出现非 jsDoc 的多行注释,如需多行注释,使用多个单行注释代替。
|
||||
- 注释合理换行:考虑中文字符较宽,建议每 40–60 个字符在标点符号后换行,不允许在句中换行;参数注释换行后保持对齐。
|
||||
- 单行注释结尾不加句号;较长的多行注释结尾可加句号。
|
||||
- 一般不建议给接口、类型别名或类本身写注释(不好看),特殊情况除外。
|
||||
|
||||
### 类型规范
|
||||
|
||||
- 不允许出现非必要的 `any` 类型。
|
||||
- 所有类的成员必须显式声明类型。
|
||||
- 无法避免类型错误时,使用 `// @ts-expect-error` 标记并说明原因。
|
||||
- 未使用的变量或方法以下划线开头命名。
|
||||
- 合理使用 `readonly`、`protected`、`private` 关键字。
|
||||
- 可选参数过多时,考虑改用对象参数。
|
||||
- 尽量避免 `as` 类型断言,除非必要。
|
||||
|
||||
### 其他要求
|
||||
|
||||
- 严格遵循 `eslint` 配置,不允许出现 eslint 报错。
|
||||
- 尽量不使用 `?.` 运算符,仅推荐在以下两种场景中使用:
|
||||
- 副作用函数调用,如 `this.obj?.func()` 或 `this.obj.func?.()`
|
||||
- 对象 Required 化,如 `{ value: obj?.value ?? 0 }`
|
||||
- 只进行必要的非空判断,非必要时直接使用非空断言 `!`。
|
||||
- 除非参数要求传入函数等情况,不建议在函数内定义局部函数。
|
||||
- 语句尽量不换行,除非必要,尤其注意三元运算符与 `private readonly` 类成员。
|
||||
|
||||
## 双端分离
|
||||
|
||||
样板将渲染端与数据端彻底分离,数据端可以单独在 `node` 环境运行,可以直接用于录像验证。渲染端仅负责向数据端发送消息,不负责任何逻辑运算。
|
||||
样板将渲染端与数据端彻底分离:
|
||||
|
||||
- `@user/data-base`: 数据端的系统层,负责核心系统。
|
||||
- `@user/data-state`: 数据端的实现层,依靠系统层实现完整的游戏实例。
|
||||
- `@user/client-base`: 渲染端的系统层,负责渲染端的核心系统。
|
||||
- `@user/client-modules`: 渲染端的实现层,依靠系统层实现客户端的渲染与用户交互。
|
||||
- **数据端**:可在 `node` 环境中单独运行,可直接用于录像验证,不负责任何渲染逻辑。
|
||||
- **渲染端**:仅负责向数据端发送消息,不负责任何逻辑运算。
|
||||
|
||||
数据端允许运行渲染端代码,但需要使用全局接口 `Mota.r(() => {})` 包裹。除非必要,否则不建议在数据端调用渲染端代码。
|
||||
| 包 | 层级 | 说明 |
|
||||
| ---------------------- | ------------ | ------------------------------------ |
|
||||
| `@user/data-base` | 数据端系统层 | 负责数据端核心系统 |
|
||||
| `@user/data-state` | 数据端实现层 | 依赖系统层实现完整的游戏实例 |
|
||||
| `@user/client-base` | 渲染端系统层 | 负责渲染端核心系统 |
|
||||
| `@user/client-modules` | 渲染端实现层 | 依赖系统层实现客户端的渲染与用户交互 |
|
||||
|
||||
数据端允许调用渲染端代码,但必须使用全局接口 `Mota.r(() => {})` 包裹。除非必要,否则不建议在数据端调用渲染端代码。
|
||||
|
||||
212
docs/dev/map-store-improve.md
Normal file
212
docs/dev/map-store-improve.md
Normal file
@ -0,0 +1,212 @@
|
||||
# 需求综述
|
||||
|
||||
本次改动目标:
|
||||
|
||||
1. **自动化分区激活器**:将楼层按游戏进程划分为若干"区域",
|
||||
到达新区域时自动激活对应楼层、失活旧区域楼层,
|
||||
从而替代目前繁琐的手动 `setActiveStatus` 调用。
|
||||
2. **楼层尺寸上移至 `LayerState`**:同一楼层的所有图层尺寸应当
|
||||
保持一致,因此将尺寸的权威来源从 `MapLayer` 移至 `LayerState`。
|
||||
|
||||
---
|
||||
|
||||
# 实现思路
|
||||
|
||||
## 1. 有序地图 id 列表
|
||||
|
||||
当前 `IMapStore.maps` 是 `ReadonlySet<string>`,无序且随楼层创建
|
||||
自动填充。区域功能需要以**下标**标识范围,因此需改为有序数组。
|
||||
|
||||
修改方案:
|
||||
|
||||
- `IMapStore.maps` 类型改为 `ReadonlyArray<string>`;
|
||||
- 新增 `setMapList(maps: string[]): void`,由外部显式指定有序列表
|
||||
(一般在游戏初始化时调用一次);
|
||||
- 新增 `useManualOrder(sort: (arr: string[]) => string[]): void`,
|
||||
允许自定义地图列表排序函数。调用时将当前 `maps` 的 `slice` 拷贝
|
||||
传入 `sort`,再对输出做合法性校验:将新旧数组各转为 `Set`,
|
||||
校验 `size` 相等且新集合是旧集合的子集(利用 `Set.prototype.isSubsetOf`);
|
||||
校验通过后用返回值替换内部的 `maps`。这样当地图是动态生成时,
|
||||
作者依然可以自定义顺序,而不必手动维护全量列表;
|
||||
- `createLayerState` 不再维护 `maps`,`maps` 完全由 `setMapList` 管理;
|
||||
- 若 `createLayerState` 传入的 id 不在 `maps` 中,仍可正常创建,
|
||||
不影响存档逻辑,但该楼层不参与任何区域判断。
|
||||
|
||||
## 2. 区域定义与管理
|
||||
|
||||
### 类型定义
|
||||
|
||||
```ts
|
||||
/** 单段闭区间 [start, end],start 和 end 均为 maps 下标 */
|
||||
export interface IMapAreaInterval {
|
||||
readonly start: number;
|
||||
readonly end: number;
|
||||
}
|
||||
|
||||
/** 一个区域由一个或多个独立区间组成 */
|
||||
export type MapArea = IMapAreaInterval[];
|
||||
```
|
||||
|
||||
### 接口
|
||||
|
||||
- `setArea(areas: Set<MapArea>): void`:一次性设置所有区域信息,
|
||||
覆盖原有区域定义;每个元素代表一个区域,区域可包含多个区间,
|
||||
使用 `Set<MapArea>` 表示无序区域集合;
|
||||
- `activeArea(id: string): void`:手动激活指定楼层所在区域的所有楼层。
|
||||
系统遍历 `areaList`,找到包含该楼层 id 的区域后,对该区域内的所有
|
||||
楼层调用 `setMapActiveStatus(floor, true)`;
|
||||
- `deactiveArea(id: string): void`:手动取消激活指定楼层所在区域的
|
||||
所有楼层,逻辑与 `activeArea` 对称;判断时遍历 `areaList`,
|
||||
此操作为低频调用,无需缓存;
|
||||
|
||||
## 3. 自动分区激活器
|
||||
|
||||
### 接口
|
||||
|
||||
- `useAutoActivitor(enable: boolean): void`:是否启用自动激活器。
|
||||
|
||||
### 触发接口
|
||||
|
||||
需要一个通知接口供玩家相关模块调用:
|
||||
|
||||
- `notifyEnterFloor(id: string): void`:玩家进入指定楼层时调用此接口,
|
||||
通知地图管理器进行自动激活判断。
|
||||
|
||||
### 逻辑
|
||||
|
||||
`notifyEnterFloor(id)` 的执行流程(每次进入楼层均调用,内部短路):
|
||||
|
||||
1. 若自动激活器未启用,直接返回;
|
||||
2. 若 `isMapActive(id)` 为 `true`,直接返回(楼层已激活,无需操作);
|
||||
3. 遍历 `areaList`,找出包含 `id` 的区域;
|
||||
4. 若未找到,直接返回(该楼层不在任何区域内);
|
||||
5. 若 `lastFloorId !== null`,调用 `deactiveArea(lastFloorId)` 失活上一个区域;
|
||||
6. 调用 `activeArea(id)` 激活新区域,更新 `lastFloorId = id`。
|
||||
|
||||
### 内部状态
|
||||
|
||||
`MapStore` 新增:
|
||||
|
||||
- `private areaList: Set<MapArea>`:所有区域定义;
|
||||
- `private lastFloorId: string | null = null`:上一次触发 `notifyEnterFloor`
|
||||
的楼层 id,用于定位并失活上一个激活区域;
|
||||
- `private autoActivitorEnabled: boolean = false`:自动激活器开关。
|
||||
|
||||
## 4. 楼层尺寸上移至 LayerState
|
||||
|
||||
### 动机
|
||||
|
||||
当前 `MapLayer.width` / `MapLayer.height` 存储在图层中,
|
||||
但同一楼层的所有图层尺寸必须一致,权威来源应当是 `LayerState`。
|
||||
|
||||
### 接口变动
|
||||
|
||||
**`ILayerState` 新增**:
|
||||
|
||||
```ts
|
||||
readonly width: number;
|
||||
readonly height: number;
|
||||
```
|
||||
|
||||
**`addLayer` 签名调整**:
|
||||
|
||||
目前 `addLayer(width: number, height: number): IMapLayer`,
|
||||
移除 width/height 参数,改为 `addLayer(): IMapLayer`,
|
||||
使用 `LayerState` 内部存储的尺寸创建图层。
|
||||
|
||||
楼层尺寸在 `createLayerState` 创建时指定,`createLayerState` 签名改为:
|
||||
|
||||
```ts
|
||||
createLayerState(id: string, width: number, height: number): ILayerState;
|
||||
```
|
||||
|
||||
运行时仍可通过 `resizeLayer` 修改楼层尺寸,该方法会同步对楼层内所有
|
||||
图层执行 resize,保持尺寸一致。
|
||||
|
||||
**`resizeLayer` 签名调整**:
|
||||
|
||||
当前 `resizeLayer(layer, width, height, keepBlock?)` 只 resize 单个图层,
|
||||
但既然尺寸是楼层级的,建议改为对该楼层的所有图层同步 resize:
|
||||
|
||||
```ts
|
||||
resizeLayer(width: number, height: number, keepBlock?: boolean): void;
|
||||
```
|
||||
|
||||
**`IMapLayer.resize` / `IMapLayer.resize2`**:
|
||||
|
||||
从 `IMapLayer` 接口中移除,保留为 `MapLayer` 的内部实现,
|
||||
仅由 `LayerState.resizeLayer` 调用。
|
||||
|
||||
**`IMapLayer.width` / `IMapLayer.height`**:
|
||||
|
||||
保留在 `IMapLayer` 接口中,供外部通过图层对象直接获取尺寸。
|
||||
其值始终与所属 `LayerState` 的 `width`/`height` 保持一致。
|
||||
|
||||
---
|
||||
|
||||
# 附加建议结论
|
||||
|
||||
1. **`IMapLayer.setMapRef` 可见性**:保留现有设计,偶尔有外部需求。
|
||||
2. **`active` 状态管理**:不需要单独维护区域激活状态;
|
||||
`activeArea(id)` / `deactiveArea(id)` 是 `setMapActiveStatus` 的
|
||||
快捷方式,遍历区域楼层批量调用即可,无需额外的区域状态字段。
|
||||
3. **`notifyEnterFloor` 返回值**:暂不添加,后续有需求再改进。
|
||||
|
||||
---
|
||||
|
||||
# 涉及文件
|
||||
|
||||
## 需要引用的文件
|
||||
|
||||
- `@user/data-base/src/map/types.ts`: 全部现有地图接口
|
||||
- `@user/data-base/src/map/mapStore.ts`: `MapStore` 实现类
|
||||
- `@user/data-base/src/map/layerState.ts`: `LayerState` 实现类
|
||||
- `@user/data-base/src/map/mapLayer.ts`: `MapLayer` 实现类
|
||||
|
||||
## 需要修改的文件
|
||||
|
||||
### `@user/data-base/src/map/types.ts`
|
||||
|
||||
- [x] 新增 `IMapAreaInterval` 接口:区间定义,含 `start`、`end`
|
||||
- [x] 新增 `MapArea` 类型别名:`IMapAreaInterval[]`,表示一个区域
|
||||
- [x] 修改 `ILayerState`:
|
||||
- [x] 新增 `readonly width: number` 和 `readonly height: number`
|
||||
- [x] 修改 `addLayer` 签名,移除 `width`/`height` 参数(使用 `LayerState` 自身尺寸)
|
||||
- [x] 修改 `resizeLayer` 签名:移除 `layer` 参数,改为对整个楼层所有图层同步 resize
|
||||
- [x] 修改 `IMapLayer`:
|
||||
- [x] 移除 `resize` / `resize2`(改为 `MapLayer` 内部方法)
|
||||
- [x] 修改 `IMapStore`:
|
||||
- [x] 将 `readonly maps` 类型改为 `ReadonlyArray<string>`
|
||||
- [x] 修改 `createLayerState` 签名:新增 `width: number`、`height: number` 参数
|
||||
- [x] 新增 `setMapList(maps: string[]): void`
|
||||
- [x] 新增 `useManualOrder(sort: (arr: string[]) => string[]): void`
|
||||
- [x] 新增 `setArea(areas: Set<MapArea>): void`
|
||||
- [x] 新增 `activeArea(id: string): void`
|
||||
- [x] 新增 `deactiveArea(id: string): void`
|
||||
- [x] 新增 `useAutoActivitor(enable: boolean): void`
|
||||
- [x] 新增 `notifyEnterFloor(id: string): void`
|
||||
|
||||
### `@user/data-base/src/map/mapStore.ts`
|
||||
|
||||
- [x] 将 `maps: Set<string>` 改为 `maps: string[]`
|
||||
- [x] 修改 `createLayerState`:添加 `width`/`height` 参数,不再维护 `maps`
|
||||
- [x] 实现 `setMapList`
|
||||
- [x] 实现 `useManualOrder`
|
||||
- [x] 新增 `private areaList: Set<MapArea>`
|
||||
- [x] 新增 `private lastFloorId: string | null`
|
||||
- [x] 新增 `private autoActivitorEnabled: boolean`
|
||||
- [x] 实现 `setArea`、`activeArea`、`deactiveArea`
|
||||
- [x] 实现 `useAutoActivitor`
|
||||
- [x] 实现 `notifyEnterFloor`
|
||||
|
||||
### `@user/data-base/src/map/layerState.ts`
|
||||
|
||||
- [x] 新增 `width: number` 和 `height: number` 成员(由构造参数初始化)
|
||||
- [x] 修改 `addLayer`,移除 `width`/`height` 参数,使用 `this.width`/`this.height`
|
||||
- [x] 修改 `resizeLayer`,移除 `layer` 参数,改为对所有图层同步 resize
|
||||
|
||||
### `@user/data-base/src/map/mapLayer.ts`
|
||||
|
||||
- [x] 将 `resize`/`resize2` 改为内部方法(从公共接口移除)
|
||||
|
||||
---
|
||||
@ -242,31 +242,31 @@ setActiveStatus(active: boolean): void;
|
||||
|
||||
### `@user/data-base/src/map/types.ts`
|
||||
|
||||
- [ ] 新增 `IMapLayerSave` 接口:单个 MapLayer 存档数据格式
|
||||
- [ ] 新增 `ILayerStateSave` 接口:单个楼层存档数据格式
|
||||
- [ ] 新增 `IMapStoreSave` 接口:MapStore 整体存档数据格式
|
||||
- [ ] 修改 `ILayerState`:新增 `readonly active: boolean` 和
|
||||
- [x] 新增 `IMapLayerSave` 接口:单个 MapLayer 存档数据格式
|
||||
- [x] 新增 `ILayerStateSave` 接口:单个楼层存档数据格式
|
||||
- [x] 新增 `IMapStoreSave` 接口:MapStore 整体存档数据格式
|
||||
- [x] 修改 `ILayerState`:新增 `readonly active: boolean` 和
|
||||
`setActiveStatus(active: boolean): void`
|
||||
- [ ] 修改 `IMapLayer`:新增 `setMapRef(array: Uint32Array): void`
|
||||
- [ ] 新增 `IMapStore` 接口:继承 `ISaveableContent<IMapStoreSave>`,
|
||||
- [x] 修改 `IMapLayer`:新增 `setMapRef(array: Uint32Array): void`
|
||||
- [x] 新增 `IMapStore` 接口:继承 `ISaveableContent<IMapStoreSave>`,
|
||||
含全部接口(见第 7 节)
|
||||
|
||||
### `@user/data-base/src/map/mapLayer.ts`
|
||||
|
||||
### `@user/data-base/src/map/layerState.ts`
|
||||
|
||||
- [ ] 新增 `active: boolean = false` 成员:楼层激活状态
|
||||
- [ ] 实现 `setActiveStatus(active: boolean): void`
|
||||
- [ ] 新增 `private dirty: boolean = false` 成员:楼层级脏标记
|
||||
- [ ] 修改 `StateMapLayerHook.onUpdateArea`、`onUpdateBlock`、`onResize`:
|
||||
- [x] 新增 `active: boolean = false` 成员:楼层激活状态
|
||||
- [x] 实现 `setActiveStatus(active: boolean): void`
|
||||
- [x] 新增 `private dirty: boolean = false` 成员:楼层级脏标记
|
||||
- [x] 修改 `StateMapLayerHook.onUpdateArea`、`onUpdateBlock`、`onResize`:
|
||||
在转发钩子的同时,将 `state.dirty` 置 `true`
|
||||
- [ ] 新增 `isDirty(): boolean` 方法:返回 `this.dirty`,供 `MapStore` 读取
|
||||
- [ ] 新增 `setDirty(dirty: boolean): void` 方法:
|
||||
- [x] 新增 `isDirty(): boolean` 方法:返回 `this.dirty`,供 `MapStore` 读取
|
||||
- [x] 新增 `setDirty(dirty: boolean): void` 方法:
|
||||
供 `MapStore.compareWith` 时根据实际比较结果设置
|
||||
|
||||
### `@user/data-base/src/map/mapLayer.ts`
|
||||
|
||||
- [ ] 新增 `setMapRef(array: Uint32Array): void` 方法:
|
||||
- [x] 新增 `setMapRef(array: Uint32Array): void` 方法:
|
||||
直接替换内部图块数组引用,跳过拷贝,供 `MapStore` 读档时使用。
|
||||
需确保传入数组长度与 `width × height` 匹配,
|
||||
并触发必要的钩子通知(不触发 `onResize`,应触发 `onUpdateArea` 通知全区域更新)。
|
||||
@ -274,21 +274,21 @@ setActiveStatus(active: boolean): void;
|
||||
|
||||
### `@user/data-base/src/map/mapStore.ts`(新文件)
|
||||
|
||||
- [ ] 实现 `MapStore` 类,实现 `IMapStore`
|
||||
- [ ] `private mapData: Map<string, LayerState>`:楼层 id 到状态对象的映射
|
||||
- [ ] `readonly maps: ReadonlySet<string>`:所有楼层 id 的只读集合视图
|
||||
- [ ] `private refData: Map<string, Map<number, Uint32Array>> | null`:参考基准
|
||||
- [ ] 实现 `getLayerState`、`getActiveMap`、`createLayerState`
|
||||
- [ ] 实现 `isMapActive`、`setMapActiveStatus`、`iterateActiveMaps`、`iterateInactiveMaps`、`iterateAllMaps`
|
||||
- [ ] 实现 `compareWith`
|
||||
- [ ] 实现 `saveNoCompression`、`saveLowCompression`、`saveHighCompression`
|
||||
- [ ] 实现 `loadNoCompression`、`loadLowCompression`、`loadHighCompression`
|
||||
- [ ] 实现 `saveState(compression)` 和 `loadState(state, compression)` 分发
|
||||
- [x] 实现 `MapStore` 类,实现 `IMapStore`
|
||||
- [x] `private mapData: Map<string, LayerState>`:楼层 id 到状态对象的映射
|
||||
- [x] `readonly maps: ReadonlySet<string>`:所有楼层 id 的只读集合视图
|
||||
- [x] `private refData: Map<string, Map<number, Uint32Array>> | null`:参考基准
|
||||
- [x] 实现 `getLayerState`、`getActiveMap`、`createLayerState`
|
||||
- [x] 实现 `isMapActive`、`setMapActiveStatus`、`iterateActiveMaps`、`iterateInactiveMaps`、`iterateAllMaps`
|
||||
- [x] 实现 `compareWith`
|
||||
- [x] 实现 `saveNoCompression`、`saveLowCompression`、`saveHighCompression`
|
||||
- [x] 实现 `loadNoCompression`、`loadLowCompression`、`loadHighCompression`
|
||||
- [x] 实现 `saveState(compression)` 和 `loadState(state, compression)` 分发
|
||||
|
||||
### `@user/data-base/src/map/index.ts`
|
||||
|
||||
- [ ] 补充导出 `mapStore.ts`
|
||||
- [x] 补充导出 `mapStore.ts`
|
||||
|
||||
### `@user/data-base/src/types.ts`
|
||||
|
||||
- [ ] 将 `IStateBase.layer` 类型由 `ILayerState` 改为 `IMapStore`
|
||||
- [x] 将 `IStateBase.layer` 类型由 `ILayerState` 改为 `IMapStore`
|
||||
|
||||
@ -18,6 +18,8 @@ export class LayerState
|
||||
implements ILayerState
|
||||
{
|
||||
readonly layerList: Set<IMapLayer> = new Set();
|
||||
/** 具体 MapLayer 实例列表,供内部 resize 使用 */
|
||||
private readonly mapLayerList: Set<MapLayer> = new Set();
|
||||
/** 图层到图层别名映射 */
|
||||
readonly layerAliasMap: WeakMap<IMapLayer, string> = new WeakMap();
|
||||
/** 图层别名到图层的映射 */
|
||||
@ -35,10 +37,18 @@ export class LayerState
|
||||
/** 楼层级脏标记 */
|
||||
private dirty: boolean = false;
|
||||
|
||||
addLayer(width: number, height: number): IMapLayer {
|
||||
const array = new Uint32Array(width * height);
|
||||
const layer = new MapLayer(array, width, height);
|
||||
constructor(
|
||||
public width: number,
|
||||
public height: number
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
addLayer(): IMapLayer {
|
||||
const array = new Uint32Array(this.width * this.height);
|
||||
const layer = new MapLayer(array, this.width, this.height);
|
||||
this.layerList.add(layer);
|
||||
this.mapLayerList.add(layer);
|
||||
this.forEachHook(hook => {
|
||||
hook.onUpdateLayer?.(this.layerList);
|
||||
});
|
||||
@ -50,6 +60,7 @@ export class LayerState
|
||||
|
||||
removeLayer(layer: IMapLayer): void {
|
||||
this.layerList.delete(layer);
|
||||
this.mapLayerList.delete(layer as MapLayer);
|
||||
const alias = this.layerAliasMap.get(layer);
|
||||
if (alias) {
|
||||
const symbol = Symbol.for(alias);
|
||||
@ -89,15 +100,18 @@ export class LayerState
|
||||
}
|
||||
|
||||
resizeLayer(
|
||||
layer: IMapLayer,
|
||||
width: number,
|
||||
height: number,
|
||||
keepBlock: boolean = false
|
||||
): void {
|
||||
if (keepBlock) {
|
||||
layer.resize(width, height);
|
||||
} else {
|
||||
layer.resize2(width, height);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
for (const layer of this.mapLayerList) {
|
||||
if (keepBlock) {
|
||||
layer.resize(width, height);
|
||||
} else {
|
||||
layer.resize2(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,21 +6,65 @@ import {
|
||||
IMapLayer,
|
||||
IMapLayerSave,
|
||||
IMapStore,
|
||||
IMapStoreSave
|
||||
IMapStoreSave,
|
||||
MapArea
|
||||
} from './types';
|
||||
import { LayerState } from './layerState';
|
||||
import { uniq } from 'lodash-es';
|
||||
|
||||
export class MapStore implements IMapStore {
|
||||
/** 楼层 id 到状态对象的映射 */
|
||||
private readonly mapData: Map<string, LayerState> = new Map();
|
||||
|
||||
/** 所有楼层 id 的只读集合视图 */
|
||||
readonly maps: Set<string> = new Set();
|
||||
/** 所有楼层 id 的有序数组 */
|
||||
readonly maps: string[] = [];
|
||||
|
||||
/** 差分压缩参考基准,首次 compareWith 后设置,之后不再更新 */
|
||||
private refData: Map<string, Map<number, Uint32Array>> | null = null;
|
||||
|
||||
//#region 楼层访问
|
||||
/** 分区列表 */
|
||||
private areaList: Set<MapArea> = new Set();
|
||||
|
||||
/** 上一次调用 notifyEnterFloor 传入的楼层 id */
|
||||
private lastFloorId: string | null = null;
|
||||
|
||||
/** 自动分区激活器开关 */
|
||||
private autoActivitorEnabled: boolean = false;
|
||||
|
||||
//#region 楼层管理
|
||||
|
||||
createLayerState(id: string, width: number, height: number): ILayerState {
|
||||
if (this.mapData.has(id)) {
|
||||
logger.warn(121, id);
|
||||
} else {
|
||||
this.maps.push(id);
|
||||
}
|
||||
const state = new LayerState(width, height);
|
||||
// 若 refData 已存在,新楼层直接视为全脏
|
||||
if (this.refData !== null) {
|
||||
state.setDirty(true);
|
||||
}
|
||||
this.mapData.set(id, state);
|
||||
return state;
|
||||
}
|
||||
|
||||
setMapList(maps: string[]): void {
|
||||
this.maps.length = 0;
|
||||
this.maps.push(...uniq(maps));
|
||||
}
|
||||
|
||||
useManualOrder(sort: (arr: string[]) => string[]): void {
|
||||
const copy = this.maps.slice();
|
||||
const sorted = sort(copy);
|
||||
const oldSet = new Set(this.maps);
|
||||
const newSet = new Set(sorted);
|
||||
if (oldSet.size !== newSet.size || !newSet.isSubsetOf(oldSet)) {
|
||||
logger.warn(125);
|
||||
return;
|
||||
}
|
||||
this.maps.length = 0;
|
||||
this.maps.push(...uniq(sorted));
|
||||
}
|
||||
|
||||
getLayerState(id: string): ILayerState | null {
|
||||
return this.mapData.get(id) ?? null;
|
||||
@ -34,20 +78,65 @@ export class MapStore implements IMapStore {
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 楼层管理
|
||||
//#region 分区管理
|
||||
|
||||
createLayerState(id: string): ILayerState {
|
||||
if (this.mapData.has(id)) {
|
||||
logger.warn(121, id);
|
||||
setArea(areas: Set<MapArea>): void {
|
||||
this.areaList = areas;
|
||||
}
|
||||
|
||||
activeArea(id: string): void {
|
||||
const idx = this.maps.indexOf(id);
|
||||
if (idx === -1) return;
|
||||
const area = this.findAreaByIndex(idx);
|
||||
if (!area) return;
|
||||
this.setAreaActive(area, true);
|
||||
}
|
||||
|
||||
deactiveArea(id: string): void {
|
||||
const idx = this.maps.indexOf(id);
|
||||
if (idx === -1) return;
|
||||
const area = this.findAreaByIndex(idx);
|
||||
if (!area) return;
|
||||
this.setAreaActive(area, false);
|
||||
}
|
||||
|
||||
useAutoActivitor(enable: boolean): void {
|
||||
this.autoActivitorEnabled = enable;
|
||||
}
|
||||
|
||||
notifyEnterFloor(id: string): void {
|
||||
if (!this.autoActivitorEnabled) return;
|
||||
const idx = this.maps.indexOf(id);
|
||||
if (idx === -1) return;
|
||||
const area = this.findAreaByIndex(idx);
|
||||
if (!area) return;
|
||||
if (this.lastFloorId !== null) {
|
||||
this.deactiveArea(this.lastFloorId);
|
||||
}
|
||||
const state = new LayerState();
|
||||
// 若 refData 已存在,新楼层直接视为全脏
|
||||
if (this.refData !== null) {
|
||||
state.setDirty(true);
|
||||
this.activeArea(id);
|
||||
this.lastFloorId = id;
|
||||
}
|
||||
|
||||
private findAreaByIndex(idx: number): MapArea | null {
|
||||
for (const area of this.areaList) {
|
||||
for (const interval of area) {
|
||||
if (idx >= interval.start && idx <= interval.end) {
|
||||
return area;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private setAreaActive(area: MapArea, active: boolean): void {
|
||||
for (const interval of area) {
|
||||
for (let i = interval.start; i <= interval.end; i++) {
|
||||
const floorId = this.maps[i];
|
||||
if (floorId !== undefined) {
|
||||
this.setMapActiveStatus(floorId, active);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.mapData.set(id, state);
|
||||
this.maps.add(id);
|
||||
return state;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@ -75,20 +75,6 @@ export interface IMapLayer extends IHookable<
|
||||
/** 图层纵深 */
|
||||
readonly zIndex: number;
|
||||
|
||||
/**
|
||||
* 调整地图尺寸,维持原有图块。如果尺寸变大,那么会补零,如果尺寸变小,那么会将当前数组裁剪
|
||||
* @param width 地图宽度
|
||||
* @param height 地图高度
|
||||
*/
|
||||
resize(width: number, height: number): void;
|
||||
|
||||
/**
|
||||
* 调整地图尺寸,但是将地图全部重置为零,不保留原地图数据
|
||||
* @param width 地图宽度
|
||||
* @param height 地图高度
|
||||
*/
|
||||
resize2(width: number, height: number): void;
|
||||
|
||||
/**
|
||||
* 设置某一点的图块
|
||||
* @param block 图块数字
|
||||
@ -225,13 +211,15 @@ export interface ILayerState extends IHookable<ILayerStateHooks> {
|
||||
readonly layerList: Set<IMapLayer>;
|
||||
/** 此楼层是否处于激活状态 */
|
||||
readonly active: boolean;
|
||||
/** 此楼层的地图宽度 */
|
||||
readonly width: number;
|
||||
/** 此楼层的地图高度 */
|
||||
readonly height: number;
|
||||
|
||||
/**
|
||||
* 添加图层
|
||||
* @param width 地图宽度
|
||||
* @param height 地图高度
|
||||
* 添加图层,使用楼层预设的宽高
|
||||
*/
|
||||
addLayer(width: number, height: number): IMapLayer;
|
||||
addLayer(): IMapLayer;
|
||||
|
||||
/**
|
||||
* 移除指定图层
|
||||
@ -265,18 +253,12 @@ export interface ILayerState extends IHookable<ILayerStateHooks> {
|
||||
getLayerAlias(layer: IMapLayer): string | undefined;
|
||||
|
||||
/**
|
||||
* 重新设置图层的大小
|
||||
* @param layer 图层对象
|
||||
* @param width 新的图层宽度
|
||||
* @param height 新的图层高度
|
||||
* 重新设置所有图层的大小,同时更新楼层预设宽高
|
||||
* @param width 新的地图宽度
|
||||
* @param height 新的地图高度
|
||||
* @param keepBlock 是否保留原有图块,默认不保留
|
||||
*/
|
||||
resizeLayer(
|
||||
layer: IMapLayer,
|
||||
width: number,
|
||||
height: number,
|
||||
keepBlock?: boolean
|
||||
): void;
|
||||
resizeLayer(width: number, height: number, keepBlock?: boolean): void;
|
||||
|
||||
/**
|
||||
* 设置背景图块
|
||||
@ -336,9 +318,18 @@ export interface IMapStoreSave {
|
||||
readonly floors: ReadonlyMap<string, ILayerStateSave>;
|
||||
}
|
||||
|
||||
/** 单段闭区间 [start, end],start 和 end 均为 maps 下标 */
|
||||
export interface IMapAreaInterval {
|
||||
readonly start: number;
|
||||
readonly end: number;
|
||||
}
|
||||
|
||||
/** 一个区域由一个或多个独立区间组成 */
|
||||
export type MapArea = IMapAreaInterval[];
|
||||
|
||||
export interface IMapStore extends ISaveableContent<IMapStoreSave> {
|
||||
/** 所有楼层的 id 集合 */
|
||||
readonly maps: ReadonlySet<string>;
|
||||
/** 所有楼层的 id 有序数组 */
|
||||
readonly maps: ReadonlyArray<string>;
|
||||
|
||||
/**
|
||||
* 获取指定 id 的楼层状态,不存在则返回 null
|
||||
@ -355,8 +346,10 @@ export interface IMapStore extends ISaveableContent<IMapStoreSave> {
|
||||
/**
|
||||
* 创建并注册一个空白楼层,若 id 已存在则警告并覆盖,返回楼层状态对象
|
||||
* @param id 楼层 id
|
||||
* @param width 地图宽度
|
||||
* @param height 地图高度
|
||||
*/
|
||||
createLayerState(id: string): ILayerState;
|
||||
createLayerState(id: string, width: number, height: number): ILayerState;
|
||||
|
||||
/**
|
||||
* 获取指定 id 的楼层是否激活,不存在的 id 返回 false
|
||||
@ -391,4 +384,47 @@ export interface IMapStore extends ISaveableContent<IMapStoreSave> {
|
||||
* @param ref 外层 key = 楼层 id,内层 key = zIndex,value = 图层完整图块数据
|
||||
*/
|
||||
compareWith(ref: Map<string, Map<number, Uint32Array>>): void;
|
||||
|
||||
/**
|
||||
* 设定楼层有序列表。设定后有序列表将用于分区索引计算
|
||||
* @param maps 楼层 id 数组
|
||||
*/
|
||||
setMapList(maps: string[]): void;
|
||||
|
||||
/**
|
||||
* 使用自定义排序函数重排 maps。排序函数接收当前列表的副本,返回新顺序。
|
||||
* 若返回的数组元素集合与原列表不一致,则警告并放弃本次排序
|
||||
* @param sort 排序函数
|
||||
*/
|
||||
useManualOrder(sort: (arr: string[]) => string[]): void;
|
||||
|
||||
/**
|
||||
* 设定分区列表。每个分区由一个或多个区间组成
|
||||
* @param areas 分区集合
|
||||
*/
|
||||
setArea(areas: Set<MapArea>): void;
|
||||
|
||||
/**
|
||||
* 激活指定楼层所属分区的所有楼层
|
||||
* @param id 楼层 id
|
||||
*/
|
||||
activeArea(id: string): void;
|
||||
|
||||
/**
|
||||
* 去激活指定楼层所属分区的所有楼层
|
||||
* @param id 楼层 id
|
||||
*/
|
||||
deactiveArea(id: string): void;
|
||||
|
||||
/**
|
||||
* 开启或关闭自动分区激活器
|
||||
* @param enable 是否开启
|
||||
*/
|
||||
useAutoActivitor(enable: boolean): void;
|
||||
|
||||
/**
|
||||
* 通知当前进入的楼层。开启自动激活器时,将自动去激活上一个分区并激活新分区
|
||||
* @param id 楼层 id
|
||||
*/
|
||||
notifyEnterFloor(id: string): void;
|
||||
}
|
||||
|
||||
@ -214,12 +214,16 @@ export class CoreState implements ICoreState {
|
||||
const reference = new Map<string, Map<number, Uint32Array>>();
|
||||
for (const id of floors) {
|
||||
const floor = data[id];
|
||||
const state = this.maps.createLayerState(id);
|
||||
const bg = state.addLayer(floor.width, floor.height);
|
||||
const bg2 = state.addLayer(floor.width, floor.height);
|
||||
const event = state.addLayer(floor.width, floor.height);
|
||||
const fg = state.addLayer(floor.width, floor.height);
|
||||
const fg2 = state.addLayer(floor.width, floor.height);
|
||||
const state = this.maps.createLayerState(
|
||||
id,
|
||||
floor.width,
|
||||
floor.height
|
||||
);
|
||||
const bg = state.addLayer();
|
||||
const bg2 = state.addLayer();
|
||||
const event = state.addLayer();
|
||||
const fg = state.addLayer();
|
||||
const fg2 = state.addLayer();
|
||||
bg.setZIndex(BG_ZINDEX);
|
||||
bg2.setZIndex(BG2_ZINDEX);
|
||||
event.setZIndex(EVENT_ZINDEX);
|
||||
|
||||
@ -182,6 +182,7 @@
|
||||
"122": "MapStore.loadState: floor '$1' not found in current map data, skipping.",
|
||||
"123": "MapLayer.setMapRef: array length $1 does not match expected size $2, setMapRef will be ignored.",
|
||||
"124": "MapStore.loadState: floor '$1' or its layer(s) not found in current reference data, skipping.",
|
||||
"125": "Expected sorted floor id array has a same floor id set, but an array with a different floor id set is returned.",
|
||||
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency."
|
||||
}
|
||||
}
|
||||
|
||||
34
prompt.md
34
prompt.md
@ -2,32 +2,34 @@
|
||||
|
||||
以下规则必须时刻遵守,任何情况下都不允许违反。
|
||||
|
||||
1. 将我已经写好的代码视为绝对正确,除非我**明确允许**,否则**不允许任何修改**,哪怕因为接口变化或其他原因导致其中出现类型错误。如果你认为我的代码中存在逻辑错误,应当在对话中提出,而不是直接修改。
|
||||
2. 我做的任何代码修改都是有原因的,如果我在两次对话期间新增、删除或修改了部分代码,不要将其恢复。
|
||||
3. 时刻以目的进行驱动,想明白我为什么要这么设计接口,这个接口设计的目的是什么,而不是简单地以实现接口为目标。
|
||||
4. 如果思考或实现时有任何问题,比如我的描述比较模糊,或接口描述比较模糊,或某些地方会产生歧义等等,应该立刻向我提问,而不是按照自己的想法去写。
|
||||
5. 如果我的目标是重构某个接口,按照我说的方式进行重构。如果是彻底性的重构(接口完全没有重合),则按照正常的方式进行实现,旧代码仅做逻辑与思路上的参考;如果是结构性的重构(接口基本一致,但有一些细节上的差距),则应该将旧代码搬到新的接口上,然后进行一些微调,**不要**擅自新增任何参数、任何新的方法或接口,**不要**仅仅通过新增一个兼容层兼容旧代码来实现重构。
|
||||
1. **不擅自修改已有代码**:将我已经写好的代码视为绝对正确。除非我**明确允许**,否则**不允许任何修改**,哪怕因为接口变化或其他原因导致其中出现类型错误。若认为我的代码存在逻辑错误,应在对话中提出,而不是直接修改。
|
||||
2. **不恢复我的修改**:我做的任何代码修改都是有原因的。若我在两次对话期间新增、删除或修改了部分代码,不要将其恢复。
|
||||
3. **以目的驱动,而非以接口驱动**:实现前先想清楚我为什么要这样设计接口、这个接口设计的目的是什么,而不是单纯地以将接口填满为目标。
|
||||
4. **遇到歧义立即提问**:若思考或实现时遇到任何问题——例如描述模糊、接口不清晰、某些地方存在歧义等——应立即向我提问,而不是按自己的想法去写。
|
||||
5. **按我说的方式重构**:若目标是重构某个接口,按照我指定的方式执行:
|
||||
- **彻底性重构**(新旧接口完全没有重合):按正常方式全新实现,旧代码仅作逻辑与思路上的参考。
|
||||
- **结构性重构**(新旧接口基本一致,细节有差距):将旧代码搬移到新接口上后进行微调。**不要**擅自新增任何参数、方法或接口,**不要**仅通过新增兼容层的方式应对重构。
|
||||
|
||||
# 建议规则
|
||||
|
||||
以下规则为建议性,尽量遵守,但是一些特殊情况也可以违反,由你自己把控。
|
||||
以下规则为建议性,尽量遵守,特殊情况下可灵活处理。
|
||||
|
||||
1. 我有时会在对话中给你提出实现建议,你应该对建议内容进行合理的参考,合理运用建议内容,一定注意不要滥用。
|
||||
2. 如果实现与类型标注有冲突,应当以类型标注(一般是 `types.ts`)中的内容为参考来源。
|
||||
3. 如果你认为类型标注中的接口设计有问题,或在实现中发现其缺少某些接口,应该向我提问是否添加,我同意后方可添加。
|
||||
1. **合理参考建议**:我有时会在对话中给出实现建议,应合理参考,切忌滥用。
|
||||
2. **以类型标注为参考依据**:实现与类型标注有冲突时,以类型标注(一般是 `types.ts`)中的内容为准。
|
||||
3. **发现接口问题时提问**:若认为类型标注中的接口设计有问题,或在实现中发现缺少某些接口,应向我提问是否添加,经我同意后方可添加。
|
||||
|
||||
**时刻谨记上述要求,避免一个需求写好几次都写不出来,或写出我不满意的代码而挨骂**
|
||||
**时刻谨记上述要求,避免一个需求反复修改仍无法满足预期。**
|
||||
|
||||
# 开发流程
|
||||
|
||||
当我提出需求时,如果没有明确说明直接实现或有其他明确要求,则遵循如下开发流程:
|
||||
当我提出需求时,若没有明确说明直接实现或有其他明确要求,则遵循如下开发流程:
|
||||
|
||||
1. 阅读当前代码,分析需求,将需求整理为一个 markdown 文档,文档中明确标记需求细节,以及代码实现的大体思路。这一阶段中应当考虑全面,遇到任何问题应向我提问并确认。文档可以放在 `docs/dev` 目录下。
|
||||
2. 我会对文档进行全面的阅读,确保实现细节与思路没有问题后,允许你开始实现。这一步中我可能会对文档进行细微的调整,确保重新仔细阅读文档。如果实现时遇到了任何问题,应该向我提问,而不是按照自己的想法去写。
|
||||
1. 阅读当前代码,分析需求,将需求整理为一个 markdown 文档,放在 `docs/dev` 目录下。文档中需明确标注需求细节,以及代码实现的大体思路。此阶段需考虑全面,遇到任何问题应向我提问并确认,不得自行假设。
|
||||
2. 我会对文档进行全面阅读,确认实现细节与思路无误后,方允许开始实现。我可能会对文档进行细微调整,请在实现前重新仔细阅读最终版本。实现过程中如有任何问题,应向我提问,而不是自行决定。
|
||||
|
||||
## 示例文档
|
||||
|
||||
大致按照下述示例文档的格式编写,如果某些场景需要详细描述某个东西,可以单独开一个标题来写。
|
||||
大致按照以下格式编写,如某部分需要详细描述,可单独开设标题。我会使用引用块的形式在文档中提出建议或回答。
|
||||
|
||||
```md
|
||||
# 需求综述
|
||||
@ -50,7 +52,7 @@
|
||||
|
||||
## 需要引用的文件
|
||||
|
||||
按照第三方库-其他包-当前包的其他文件的顺序写。
|
||||
按照第三方库 → 其他包 → 当前包的其他文件的顺序写。
|
||||
|
||||
- `xxx 库`: 引用第三方库,说明引用目的,以及需要的接口
|
||||
- `@user/xxx`: 引用的目的,需要这个文件的哪些接口
|
||||
@ -77,7 +79,7 @@
|
||||
|
||||
# 问题
|
||||
|
||||
如果我的描述中有歧义或比较模糊,可以在这把问题写出来,或者直接向我提问。
|
||||
如果描述中有歧义或比较模糊的地方,可以在此列出,或者直接向我提问。
|
||||
|
||||
1. xxxxxx?
|
||||
2. xxxxxx?
|
||||
|
||||
Loading…
Reference in New Issue
Block a user