@benev/archimedes 0.1.0-4 → 0.1.0-5

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 (38) hide show
  1. package/README.md +12 -18
  2. package/package.json +1 -1
  3. package/s/ecs/index.ts +1 -1
  4. package/s/ecs/parts/apply-delta.ts +37 -0
  5. package/s/ecs/parts/change.ts +24 -12
  6. package/s/ecs/parts/execute-systems.ts +13 -10
  7. package/s/ecs/parts/lifecycle.ts +12 -13
  8. package/s/ecs/parts/types.ts +11 -8
  9. package/s/ecs/test/setup-example.ts +13 -12
  10. package/s/ecs/test.ts +83 -40
  11. package/x/ecs/index.d.ts +1 -1
  12. package/x/ecs/index.js +1 -1
  13. package/x/ecs/index.js.map +1 -1
  14. package/x/ecs/parts/apply-delta.d.ts +3 -0
  15. package/x/ecs/parts/apply-delta.js +34 -0
  16. package/x/ecs/parts/apply-delta.js.map +1 -0
  17. package/x/ecs/parts/change.d.ts +10 -8
  18. package/x/ecs/parts/change.js +28 -8
  19. package/x/ecs/parts/change.js.map +1 -1
  20. package/x/ecs/parts/execute-systems.d.ts +2 -2
  21. package/x/ecs/parts/execute-systems.js +11 -9
  22. package/x/ecs/parts/execute-systems.js.map +1 -1
  23. package/x/ecs/parts/lifecycle.d.ts +2 -3
  24. package/x/ecs/parts/lifecycle.js +10 -9
  25. package/x/ecs/parts/lifecycle.js.map +1 -1
  26. package/x/ecs/parts/types.d.ts +11 -9
  27. package/x/ecs/parts/types.js +6 -6
  28. package/x/ecs/parts/types.js.map +1 -1
  29. package/x/ecs/test/setup-example.d.ts +9 -2
  30. package/x/ecs/test/setup-example.js +12 -11
  31. package/x/ecs/test/setup-example.js.map +1 -1
  32. package/x/ecs/test.d.ts +4 -0
  33. package/x/ecs/test.js +79 -40
  34. package/x/ecs/test.js.map +1 -1
  35. package/s/ecs/parts/apply-change.ts +0 -33
  36. package/x/ecs/parts/apply-change.d.ts +0 -3
  37. package/x/ecs/parts/apply-change.js +0 -30
  38. package/x/ecs/parts/apply-change.js.map +0 -1
package/README.md CHANGED
@@ -21,7 +21,7 @@ npm install @benev/archimedes
21
21
  ## 🧩 ecs — entities, components, systems
22
22
 
23
23
  ```ts
24
- import {Entities, asSystems, change, executeSystems} from "@benev/archimedes"
24
+ import {Entities, asSystems, makeId, executeSystems} from "@benev/archimedes"
25
25
  ```
26
26
 
27
27
  1. ***define components.*** json-friendly data that entities could have.
@@ -31,45 +31,41 @@ import {Entities, asSystems, change, executeSystems} from "@benev/archimedes"
31
31
  bleed: number
32
32
  }
33
33
  ```
34
- 1. ***create entities map.*** fancy caching for speedy-fast lookups.
34
+ 1. ***create entities map.*** indexed for speedy-fast lookups.
35
35
  ```ts
36
- export const entitiesWritable = new Entities<MyComponents>()
37
-
38
- // only let systems touch the readonly variant!
39
- export const entities = entitiesWritable.readonly()
36
+ export const entities = new Entities<MyComponents>()
40
37
  ```
41
- 1. ***define systems.*** select entities by components. yields changes.
38
+ 1. ***define systems.*** select entities by components. formal changes.
42
39
  ```ts
