@benev/archimedes 0.1.0-3 → 0.1.0-4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +86 -10
  2. package/package.json +4 -4
  3. package/s/ecs/index.ts +6 -5
  4. package/s/ecs/parts/apply-change.ts +33 -0
  5. package/s/ecs/parts/change.ts +27 -0
  6. package/s/ecs/parts/entities.ts +98 -0
  7. package/s/ecs/parts/execute-systems.ts +18 -0
  8. package/s/ecs/parts/lifecycle.ts +30 -0
  9. package/s/ecs/parts/types.ts +25 -0
  10. package/s/ecs/test/setup-example.ts +49 -0
  11. package/s/ecs/test.ts +71 -61
  12. package/x/ecs/index.d.ts +6 -4
  13. package/x/ecs/index.js +6 -4
  14. package/x/ecs/index.js.map +1 -1
  15. package/x/ecs/parts/apply-change.d.ts +3 -0
  16. package/x/ecs/parts/apply-change.js +30 -0
  17. package/x/ecs/parts/apply-change.js.map +1 -0
  18. package/x/ecs/parts/change.d.ts +13 -0
  19. package/x/ecs/parts/change.js +15 -0
  20. package/x/ecs/parts/change.js.map +1 -0
  21. package/x/ecs/parts/entities.d.ts +11 -0
  22. package/x/ecs/parts/entities.js +70 -0
  23. package/x/ecs/parts/entities.js.map +1 -0
  24. package/x/ecs/parts/execute-systems.d.ts +3 -0
  25. package/x/ecs/parts/execute-systems.js +12 -0
  26. package/x/ecs/parts/execute-systems.js.map +1 -0
  27. package/x/ecs/parts/lifecycle.d.ts +3 -0
  28. package/x/ecs/parts/lifecycle.js +19 -0
  29. package/x/ecs/parts/lifecycle.js.map +1 -0
  30. package/x/ecs/parts/types.d.ts +21 -0
  31. package/x/ecs/parts/types.js +9 -0
  32. package/x/ecs/parts/types.js.map +1 -0
  33. package/x/ecs/test/setup-example.d.ts +15 -0
  34. package/x/ecs/test/setup-example.js +32 -0
  35. package/x/ecs/test/setup-example.js.map +1 -0
  36. package/x/ecs/test.d.ts +2 -2
  37. package/x/ecs/test.js +71 -61
  38. package/x/ecs/test.js.map +1 -1
  39. package/s/ecs/parts/changers.ts +0 -20
  40. package/s/ecs/parts/world.ts +0 -65
  41. package/s/ecs/test/setup-example-world.ts +0 -42
  42. package/s/ecs/types.ts +0 -24
  43. package/s/ecs/utils/apply-change.ts +0 -32
  44. package/s/ecs/utils/is-match.ts +0 -7
  45. package/s/ecs/utils/optimizer.ts +0 -38
  46. package/x/ecs/parts/changers.d.ts +0 -5
  47. package/x/ecs/parts/changers.js +0 -15
  48. package/x/ecs/parts/changers.js.map +0 -1
  49. package/x/ecs/parts/world.d.ts +0 -11
  50. package/x/ecs/parts/world.js +0 -58
  51. package/x/ecs/parts/world.js.map +0 -1
  52. package/x/ecs/test/setup-example-world.d.ts +0 -10
  53. package/x/ecs/test/setup-example-world.js +0 -31
  54. package/x/ecs/test/setup-example-world.js.map +0 -1
  55. package/x/ecs/types.d.ts +0 -21
  56. package/x/ecs/types.js +0 -6
  57. package/x/ecs/types.js.map +0 -1
  58. package/x/ecs/utils/apply-change.d.ts +0 -2
  59. package/x/ecs/utils/apply-change.js +0 -30
  60. package/x/ecs/utils/apply-change.js.map +0 -1
  61. package/x/ecs/utils/is-match.d.ts +0 -2
  62. package/x/ecs/utils/is-match.js +0 -4
  63. package/x/ecs/utils/is-match.js.map +0 -1
  64. package/x/ecs/utils/optimizer.d.ts +0 -9
  65. package/x/ecs/utils/optimizer.js +0 -37
  66. package/x/ecs/utils/optimizer.js.map +0 -1
