@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.
- package/README.md +12 -18
- package/package.json +1 -1
- package/s/ecs/index.ts +1 -1
- package/s/ecs/parts/apply-delta.ts +37 -0
- package/s/ecs/parts/change.ts +24 -12
- package/s/ecs/parts/execute-systems.ts +13 -10
- package/s/ecs/parts/lifecycle.ts +12 -13
- package/s/ecs/parts/types.ts +11 -8
- package/s/ecs/test/setup-example.ts +13 -12
- package/s/ecs/test.ts +83 -40
- package/x/ecs/index.d.ts +1 -1
- package/x/ecs/index.js +1 -1
- package/x/ecs/index.js.map +1 -1
- package/x/ecs/parts/apply-delta.d.ts +3 -0
- package/x/ecs/parts/apply-delta.js +34 -0
- package/x/ecs/parts/apply-delta.js.map +1 -0
- package/x/ecs/parts/change.d.ts +10 -8
- package/x/ecs/parts/change.js +28 -8
- package/x/ecs/parts/change.js.map +1 -1
- package/x/ecs/parts/execute-systems.d.ts +2 -2
- package/x/ecs/parts/execute-systems.js +11 -9
- package/x/ecs/parts/execute-systems.js.map +1 -1
- package/x/ecs/parts/lifecycle.d.ts +2 -3
- package/x/ecs/parts/lifecycle.js +10 -9
- package/x/ecs/parts/lifecycle.js.map +1 -1
- package/x/ecs/parts/types.d.ts +11 -9
- package/x/ecs/parts/types.js +6 -6
- package/x/ecs/parts/types.js.map +1 -1
- package/x/ecs/test/setup-example.d.ts +9 -2
- package/x/ecs/test/setup-example.js +12 -11
- package/x/ecs/test/setup-example.js.map +1 -1
- package/x/ecs/test.d.ts +4 -0
- package/x/ecs/test.js +79 -40
- package/x/ecs/test.js.map +1 -1
- package/s/ecs/parts/apply-change.ts +0 -33
- package/x/ecs/parts/apply-change.d.ts +0 -3
- package/x/ecs/parts/apply-change.js +0 -30
- 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,
|
|
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.***
|
|
34
|
+
1. ***create entities map.*** indexed for speedy-fast lookups.
|
|
35
35
|
```ts
|
|
36
|
-
export const
|
|
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.
|
|
38
|
+
1. ***define systems.*** select entities by components. formal changes.
|
|
42
39
|
```ts
|
|
43
40
|
const systems = asSystems<MyComponents>(
|
|
44
|
-
function
|
|
41
|
+
function bleeding(entities, change) {
|
|
45
42
|
for (const [id, components] of entities.select("health", "bleed")) {
|
|
46
|
-
if (components.bleed
|
|
43
|
+
if (components.bleed > 0) {
|
|
47
44
|
const health = components.health - components.bleed
|
|
48
|
-
|
|
49
|
-
yield change.merge(id, {health, bleed})
|
|
45
|
+
change.merge(id, {health})
|
|
50
46
|
}
|
|
51
47
|
}
|
|
52
48
|
},
|
|
53
49
|
|
|
54
|
-
function
|
|
50
|
+
function death(entities, change) {
|
|
55
51
|
for (const [id, components] of entities.select("health")) {
|
|
56
52
|
if (components.health <= 0)
|
|
57
|
-
|
|
53
|
+
change.delete(id)
|
|
58
54
|
}
|
|
59
55
|
},
|
|
60
56
|
)
|
|
61
57
|
```
|
|
62
|
-
1. ***
|
|
58
|
+
1. ***manually insert your first entity.***
|
|
63
59
|
```ts
|
|
64
60
|
const wizardId = makeId()
|
|
65
|
-
|
|
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(
|
|
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
package/s/ecs/index.ts
CHANGED
|
@@ -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
|
+
|
package/s/ecs/parts/change.ts
CHANGED
|
@@ -1,27 +1,39 @@
|
|
|
1
1
|
|
|
2
2
|
import {makeId} from "./make-id.js"
|
|
3
|
-
import {Components,
|
|
3
|
+
import {Components, Id, DeltaKind, Delta} from "./types.js"
|
|
4
4
|
|
|
5
|
-
export
|
|
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
|
|
9
|
-
|
|
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
|
|
13
|
-
[
|
|
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
|
|
17
|
-
[
|
|
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
|
|
21
|
-
[
|
|
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
|
|
25
|
-
[
|
|
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 {
|
|
4
|
-
import {
|
|
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
|
|
8
|
+
const entitiesReadonly = entities.readonly()
|
|
9
|
+
const deltas: Delta<C>[] = []
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
}
|
|
11
|
+
const change = new Change<C>(delta => {
|
|
12
|
+
applyDelta(entities, delta)
|
|
13
|
+
deltas.push(delta)
|
|
14
|
+
})
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
for (const system of systems)
|
|
17
|
+
system(entitiesReadonly, change)
|
|
18
|
+
|
|
19
|
+
return deltas
|
|
17
20
|
}
|
|
18
21
|
|
package/s/ecs/parts/lifecycle.ts
CHANGED
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
|
|
2
2
|
import {GMap} from "@e280/stz"
|
|
3
|
-
import {
|
|
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
|
|
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 (
|
|
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
|
|
package/s/ecs/parts/types.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
-
|
|
20
|
+
change.merge(id, {mana})
|
|
23
21
|
}
|
|
24
22
|
}
|
|
25
23
|
},
|
|
26
24
|
|
|
27
|
-
function
|
|
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
|
-
|
|
30
|
+
change.merge(id, {health, bleed})
|
|
33
31
|
}
|
|
34
32
|
if (components.bleed <= 0)
|
|
35
|
-
|
|
33
|
+
change.drop(id, "bleed")
|
|
36
34
|
}
|
|
37
35
|
},
|
|
38
36
|
|
|
39
|
-
function
|
|
37
|
+
function death(entities, change) {
|
|
40
38
|
for (const [id, components] of entities.select("health")) {
|
|
41
39
|
if (components.health <= 0)
|
|
42
|
-
|
|
40
|
+
change.delete(id)
|
|
43
41
|
}
|
|
44
42
|
},
|
|
45
43
|
)
|
|
46
44
|
|
|
47
|
-
|
|
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
|
-
|
|
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 =
|
|
17
|
+
const {entities, change} = setupExample()
|
|
18
|
+
const id = change.create({health: 100})
|
|
21
19
|
expect(entities.size).is(1)
|
|
22
|
-
|
|
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 =
|
|
25
|
+
const {entities, change} = setupExample()
|
|
26
|
+
const id = change.create({health: 100, mana: 100})
|
|
29
27
|
expect(entities.require(id).health).is(100)
|
|
30
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
128
|
+
change.merge(wizardId, {health: 100, mana: 100})
|
|
117
129
|
executeSystems(entities, [system])
|
|
118
130
|
counts.expect(1, 2, 0)
|
|
119
131
|
|
|
120
|
-
|
|
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
package/x/ecs/index.js
CHANGED
package/x/ecs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../s/ecs/index.ts"],"names":[],"mappings":"AACA,cAAc,
|
|
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,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"}
|
package/x/ecs/parts/change.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { Components,
|
|
2
|
-
export declare
|
|
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
|
|
6
|
+
create(components: Partial<C>): string;
|
|
5
7
|
/** overwrite a whole entity to the given components */
|
|
6
|
-
set
|
|
8
|
+
set(id: Id, components: Partial<C>): string;
|
|
7
9
|
/** remove the entity */
|
|
8
|
-
delete
|
|
10
|
+
delete(id: Id): string;
|
|
9
11
|
/** update or add the given components onto the entity */
|
|
10
|
-
merge
|
|
12
|
+
merge(id: Id, components: Partial<C>): string;
|
|
11
13
|
/** delete specific components off the entity */
|
|
12
|
-
drop
|
|
13
|
-
}
|
|
14
|
+
drop(id: Id, ...keys: (keyof C)[]): string;
|
|
15
|
+
}
|