@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.
Files changed (44) hide show
  1. package/README.md +45 -27
  2. package/dist/index.js +318 -80
  3. package/dist/index.js.map +1 -1
  4. package/dist/types/decode.d.ts +7 -4
  5. package/dist/types/encode.d.ts +2 -2
  6. package/dist/types/format/format.d.ts +4 -4
  7. package/dist/types/guide/01-saving.d.ts +40 -0
  8. package/dist/types/guide/02-copying.d.ts +23 -0
  9. package/dist/types/guide/03-chunks.d.ts +23 -0
  10. package/dist/types/guide/04-settings.d.ts +45 -0
  11. package/dist/types/index.d.ts +2 -3
  12. package/dist/types/match/keys.d.ts +36 -0
  13. package/dist/types/match/match.d.ts +13 -0
  14. package/dist/types/program/contents.d.ts +5 -3
  15. package/dist/types/program/program.d.ts +12 -5
  16. package/dist/types/selection/selection.d.ts +10 -3
  17. package/dist/types/snapshot.d.ts +15 -5
  18. package/dist/types/spawn.d.ts +22 -13
  19. package/package.json +10 -3
  20. package/src/decode.ts +6 -5
  21. package/src/encode.ts +2 -2
  22. package/src/format/format.ts +8 -8
  23. package/src/guide/01-saving.md +18 -0
  24. package/src/guide/01-saving.out +4 -0
  25. package/src/guide/01-saving.ts +55 -0
  26. package/src/guide/02-copying.md +15 -0
  27. package/src/guide/02-copying.out +4 -0
  28. package/src/guide/02-copying.ts +70 -0
  29. package/src/guide/03-chunks.md +15 -0
  30. package/src/guide/03-chunks.out +3 -0
  31. package/src/guide/03-chunks.ts +75 -0
  32. package/src/guide/04-settings.md +15 -0
  33. package/src/guide/04-settings.out +4 -0
  34. package/src/guide/04-settings.ts +75 -0
  35. package/src/guide/README.md +10 -0
  36. package/src/index.ts +2 -3
  37. package/src/match/keys.ts +105 -0
  38. package/src/match/match.ts +165 -0
  39. package/src/program/contents.ts +5 -3
  40. package/src/program/program.ts +26 -15
  41. package/src/selection/selection.ts +22 -8
  42. package/src/snapshot.ts +16 -8
  43. package/src/spawn.ts +36 -17
  44. 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. A snapshot holds whole archetypes' columns: a struct's
4
- or a number's bytes as the world stores them, which saving and loading copy as blocks, and the values of strings and
5
- other data. Its entity values are rewritten as they're loaded, so references between the entities it holds still name
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
- ## Marking what persists
8
+ ## Saving by a trait of your own
9
9
 
10
- A component or resource persists when its declaration names it with the `persist` trait, by a stable name: saved data
11
- names it so, whatever its module's path or version.
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 score = Symbol();
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; [persist]: "position" };
23
- [parent]: { type: Entity; reference: "owner"; [persist]: "parent" };
25
+ [position]: { type: Vec3; [file]: "position" };
26
+ [parent]: { type: Entity; reference: "owner"; [file]: "parent" };
24
27
  }
25
28
  interface Resources {
26
- [score]: { type: number; default: 0; [persist]: "score" };
29
+ [tool]: { type: string; default: "select"; [settings]: "tool" };
27
30
  }
28
31
  }
29
32
  ```
30
33
 
31
- Only data persists: a value with a schema. A derived component doesn't: its rule derives it again once its entity is
32
- loaded.
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 every entity holding a persisted component, those matching `with` and `not`, or
49
- `root` and everything it owns through persisted owner references. Except with `root` or `resources: false`, it
50
- takes the persisted resources too.
51
- - `encode` writes it as bytes, and `decode` reads them back, viewing each column's bytes where they are in the buffer.
52
- It refuses bytes naming a component or resource this program doesn't persist, or persists with another schema.
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: `spawn({ world: to, snapshot: await snapshot({ world: from }) })`, without
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 310 ms against 1,450 ms, most of it the world's bookkeeping of owner references at its sync
72
- point; without references, 1M entities spawn in about 26 ms.
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.