@adobe/ecs-persistence 0.0.1 → 0.1.0
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 +45 -27
- package/dist/index.js +318 -80
- package/dist/index.js.map +1 -1
- package/dist/types/decode.d.ts +7 -4
- package/dist/types/encode.d.ts +2 -2
- package/dist/types/format/format.d.ts +4 -4
- package/dist/types/guide/01-saving.d.ts +40 -0
- package/dist/types/guide/02-copying.d.ts +23 -0
- package/dist/types/guide/03-chunks.d.ts +23 -0
- package/dist/types/guide/04-settings.d.ts +45 -0
- package/dist/types/index.d.ts +2 -3
- package/dist/types/match/keys.d.ts +36 -0
- package/dist/types/match/match.d.ts +13 -0
- package/dist/types/program/contents.d.ts +5 -3
- package/dist/types/program/program.d.ts +12 -5
- package/dist/types/selection/selection.d.ts +10 -3
- package/dist/types/snapshot.d.ts +15 -5
- package/dist/types/spawn.d.ts +22 -13
- package/package.json +10 -3
- package/src/decode.ts +6 -5
- package/src/encode.ts +2 -2
- package/src/format/format.ts +8 -8
- package/src/guide/01-saving.md +18 -0
- package/src/guide/01-saving.out +4 -0
- package/src/guide/01-saving.ts +55 -0
- package/src/guide/02-copying.md +15 -0
- package/src/guide/02-copying.out +4 -0
- package/src/guide/02-copying.ts +70 -0
- package/src/guide/03-chunks.md +15 -0
- package/src/guide/03-chunks.out +3 -0
- package/src/guide/03-chunks.ts +75 -0
- package/src/guide/04-settings.md +15 -0
- package/src/guide/04-settings.out +4 -0
- package/src/guide/04-settings.ts +75 -0
- package/src/guide/README.md +10 -0
- package/src/index.ts +2 -3
- package/src/match/keys.ts +105 -0
- package/src/match/match.ts +165 -0
- package/src/program/contents.ts +5 -3
- package/src/program/program.ts +26 -15
- package/src/selection/selection.ts +22 -8
- package/src/snapshot.ts +16 -8
- package/src/spawn.ts +36 -17
- package/src/persist.ts +0 -15
package/README.md
CHANGED
|
@@ -1,72 +1,90 @@
|
|
|
1
1
|
# @adobe/ecs-persistence
|
|
2
2
|
|
|
3
|
-
Saving, loading and copying the entities of `@adobe/ecs` worlds
|
|
4
|
-
or a number's bytes as the world stores them, which
|
|
5
|
-
|
|
6
|
-
each other.
|
|
3
|
+
Saving, loading and copying the entities of `@adobe/ecs` worlds, by traits the application declares for the places it
|
|
4
|
+
saves to. A snapshot holds whole archetypes' columns: a struct's or a number's bytes as the world stores them, which
|
|
5
|
+
saving and loading copy as blocks, and the values of strings and other data. Its entity values are rewritten as
|
|
6
|
+
they're loaded, so references between the entities it holds still name each other.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Saving by a trait of your own
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
Each place an application saves to is a trait of its own: a `Symbol()` declared in `Traits`, whose value on a
|
|
11
|
+
component's or resource's declaration is the stable name the key is saved under there, whatever its module's path or
|
|
12
|
+
version. A document and the user's settings, say:
|
|
12
13
|
|
|
13
14
|
```ts
|
|
14
15
|
import type { Entity, Vec3 } from "@adobe/ecs";
|
|
15
|
-
import { persist } from "@adobe/ecs-persistence";
|
|
16
16
|
|
|
17
|
+
export const file = Symbol(); // saved in a document's file, under this name
|
|
18
|
+
export const settings = Symbol(); // saved in the user's own settings, under this name
|
|
17
19
|
export const position = Symbol();
|
|
18
20
|
export const parent = Symbol();
|
|
19
|
-
export const
|
|
21
|
+
export const tool = Symbol();
|
|
20
22
|
declare module "@adobe/ecs" {
|
|
23
|
+
interface Traits<N> { readonly [file]: string; readonly [settings]: string }
|
|
21
24
|
interface Components {
|
|
22
|
-
[position]: { type: Vec3; [
|
|
23
|
-
[parent]: { type: Entity; reference: "owner"; [
|
|
25
|
+
[position]: { type: Vec3; [file]: "position" };
|
|
26
|
+
[parent]: { type: Entity; reference: "owner"; [file]: "parent" };
|
|
24
27
|
}
|
|
25
28
|
interface Resources {
|
|
26
|
-
[
|
|
29
|
+
[tool]: { type: string; default: "select"; [settings]: "tool" };
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
32
|
```
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
A trait covers each key whose declaration gives it a string. A name only has to be unique among the keys one trait
|
|
35
|
+
covers, and a key may carry several traits, under the same or different names, and is saved in each place. Only data
|
|
36
|
+
is saved: a value with a schema. A derived component isn't, as its rule derives it again once its entity is loaded; a
|
|
37
|
+
trait covering one fails as data.
|
|
33
38
|
|
|
34
39
|
## Saving and loading
|
|
35
40
|
|
|
36
41
|
```ts
|
|
37
42
|
import { decode, encode, snapshot, spawn } from "@adobe/ecs-persistence";
|
|
38
43
|
|
|
39
|
-
const taken = await snapshot({ world });
|
|
44
|
+
const taken = await snapshot({ world, by: file });
|
|
40
45
|
if (taken.failure !== undefined) throw new Error(taken.failure);
|
|
41
46
|
const bytes = encode(taken);
|
|
42
47
|
|
|
43
|
-
const loaded = decode(bytes);
|
|
48
|
+
const loaded = decode(bytes, { by: file });
|
|
44
49
|
if (loaded.failure !== undefined) throw new Error(loaded.failure);
|
|
45
50
|
const spawned = await spawn({ world: other, snapshot: loaded });
|
|
46
51
|
```
|
|
47
52
|
|
|
48
|
-
- `snapshot({ world, ... })` takes
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
- `snapshot({ world, by, ... })` takes, of each entity it takes, the components the trait `by` covers: every entity
|
|
54
|
+
holding one of them, those matching `with`, `not` and `where`, or `root` and everything it owns through owner
|
|
55
|
+
references the trait covers. Except with `root` or `resources: false`, it takes the resources the trait covers too.
|
|
56
|
+
A snapshot holds the trait it was taken or read by.
|
|
57
|
+
- **Chunks:** `where` takes values of shared components, as a system's `where` of values does:
|
|
58
|
+
`snapshot({ world, by: file, where: { [chunk]: [4, 0] } })` reads the archetypes holding them whole and no others, so
|
|
59
|
+
one chunk's snapshot costs what it holds, not the world. `spawn` puts each entity back into the archetype of its
|
|
60
|
+
shared values.
|
|
61
|
+
- `encode` writes it as bytes, and `decode(bytes, { by })` reads them back by the trait `by`, viewing each column's
|
|
62
|
+
bytes where they are in the buffer. It refuses bytes naming a component or resource the trait doesn't cover, or
|
|
63
|
+
covers with another schema, so a document's bytes can't be loaded as settings, nor the reverse.
|
|
53
64
|
- `spawn({ world, snapshot, entities? })` spawns an entity for each of the snapshot's and sets its resources, its entity values
|
|
54
65
|
naming the entities spawned for theirs. An entity value naming an entity outside the snapshot follows what
|
|
55
66
|
despawning that entity would do: an entity it owns isn't spawned, nor what that owns, and the result counts them in
|
|
56
67
|
`dropped`; a weak reference to it is left out; any other value naming it is `Entity.none`. The result's `entities`
|
|
57
68
|
maps each of the snapshot's entities to the one spawned for it, or null; `entities: false` leaves the map out, which
|
|
58
69
|
saves about 85 ms and 50 MB at a million entities.
|
|
59
|
-
- Copying entities from one world into another is a snapshot spawned there:
|
|
60
|
-
bytes.
|
|
70
|
+
- Copying entities from one world into another is a snapshot spawned there:
|
|
71
|
+
`spawn({ world: to, snapshot: await snapshot({ world: from, by: file }) })`, without bytes.
|
|
72
|
+
- **Merging:** `match` takes unique indexes saying which of the snapshot's entities are already in the world:
|
|
73
|
+
`spawn({ world, snapshot, match: [materialByName] })`. An entity an index holds in the world, with the key of one
|
|
74
|
+
of the snapshot's it holds, stands for that one, keeping its own values, and every entity value naming the
|
|
75
|
+
snapshot's names it; `entities` maps the one to the other. Keys compare as stored, a number's or a vector's bytes and
|
|
76
|
+
a string's or an object's value. It fails for an index whose key, `with` or `not` names a component the snapshot's
|
|
77
|
+
trait doesn't cover, or two of the snapshot's entities one index holds with one key.
|
|
61
78
|
|
|
62
79
|
Each reports why it didn't happen as `{ failure }`, rather than throwing, and `result.failure !== undefined` tells
|
|
63
|
-
which. A snapshot is opaque: only these functions
|
|
64
|
-
read it.
|
|
80
|
+
which. A snapshot is opaque: only these functions read it.
|
|
65
81
|
|
|
66
82
|
## Speed
|
|
67
83
|
|
|
68
84
|
`pnpm test:perf` compares, at 100K and 1M entities with a `Vec3`, a number, an owner reference and a struct holding an
|
|
69
85
|
entity, this package with reading rows and spawning entity by entity. At 1M entities, a snapshot takes about 6 ms
|
|
70
86
|
against 110 ms, encoding 5 ms against 150 ms for `JSON.stringify` of the rows, and decoding views the bytes where they
|
|
71
|
-
are. Spawning takes about
|
|
72
|
-
|
|
87
|
+
are. Spawning takes about 165 ms against 1,450 ms. Matching reads the world's entities each index holds, so its cost
|
|
88
|
+
grows with those: a merge of 1M entities, each matched by a unique index on its `Vec3`, takes about 440 ms. One
|
|
89
|
+
chunk of 10K among 1M entities, told apart by a shared component, snapshots in about 0.13 ms by `where`, against 6 ms
|
|
90
|
+
for the whole world and 55 ms for its root's subtree, and spawns in about 2 ms.
|