@oasys/oecs 0.5.3 → 0.5.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.
- package/CHANGELOG.md +194 -28
- package/README.md +198 -145
- package/dist/core/ecs/access_check.d.cts +3 -3
- package/dist/core/ecs/access_check.d.ts +3 -3
- package/dist/core/ecs/archetype.d.cts +103 -43
- package/dist/core/ecs/archetype.d.ts +103 -43
- package/dist/core/ecs/archetype.d.ts.map +1 -1
- package/dist/core/ecs/archetype_graph.d.cts +8 -8
- package/dist/core/ecs/archetype_graph.d.ts +8 -8
- package/dist/core/ecs/archetype_graph.d.ts.map +1 -1
- package/dist/core/ecs/builtin_relations.d.cts +1 -1
- package/dist/core/ecs/builtin_relations.d.ts +1 -1
- package/dist/core/ecs/command_log.d.cts +4 -4
- package/dist/core/ecs/command_log.d.ts +4 -4
- package/dist/core/ecs/command_log.d.ts.map +1 -1
- package/dist/core/ecs/component.d.cts +4 -4
- package/dist/core/ecs/component.d.ts +4 -4
- package/dist/core/ecs/deferred_commands.d.cts +1 -1
- package/dist/core/ecs/deferred_commands.d.ts +1 -1
- package/dist/core/ecs/deferred_commands.d.ts.map +1 -1
- package/dist/core/ecs/ecs.d.cts +85 -43
- package/dist/core/ecs/ecs.d.ts +85 -43
- package/dist/core/ecs/ecs.d.ts.map +1 -1
- package/dist/core/ecs/ecs_memory.d.cts +10 -10
- package/dist/core/ecs/ecs_memory.d.ts +10 -10
- package/dist/core/ecs/ecs_memory.d.ts.map +1 -1
- package/dist/core/ecs/entity_allocator.d.cts +6 -6
- package/dist/core/ecs/entity_allocator.d.ts +6 -6
- package/dist/core/ecs/entity_allocator.d.ts.map +1 -1
- package/dist/core/ecs/event.d.cts +2 -2
- package/dist/core/ecs/event.d.ts +2 -2
- package/dist/core/ecs/event_registry.d.cts +1 -1
- package/dist/core/ecs/event_registry.d.ts +1 -1
- package/dist/core/ecs/event_registry.d.ts.map +1 -1
- package/dist/core/ecs/facades.d.cts +15 -15
- package/dist/core/ecs/facades.d.ts +15 -15
- package/dist/core/ecs/facades.d.ts.map +1 -1
- package/dist/core/ecs/frame_trace.d.cts +2 -2
- package/dist/core/ecs/frame_trace.d.ts +2 -2
- package/dist/core/ecs/host_commands.d.cts +12 -12
- package/dist/core/ecs/host_commands.d.ts +12 -12
- package/dist/core/ecs/host_commands.d.ts.map +1 -1
- package/dist/core/ecs/index.d.cts +1 -1
- package/dist/core/ecs/index.d.ts +1 -1
- package/dist/core/ecs/index.d.ts.map +1 -1
- package/dist/core/ecs/observer.d.cts +9 -9
- package/dist/core/ecs/observer.d.ts +9 -9
- package/dist/core/ecs/observer.d.ts.map +1 -1
- package/dist/core/ecs/query.d.cts +91 -70
- package/dist/core/ecs/query.d.ts +91 -70
- package/dist/core/ecs/query.d.ts.map +1 -1
- package/dist/core/ecs/ref.d.cts +53 -1
- package/dist/core/ecs/ref.d.ts +53 -1
- package/dist/core/ecs/ref.d.ts.map +1 -1
- package/dist/core/ecs/relation.d.cts +15 -15
- package/dist/core/ecs/relation.d.ts +15 -15
- package/dist/core/ecs/relation.d.ts.map +1 -1
- package/dist/core/ecs/relation_service.d.cts +14 -14
- package/dist/core/ecs/relation_service.d.ts +14 -14
- package/dist/core/ecs/relation_service.d.ts.map +1 -1
- package/dist/core/ecs/resource.d.cts +1 -1
- package/dist/core/ecs/resource.d.ts +1 -1
- package/dist/core/ecs/resource_registry.d.cts +2 -2
- package/dist/core/ecs/resource_registry.d.ts +2 -2
- package/dist/core/ecs/resume.d.cts +3 -3
- package/dist/core/ecs/resume.d.ts +3 -3
- package/dist/core/ecs/resume.d.ts.map +1 -1
- package/dist/core/ecs/schedule.d.cts +22 -9
- package/dist/core/ecs/schedule.d.ts +22 -9
- package/dist/core/ecs/schedule.d.ts.map +1 -1
- package/dist/core/ecs/snapshot_service.d.cts +3 -3
- package/dist/core/ecs/snapshot_service.d.ts +3 -3
- package/dist/core/ecs/sparse_store.d.cts +6 -6
- package/dist/core/ecs/sparse_store.d.ts +6 -6
- package/dist/core/ecs/sparse_store.d.ts.map +1 -1
- package/dist/core/ecs/store.d.cts +189 -110
- package/dist/core/ecs/store.d.ts +189 -110
- package/dist/core/ecs/store.d.ts.map +1 -1
- package/dist/core/ecs/store_layout_listener.d.cts +2 -2
- package/dist/core/ecs/store_layout_listener.d.ts +2 -2
- package/dist/core/ecs/system.d.cts +8 -8
- package/dist/core/ecs/system.d.ts +8 -8
- package/dist/core/ecs/system.d.ts.map +1 -1
- package/dist/core/ecs/utils/error.d.cts +7 -0
- package/dist/core/ecs/utils/error.d.ts +7 -0
- package/dist/core/ecs/utils/error.d.ts.map +1 -1
- package/dist/core/store/action_ring.d.cts +7 -8
- package/dist/core/store/action_ring.d.ts +7 -8
- package/dist/core/store/action_ring.d.ts.map +1 -1
- package/dist/core/store/allocator.d.cts +26 -28
- package/dist/core/store/allocator.d.ts +26 -28
- package/dist/core/store/allocator.d.ts.map +1 -1
- package/dist/core/store/buffer_backed_column.d.cts +5 -6
- package/dist/core/store/buffer_backed_column.d.ts +5 -6
- package/dist/core/store/buffer_backed_column.d.ts.map +1 -1
- package/dist/core/store/column_store.d.cts +22 -22
- package/dist/core/store/column_store.d.ts +22 -22
- package/dist/core/store/column_store.d.ts.map +1 -1
- package/dist/core/store/command_dispatch.d.cts +1 -1
- package/dist/core/store/command_dispatch.d.ts +1 -1
- package/dist/core/store/command_ring.d.cts +6 -7
- package/dist/core/store/command_ring.d.ts +6 -7
- package/dist/core/store/command_ring.d.ts.map +1 -1
- package/dist/core/store/descriptor.d.cts +2 -2
- package/dist/core/store/descriptor.d.ts +2 -2
- package/dist/core/store/entity_index.d.cts +3 -3
- package/dist/core/store/entity_index.d.ts +3 -3
- package/dist/core/store/event_ring.d.cts +7 -7
- package/dist/core/store/event_ring.d.ts +7 -7
- package/dist/core/store/extend.d.cts +2 -2
- package/dist/core/store/extend.d.ts +2 -2
- package/dist/core/store/grow.d.cts +1 -1
- package/dist/core/store/grow.d.ts +1 -1
- package/dist/core/store/grow.d.ts.map +1 -1
- package/dist/core/store/header.d.cts +9 -9
- package/dist/core/store/header.d.ts +9 -9
- package/dist/core/store/header.d.ts.map +1 -1
- package/dist/core/store/layout_ops.d.cts +7 -7
- package/dist/core/store/layout_ops.d.ts +7 -7
- package/dist/core/store/state_hash.d.cts +3 -3
- package/dist/core/store/state_hash.d.ts +3 -3
- package/dist/extensions/editor/editor.d.cts +4 -4
- package/dist/extensions/editor/editor.d.ts +4 -4
- package/dist/extensions/editor/editor.d.ts.map +1 -1
- package/dist/extensions/editor/field_handle.d.cts +1 -1
- package/dist/extensions/editor/field_handle.d.ts +1 -1
- package/dist/extensions/editor/index.d.cts +2 -2
- package/dist/extensions/editor/index.d.ts +2 -2
- package/dist/extensions/editor/index.development.js +4 -4
- package/dist/extensions/editor/index.js +4 -4
- package/dist/extensions/reactive/ecs_sync.d.cts +4 -4
- package/dist/extensions/reactive/ecs_sync.d.ts +4 -4
- package/dist/extensions/reactive/index.d.cts +4 -4
- package/dist/extensions/reactive/index.d.ts +4 -4
- package/dist/extensions/reactive/index.development.js +3 -3
- package/dist/extensions/reactive/index.js +3 -3
- package/dist/extensions/solid/index.d.cts +1 -1
- package/dist/extensions/solid/index.d.ts +1 -1
- package/dist/extensions/solid/index.development.js +1 -1
- package/dist/extensions/solid/index.js +1 -1
- package/dist/extensions/solid/kernel_solid.d.cts +1 -1
- package/dist/extensions/solid/kernel_solid.d.ts +1 -1
- package/dist/extensions/solid/kernel_solid.d.ts.map +1 -1
- package/dist/{host_commands-3jyFfWNg.js → host_commands-BlR3oZ3x.js} +472 -392
- package/dist/host_commands-C2cpU3yC.cjs +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.development.cjs +1 -1
- package/dist/index.development.js +3344 -2965
- package/dist/index.js +1494 -1132
- package/dist/{internal-C9jjL90H.js → internal-BoxWl4dG.js} +409 -329
- package/dist/internal-CGBEwfeB.cjs +2 -0
- package/dist/internal.cjs +1 -1
- package/dist/internal.development.cjs +1 -1
- package/dist/internal.development.js +1 -1
- package/dist/internal.js +1 -1
- package/dist/primitives.development.js +1 -1
- package/dist/primitives.js +1 -1
- package/dist/reactive/index.d.cts +2 -2
- package/dist/reactive/index.d.ts +2 -2
- package/dist/reactive/index.development.js +1 -1
- package/dist/reactive/index.js +1 -1
- package/dist/reactive/kernel.d.cts +5 -5
- package/dist/reactive/kernel.d.ts +5 -5
- package/dist/reactive/shallow.d.cts +1 -1
- package/dist/reactive/shallow.d.ts +1 -1
- package/dist/{shallow-DfnAm9vG.js → shallow-Bs39EPh1.js} +1 -1
- package/dist/{shallow-9S1pY_Iw.js → shallow-CiFC70Dz.js} +1 -1
- package/dist/{shared-DQKK0i-E.js → shared-B4v0L1cw.js} +4 -4
- package/dist/shared-DU78k0pU.cjs +1 -0
- package/dist/shared.cjs +1 -1
- package/dist/shared.development.cjs +1 -1
- package/dist/shared.development.js +1 -1
- package/dist/shared.js +1 -1
- package/dist/type_primitives/typed_arrays/typed_arrays.d.cts +2 -2
- package/dist/type_primitives/typed_arrays/typed_arrays.d.ts +2 -2
- package/dist/{typed_arrays-BcXcnzB4.js → typed_arrays-DIVwVy5_.js} +1 -1
- package/dist/version.d.cts +2 -2
- package/dist/version.d.ts +2 -2
- package/package.json +1 -1
- package/dist/host_commands-DNf0f6ko.cjs +0 -1
- package/dist/internal-RZN14uMw.cjs +0 -2
- package/dist/shared-BMXh9hxm.cjs +0 -1
package/README.md
CHANGED
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
# oecs
|
|
2
2
|
|
|
3
|
-
**A
|
|
4
|
-
|
|
5
|
-
`@oasys/oecs`
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
3
|
+
**A complete, archetype-based Entity Component System for TypeScript.**
|
|
4
|
+
|
|
5
|
+
`@oasys/oecs` gives you more than storage and queries. It gives you the tools that a mature engine
|
|
6
|
+
has:
|
|
7
|
+
|
|
8
|
+
- observers;
|
|
9
|
+
- relations with wildcards;
|
|
10
|
+
- sparse storage;
|
|
11
|
+
- system sets and run conditions;
|
|
12
|
+
- enable and disable for an entity;
|
|
13
|
+
- templates;
|
|
14
|
+
- deterministic hashing, with snapshot and restore;
|
|
15
|
+
- a typed write path from the host into the ECS;
|
|
16
|
+
- an optional reactive UI bridge.
|
|
17
|
+
|
|
18
|
+
The package is **pure TypeScript, and it has no dependencies by default**. It runs over one plain
|
|
19
|
+
`ArrayBuffer`. So it does not need a `SharedArrayBuffer`, and it does not need cross-origin
|
|
20
|
+
isolation (COOP/COEP). An optional shared-memory profile uses a `SharedArrayBuffer` instead. Use
|
|
21
|
+
that profile for worker offload, or for a WASM compute backend. The two profiles use one core, and
|
|
22
|
+
they agree byte-for-byte on `stateHash`.
|
|
23
|
+
|
|
24
|
+
- **Data-oriented** — Columns use struct-of-arrays storage, grouped by archetype. Iteration is a
|
|
25
|
+
small loop over typed arrays. The loop allocates no object for each entity.
|
|
26
|
+
- **Type-safe** — A component handle is a callable definition. It has a stable numeric id at run
|
|
27
|
+
time and a full schema type at compile time. A field name with a spelling error is a compile
|
|
28
|
+
error.
|
|
29
|
+
- **Deterministic** — An optional mode gives you a `stateHash` that is independent of the storage
|
|
30
|
+
type. It also gives you snapshot, restore, and replay of a command log.
|
|
31
|
+
- **Complete** — The features below are the full engine. They are not only a start.
|
|
20
32
|
|
|
21
33
|
## Installation
|
|
22
34
|
|
|
@@ -28,8 +40,10 @@ deno add jsr:@oasys/oecs # JSR (Deno)
|
|
|
28
40
|
npx jsr add @oasys/oecs # JSR (npm-compatible)
|
|
29
41
|
```
|
|
30
42
|
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
Supported runtimes: Node 20 or later, Deno 1.38 or later, Chrome 111 or later, Firefox 128 or
|
|
44
|
+
later, and Safari 16.4 or later. The default heap profile uses a plain, fixed `ArrayBuffer`. The
|
|
45
|
+
optional shared and WASM profiles need a growable `SharedArrayBuffer` or `WebAssembly.Memory`, and
|
|
46
|
+
those requirements set the version limits.
|
|
33
47
|
|
|
34
48
|
## Quick start
|
|
35
49
|
|
|
@@ -42,17 +56,17 @@ const ecs = new ECS(); // pure-TS heap profile — no SharedArrayBuffer needed
|
|
|
42
56
|
const Pos = ecs.registerComponent({ x: "f64", y: "f64" });
|
|
43
57
|
const Vel = ecs.registerComponent(["vx", "vy"] as const);
|
|
44
58
|
|
|
45
|
-
// A query is a live, cached view
|
|
59
|
+
// A query is a live, cached view of the matching archetypes. Build it once, then use it again.
|
|
46
60
|
const movers = ecs.query(Pos, Vel);
|
|
47
61
|
|
|
48
|
-
// Systems declare the components they read
|
|
62
|
+
// Systems declare the components that they read and write (checked in development builds).
|
|
49
63
|
const move = ecs.registerSystem({
|
|
50
64
|
reads: [Vel],
|
|
51
|
-
writes: [Pos], // a declared write
|
|
65
|
+
writes: [Pos], // a declared write also gives read access to the same component
|
|
52
66
|
fn: (ctx, dt) => {
|
|
53
67
|
movers.eachChunk((cols, count) => {
|
|
54
|
-
const { x, y } = cols.mut(Pos); //
|
|
55
|
-
const { vx, vy } = cols.read(Vel); // read-only group
|
|
68
|
+
const { x, y } = cols.mut(Pos); // the full group; sets the change tick of Pos one time
|
|
69
|
+
const { vx, vy } = cols.read(Vel); // a read-only group
|
|
56
70
|
for (let i = 0; i < count; i++) {
|
|
57
71
|
x[i] += vx[i] * dt;
|
|
58
72
|
y[i] += vy[i] * dt;
|
|
@@ -69,141 +83,180 @@ ecs.addComponent(e, Pos, { x: 0, y: 0 });
|
|
|
69
83
|
ecs.addComponent(e, Vel, { vx: 100, vy: 50 });
|
|
70
84
|
|
|
71
85
|
ecs.update(1 / 60);
|
|
72
|
-
ecs.getField(e, Pos, "x"); //
|
|
86
|
+
ecs.getField(e, Pos, "x"); // about 1.667
|
|
73
87
|
```
|
|
74
88
|
|
|
75
89
|
## Features
|
|
76
90
|
|
|
77
|
-
**Storage
|
|
78
|
-
|
|
79
|
-
- **Archetype
|
|
80
|
-
share
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
**Storage and the data model**
|
|
92
|
+
|
|
93
|
+
- **Archetype storage in struct-of-arrays form**, above a storage-neutral `ColumnStore`. Entities
|
|
94
|
+
with the same set of components share adjacent typed-array columns. Loops use the cache well, and
|
|
95
|
+
they allocate no object for each entity.
|
|
96
|
+
- **Components with phantom types** — `registerComponent({ x: "f64", y: "f64" })` gives you a
|
|
97
|
+
callable `ComponentDef`. The definition has a stable numeric `.id` at run time and a full schema
|
|
98
|
+
type at compile time. Use the record syntax for different types in each field. Use the array
|
|
99
|
+
shorthand when all fields are `f64`. Use `registerTag()` for markers that hold no data. The field
|
|
100
|
+
types are `f32 f64 i8 i16 i32 u8 u16 u32`.
|
|
101
|
+
- **Two storage profiles, one core** — The default is a pure-TS heap (`ArrayBuffer`). A
|
|
102
|
+
`SharedArrayBuffer` for workers or WASM is optional. The code path is the same, the `stateHash`
|
|
103
|
+
is the same, and one `memory` option sets the size (an entity budget, a byte limit, or a fixed
|
|
104
|
+
capacity).
|
|
88
105
|
|
|
89
106
|
**Queries**
|
|
90
107
|
|
|
91
|
-
- **Live, cached queries** — `ecs.query(Pos, Vel)
|
|
92
|
-
new matching archetypes
|
|
93
|
-
- **Two iteration verbs** — `forEach(arch => …)`
|
|
94
|
-
for the
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
- **Live, cached queries** — Write `ecs.query(Pos, Vel)`, then make it more exact with `.and()`,
|
|
109
|
+
`.without()`, or `.anyOf()`. The store adds new matching archetypes to the query automatically.
|
|
110
|
+
- **Two iteration verbs** — Use `forEach(arch => …)` to read archetypes. Use
|
|
111
|
+
`eachChunk((cols, count) => …)` for the high-frequency loop that writes. In that loop, `cols.mut`
|
|
112
|
+
and `cols.read` give you all the columns of one component at the same time.
|
|
113
|
+
- **Change detection** — Each `(archetype, component)` pair has a change tick.
|
|
114
|
+
`query.changed(Pos)` visits only the archetypes that changed at or after the threshold tick of
|
|
115
|
+
the system.
|
|
116
|
+
- **Queries for relations and hierarchies** — Use the wildcards `(R, *)` and `(*, T)`, plus
|
|
117
|
+
`forEachRelatedTo` and `query.hierarchy(rel, depth)`. For **sparse queries**, use
|
|
118
|
+
`query.withSparse(...)`. Queries skip disabled entities. To include them, use
|
|
119
|
+
`query.includeDisabled()`.
|
|
120
|
+
|
|
121
|
+
**Systems and the schedule**
|
|
122
|
+
|
|
123
|
+
- **Systems that declare their access** — A system is a plain function in a `SystemConfig` that
|
|
124
|
+
declares `reads` and `writes`. A development-mode access checker holds you to that declaration,
|
|
125
|
+
and the build tool removes that checker from a production build. There are also `(ctx, dt)` and
|
|
126
|
+
`(q, ctx, dt)`
|
|
127
|
+
forms with a query builder, for connection code that touches no data. The lifecycle hooks are
|
|
128
|
+
`onAdded`, `onRemoved`, and `dispose`. Set `exclusive: true` for full-world setup or teardown.
|
|
129
|
+
- **A topological scheduler** — There are seven phases: `PRE_STARTUP`, `STARTUP`, `POST_STARTUP`,
|
|
130
|
+
`FIXED_UPDATE`, `PRE_UPDATE`, `UPDATE`, and `POST_UPDATE`. Each phase does a Kahn sort on the
|
|
131
|
+
`before` and `after` constraints. Insertion order breaks a tie, which keeps the result
|
|
132
|
+
deterministic. Cycle detection is always active.
|
|
133
|
+
- **A fixed timestep** — An accumulator loop uses the `fixedTimestep` value that you set. A limit
|
|
134
|
+
protects against the spiral of death.
|
|
135
|
+
- **System sets and run conditions** — Use `systemSet(...)` with `configureSet(...)`. The supplied
|
|
136
|
+
conditions are `runIfResourceEq`, `runEveryNTicks`, and `runIfAnyMatch`. You can also write your
|
|
137
|
+
own `RunCondition`.
|
|
113
138
|
|
|
114
139
|
**Structural changes**
|
|
115
140
|
|
|
116
|
-
- **Deferred
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
- **
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
- **
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
- **
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
- **
|
|
157
|
-
|
|
141
|
+
- **Deferred in a system, immediate on the host** — `ctx.commands` is a facade in the style of the
|
|
142
|
+
Bevy `Commands` type. It holds add, remove, despawn, enable, and disable operations until the
|
|
143
|
+
flush at the end of the phase, so that iterators stay correct. `commands.spawn` gives you the id
|
|
144
|
+
immediately, but it attaches the components later. Each mutation on the host
|
|
145
|
+
(`ecs.addComponent`, `ecs.removeComponent`, `ecs.despawn`, `ecs.disable`, and `ecs.enable`)
|
|
146
|
+
applies immediately.
|
|
147
|
+
- **Enable and disable for an entity** — Use `disable`, `enable`, and `isDisabled`. Disabled rows
|
|
148
|
+
stay in a partition at the end of the archetype, and queries skip them by default.
|
|
149
|
+
- **Templates and bundles** — `ecs.template(Pos({ x, y }), …)` makes a blueprint. `spawn` and
|
|
150
|
+
`spawnMany` use that blueprint to create entities with no archetype transition. The same callable
|
|
151
|
+
bundles are the arguments to `spawnBundle(...)` and `addComponents(...)`.
|
|
152
|
+
|
|
153
|
+
**Reactions and relationships**
|
|
154
|
+
|
|
155
|
+
- **Observers** — `ecs.observe(...)` registers `onAdd`, `onRemove`, `onSet`, `onEnable`, and
|
|
156
|
+
`onDisable` callbacks, for a structure or for one entity.
|
|
157
|
+
- **Relations** — A relation is a `(relation, target)` pair. The presets are `ChildOf` and `IsA`.
|
|
158
|
+
A relation is exclusive or multi. Queries go in both directions (`targetOf`, `sourcesOf`,
|
|
159
|
+
`ancestorsOf`, `rootOf`, and `cascadeOf`). The cleanup policy for a deleted target is
|
|
160
|
+
`delete`, `clear`, or `orphan`. Relations use sparse storage. So they cause no archetype
|
|
161
|
+
transition, and they use no identity bit.
|
|
162
|
+
- **Sparse storage** — Use `registerSparseComponent` and `registerSparseTag`, then `addSparse` and
|
|
163
|
+
`removeSparse`. Sparse storage is correct for data that changes frequently or is rare, because it
|
|
164
|
+
causes no archetype transition.
|
|
165
|
+
- **Resources** — A resource is a typed global value, keyed with `resourceKey<T>`. **Events** are
|
|
166
|
+
send-and-forget channels in struct-of-arrays form, keyed with `eventKey<F>` or `signalKey`. The
|
|
167
|
+
ECS clears the events at the end of each `update`.
|
|
168
|
+
- **Cached refs** — `ctx.ref(def, e)` gives you a writable ref, and it sets the change tick.
|
|
169
|
+
`ctx.refRead(def, e)` gives you a read-only ref. A ref finds the archetype, the row, and the
|
|
170
|
+
columns one time. Then you can write `pos.x += vel.vx * dt`.
|
|
171
|
+
- **Cursors** — `ctx.cursor(def)` gives you an accessor that you can use again for a different
|
|
172
|
+
entity. `ctx.cursorRead(def)` gives you the read-only form. The same two functions are on `ecs`.
|
|
173
|
+
You make a cursor one time. Then each `at(entity)` call moves it to a different entity. Use a
|
|
174
|
+
cursor when you read or write many entities from a list of ids. The loop then makes no ref for
|
|
175
|
+
each entity, and the cursor finds the position of each field one time. Each `at` call finds the
|
|
176
|
+
archetype and the row again. Thus a structural change between two `at` calls cannot make a
|
|
177
|
+
cursor read a different entity.
|
|
178
|
+
|
|
179
|
+
**Determinism, storage of state, and integration**
|
|
180
|
+
|
|
181
|
+
- **Determinism** (optional) — Construct the ECS with `new ECS({ deterministic: true })`. Then use
|
|
182
|
+
`ecs.snapshots.stateHash()`, which gives a 32-bit digest in FNV-1a style over the live dense
|
|
183
|
+
bytes, the sparse stores, and the target sets of multi relations. Also use
|
|
184
|
+
`ecs.snapshots.capture()`, `ecs.snapshots.restore(...)`, and the equivalent functions for sparse
|
|
185
|
+
data. The hash is independent of the storage type: a heap ECS and a shared ECS with the same
|
|
186
|
+
history give the same hash.
|
|
187
|
+
- **A write path from the host into the ECS** — `installHostCommandSeam(ecs)` applies typed
|
|
188
|
+
`HostCommand` values from outside the schedule, through one approved `exclusive` system. It
|
|
189
|
+
supports record and replay (`HostCommandRecorder` and `replayCommandLog`), and a ring transport
|
|
190
|
+
between threads.
|
|
191
|
+
- **A reactive UI connection** (optional) — There are three parts. `@oasys/oecs/reactive` is a
|
|
192
|
+
signals kernel with no dependencies. `@oasys/oecs/reactive-sync` is a bridge from the ECS to that
|
|
193
|
+
kernel, and it publishes only the changed entities and columns. `@oasys/oecs/solid` is a SolidJS
|
|
194
|
+
adapter.
|
|
195
|
+
- **An editor layer** — It adds undo, redo, and field handles above the host write path
|
|
196
|
+
(`@oasys/oecs/editor`).
|
|
197
|
+
- **Frame traces** — `ecs.setTrace(sink)` with `FrameTraceRecorder` gives you a structured stream
|
|
198
|
+
of the events in each frame. It is available in development builds only.
|
|
199
|
+
- **A compute backend connection** — `ecs.attachBackend(...)` runs the body of a system on a
|
|
200
|
+
compiled backend, such as WASM, instead of its TypeScript closure.
|
|
158
201
|
|
|
159
202
|
**Reference**
|
|
160
203
|
|
|
161
|
-
- **Typed errors** — an `ECSError` taxonomy with a `category` enum and an `isEcsError`
|
|
162
|
-
|
|
163
|
-
|
|
204
|
+
- **Typed errors** — There is an `ECSError` taxonomy with a `category` enum and an `isEcsError`
|
|
205
|
+
guard. The package exports all of them.
|
|
206
|
+
- **Primitives that you can use again** (`@oasys/oecs/primitives`) — `BitSet`, `SparseSet`,
|
|
207
|
+
`SparseMap`, `GrowableTypedArray`, `BinaryHeap`, and `topologicalSort` also operate alone.
|
|
164
208
|
|
|
165
209
|
## Entry points
|
|
166
210
|
|
|
167
|
-
The core is `@oasys/oecs
|
|
211
|
+
The core is `@oasys/oecs`. Each other entry point is optional, and it costs nothing until you
|
|
212
|
+
import it.
|
|
168
213
|
|
|
169
214
|
| Import | What it is |
|
|
170
215
|
| --- | --- |
|
|
171
|
-
| `@oasys/oecs` | the ECS — pure-TS heap profile by default (production build
|
|
172
|
-
| `@oasys/oecs/dev` | the same ECS with the
|
|
173
|
-
| `@oasys/oecs/shared` |
|
|
174
|
-
| `@oasys/oecs/reactive` |
|
|
175
|
-
| `@oasys/oecs/reactive-sync` |
|
|
176
|
-
| `@oasys/oecs/editor` | undo
|
|
177
|
-
| `@oasys/oecs/solid` | SolidJS adapter (`solid-js` is an **optional** peer dependency) |
|
|
178
|
-
| `@oasys/oecs/primitives` | the
|
|
179
|
-
| `@oasys/oecs/internal` | unstable
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
the
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
216
|
+
| `@oasys/oecs` | the ECS — the pure-TS heap profile by default (a production build, with the development guards removed) |
|
|
217
|
+
| `@oasys/oecs/dev` | the same ECS with the development guards **on** — import this to get the guards directly; see [Development and production](#dev-vs-prod) |
|
|
218
|
+
| `@oasys/oecs/shared` | the optional `SharedArrayBuffer` allocators, for worker offload or a WASM backend (this needs COOP/COEP) |
|
|
219
|
+
| `@oasys/oecs/reactive` | the reactive kernel, which has no dependencies (`signal`, `computed`, `effect`, and reactive collections) |
|
|
220
|
+
| `@oasys/oecs/reactive-sync` | the bridge from the ECS to the kernel — it publishes only the changed entities and columns |
|
|
221
|
+
| `@oasys/oecs/editor` | undo, redo, and field handles above the host write path |
|
|
222
|
+
| `@oasys/oecs/solid` | the SolidJS adapter (`solid-js` is an **optional** peer dependency) |
|
|
223
|
+
| `@oasys/oecs/primitives` | the data structures that oecs is built from, which also operate alone |
|
|
224
|
+
| `@oasys/oecs/internal` | unstable internal parts (codecs, ABI constants, the access checker) — **there are no semver guarantees** |
|
|
225
|
+
|
|
226
|
+
<a id="dev-vs-prod"></a>
|
|
227
|
+
|
|
228
|
+
## Development and production
|
|
229
|
+
|
|
230
|
+
A compile-time flag, `__DEV__`, controls each run-time check. The checks include bounds and
|
|
231
|
+
liveness checks, detection of a system that you added two times, validation at registration, and
|
|
232
|
+
the system access checker for `reads` and `writes`. The build tool **removes these checks from a
|
|
233
|
+
production build**. So, when the documentation says that an operation "throws in development",
|
|
234
|
+
that behavior is a development aid. It is not a production guarantee. Two checks stay active in
|
|
235
|
+
each build: cycle detection in the scheduler, and validation of the constructor options (the
|
|
236
|
+
timestep, the memory options, and the cardinality of a relation).
|
|
237
|
+
|
|
238
|
+
**Production is the default on both channels. You must turn the guards on.** On **npm**,
|
|
239
|
+
`@oasys/oecs` is the production build, with the guards removed. A bundler in development mode
|
|
240
|
+
(`vite dev` or `webpack --mode development`) selects the build with the guards automatically,
|
|
241
|
+
through the `development` export condition. As an alternative, import `@oasys/oecs/dev` directly.
|
|
242
|
+
On **JSR and Deno** there is no bundler, because the package is raw source. The default is also
|
|
243
|
+
production (`__DEV__ = false`). To turn the guards on while you develop, set
|
|
244
|
+
`globalThis.__DEV__ = true` before the first import. For the full details, which include the
|
|
245
|
+
browser, CDN, and manual paths, read the
|
|
246
|
+
[**Development guards and production builds**](docs/PRODUCTION.md) guide.
|
|
196
247
|
|
|
197
248
|
## Documentation
|
|
198
249
|
|
|
199
|
-
- **
|
|
200
|
-
[Best
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
- **
|
|
250
|
+
- **If oecs is new to you**, start with the [Getting started](docs/GETTING_STARTED.md) tutorial.
|
|
251
|
+
Then read [Best practices](docs/BEST_PRACTICES.md) and the
|
|
252
|
+
[Architecture](docs/ARCHITECTURE.md) overview.
|
|
253
|
+
- **If you use the optional extensions**, read the [Extensions guide](docs/EXTENSIONS.md) for the
|
|
254
|
+
reactive UI, the editor, Solid, shared memory, and the primitives.
|
|
255
|
+
- **If you upgrade from 0.4**, read the
|
|
256
|
+
[Migration guide (0.4 to 0.5)](docs/MIGRATION-0.4-to-0.5.md) and the [CHANGELOG](CHANGELOG.md).
|
|
257
|
+
- **If you upgrade from 0.3**, read the
|
|
258
|
+
[Migration guide (0.3 to 0.4)](docs/MIGRATION-0.3-to-0.4.md).
|
|
259
|
+
- **The full API reference** — Start at the [reference index](docs/api/index.md):
|
|
207
260
|
[components](docs/api/components.md) ·
|
|
208
261
|
[entities](docs/api/entities.md) ·
|
|
209
262
|
[queries](docs/api/queries.md) ·
|
|
@@ -219,11 +272,11 @@ while developing. Full details — including the browser/CDN and manual-override
|
|
|
219
272
|
[determinism](docs/api/determinism.md) ·
|
|
220
273
|
[memory](docs/api/memory.md) ·
|
|
221
274
|
[WASM backends](docs/api/wasm.md) ·
|
|
222
|
-
[
|
|
223
|
-
[host
|
|
275
|
+
[parallel execution](docs/api/parallel.md) ·
|
|
276
|
+
[the host write path](docs/api/host-write-seam.md) ·
|
|
224
277
|
[reactive](docs/api/reactive.md) ·
|
|
225
278
|
[editor](docs/api/editor.md) ·
|
|
226
|
-
[
|
|
279
|
+
[traces](docs/api/tracing.md) ·
|
|
227
280
|
[primitives](docs/api/primitives.md) ·
|
|
228
281
|
[errors](docs/api/errors.md)
|
|
229
282
|
|
|
@@ -239,13 +292,13 @@ pnpm exec tsc --noEmit # type check
|
|
|
239
292
|
|
|
240
293
|
## Acknowledgements
|
|
241
294
|
|
|
242
|
-
oecs
|
|
295
|
+
oecs is built on the work of the ECS community. We thank:
|
|
243
296
|
|
|
244
297
|
- **[Bevy](https://bevyengine.org)**, **[Flecs](https://github.com/SanderMertens/flecs)**, and
|
|
245
|
-
**[bitECS](https://github.com/NateTheGreatt/bitECS)** — a
|
|
246
|
-
designs
|
|
247
|
-
- **[@clinuxrulz](https://github.com/clinuxrulz)** — for
|
|
248
|
-
the ECS.
|
|
298
|
+
**[bitECS](https://github.com/NateTheGreatt/bitECS)** — a continuous source of ideas. Their
|
|
299
|
+
designs gave shape to the archetypes, relations, schedule, and change detection in oecs.
|
|
300
|
+
- **[@clinuxrulz](https://github.com/clinuxrulz)** — for an excellent demonstration and for very
|
|
301
|
+
valuable comments on the ECS.
|
|
249
302
|
|
|
250
303
|
## License
|
|
251
304
|
|
|
@@ -17,7 +17,7 @@ interface AccessSets {
|
|
|
17
17
|
relationWrites: Set<number>;
|
|
18
18
|
}
|
|
19
19
|
declare function setsFor(desc: SystemDescriptor): AccessSets;
|
|
20
|
-
/** The reads-only access surface a run condition declares
|
|
20
|
+
/** The reads-only access surface a run condition declares. A condition
|
|
21
21
|
* can only `reads` components (via a captured query) and `resourceReads`; every
|
|
22
22
|
* mutation set is empty by construction, so the same `check_*` machinery rejects
|
|
23
23
|
* any write/structural/resource-write a misbehaving predicate attempts. */
|
|
@@ -32,7 +32,7 @@ declare class AccessCheck {
|
|
|
32
32
|
private activeName;
|
|
33
33
|
private exclusive;
|
|
34
34
|
enter(desc: SystemDescriptor): void;
|
|
35
|
-
/** Open a reads-only span for a run condition
|
|
35
|
+
/** Open a reads-only span for a run condition. No descriptor — a
|
|
36
36
|
* condition can gate a whole SystemSet, so it isn't attributable to one
|
|
37
37
|
* system — just its declared reads/resource_reads and a name for diagnostics.
|
|
38
38
|
* Paired with `leave()`. */
|
|
@@ -52,7 +52,7 @@ declare class AccessCheck {
|
|
|
52
52
|
checkSparseWrite(def: SparseComponentDef): void;
|
|
53
53
|
checkRelationRead(def: RelationDef): void;
|
|
54
54
|
checkRelationWrite(def: RelationDef): void;
|
|
55
|
-
/** A `(*, T)` wildcard (`Query.forEachRelatedTo
|
|
55
|
+
/** A `(*, T)` wildcard (`Query.forEachRelatedTo`) reads every
|
|
56
56
|
* relation's reverse index, so it can't name a specific relation — it is
|
|
57
57
|
* authorised by the `ANY_RELATION` sentinel in `relationReads`. Honoured here
|
|
58
58
|
* exactly like a per-relation read, just keyed on the reserved sentinel id
|
|
@@ -17,7 +17,7 @@ interface AccessSets {
|
|
|
17
17
|
relationWrites: Set<number>;
|
|
18
18
|
}
|
|
19
19
|
declare function setsFor(desc: SystemDescriptor): AccessSets;
|
|
20
|
-
/** The reads-only access surface a run condition declares
|
|
20
|
+
/** The reads-only access surface a run condition declares. A condition
|
|
21
21
|
* can only `reads` components (via a captured query) and `resourceReads`; every
|
|
22
22
|
* mutation set is empty by construction, so the same `check_*` machinery rejects
|
|
23
23
|
* any write/structural/resource-write a misbehaving predicate attempts. */
|
|
@@ -32,7 +32,7 @@ declare class AccessCheck {
|
|
|
32
32
|
private activeName;
|
|
33
33
|
private exclusive;
|
|
34
34
|
enter(desc: SystemDescriptor): void;
|
|
35
|
-
/** Open a reads-only span for a run condition
|
|
35
|
+
/** Open a reads-only span for a run condition. No descriptor — a
|
|
36
36
|
* condition can gate a whole SystemSet, so it isn't attributable to one
|
|
37
37
|
* system — just its declared reads/resource_reads and a name for diagnostics.
|
|
38
38
|
* Paired with `leave()`. */
|
|
@@ -52,7 +52,7 @@ declare class AccessCheck {
|
|
|
52
52
|
checkSparseWrite(def: SparseComponentDef): void;
|
|
53
53
|
checkRelationRead(def: RelationDef): void;
|
|
54
54
|
checkRelationWrite(def: RelationDef): void;
|
|
55
|
-
/** A `(*, T)` wildcard (`Query.forEachRelatedTo
|
|
55
|
+
/** A `(*, T)` wildcard (`Query.forEachRelatedTo`) reads every
|
|
56
56
|
* relation's reverse index, so it can't name a specific relation — it is
|
|
57
57
|
* authorised by the `ANY_RELATION` sentinel in `relationReads`. Honoured here
|
|
58
58
|
* exactly like a per-relation read, just keyed on the reserved sentinel id
|