{"id":33731347,"url":"https://github.com/codehz/ecs","last_synced_at":"2026-05-04T08:01:43.367Z","repository":{"id":322501272,"uuid":"1089742552","full_name":"codehz/ecs","owner":"codehz","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-29T02:13:33.000Z","size":539,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-29T03:37:59.502Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codehz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-11-04T18:47:12.000Z","updated_at":"2026-04-29T02:13:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/codehz/ecs","commit_stats":null,"previous_names":["codehz/ecs"],"tags_count":64,"template":false,"template_full_name":null,"purl":"pkg:github/codehz/ecs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codehz%2Fecs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codehz%2Fecs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codehz%2Fecs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codehz%2Fecs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codehz","download_url":"https://codeload.github.com/codehz/ecs/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codehz%2Fecs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32596533,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T22:12:39.696Z","status":"online","status_checked_at":"2026-05-04T02:00:06.625Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-04T15:05:10.106Z","updated_at":"2026-05-04T08:01:43.360Z","avatar_url":"https://github.com/codehz.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @codehz/ecs\n\n\u003e **English version:** [README.en.md](./README.en.md)\n\n一个高性能的 Entity Component System (ECS) 库，使用 TypeScript 和 Bun 运行时构建。\n\n## 特性\n\n- 🚀 高性能：基于 Archetype 的组件存储和高效的查询系统\n- 🔧 类型安全：完整的 TypeScript 支持\n- 🏗️ 模块化：清晰的架构，支持自定义组件\n- 📦 轻量级：零依赖，易于集成\n- ⚡ 内存高效：连续内存布局，优化的迭代性能\n- 🎣 生命周期钩子：支持多组件和通配符关系的事件监听\n\n## 安装\n\n```bash\nbun install\n```\n\n## 用法\n\n### 基本示例\n\n```typescript\nimport { World, component } from \"@codehz/ecs\";\n\n// 定义组件类型\ntype Position = { x: number; y: number };\ntype Velocity = { x: number; y: number };\n\n// 定义组件 ID（自动分配）\nconst PositionId = component\u003cPosition\u003e();\nconst VelocityId = component\u003cVelocity\u003e();\n\n// 创建世界\nconst world = new World();\n\n// 创建实体并设置组件（所有更改缓冲到 sync() 时应用）\nconst entity = world.new();\nworld.set(entity, PositionId, { x: 0, y: 0 });\nworld.set(entity, VelocityId, { x: 1, y: 0.5 });\nworld.sync();\n\n// 创建可重用的查询\nconst query = world.createQuery([PositionId, VelocityId]);\n\n// 更新循环\nconst deltaTime = 1.0 / 60.0;\nquery.forEach([PositionId, VelocityId], (entity, position, velocity) =\u003e {\n  position.x += velocity.x * deltaTime;\n  position.y += velocity.y * deltaTime;\n});\n```\n\n### 定义组件（ID 自动分配）\n\n`component()` 自动从全局分配器中分配一个唯一 ID，也可以指定名称或选项：\n\n```typescript\nimport { component } from \"@codehz/ecs\";\n\n// 无参自动分配 ID\nconst Position = component\u003cPosition\u003e();\n\n// 指定名称（序列化时可读）\nconst Velocity = component\u003cVelocity\u003e(\"Velocity\");\n\n// 带选项的组件（关系专用）\nconst ChildOf = component({ exclusive: true, name: \"ChildOf\" });\n```\n\n**`ComponentOptions` 选项：**\n\n| 选项            | 类型                | 说明                                                                                                                           |\n| --------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------ |\n| `name`          | `string`            | 组件名称，用于序列化/调试                                                                                                      |\n| `exclusive`     | `boolean`           | 仅关系组件：同一实体对同一基础组件最多只能有一个关系                                                                           |\n| `cascadeDelete` | `boolean`           | 仅实体关系：删除目标实体时，持有该关系的**整个实体**也会被删除。区别于默认行为（默认仅清理关系组件，实体保留）。支持传递级联。 |\n| `dontFragment`  | `boolean`           | 仅关系组件：不同目标实体的关系存放在同一 Archetype，防止因目标不同而过度碎片化                                                 |\n| `merge`         | `(prev, next) =\u003e T` | 在同一 sync 批次中对同一组件反复 `set()` 时的合并策略                                                                          |\n\n### 生命周期钩子\n\n`world.hook()` 使用组件数组注册多组件生命周期钩子：\n\n```typescript\n// 返回卸载函数\nconst unhook = world.hook([PositionId, VelocityId], {\n  on_init: (entityId, position, velocity) =\u003e {\n    // 钩子注册时，为每个已同时满足条件的实体调用\n  },\n  on_set: (entityId, position, velocity) =\u003e {\n    // 当实体「进入」匹配集合时调用（添加/更新组件后）\n  },\n  on_remove: (entityId, position, velocity) =\u003e {\n    // 当实体「退出」匹配集合时调用（移除组件或删除实体后）\n  },\n});\n// 卸载钩子\nunhook();\n```\n\n也支持回调简写形式：\n\n```typescript\nconst unhook = world.hook([PositionId, VelocityId], (type, entityId, position, velocity) =\u003e {\n  if (type === \"init\") console.log(\"初始化\");\n  if (type === \"set\") console.log(\"设置\");\n  if (type === \"remove\") console.log(\"移除\");\n});\n```\n\n可选组件与过滤器：\n\n```typescript\n// 可选组件：即使 Velocity 不存在也会触发钩子\nworld.hook([PositionId, { optional: VelocityId }], {\n  on_set: (entityId, position, velocity) =\u003e {\n    if (velocity !== undefined) {\n      console.log(\"拥有速度和位置\");\n    } else {\n      console.log(\"仅拥有位置\");\n    }\n  },\n});\n\n// 过滤器：排除带有指定负面组件的实体\nconst DisabledId = component\u003cvoid\u003e();\nworld.hook(\n  [PositionId, VelocityId],\n  {\n    on_set: (entityId, position, velocity) =\u003e console.log(\"进入匹配集合\"),\n    on_remove: (entityId, position, velocity) =\u003e console.log(\"退出匹配集合\"),\n  },\n  { negativeComponentTypes: [DisabledId] },\n);\n```\n\n### 关系组件\n\n```typescript\nimport { World, component, relation } from \"@codehz/ecs\";\n\nconst ChildOf = component\u003cvoid\u003e({ exclusive: true });\nconst world = new World();\nconst child = world.new();\nconst parent1 = world.new();\nconst parent2 = world.new();\n\n// 添加关系\nworld.set(child, relation(ChildOf, parent1));\nworld.sync();\n\n// 独占关系：添加新关系时自动移除旧关系\nworld.set(child, relation(ChildOf, parent2));\nworld.sync();\nconsole.log(world.has(child, relation(ChildOf, parent1))); // false\nconsole.log(world.has(child, relation(ChildOf, parent2))); // true\n```\n\n### 通配符关系钩子\n\n```typescript\nimport { World, component, relation } from \"@codehz/ecs\";\nconst PositionId = component\u003cPosition\u003e();\n\nconst world = new World();\nconst wildcardPos = relation(PositionId, \"*\");\n\n// 监听所有该类型关系的变动\nworld.hook([wildcardPos], {\n  on_set: (entityId, relations) =\u003e {\n    for (const [targetId, position] of relations) {\n      console.log(`实体 ${entityId} -\u003e 目标 ${targetId}:`, position);\n    }\n  },\n  on_remove: (entityId, relations) =\u003e {\n    console.log(`实体 ${entityId} 移除了所有 Position 关系`);\n  },\n});\n```\n\n### EntityBuilder 流式创建\n\n```typescript\nconst entity = world\n  .spawn()\n  .with(Position, { x: 0, y: 0 })\n  .with(Marker) // void 组件无需传值\n  .withRelation(ChildOf, parentEntity)\n  .build();\nworld.sync(); // 统一应用\n```\n\n### 批量创建\n\n```typescript\nconst entities = world.spawnMany(100, (builder, index) =\u003e builder.with(Position, { x: index * 10, y: 0 }));\nworld.sync();\n```\n\n### 运行示例\n\n```bash\nbun run examples/simple/demo.ts\nbun run examples/advanced-scheduling/demo.ts\n```\n\n## API 概述\n\n### World\n\n| 方法                                  | 说明                                                                                |\n| ------------------------------------- | ----------------------------------------------------------------------------------- |\n| `new\u003cT\u003e()`                            | 创建新实体，返回 `EntityId\u003cT\u003e`                                                      |\n| `create\u003cT\u003e()`                         | `new()` 的语义别名                                                                  |\n| `spawn()`                             | 返回 `EntityBuilder` 用于流式创建                                                   |\n| `spawnMany(count, configure)`         | 批量创建多个实体                                                                    |\n| `exists(entity)`                      | 检查实体是否存在                                                                    |\n| `set(entity, componentId, data?)`     | 添加/更新组件（缓冲，`sync()` 后生效）。对 `void` 组件可不传 data                   |\n| `set(componentId, data)`              | 单例组件简写：`world.set(GlobalConfig, { ... })`                                    |\n| `get(entity, componentId?)`           | 获取组件数据。**若组件不存在会抛出异常**，请先用 `has()` 检查或使用 `getOptional()` |\n| `getOptional(entity, componentId?)`   | 安全获取组件，返回 `{ value: T } \\| undefined`                                      |\n| `has(entity, componentId?)`           | 检查组件是否存在                                                                    |\n| `remove(entity, componentId?)`        | 移除组件（缓冲），也有单例简写                                                      |\n| `delete(entity)`                      | 销毁实体及其所有组件（缓冲）                                                        |\n| `query(componentIds)`                 | 快速查询（不缓存）                                                                  |\n| `query(componentIds, true)`           | 快速查询并返回实体及组件数据                                                        |\n| `createQuery(componentIds, filter?)`  | 创建可重用的缓存查询                                                                |\n| `releaseQuery(query)`                 | 释放查询（可选清理）                                                                |\n| `hook(componentTypes, hook, filter?)` | 注册生命周期钩子，返回卸载函数                                                      |\n| `serialize()`                         | 序列化世界状态为快照对象                                                            |\n| `sync()`                              | 执行所有延迟命令                                                                    |\n\n### Query\n\n查询通过 `world.createQuery()` 创建，应**跨帧复用**以获得最佳性能。\n\n| 方法                                | 说明                                     |\n| ----------------------------------- | ---------------------------------------- |\n| `forEach(componentTypes, callback)` | 遍历匹配实体                             |\n| `getEntities()`                     | 获取所有匹配实体的 ID 列表               |\n| `getEntitiesWithComponents(types)`  | 获取实体及组件数据的对象数组             |\n| `iterate(types)`                    | 返回生成器，用于 `for...of` 遍历         |\n| `getComponentData(type)`            | 获取所有匹配实体的单组件数据数组         |\n| `dispose()`                         | 释放查询（引用计数减一，归零时完全释放） |\n| `get disposed()`                    | 检查查询是否已释放                       |\n\n### QueryFilter\n\n```typescript\ninterface QueryFilter {\n  negativeComponentTypes?: EntityId\u003cany\u003e[]; // 排除的组件\n}\n```\n\n### EntityBuilder\n\n| 方法                                         | 说明                                         |\n| -------------------------------------------- | -------------------------------------------- |\n| `with(componentId, ...args)`                 | 添加普通组件。`void` 类型不传值              |\n| `withRelation(componentId, target, ...args)` | 添加关系组件。`void` 类型不传值              |\n| `build()`                                    | 创建实体并返回 `EntityId`（仍需要 `sync()`） |\n\n### component()\n\n```typescript\n// 自动分配 ID\ncomponent\u003cT\u003e();\n// 指定名称\ncomponent\u003cT\u003e(\"Name\");\n// 带选项\ncomponent\u003cT\u003e({ name?: string, exclusive?: boolean, cascadeDelete?: boolean, dontFragment?: boolean, merge?: (prev, next) =\u003e T });\n```\n\n### relation()\n\n```typescript\n// 创建关系 ID\nrelation(componentId, targetEntity);\n// 通配符（查询所有目标）\nrelation(componentId, \"*\");\n// 单例目标（关联到另一个组件）\nrelation(componentId, otherComponentId);\n```\n\n### 组件 / 实体 ID 规则\n\n- 组件 ID：`1` ~ `1023`\n- 实体 ID：`1024+`\n- 关系 ID：负数编码 `-(componentId * 2^42 + targetId)`\n\n## 序列化（快照）\n\n库提供对世界状态的「内存快照」序列化接口，用于保存/恢复实体与组件数据。\n\n```typescript\n// 创建快照（内存对象）\nconst snapshot = world.serialize();\n\n// 在同一进程内直接恢复\nconst restored = new World(snapshot);\n```\n\n**设计要点：**\n\n- `world.serialize()` 返回内存快照对象，**不会**对组件值执行 `JSON.stringify`，也不会尝试将组件值转换为可序列化格式。\n- `new World(snapshot)` 是反序列化的唯一入口（没有 `World.deserialize()` 静态方法）。\n- 快照包含实体、组件以及 `EntityIdManager` 分配器状态（保留下一次分配的 ID）；**不会**自动恢复查询缓存或生命周期钩子。\n\n**持久化示例（组件值为 JSON 友好时）：**\n\n```typescript\nconst snapshot = world.serialize();\nconst json = JSON.stringify(snapshot);\n// 写入文件或发送到网络 ...\n\nconst parsed = JSON.parse(json);\nconst restored = new World(parsed);\n```\n\n**自定义编码示例：**\n\n```typescript\nconst snapshot = world.serialize();\nconst encoded = {\n  ...snapshot,\n  entities: snapshot.entities.map((e) =\u003e ({\n    id: e.id,\n    components: e.components.map((c) =\u003e ({ type: c.type, value: myEncode(c.value) })),\n  })),\n};\n// 持久化 encoded ...\n\n// 恢复时反向解码\nconst decodedSnapshot = {\n  ...decoded,\n  entities: decoded.entities.map((e) =\u003e ({\n    id: e.id,\n    components: e.components.map((c) =\u003e ({ type: c.type, value: myDecode(c.value) })),\n  })),\n};\nconst restored = new World(decodedSnapshot);\n```\n\n**重要：** `get()` 在组件不存在时会抛出异常。由于 `undefined` 是组件的有效值，不能用 `get()` 的返回值是否为 `undefined` 来判断组件是否存在。请使用 `has()` 或 `getOptional()`。\n\n## System / Pipeline 集成\n\n从 v0.4.0 开始，库移除了内置的 `System` 和 `SystemScheduler`。推荐使用 `@codehz/pipeline` 来组织游戏循环，**务必在最后一个 pass 调用 `world.sync()`**。\n\n```bash\nbun add @codehz/pipeline\n```\n\n```typescript\nimport { pipeline } from \"@codehz/pipeline\";\nimport { World, component } from \"@codehz/ecs\";\n\nconst world = new World();\nconst movementQuery = world.createQuery([PositionId, VelocityId]);\n\nconst gameLoop = pipeline\u003c{ deltaTime: number }\u003e()\n  .addPass((env) =\u003e {\n    movementQuery.forEach([PositionId, VelocityId], (entity, position, velocity) =\u003e {\n      position.x += velocity.x * env.deltaTime;\n      position.y += velocity.y * env.deltaTime;\n    });\n  })\n  .addPass(() =\u003e {\n    world.sync(); // 必须作为最后一个 pass\n  })\n  .build();\n\ngameLoop({ deltaTime: 0.016 });\n```\n\n## 项目结构\n\n```\nsrc/\n├── index.ts                 # 入口文件（统一导出）\n├── core/                    # 核心实现\n│   ├── world.ts             # 世界管理\n│   ├── archetype.ts         # Archetype 系统（高效组件存储）\n│   ├── builder.ts           # EntityBuilder 流式创建\n│   ├── component-registry.ts # 组件注册表\n│   ├── component-entity-store.ts # 单例组件存储\n│   ├── component-type-utils.ts   # 组件类型工具\n│   ├── dont-fragment-store.ts    # DontFragment 存储\n│   ├── entity.ts            # 实体/组件/关系类型导出（聚合）\n│   ├── entity-types.ts      # 实体 ID 类型定义与常量\n│   ├── entity-relation.ts   # 关系 ID 编码/解码\n│   ├── entity-manager.ts    # ID 分配器\n│   ├── query-registry.ts    # 查询注册表\n│   ├── serialization.ts     # 序列化 ID 编解码\n│   ├── world-serialization.ts # 世界序列化/反序列化\n│   ├── world-commands.ts    # 世界命令\n│   ├── world-hooks.ts       # 钩子执行逻辑\n│   ├── world-references.ts  # 实体引用追踪\n│   └── types.ts             # 类型定义\n├── query/                   # 查询系统\n│   ├── query.ts             # Query 类\n│   └── filter.ts            # 查询过滤器\n├── commands/                # 命令缓冲区\n├── utils/                   # 工具函数\n├── testing/                 # 测试工具\n└── __tests__/               # 单元测试 \u0026 性能测试\n\nexamples/\n├── simple/\n│   ├── demo.ts              # 基本示例\n│   └── README.md            # 示例说明\n└── advanced-scheduling/\n    └── demo.ts              # Pipeline 调度示例\n\nscripts/\n├── build.ts                 # 构建脚本\n└── release.ts               # 发布脚本\n```\n\n## 开发\n\n```bash\nbun install\nbun test                    # 运行测试\nbunx tsc --noEmit           # 类型检查\nbun run examples/simple/demo.ts  # 运行示例\nbun run scripts/build.ts    # 构建\n```\n\n## 许可证\n\nMIT\n\n## 贡献\n\n欢迎提交 Issue 和 Pull Request！\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodehz%2Fecs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodehz%2Fecs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodehz%2Fecs/lists"}