package/README.md CHANGED
@@ -1,19 +1,95 @@
1
1
 
2
- ![](https://i.imgur.com/JNCvW1J.png)
2
+ ![](https://i.imgur.com/DYcrs49.png)
3
3
 
4
- # 🏛️ archimedes netlogic engine
4
+ # 🌀 archimedes, netlogic for multiplayer web games
5
5
 
6
- > [***"do not disturb my circles!"***](https://en.wikipedia.org/wiki/noli_turbare_circulos_meos!)
6
+ > [***"do not disturb my circles!"***](https://en.wikipedia.org/wiki/noli_turbare_circulos_meos!)
7
7
  >     — *archimedes, c. 212 bc*
8
8
 
9
- ## rollforward netcode for web games
9
+ ```bash
10
+ npm install @benev/archimedes
11
+ ```
10
12
 
11
- 🔮 **automatic networking**
12
- you just code your game naively as though it's singleplayer, archimedes handles the multiplayer.
13
+ - 🧩 [**#ecs,**](#ecs) entities, components, systems.
14
+ - 🔮 [**#sim,**](#sim) code like it's single-player, archimedes makes it multiplayer.
15
+ - 🌎 [**#net,**](#net) whole-world rollforward, everything is clientside predicted for insta-feels.
16
+
17
+
18
+
19
+ <br/><a id="ecs"></a>
20
+
21
+ ## 🧩 ecs — entities, components, systems
22
+
23
+ ```ts
24
+ import {Entities, asSystems, change, executeSystems} from "@benev/archimedes"
25
+ ```
26
+
27
+ 1. ***define components.*** json-friendly data that entities could have.
28
+ ```ts
29
+ export type MyComponents = {
30
+ health: number
31
+ bleed: number
32
+ }
33
+ ```
34
+ 1. ***create entities map.*** fancy caching for speedy-fast lookups.
35
+ ```ts
36
+ export const entitiesWritable = new Entities<MyComponents>()
37
+
38
+ // only let systems touch the readonly variant!
39
+ export const entities = entitiesWritable.readonly()
40
+ ```
41
+ 1. ***define systems.*** select entities by components. yields changes.
42
+ ```ts
43
+ const systems = asSystems<MyComponents>(
44
+ function* bleeding() {
45
+ for (const [id, components] of entities.select("health", "bleed")) {
46
+ if (components.bleed >= 0) {
47
+ const health = components.health - components.bleed
48
+ const bleed = components.bleed - 1
49
+ yield change.merge(id, {health, bleed})
50
+ }
51
+ }
52
+ },
53
+
54
+ function* death() {
55
+ for (const [id, components] of entities.select("health")) {
56
+ if (components.health <= 0)
57
+ yield change.delete(id)
58
+ }
59
+ },
60
+ )
61
+ ```
62
+ 1. ***create your first entity.***
63
+ ```ts
64
+ const wizardId = makeId()
65
+ entitiesWritable.set(wizardId, {health: 100, bleed: 2})
66
+
67
+ console.log(entities.get(wizardId)?.health)
68
+ // 100
69
+ ```
70
+ 1. ***execute systems to simulate each tick.***
71
+ ```ts
72
+ executeSystems(entitiesWritable, systems)
73
+
74
+ console.log(entities.get(wizardId)?.health)
75
+ // 98
76
+ ```
77
+
78
+
79
+
80
+ <br/><a id="sim"></a>
81
+
82
+ ## 🔮 sim — networkable simulation architecture
83
+
84
+ *coming soon*
85
+
86
+
87
+
88
+ <br/><a id="net"></a>
89
+
90
+ ## 🌎 net — connect and run multiplayer games
91
+
92
+ *coming soon*
13
93
 
14
- 🌎 **whole-world rollforward**
15
- clientside prediction applies to the whole simulation. all effects of player inputs feel instant.
16
94
 
17
- 🧩 **ecs architecture**
18
- program your simulation with entities, components, and systems.
19
95
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@benev/archimedes",
3
- "version": "0.1.0-3",
3
+ "version": "0.1.0-4",
4
4
  "description": "game ecs with auto networking",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,20 +10,20 @@
10
10
  "s"
11
11
  ],
12
12
  "scripts": {
13
- "build": "octo-s 'rm -rf x' 'mkdir x' 'tsc'",
13
+ "build": "rm -rf x && mkdir x && tsc",
14
14
  "dev": "octo 'node --watch x/test.js' 'tsc -w'",
15
15
  "test": "node x/test.js",
16
16
  "count": "find s -path '*/_archive' -prune -o -name '*.ts' -exec wc -l {} +"
17
17
  },
18
18
  "dependencies": {
19
19
  "@e280/renraku": "^0.5.7",
20
- "@e280/stz": "^0.2.27",
20
+ "@e280/stz": "^0.2.29",
21
21
  "@msgpack/msgpack": "^3.1.3",
22
22
  "sparrow-rtc": "^0.2.15"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@e280/octo": "^0.1.0",
26
- "@e280/science": "^0.1.9",
26
+ "@e280/science": "^0.1.10",
27
27
  "npm-run-all": "^4.1.5",
28
28
  "typescript": "^6.0.2"
29
29
  },
package/s/ecs/index.ts CHANGED
@@ -1,8 +1,9 @@
1
1
 
2
- export * from "./parts/changers.js"
3
- export * as change from "./parts/changers.js"
2
+ export * from "./parts/apply-change.js"
3
+ export * from "./parts/change.js"
4
+ export * from "./parts/entities.js"
5
+ export * from "./parts/execute-systems.js"
6
+ export * from "./parts/lifecycle.js"
4
7
  export * from "./parts/make-id.js"
5
- export * from "./parts/world.js"
6
-
7
- export * from "./types.js"
8
+ export * from "./parts/types.js"
8
9
 
@@ -0,0 +1,33 @@
1
+
2
+ import {Entities} from "./entities.js"
3
+ import {ChangeSet, Change, Components, ChangeKind, ChangeMerge, ChangeDrop} from "./types.js"
4
+
5
+ export function applyChange<C extends Components>(entities: Entities<C>, change: Change<C>) {
6
+ switch (change[0]) {
7
+ case ChangeKind.Set: return applySet<C>(entities, <ChangeSet<C>>change)
8
+ case ChangeKind.Merge: return applyMerge<C>(entities, <ChangeMerge<C>>change)
9
+ case ChangeKind.Drop: return applyDrop<C>(entities, <ChangeDrop<C>>change)
10
+ default: throw new Error(`unknown change kind "${change[0]}"`)
11
+ }
12
+ }
13
+
14
+ function applySet<C extends Components>(entities: Entities<C>, [, id, components]: ChangeSet<C>) {
15
+ if (components) entities.set(id, components as Partial<C>)
16
+ else entities.delete(id)
17
+ return id
18
+ }
19
+
20
+ function applyMerge<C extends Components>(entities: Entities<C>, [, id, patch]: ChangeMerge<C>) {
21
+ const components = entities.guarantee(id, () => ({}))
22
+ Object.assign(components, patch)
23
+ entities.set(id, components)
24
+ return id
25
+ }
26
+
27
+ function applyDrop<C extends Components>(entities: Entities<C>, [, id, keys]: ChangeDrop<C>) {
28
+ const components = entities.guarantee(id, () => ({}))
29
+ for (const key of keys) delete components[key]
30
+ entities.set(id, components)
31
+ return id
32
+ }
33
+
@@ -0,0 +1,27 @@
1
+
2
+ import {makeId} from "./make-id.js"
3
+ import {Components, ChangeSet, ChangeMerge, Id, ChangeKind, ChangeDrop} from "./types.js"
4
+
5
+ export const change = {
6
+
7
+ /** create a new entity with the given components */
8
+ create: (components: Partial<Components>): ChangeSet<Components> =>
9
+ [ChangeKind.Set, makeId(), components],
10
+
11
+ /** overwrite a whole entity to the given components */
12
+ set: (id: Id, components: Partial<Components>): ChangeSet<Components> =>
13
+ [ChangeKind.Set, id, components],
14
+
15
+ /** remove the entity */
16
+ delete: (id: Id): ChangeSet<Components> =>
17
+ [ChangeKind.Set, id],
18
+
19
+ /** update or add the given components onto the entity */
20
+ merge: (id: Id, components: Partial<Components>): ChangeMerge<Components> =>
21
+ [ChangeKind.Merge, id, components],
22
+
23
+ /** delete specific components off the entity */
24
+ drop: <C extends Components>(id: Id, ...keys: (keyof C)[]): ChangeDrop<C> =>
25
+ [ChangeKind.Drop, id, keys],
26
+ }
27
+
@@ -0,0 +1,98 @@
1
+
2
+ import {GMap} from "@e280/stz"
3
+ import {Components, Id, Select} from "./types.js"
4
+
5
+ export class Entities<C extends Components> extends GMap<Id, Partial<C>> {
6
+ #index = new GMap<Set<keyof C>, GMap<Id, Partial<C>>>()
7
+
8
+ set(id: Id, components: Partial<C>) {
9
+ super.set(id, components)
10
+ for (const [set, entities] of this.#index) {
11
+ if (componentsSatisfySet(components, set))
12
+ entities.set(id, components)
13
+ else
14
+ entities.delete(id)
15
+ }
16
+ return this
17
+ }
18
+
19
+ delete(id: Id) {
20
+ const didDelete = super.delete(id)
21
+ if (!didDelete)
22
+ return false
23
+
24
+ for (const entities of this.#index.values())
25
+ entities.delete(id)
26
+
27
+ return true
28
+ }
29
+
30
+ clear() {
31
+ super.clear()
32
+ for (const entities of this.#index.values())
33
+ entities.clear()
34
+ }
35
+
36
+ *select<K extends keyof C>(...componentKeys: K[]): Iterable<[Id, Select<C, K>]> {
37
+ const cached = this.#getCache(componentKeys)
38
+ if (cached)
39
+ yield* cached
40
+ else
41
+ yield* this.#makeCache(componentKeys)
42
+ }
43
+
44
+ readonly() {
45
+ return this as EntitiesReadonly<C>
46
+ }
47
+
48
+ #getCache<K extends keyof C>(componentKeys: K[]) {
49
+ for (const set of this.#index.keys()) {
50
+ if (setHasSameValuesAsArray(set, componentKeys))
51
+ return this.#index.require(set) as GMap<Id, Select<C, K>>
52
+ }
53
+ }
54
+
55
+ *#makeCache<K extends keyof C>(componentKeys: K[]) {
56
+ const set = new Set(componentKeys)
57
+ const entities = new GMap<Id, Partial<C>>()
58
+ this.#index.set(set, entities)
59
+
60
+ for (const entity of this) {
61
+ const [id, components] = entity
62
+
63
+ if (componentsSatisfyKeys(components, componentKeys)) {
64
+ entities.set(id, components)
65
+ yield entity as [Id, Select<C, K>]
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ export type EntitiesReadonly<C extends Components> = Pick<Entities<Readonly<C>>, (
72
+ | "has"
73
+ | "get"
74
+ | "require"
75
+ | "keys"
76
+ | "values"
77
+ | "entries"
78
+ | "select"
79
+ | typeof Symbol.iterator
80
+ )>
81
+
82
+ function componentsSatisfySet(components: Components, set: Set<PropertyKey>) {
83
+ for (const key of set)
84
+ if (!(key in components))
85
+ return false
86
+ return true
87
+ }
88
+
89
+ function setHasSameValuesAsArray(set: Set<unknown>, keys: unknown[]) {
90
+ if (set.size !== keys.length)
91
+ return false
92
+ return keys.every(key => set.has(key))
93
+ }
94
+
95
+ function componentsSatisfyKeys(components: object, keys: PropertyKey[]) {
96
+ return keys.every(key => key in components)
97
+ }
98
+
@@ -0,0 +1,18 @@
1
+
2
+ import {Entities} from "./entities.js"
3
+ import {applyChange} from "./apply-change.js"
4
+ import {Change, Components, System} from "./types.js"
5
+
6
+ export function executeSystems<C extends Components>(entities: Entities<C>, systems: System<C>[]) {
7
+ const changes: Change<C>[] = []
8
+
9
+ for (const system of systems) {
10
+ for (const change of system()) {
11
+ applyChange(entities, change)
12
+ changes.push(change)
13
+ }
14
+ }
15
+
16
+ return changes
17
+ }
18
+
@@ -0,0 +1,30 @@
1
+
2
+ import {GMap} from "@e280/stz"
3
+ import {EntitiesReadonly} from "./entities.js"
4
+ import {Components, Id, LifecycleCallbacks, LifecycleEnter} from "./types.js"
5
+
6
+ export function lifecycle<C extends Components, K extends keyof C>(
7
+ entities: EntitiesReadonly<C>,
8
+ componentKeys: K[],
9
+ enter: LifecycleEnter<C, K>
10
+ ) {
11
+
12
+ const alive = new GMap<Id, LifecycleCallbacks<C, K>>()
13
+ const sel = () => entities.select(...componentKeys)
14
+
15
+ return function*() {
16
+ const current = new Map(sel())
17
+
18
+ for (const [id, callbacks] of alive) {
19
+ if (current.has(id)) continue
20
+ alive.delete(id)
21
+ callbacks.exit(id)
22
+ }
23
+
24
+ for (const [id, components] of current) {
25
+ const callbacks = alive.guarantee(id, () => enter(id, components))
26
+ callbacks.tick(id, components)
27
+ }
28
+ }
29
+ }
30
+
@@ -0,0 +1,25 @@
1
+
2
+ export type Id = string
3
+ export type Components = Record<string, unknown>
4
+ export type AsComponents<C extends Components> = C
5
+ export type Select<C extends Components, K extends keyof C> = Pick<C, K> & Partial<C>
6
+ export type System<C extends Components> = () => Generator<Change<C>>
7
+
8
+ export const asSystem = <C extends Components>(system: System<C>) => system
9
+ export const asSystems = <C extends Components>(...systems: System<C>[]) => systems
10
+
11
+ export enum ChangeKind {Set, Merge, Drop}
12
+ export type ChangeSet<C extends Components> = [kind: ChangeKind.Set, id: Id, components?: Partial<C>]
13
+ export type ChangeMerge<C extends Components> = [kind: ChangeKind.Merge, id: Id, patch: Partial<C>]
14
+ export type ChangeDrop<C extends Components> = [kind: ChangeKind.Drop, id: Id, keys: (keyof C)[]]
15
+ export type Change<C extends Components> = ChangeSet<C> | ChangeMerge<C> | ChangeDrop<C>
16
+
17
+ export type LifecycleCallbacks<C extends Components, K extends keyof C> = {
18
+ tick: (id: Id, components: Select<C, K>) => void
19
+ exit: (id: Id) => void
20
+ }
21
+
22
+ export type LifecycleEnter<C extends Components, K extends keyof C> = (
23
+ (id: Id, components: Select<C, K>) => LifecycleCallbacks<C, K>
24
+ )
25
+
@@ -0,0 +1,49 @@
1
+
2
+ import {change} from "../parts/change.js"
3
+ import {asSystems} from "../parts/types.js"
4
+ import {Entities} from "../parts/entities.js"
5
+
6
+ export function setupExample() {
7
+ type MyComponents = {
8
+ health: number
9
+ bleed: number
10
+ mana: number
11
+ manaRegen: number
12
+ }
13
+
14
+ const writableEntities = new Entities<MyComponents>()
15
+ const entities = writableEntities.readonly()
16
+
17
+ const systems = asSystems<MyComponents>(
18
+ function* manaRegen() {
19
+ for (const [id, components] of entities.select("mana", "manaRegen")) {
20
+ if (components.manaRegen !== 0) {
21
+ const mana = components.mana + components.manaRegen
22
+ yield change.merge(id, {mana})
23
+ }
24
+ }
25
+ },
26
+
27
+ function* bleeding() {
28
+ for (const [id, components] of entities.select("health", "bleed")) {
29
+ if (components.bleed >= 0) {
30
+ const health = components.health - components.bleed
31
+ const bleed = components.bleed - 1
32
+ yield change.merge(id, {health, bleed})
33
+ }
34
+ if (components.bleed <= 0)
35
+ yield change.drop(id, "bleed")
36
+ }
37
+ },
38
+
39
+ function* death() {
40
+ for (const [id, components] of entities.select("health")) {
41
+ if (components.health <= 0)
42
+ yield change.delete(id)
43
+ }
44
+ },
45
+ )
46
+
47
+ return {entities: writableEntities, systems}
48
+ }
49
+
package/s/ecs/test.ts CHANGED
@@ -1,96 +1,104 @@
1
1
 
2
2
  import {suite, test, expect} from "@e280/science"
3
- import {create, del, update} from "./parts/changers.js"
4
- import {setupExampleWorld} from "./test/setup-example-world.js"
3
+ import {change} from "./parts/change.js"
4
+ import {lifecycle} from "./parts/lifecycle.js"
5
+ import {applyChange} from "./parts/apply-change.js"
6
+ import {setupExample} from "./test/setup-example.js"
7
+ import {executeSystems} from "./parts/execute-systems.js"
5
8
  import {setupLifecycleCounts} from "./test/setup-lifecycle-counts.js"
6
9
 
7
10
  export default suite({
8
11
  "create an entity": test(async() => {
9
- const {world} = setupExampleWorld()
10
- expect(world.entities.size).is(0)
11
- world.apply(create({health: 100}))
12
- expect(world.entities.size).is(1)
12
+ const {entities} = setupExample()
13
+ expect(entities.size).is(0)
14
+ applyChange(entities, change.create({health: 100}))
15
+ expect(entities.size).is(1)
13
16
  }),
14
17
 
15
18
  "delete an entity": test(async() => {
16
- const {world} = setupExampleWorld()
17
- const id = world.apply(create({health: 100}))
18
- expect(world.entities.size).is(1)
19
- world.apply(del(id))
20
- expect(world.entities.size).is(0)
19
+ const {entities} = setupExample()
20
+ const id = applyChange(entities, change.create({health: 100}))
21
+ expect(entities.size).is(1)
22
+ applyChange(entities, change.delete(id))
23
+ expect(entities.size).is(0)
21
24
  }),
22
25
 
23
- "partial updates": test(async() => {
24
- const {world} = setupExampleWorld()
25
- const id = world.apply(create({health: 100, mana: 100}))
26
- expect(world.require(id).health).is(100)
27
- world.apply(update(id, {health: 99}))
28
- expect(world.require(id).health).is(99)
29
- expect(world.require(id).mana).is(100)
26
+ "merge components into entity": test(async() => {
27
+ const {entities} = setupExample()
28
+ const id = applyChange(entities, change.create({health: 100, mana: 100}))
29
+ expect(entities.require(id).health).is(100)
30
+ applyChange(entities, change.merge(id, {health: 99}))
31
+ expect(entities.require(id).health).is(99)
32
+ expect(entities.require(id).mana).is(100)
30
33
  }),
31
34
 
32
35
  "select an entity": test(async() => {
33
- const {world} = setupExampleWorld()
34
- world.apply(create({health: 100}))
35
- expect([...world.select("health")].length).is(1)
36
+ const {entities} = setupExample()
37
+ applyChange(entities, change.create({health: 100}))
38
+ expect([...entities.select("health")].length).is(1)
36
39
  }),
37
40
 
38
- "select an entity after shape change": test(async() => {
39
- const {world} = setupExampleWorld()
40
- const id = world.apply(create({health: 100, mana: 100}))
41
- expect([...world.select("health", "mana")].length).is(1)
42
- world.apply(update(id, {health: 99, mana: null}))
43
- expect([...world.select("health", "mana")].length).is(0)
41
+ "drop components from entity": test(async() => {
42
+ const {entities} = setupExample()
43
+ const id = applyChange(entities, change.create({health: 100, mana: 100}))
44
+ expect([...entities.select("health", "mana")].length).is(1)
45
+ applyChange(entities, change.drop(id, "mana"))
46
+ expect("mana" in entities.require(id)).is(false)
47
+ expect([...entities.select("health", "mana")].length).is(0)
44
48
  }),
45
49
 
46
50
  "select two entities": test(async() => {
47
- const {world} = setupExampleWorld()
48
- world.apply(create({health: 100}))
49
- world.apply(create({health: 100}))
50
- expect([...world.select("health")].length).is(2)
51
+ const {entities} = setupExample()
52
+ applyChange(entities, change.create({health: 100}))
53
+ applyChange(entities, change.create({health: 100}))
54
+ expect([...entities.select("health")].length).is(2)
51
55
  }),
52
56
 
53
57
  "select with no component keys selects all": test(async() => {
54
- const {world} = setupExampleWorld()
55
- world.apply(create({health: 100}))
56
- world.apply(create({health: 100}))
57
- expect([...world.select()].length).is(2)
58
+ const {entities} = setupExample()
59
+ applyChange(entities, change.create({health: 100}))
60
+ applyChange(entities, change.create({health: 100}))
61
+ expect([...entities.select()].length).is(2)
58
62
  }),
59
63
 
60
64
  "select doesn't include non-match": test(async() => {
61
- const {world} = setupExampleWorld()
62
- world.apply(create({health: 100}))
63
- expect([...world.select("mana")].length).is(0)
65
+ const {entities} = setupExample()
66
+ applyChange(entities, change.create({health: 100}))
67
+ expect([...entities.select("mana")].length).is(0)
64
68
  }),
65
69
 
66
70
  "select includes entities with extra components": test(async() => {
67
- const {world} = setupExampleWorld()
68
- world.apply(create({health: 100, mana: 100}))
69
- expect([...world.select("health")].length).is(1)
71
+ const {entities} = setupExample()
72
+ applyChange(entities, change.create({health: 100, mana: 100}))
73
+ expect([...entities.select("health")].length).is(1)
70
74
  }),
71
75
 
72
76
  "wizard regens mana": test(async() => {
73
- const {world, systems} = setupExampleWorld()
74
- const wizardId = world.apply(create({health: 100, mana: 50, manaRegen: 1}))
75
- const changes = world.execute(systems)
77
+ const {entities, systems} = setupExample()
78
+ const creating = change.create({health: 100, mana: 50, manaRegen: 1})
79
+ const wizardId = creating[1]
80
+ applyChange(entities, creating)
81
+ const changes = executeSystems(entities, systems)
76
82
  expect(changes.length).is(1)
77
- expect(world.require(wizardId).mana).is(51)
83
+ expect(entities.require(wizardId).mana).is(51)
78
84
  }),
79
85
 
80
86
  "death by bleeding": test(async() => {
81
- const {world, systems} = setupExampleWorld()
82
- const wizardId = world.apply(create({health: 2, bleed: 1}))
83
- expect(world.require(wizardId).health).is(2)
84
- world.execute(systems)
85
- expect(world.require(wizardId).health).is(1)
86
- world.execute(systems)
87
- expect(world.entities.has(wizardId)).is(false)
87
+ const {entities, systems} = setupExample()
88
+ const creating = change.create({health: 3, bleed: 2})
89
+ const wizardId = creating[1]
90
+ applyChange(entities, creating)
91
+ expect(entities.require(wizardId).health).is(3)
92
+ executeSystems(entities, systems)
93
+ expect(entities.require(wizardId).health).is(1)
94
+ executeSystems(entities, systems)
95
+ expect(entities.has(wizardId)).is(false)
88
96
  }),
89
97
 
90
98
  "lifecycles": test(async() => {
91
- const {world} = setupExampleWorld()
99
+ const {entities} = setupExample()
92
100
  const counts = setupLifecycleCounts()
93
- const system = world.lifecycle(["health"], () => {
101
+ const system = lifecycle(entities.readonly(), ["health"], () => {
94
102
  counts.enters++
95
103
  return {
96
104
  tick: () => void counts.ticks++,
@@ -99,18 +107,20 @@ export default suite({
99
107
  })
100
108
  counts.expect(0, 0, 0)
101
109
 
102
- const wizardId = world.apply(create({health: 100, mana: 50}))
103
- world.execute([system])
110
+ const creating = change.create({health: 100, mana: 50})
111
+ const wizardId = creating[1]
112
+ applyChange(entities, creating)
113
+ executeSystems(entities, [system])
104
114
  counts.expect(1, 1, 0)
105
115
 
106
- world.apply(update(wizardId, {health: 100, mana: 100}))
107
- world.execute([system])
116
+ applyChange(entities, change.merge(wizardId, {health: 100, mana: 100}))
117
+ executeSystems(entities, [system])
108
118
  counts.expect(1, 2, 0)
109
119
 
110
- world.apply(del(wizardId))
111
- world.execute([system])
120
+ applyChange(entities, change.delete(wizardId))
121
+ executeSystems(entities, [system])
112
122
  counts.expect(1, 2, 1)
113
- expect(world.entities.size).is(0)
123
+ expect(entities.size).is(0)
114
124
  }),
115
125
  })
116
126
 
package/x/ecs/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- export * from "./parts/changers.js";
2
- export * as change from "./parts/changers.js";
1
+ export * from "./parts/apply-change.js";
2
+ export * from "./parts/change.js";
3
+ export * from "./parts/entities.js";
4
+ export * from "./parts/execute-systems.js";
5
+ export * from "./parts/lifecycle.js";
3
6
  export * from "./parts/make-id.js";
4
- export * from "./parts/world.js";
5
- export * from "./types.js";
7
+ export * from "./parts/types.js";
package/x/ecs/index.js CHANGED
@@ -1,6 +1,8 @@
1
- export * from "./parts/changers.js";
2
- export * as change from "./parts/changers.js";
1
+ export * from "./parts/apply-change.js";
2
+ export * from "./parts/change.js";
3
+ export * from "./parts/entities.js";
4
+ export * from "./parts/execute-systems.js";
5
+ export * from "./parts/lifecycle.js";
3
6
  export * from "./parts/make-id.js";
4
- export * from "./parts/world.js";
5
- export * from "./types.js";
7
+ export * from "./parts/types.js";
6
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../s/ecs/index.ts"],"names":[],"mappings":"AACA,cAAc,qBAAqB,CAAA;AACnC,OAAO,KAAK,MAAM,MAAM,qBAAqB,CAAA;AAC7C,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAEhC,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../s/ecs/index.ts"],"names":[],"mappings":"AACA,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA;AACnC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA"}
@@ -0,0 +1,3 @@
1
+ import { Entities } from "./entities.js";
2
+ import { Change, Components } from "./types.js";
3
+ export declare function applyChange<C extends Components>(entities: Entities<C>, change: Change<C>): string;