koota 0.0.3
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/LICENSE +15 -0
- package/README.md +337 -0
- package/dist/chunk-AQ5VUG5P.js +17 -0
- package/dist/index.cjs +1266 -0
- package/dist/index.d.cts +39 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +1226 -0
- package/dist/react.cjs +111 -0
- package/dist/react.d.cts +19 -0
- package/dist/react.d.ts +19 -0
- package/dist/react.js +82 -0
- package/dist/world-BRz2NI5_.d.cts +229 -0
- package/dist/world-BRz2NI5_.d.ts +229 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [year] [fullname]
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# Koota
|
|
2
|
+
|
|
3
|
+
Koota is an ECS-based state management library optimized for real-time apps, games, and XR experiences. Use as much or as little as you need.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm i koota
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
### First, define traits
|
|
10
|
+
|
|
11
|
+
Traits are the building blocks of your state. They represent slices of data with specific meanings.
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { trait } from 'koota';
|
|
15
|
+
|
|
16
|
+
// Basic trait with default values
|
|
17
|
+
const Position = trait({ x: 0, y: 0 });
|
|
18
|
+
const Velocity = trait({ x: 0, y: 0 });
|
|
19
|
+
|
|
20
|
+
// Trait with a callback for initial value
|
|
21
|
+
const Mesh = trait({ value: () => THREE.Mesh() });
|
|
22
|
+
|
|
23
|
+
// Tag trait (no data)
|
|
24
|
+
const IsActive = trait();
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Spawn entities
|
|
28
|
+
|
|
29
|
+
Entities are spawned in a world. By adding traits to an entity they gain content.
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import { createWorld } from 'koota';
|
|
33
|
+
|
|
34
|
+
const world = createWorld();
|
|
35
|
+
|
|
36
|
+
const player = world.spawn(Position, Velocity);
|
|
37
|
+
// Initial values can be passed in to the trait by using it as a function
|
|
38
|
+
const goblin = world.spawn(Position({ x: 10, y: 10 }), Velocity, Mesh);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Query and update data
|
|
42
|
+
|
|
43
|
+
Queries fetch entities sharing traits (archetypes). Use them to batch update entities efficiently.
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
// Run this in a loop
|
|
47
|
+
world.query(Position, Velocity).updateEach(([position, velocity]) => {
|
|
48
|
+
position.x += velocity.x * delta;
|
|
49
|
+
position.y += velocity.y * delta;
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Use in your React components
|
|
54
|
+
|
|
55
|
+
Traits can be used reactievely inside of React components.
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
import { WorldProvider, useQuery, useObserve } from 'koota/react'
|
|
59
|
+
|
|
60
|
+
// Wrap your app in WorldProvider
|
|
61
|
+
createRoot(document.getElementById('root')!).render(
|
|
62
|
+
<WorldProvider world={world}>
|
|
63
|
+
<App />
|
|
64
|
+
</WorldProvider>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
function RocketRenderer() {
|
|
68
|
+
// Reactively update whenever the query updates with new entities
|
|
69
|
+
const rockets = useQuery(Position, Velocity)
|
|
70
|
+
return (
|
|
71
|
+
<>
|
|
72
|
+
{rockets.map((entity) => <Rocket key={entity} entity={entity} />)}
|
|
73
|
+
</>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function Rocket({ entity }) {
|
|
78
|
+
// Observes this entity's position trait and reactively updates when it changes
|
|
79
|
+
const position = useObserve(entity, Position)
|
|
80
|
+
return (
|
|
81
|
+
<div style={{ position: 'absolute', left: position.x ?? 0, top: position.y ?? 0 }}>
|
|
82
|
+
🚀
|
|
83
|
+
</div>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Modify Koota state safely with actions
|
|
89
|
+
|
|
90
|
+
Use actions to safely modify Koota from inside of React in either effects or events.
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
import { createActions } from 'koota/react';
|
|
94
|
+
|
|
95
|
+
const useMyActions = createActions((world) => ({
|
|
96
|
+
spawnShip: (position) => world.spawn(Position(position), Velocity),
|
|
97
|
+
destroyAllShips: (world) => {
|
|
98
|
+
world.query(Position, Velocity).forEach((entity) => {
|
|
99
|
+
entity.destroy();
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
function DoomButton() {
|
|
105
|
+
const { spawnShip, destroyAllShips } = useMyActions();
|
|
106
|
+
|
|
107
|
+
// Spawn three ships on mount
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
spawnShip({ x: 0, y: 1 });
|
|
110
|
+
spawnShip({ x: 1, y: 0 });
|
|
111
|
+
spawnShip({ x: 1, y: 1 });
|
|
112
|
+
|
|
113
|
+
// Destroy all ships during cleanup
|
|
114
|
+
return () => drestroyAllShips();
|
|
115
|
+
}, []);
|
|
116
|
+
|
|
117
|
+
// And destroy all ships on click!
|
|
118
|
+
return <button onClick={destroyAllShips}>Boom!</button>;
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Or access world directly and use it.
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
const world = useWorld();
|
|
126
|
+
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
const entity = world.spawn(Velocity, Position);
|
|
129
|
+
return () => entity.destroy();
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Advanced
|
|
134
|
+
|
|
135
|
+
### Relationships
|
|
136
|
+
|
|
137
|
+
Koota supports relationships between entities using the `relation` function. Relationships allow you to create connections between entities and query them efficiently.
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
const ChildOf = relation();
|
|
141
|
+
|
|
142
|
+
const parent = world.spawn();
|
|
143
|
+
const child = world.spawn(ChildOf(parent));
|
|
144
|
+
|
|
145
|
+
const entity = world.queryFirst(ChildOf(parent)); // Returns child
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### With data
|
|
149
|
+
|
|
150
|
+
Relationships can contain data like any trait.
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
const Contains = relation({ store: { amount: 0 } });
|
|
154
|
+
|
|
155
|
+
const inventory = world.spawn();
|
|
156
|
+
const gold = world.spawn();
|
|
157
|
+
inventory.add(Contains(gold));
|
|
158
|
+
inventory.set(Contains(gold), { amount: 10 });
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### Auto remove target
|
|
162
|
+
|
|
163
|
+
Relations can automatically remove target entities and their descendants.
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
const ChildOf = relation({ autoRemoveTarget: true });
|
|
167
|
+
|
|
168
|
+
const parent = world.spawn();
|
|
169
|
+
const child = world.spawn(ChildOf(parent));
|
|
170
|
+
const grandchild = world.spawn(ChildOf(child));
|
|
171
|
+
|
|
172
|
+
parent.destroy();
|
|
173
|
+
|
|
174
|
+
world.has(child); // False, the child and grandchild are destroyed too
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### Exclusive Relationships
|
|
178
|
+
|
|
179
|
+
Exclusive relationships ensure each entity can only have one target.
|
|
180
|
+
|
|
181
|
+
```js
|
|
182
|
+
const Targeting = relation({ exclusive: true });
|
|
183
|
+
|
|
184
|
+
const hero = world.spawn();
|
|
185
|
+
const rat = world.spawn();
|
|
186
|
+
const goblin = world.spawn();
|
|
187
|
+
|
|
188
|
+
hero.add(Targeting(rat));
|
|
189
|
+
hero.add(Targeting(goblin));
|
|
190
|
+
|
|
191
|
+
hero.has(Targeting(rat)); // False
|
|
192
|
+
hero.has(Targeting(goblin)); // True
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### Querying relationships
|
|
196
|
+
|
|
197
|
+
Relationships can be queried with specific targets, wildcard targets using `*` and even inverted wildcard searches with `Wildcard` to get all entities with a relationships targeting another entity.
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
const gold = world.spawn();
|
|
201
|
+
const silver = world.spawn();
|
|
202
|
+
const inventory = world.spawn(Contains(gold), Contains(silver));
|
|
203
|
+
|
|
204
|
+
const targets = inventory.targetsFor(Contains); // Returns [gold, silver]
|
|
205
|
+
|
|
206
|
+
const chest = world.spawn(Contains(gold));
|
|
207
|
+
const dwarf = world.spawn(Desires(gold));
|
|
208
|
+
|
|
209
|
+
const constainsSilver = world.query(Contains(silver)); // Returns [inventory]
|
|
210
|
+
const containsAnything = world.query(Contains('*')); // Returns [inventory, chest]
|
|
211
|
+
const relatesToGold = world.query(Widlcard(gold)); // Returns [inventory, chest, dwarf]
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Query modifiers
|
|
215
|
+
|
|
216
|
+
Modifiers are used to filter query results enabling powerful patterns. All modifiers can mixed together.
|
|
217
|
+
|
|
218
|
+
#### Not
|
|
219
|
+
|
|
220
|
+
The `Not` modifier excludes entities that have specific traits from the query results.
|
|
221
|
+
|
|
222
|
+
```js
|
|
223
|
+
import { Not } from 'koota';
|
|
224
|
+
|
|
225
|
+
const staticEntities = world.query(Position, Not(Velocity));
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
#### Or
|
|
229
|
+
|
|
230
|
+
By default all query paramters are combined with logical AND. The `Or` modifier enables using logical OR instead.
|
|
231
|
+
|
|
232
|
+
```js
|
|
233
|
+
import { Or } from 'koota';
|
|
234
|
+
|
|
235
|
+
const movingOrVisible = world.query(Or(Velocity, Renderable));
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
#### Added
|
|
239
|
+
|
|
240
|
+
The `Added` modifier tracks all entities that have added the specified traits since the last time the query was run. A new instance of the modifier must be created for tracking to be unique.
|
|
241
|
+
|
|
242
|
+
```js
|
|
243
|
+
import { createAdded } from 'koota';
|
|
244
|
+
|
|
245
|
+
const Added = createAdded();
|
|
246
|
+
|
|
247
|
+
// This query will return entities that have just added the Position trait
|
|
248
|
+
const newPositions = world.query(Added(Position));
|
|
249
|
+
|
|
250
|
+
// After running the query, the Added modifier is reset
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### Removed
|
|
254
|
+
|
|
255
|
+
The `Removed` modifier tracks all entities that have removed the specified traits since the last time the query was run. This includes entities that have been destroyed. A new instance of the modifier must be created for tracking to be unique.
|
|
256
|
+
|
|
257
|
+
```js
|
|
258
|
+
import { createRemoved } from 'koota';
|
|
259
|
+
|
|
260
|
+
const Removed = createRemoved();
|
|
261
|
+
|
|
262
|
+
// This query will return entities that have just removed the Velocity trait
|
|
263
|
+
const stoppedEntities = world.query(Removed(Velocity));
|
|
264
|
+
|
|
265
|
+
// After running the query, the Removed modifier is reset
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
#### Changed
|
|
269
|
+
|
|
270
|
+
The `Changed` modifier tracks all entities that have had the specified traits values change since the last time the query was run. A new instance of the modifier must be created for tracking to be unique.
|
|
271
|
+
|
|
272
|
+
```js
|
|
273
|
+
import { createChanged } from 'koota';
|
|
274
|
+
|
|
275
|
+
const Changed = createChanged();
|
|
276
|
+
|
|
277
|
+
// This query will return entities whose Position has changed
|
|
278
|
+
const movedEntities = world.query(Changed(Position));
|
|
279
|
+
|
|
280
|
+
// After running the query, the Changed modifier is reset
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Add, remove and change events
|
|
284
|
+
|
|
285
|
+
Koota allows you to subscribe to add, remove, and change events for specific traits.
|
|
286
|
+
|
|
287
|
+
```js
|
|
288
|
+
// Subscribe to Position changes
|
|
289
|
+
const unsub = world.onChange(Position, (entity) => {
|
|
290
|
+
console.log(`Entity ${entity} changed position`);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Subscribe to Position additions
|
|
294
|
+
const unsub = world.onAdd(Position, (entity) => {
|
|
295
|
+
console.log(`Entity ${entity} added position`);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Subscribe to Position removals
|
|
299
|
+
const unsub = world.onRemove(Position, (entity) => {
|
|
300
|
+
console.log(`Entity ${entity} removed position`);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Trigger events
|
|
304
|
+
const entity = world.spawn(Position);
|
|
305
|
+
entity.set(Position, { x: 10, y: 20 });
|
|
306
|
+
entity.remove(Position);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### World traits
|
|
310
|
+
|
|
311
|
+
For global data like time, these can be traits added to the world. **World traits do not appear in queries.**
|
|
312
|
+
|
|
313
|
+
```js
|
|
314
|
+
const Time = trait({ delta: 0, current: 0 });
|
|
315
|
+
world.add(Time);
|
|
316
|
+
|
|
317
|
+
const time = world.get(Time);
|
|
318
|
+
world.set(Time, { current: performance.now() });
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Modifying trait stores direclty
|
|
322
|
+
|
|
323
|
+
For performance-critical operations, you can modify trait stores directly using the useStore hook. This approach bypasses some of the safety checks and event triggers, so use it with caution. All stores are structure of arrays for performance purposes.
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
// Returns the SoA stores
|
|
327
|
+
world.query(Position, Velocity).useStore(([position, store], entities) => {
|
|
328
|
+
// Write our own loop over the stores
|
|
329
|
+
for (let i = 0; i > entities.length; i++) {
|
|
330
|
+
// Get the entity ID to use as the array index
|
|
331
|
+
const eid = entities[i].id();
|
|
332
|
+
// Write to each array in the store
|
|
333
|
+
position.x[eid] += velocity.x[eid] * delta;
|
|
334
|
+
position.y[eid] += velocity.y[eid] * delta;
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __typeError = (msg) => {
|
|
3
|
+
throw TypeError(msg);
|
|
4
|
+
};
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
7
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
8
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
9
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
10
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
__publicField,
|
|
14
|
+
__privateGet,
|
|
15
|
+
__privateAdd,
|
|
16
|
+
__privateSet
|
|
17
|
+
};
|