43
40
  const systems = asSystems<MyComponents>(
44
- function* bleeding() {
41
+ function bleeding(entities, change) {
45
42
  for (const [id, components] of entities.select("health", "bleed")) {
46
- if (components.bleed >= 0) {
43
+ if (components.bleed > 0) {
47
44
  const health = components.health - components.bleed
48
- const bleed = components.bleed - 1
49
- yield change.merge(id, {health, bleed})
45
+ change.merge(id, {health})
50
46
  }
51
47
  }
52
48
  },
53
49
 
54
- function* death() {
50
+ function death(entities, change) {
55
51
  for (const [id, components] of entities.select("health")) {
56
52
  if (components.health <= 0)
57
- yield change.delete(id)
53
+ change.delete(id)
58
54
  }
59
55
  },
60
56
  )
61
57
  ```
62
- 1. ***create your first entity.***
58
+ 1. ***manually insert your first entity.***
63
59
  ```ts
64
60
  const wizardId = makeId()
65
- entitiesWritable.set(wizardId, {health: 100, bleed: 2})
61
+ entities.set(wizardId, {health: 100, bleed: 2})
66
62
 
67
63
  console.log(entities.get(wizardId)?.health)
68
64
  // 100
69
65
  ```
70
66
  1. ***execute systems to simulate each tick.***
71
67
  ```ts
72
- executeSystems(entitiesWritable, systems)
68
+ executeSystems(entities, systems)
73
69
 
74
70
  console.log(entities.get(wizardId)?.health)
75
71
  // 98
@@ -91,5 +87,3 @@ import {Entities, asSystems, change, executeSystems} from "@benev/archimedes"
91
87
 
92
88
  *coming soon*
93
89
 
94
-
95
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@benev/archimedes",
3
- "version": "0.1.0-4",
3
+ "version": "0.1.0-5",
4
4
  "description": "game ecs with auto networking",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/s/ecs/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- export * from "./parts/apply-change.js"
2
+ export * from "./parts/apply-delta.js"
3
3
  export * from "./parts/change.js"
4
4
  export * from "./parts/entities.js"
5
5
  export * from "./parts/execute-systems.js"
@@ -0,0 +1,37 @@
1
+
2
+ import {Entities} from "./entities.js"
3
+ import {DeltaSet, Delta, Components, DeltaKind, DeltaMerge, DeltaDrop} from "./types.js"
4
+
5
+ export function applyDelta<C extends Components>(entities: Entities<C>, delta: Delta<C>) {
6
+ switch (delta[0]) {
7
+ case DeltaKind.Set: return applySet<C>(entities, <DeltaSet<C>>delta)
8
+ case DeltaKind.Merge: return applyMerge<C>(entities, <DeltaMerge<C>>delta)
9
+ case DeltaKind.Drop: return applyDrop<C>(entities, <DeltaDrop<C>>delta)
10
+ default: throw new Error(`unknown delta kind "${delta[0]}"`)
11
+ }
12
+ }
13
+
14
+ function applySet<C extends Components>(entities: Entities<C>, [, id, components]: DeltaSet<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]: DeltaMerge<C>) {
21
+ const components = entities.get(id)
22
+ if (!components)
23
+ return id
24
+ Object.assign(components, patch)
25
+ entities.set(id, components)
26
+ return id
27
+ }
28
+
29
+ function applyDrop<C extends Components>(entities: Entities<C>, [, id, keys]: DeltaDrop<C>) {
30
+ const components = entities.get(id)
31
+ if (!components)
32
+ return id
33
+ for (const key of keys) delete components[key]
34
+ entities.set(id, components)
35
+ return id
36
+ }
37
+
@@ -1,27 +1,39 @@
1
1
 
2
2
  import {makeId} from "./make-id.js"
3
- import {Components, ChangeSet, ChangeMerge, Id, ChangeKind, ChangeDrop} from "./types.js"
3
+ import {Components, Id, DeltaKind, Delta} from "./types.js"
4
4
 
5
- export const change = {
5
+ export class Change<C extends Components> {
6
+ constructor(private commit: (delta: Delta<C>) => void) {}
6
7
 
7
8
  /** create a new entity with the given components */
8
- create: (components: Partial<Components>): ChangeSet<Components> =>
9
- [ChangeKind.Set, makeId(), components],
9
+ create(components: Partial<C>) {
10
+ const id = makeId()
11
+ this.commit([DeltaKind.Set, id, components])
12
+ return id
13
+ }
10
14
 
11
15
  /** overwrite a whole entity to the given components */
12
- set: (id: Id, components: Partial<Components>): ChangeSet<Components> =>
13
- [ChangeKind.Set, id, components],
16
+ set(id: Id, components: Partial<C>) {
17
+ this.commit([DeltaKind.Set, id, components])
18
+ return id
19
+ }
14
20
 
15
21
  /** remove the entity */
16
- delete: (id: Id): ChangeSet<Components> =>
17
- [ChangeKind.Set, id],
22
+ delete(id: Id) {
23
+ this.commit([DeltaKind.Set, id])
24
+ return id
25
+ }
18
26
 
19
27
  /** update or add the given components onto the entity */
20
- merge: (id: Id, components: Partial<Components>): ChangeMerge<Components> =>
21
- [ChangeKind.Merge, id, components],
28
+ merge(id: Id, components: Partial<C>) {
29
+ this.commit([DeltaKind.Merge, id, components])
30
+ return id
31
+ }
22
32
 
23
33
  /** delete specific components off the entity */
24
- drop: <C extends Components>(id: Id, ...keys: (keyof C)[]): ChangeDrop<C> =>
25
- [ChangeKind.Drop, id, keys],
34
+ drop(id: Id, ...keys: (keyof C)[]) {
35
+ this.commit([DeltaKind.Drop, id, keys])
36
+ return id
37
+ }
26
38
  }
27
39
 
@@ -1,18 +1,21 @@
1
1
 
2
+ import {Change} from "./change.js"
2
3
  import {Entities} from "./entities.js"
3
- import {applyChange} from "./apply-change.js"
4
- import {Change, Components, System} from "./types.js"
4
+ import {applyDelta} from "./apply-delta.js"
5
+ import {Delta, Components, System} from "./types.js"
5
6
 
6
7
  export function executeSystems<C extends Components>(entities: Entities<C>, systems: System<C>[]) {
7
- const changes: Change<C>[] = []
8
+ const entitiesReadonly = entities.readonly()
9
+ const deltas: Delta<C>[] = []
8
10
 
9
- for (const system of systems) {
10
- for (const change of system()) {
11
- applyChange(entities, change)
12
- changes.push(change)
13
- }
14
- }
11
+ const change = new Change<C>(delta => {
12
+ applyDelta(entities, delta)
13
+ deltas.push(delta)
14
+ })
15
15
 
16
- return changes
16
+ for (const system of systems)
17
+ system(entitiesReadonly, change)
18
+
19
+ return deltas
17
20
  }
18
21
 
@@ -1,30 +1,29 @@
1
1
 
2
2
  import {GMap} from "@e280/stz"
3
- import {EntitiesReadonly} from "./entities.js"
4
- import {Components, Id, LifecycleCallbacks, LifecycleEnter} from "./types.js"
3
+ import {Components, Id, LifecycleCallbacks, LifecycleEnter, System} from "./types.js"
5
4
 
6
5
  export function lifecycle<C extends Components, K extends keyof C>(
7
- entities: EntitiesReadonly<C>,
8
6
  componentKeys: K[],
9
7
  enter: LifecycleEnter<C, K>
10
- ) {
8
+ ): System<C> {
11
9
 
12
10
  const alive = new GMap<Id, LifecycleCallbacks<C, K>>()
13
- const sel = () => entities.select(...componentKeys)
14
11
 
15
- return function*() {
16
- const current = new Map(sel())
12
+ return (entities, commit) => {
17
13
 
14
+ // add fresh entities
15
+ for (const [id, components] of entities.select(...componentKeys)) {
16
+ const callbacks = alive.guarantee(id, () => enter(id, components, commit))
17
+ callbacks.tick(id, components)
18
+ }
19
+
20
+ // delete stale entities
21
+ const currentIds = new Set([...entities.select(...componentKeys)].map(([id]) => id))
18
22
  for (const [id, callbacks] of alive) {
19
- if (current.has(id)) continue
23
+ if (currentIds.has(id)) continue
20
24
  alive.delete(id)
21
25
  callbacks.exit(id)
22
26
  }
23
-
24
- for (const [id, components] of current) {
25
- const callbacks = alive.guarantee(id, () => enter(id, components))
26
- callbacks.tick(id, components)
27
- }
28
27
  }
29
28
  }
30
29
 
@@ -1,25 +1,28 @@
1
1
 
2
+ import {Change} from "./change.js"
3
+ import {EntitiesReadonly} from "./entities.js"
4
+
2
5
  export type Id = string
3
6
  export type Components = Record<string, unknown>
4
7
  export type AsComponents<C extends Components> = C
5
8
  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
9
 
10
+ export enum DeltaKind {Set, Merge, Drop}
11
+ export type DeltaSet<C extends Components> = [kind: DeltaKind.Set, id: Id, components?: Partial<C>]
12
+ export type DeltaMerge<C extends Components> = [kind: DeltaKind.Merge, id: Id, patch: Partial<C>]
13
+ export type DeltaDrop<C extends Components> = [kind: DeltaKind.Drop, id: Id, keys: (keyof C)[]]
14
+ export type Delta<C extends Components> = DeltaSet<C> | DeltaMerge<C> | DeltaDrop<C>
15
+
16
+ export type System<C extends Components> = (entities: EntitiesReadonly<C>, change: Change<C>) => void
8
17
  export const asSystem = <C extends Components>(system: System<C>) => system
9
18
  export const asSystems = <C extends Components>(...systems: System<C>[]) => systems
10
19
 
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
20
  export type LifecycleCallbacks<C extends Components, K extends keyof C> = {
18
21
  tick: (id: Id, components: Select<C, K>) => void
19
22
  exit: (id: Id) => void
20
23
  }
21
24
 
22
25
  export type LifecycleEnter<C extends Components, K extends keyof C> = (
23
- (id: Id, components: Select<C, K>) => LifecycleCallbacks<C, K>
26
+ (id: Id, components: Select<C, K>, change: Change<C>) => LifecycleCallbacks<C, K>
24
27
  )
25
28
 
@@ -1,7 +1,8 @@
1
1
 
2
- import {change} from "../parts/change.js"
2
+ import {Change} from "../parts/change.js"
3
3
  import {asSystems} from "../parts/types.js"
4
4
  import {Entities} from "../parts/entities.js"
5
+ import {applyDelta} from "../parts/apply-delta.js"
5
6
 
6
7
  export function setupExample() {
7
8
  type MyComponents = {
@@ -11,39 +12,39 @@ export function setupExample() {
11
12
  manaRegen: number
12
13
  }
13
14
 
14
- const writableEntities = new Entities<MyComponents>()
15
- const entities = writableEntities.readonly()
16
-
17
15
  const systems = asSystems<MyComponents>(
18
- function* manaRegen() {
16
+ function manaRegen(entities, change) {
19
17
  for (const [id, components] of entities.select("mana", "manaRegen")) {
20
18
  if (components.manaRegen !== 0) {
21
19
  const mana = components.mana + components.manaRegen
22
- yield change.merge(id, {mana})
20
+ change.merge(id, {mana})
23
21
  }
24
22
  }
25
23
  },
26
24
 
27
- function* bleeding() {
25
+ function bleeding(entities, change) {
28
26
  for (const [id, components] of entities.select("health", "bleed")) {
29
27
  if (components.bleed >= 0) {
30
28
  const health = components.health - components.bleed
31
29
  const bleed = components.bleed - 1
32
- yield change.merge(id, {health, bleed})
30
+ change.merge(id, {health, bleed})
33
31
  }
34
32
  if (components.bleed <= 0)
35
- yield change.drop(id, "bleed")
33
+ change.drop(id, "bleed")
36
34
  }
37
35
  },
38
36
 
39
- function* death() {
37
+ function death(entities, change) {
40
38
  for (const [id, components] of entities.select("health")) {
41
39
  if (components.health <= 0)
42
- yield change.delete(id)
40
+ change.delete(id)
43
41
  }
44
42
  },
45
43
  )
46
44
 
47
- return {entities: writableEntities, systems}
45
+ const entities = new Entities<MyComponents>()
46
+ const change = new Change<MyComponents>(delta => applyDelta(entities, delta))
47
+
48
+ return {systems, entities, change}
48
49
  }
49
50
 
package/s/ecs/test.ts CHANGED
@@ -1,93 +1,107 @@
1
1
 
2
2
  import {suite, test, expect} from "@e280/science"
3
- import {change} from "./parts/change.js"
4
3
  import {lifecycle} from "./parts/lifecycle.js"
5
- import {applyChange} from "./parts/apply-change.js"
6
4
  import {setupExample} from "./test/setup-example.js"
7
5
  import {executeSystems} from "./parts/execute-systems.js"
8
6
  import {setupLifecycleCounts} from "./test/setup-lifecycle-counts.js"
9
7
 
10
8
  export default suite({
11
9
  "create an entity": test(async() => {
12
- const {entities} = setupExample()
10
+ const {entities, change} = setupExample()
13
11
  expect(entities.size).is(0)
14
- applyChange(entities, change.create({health: 100}))
12
+ change.create({health: 100})
15
13
  expect(entities.size).is(1)
16
14
  }),
17
15
 
18
16
  "delete an entity": test(async() => {
19
- const {entities} = setupExample()
20
- const id = applyChange(entities, change.create({health: 100}))
17
+ const {entities, change} = setupExample()
18
+ const id = change.create({health: 100})
21
19
  expect(entities.size).is(1)
22
- applyChange(entities, change.delete(id))
20
+ change.delete(id)
23
21
  expect(entities.size).is(0)
24
22
  }),
25
23
 
26
24
  "merge components into entity": test(async() => {
27
- const {entities} = setupExample()
28
- const id = applyChange(entities, change.create({health: 100, mana: 100}))
25
+ const {entities, change} = setupExample()
26
+ const id = change.create({health: 100, mana: 100})
29
27
  expect(entities.require(id).health).is(100)
30
- applyChange(entities, change.merge(id, {health: 99}))
28
+ change.merge(id, {health: 99})
31
29
  expect(entities.require(id).health).is(99)
32
30
  expect(entities.require(id).mana).is(100)
33
31
  }),
34
32
 
33
+ "ignore merge after delete": test(async() => {
34
+ const {entities, change} = setupExample()
35
+ const id = change.create({health: 100, mana: 100})
36
+ expect(entities.get(id)).ok()
37
+ change.delete(id)
38
+ expect(entities.get(id)).not.ok()
39
+ change.merge(id, {health: 99})
40
+ expect(entities.get(id)).not.ok()
41
+ }),
42
+
43
+ "ignore drop after delete": test(async() => {
44
+ const {entities, change} = setupExample()
45
+ const id = change.create({health: 100, mana: 100})
46
+ expect(entities.get(id)).ok()
47
+ change.delete(id)
48
+ expect(entities.get(id)).not.ok()
49
+ change.drop(id, "health")
50
+ expect(entities.get(id)).not.ok()
51
+ }),
52
+
35
53
  "select an entity": test(async() => {
36
- const {entities} = setupExample()
37
- applyChange(entities, change.create({health: 100}))
54
+ const {entities, change} = setupExample()
55
+ change.create({health: 100})
38
56
  expect([...entities.select("health")].length).is(1)
39
57
  }),
40
58
 
41
59
  "drop components from entity": test(async() => {
42
- const {entities} = setupExample()
43
- const id = applyChange(entities, change.create({health: 100, mana: 100}))
60
+ const {entities, change} = setupExample()
61
+ const id = change.create({health: 100, mana: 100})
44
62
  expect([...entities.select("health", "mana")].length).is(1)
45
- applyChange(entities, change.drop(id, "mana"))
63
+ change.drop(id, "mana")
46
64
  expect("mana" in entities.require(id)).is(false)
47
65
  expect([...entities.select("health", "mana")].length).is(0)
48
66
  }),
49
67
 
50
68
  "select two entities": test(async() => {
51
- const {entities} = setupExample()
52
- applyChange(entities, change.create({health: 100}))
53
- applyChange(entities, change.create({health: 100}))
69
+ const {entities, change} = setupExample()
70
+ change.create({health: 100})
71
+ change.create({health: 100})
54
72
  expect([...entities.select("health")].length).is(2)
55
73
  }),
56
74
 
57
75
  "select with no component keys selects all": test(async() => {
58
- const {entities} = setupExample()
59
- applyChange(entities, change.create({health: 100}))
60
- applyChange(entities, change.create({health: 100}))
76
+ const {entities, change} = setupExample()
77
+ change.create({health: 100})
78
+ change.create({health: 100})
61
79
  expect([...entities.select()].length).is(2)
62
80
  }),
63
81
 
64
82
  "select doesn't include non-match": test(async() => {
65
- const {entities} = setupExample()
66
- applyChange(entities, change.create({health: 100}))
83
+ const {entities, change} = setupExample()
84
+ change.create({health: 100})
67
85
  expect([...entities.select("mana")].length).is(0)
68
86
  }),
69
87
 
70
88
  "select includes entities with extra components": test(async() => {
71
- const {entities} = setupExample()
72
- applyChange(entities, change.create({health: 100, mana: 100}))
89
+ const {entities, change} = setupExample()
90
+ change.create({health: 100, mana: 100})
73
91
  expect([...entities.select("health")].length).is(1)
74
92
  }),
75
93
 
76
94
  "wizard regens mana": test(async() => {
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)
95
+ const {entities, change, systems} = setupExample()
96
+ const wizardId = change.create({health: 100, mana: 50, manaRegen: 1})
81
97
  const changes = executeSystems(entities, systems)
82
98
  expect(changes.length).is(1)
83
99
  expect(entities.require(wizardId).mana).is(51)
84
100
  }),
85
101
 
86
102
  "death by bleeding": test(async() => {
87
- const {entities, systems} = setupExample()
88
- const creating = change.create({health: 3, bleed: 2})
89
- const wizardId = creating[1]
90
- applyChange(entities, creating)
103
+ const {entities, change, systems} = setupExample()
104
+ const wizardId = change.create({health: 3, bleed: 2})
91
105
  expect(entities.require(wizardId).health).is(3)
92
106
  executeSystems(entities, systems)
93
107
  expect(entities.require(wizardId).health).is(1)
@@ -96,9 +110,9 @@ export default suite({
96
110
  }),
97
111
 
98
112
  "lifecycles": test(async() => {
99
- const {entities} = setupExample()
113
+ const {entities, change} = setupExample()
100
114
  const counts = setupLifecycleCounts()
101
- const system = lifecycle(entities.readonly(), ["health"], () => {
115
+ const system = lifecycle(["health"], () => {
102
116
  counts.enters++
103
117
  return {
104
118
  tick: () => void counts.ticks++,
@@ -107,20 +121,49 @@ export default suite({
107
121
  })
108
122
  counts.expect(0, 0, 0)
109
123
 
110
- const creating = change.create({health: 100, mana: 50})
111
- const wizardId = creating[1]
112
- applyChange(entities, creating)
124
+ const wizardId = change.create({health: 100, mana: 50})
113
125
  executeSystems(entities, [system])
114
126
  counts.expect(1, 1, 0)
115
127
 
116
- applyChange(entities, change.merge(wizardId, {health: 100, mana: 100}))
128
+ change.merge(wizardId, {health: 100, mana: 100})
117
129
  executeSystems(entities, [system])
118
130
  counts.expect(1, 2, 0)
119
131
 
120
- applyChange(entities, change.delete(wizardId))
132
+ change.delete(wizardId)
121
133
  executeSystems(entities, [system])
122
134
  counts.expect(1, 2, 1)
123
135
  expect(entities.size).is(0)
124
136
  }),
137
+
138
+ "lifecycle can commit": test(async() => {
139
+ const {entities, change} = setupExample()
140
+ const system = lifecycle(["health"], (_id, _components, change) => {
141
+ change.create({mana: 50})
142
+ return {
143
+ tick: () => {},
144
+ exit: () => {},
145
+ }
146
+ })
147
+ change.create({health: 100})
148
+ executeSystems(entities, [system])
149
+ expect([...entities.select("mana")].length).is(1)
150
+ }),
151
+
152
+ "lifecycle self-deletion immediate cleanup": test(async() => {
153
+ const {entities, change} = setupExample()
154
+ let ranExit = 0
155
+ const system = lifecycle(["health"], (id, _components, change) => {
156
+ return {
157
+ tick: () => change.delete(id),
158
+ exit: () => { ranExit++ },
159
+ }
160
+ })
161
+ change.create({health: 100})
162
+ expect([...entities.select("health")].length).is(1)
163
+ expect(ranExit).is(0)
164
+ executeSystems(entities, [system])
165
+ expect(ranExit).is(1)
166
+ expect([...entities.select("health")].length).is(0)
167
+ }),
125
168
  })
126
169
 
package/x/ecs/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export * from "./parts/apply-change.js";
1
+ export * from "./parts/apply-delta.js";
2
2
  export * from "./parts/change.js";
3
3
  export * from "./parts/entities.js";
4
4
  export * from "./parts/execute-systems.js";
package/x/ecs/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export * from "./parts/apply-change.js";
1
+ export * from "./parts/apply-delta.js";
2
2
  export * from "./parts/change.js";
3
3
  export * from "./parts/entities.js";
4
4
  export * from "./parts/execute-systems.js";
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../s/ecs/index.ts"],"names":[],"mappings":"AACA,cAAc,wBAAwB,CAAA;AACtC,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 { Delta, Components } from "./types.js";
3
+ export declare function applyDelta<C extends Components>(entities: Entities<C>, delta: Delta<C>): string;
@@ -0,0 +1,34 @@
1
+ import { DeltaKind } from "./types.js";
2
+ export function applyDelta(entities, delta) {
3
+ switch (delta[0]) {
4
+ case DeltaKind.Set: return applySet(entities, delta);
5
+ case DeltaKind.Merge: return applyMerge(entities, delta);
6
+ case DeltaKind.Drop: return applyDrop(entities, delta);
7
+ default: throw new Error(`unknown delta kind "${delta[0]}"`);
8
+ }
9
+ }
10
+ function applySet(entities, [, id, components]) {
11
+ if (components)
12
+ entities.set(id, components);
13
+ else
14
+ entities.delete(id);
15
+ return id;
16
+ }
17
+ function applyMerge(entities, [, id, patch]) {
18
+ const components = entities.get(id);
19
+ if (!components)
20
+ return id;
21
+ Object.assign(components, patch);
22
+ entities.set(id, components);
23
+ return id;
24
+ }
25
+ function applyDrop(entities, [, id, keys]) {
26
+ const components = entities.get(id);
27
+ if (!components)
28
+ return id;
29
+ for (const key of keys)
30
+ delete components[key];
31
+ entities.set(id, components);
32
+ return id;
33
+ }
34
+ //# sourceMappingURL=apply-delta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-delta.js","sourceRoot":"","sources":["../../../s/ecs/parts/apply-delta.ts"],"names":[],"mappings":"AAEA,OAAO,EAA8B,SAAS,EAAwB,MAAM,YAAY,CAAA;AAExF,MAAM,UAAU,UAAU,CAAuB,QAAqB,EAAE,KAAe;IACtF,QAAQ,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAClB,KAAK,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,QAAQ,CAAI,QAAQ,EAAe,KAAK,CAAC,CAAA;QACpE,KAAK,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,UAAU,CAAI,QAAQ,EAAiB,KAAK,CAAC,CAAA;QAC1E,KAAK,SAAS,CAAC,IAAI,CAAC,CAAC,OAAO,SAAS,CAAI,QAAQ,EAAgB,KAAK,CAAC,CAAA;QACvE,OAAO,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,uBAAuB,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAC7D,CAAC;AACF,CAAC;AAED,SAAS,QAAQ,CAAuB,QAAqB,EAAE,CAAC,EAAE,EAAE,EAAE,UAAU,CAAc;IAC7F,IAAI,UAAU;QAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,UAAwB,CAAC,CAAA;;QACrD,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IACxB,OAAO,EAAE,CAAA;AACV,CAAC;AAED,SAAS,UAAU,CAAuB,QAAqB,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,CAAgB;IAC5F,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IACnC,IAAI,CAAC,UAAU;QACd,OAAO,EAAE,CAAA;IACV,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,CAAA;IAChC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,UAAU,CAAC,CAAA;IAC5B,OAAO,EAAE,CAAA;AACV,CAAC;AAED,SAAS,SAAS,CAAuB,QAAqB,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,CAAe;IACzF,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IACnC,IAAI,CAAC,UAAU;QACd,OAAO,EAAE,CAAA;IACV,KAAK,MAAM,GAAG,IAAI,IAAI;QAAE,OAAO,UAAU,CAAC,GAAG,CAAC,CAAA;IAC9C,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,UAAU,CAAC,CAAA;IAC5B,OAAO,EAAE,CAAA;AACV,CAAC"}
@@ -1,13 +1,15 @@
1
- import { Components, ChangeSet, ChangeMerge, Id, ChangeDrop } from "./types.js";
2
- export declare const change: {
1
+ import { Components, Id, Delta } from "./types.js";
2
+ export declare class Change<C extends Components> {
3
+ private commit;
4
+ constructor(commit: (delta: Delta<C>) => void);
3
5
  /** create a new entity with the given components */
4
- create: (components: Partial<Components>) => ChangeSet<Components>;
6
+ create(components: Partial<C>): string;
5
7
  /** overwrite a whole entity to the given components */
6
- set: (id: Id, components: Partial<Components>) => ChangeSet<Components>;
8
+ set(id: Id, components: Partial<C>): string;
7
9
  /** remove the entity */
8
- delete: (id: Id) => ChangeSet<Components>;
10
+ delete(id: Id): string;
9
11
  /** update or add the given components onto the entity */
10
- merge: (id: Id, components: Partial<Components>) => ChangeMerge<Components>;
12
+ merge(id: Id, components: Partial<C>): string;
11
13
  /** delete specific components off the entity */
12
- drop: <C extends Components>(id: Id, ...keys: (keyof C)[]) => ChangeDrop<C>;
13
- };
14
+ drop(id: Id, ...keys: (keyof C)[]): string;
15
+ }