koota 0.6.0 → 0.6.2

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 CHANGED
@@ -72,11 +72,7 @@ createRoot(document.getElementById('root')!).render(
72
72
  function RocketRenderer() {
73
73
  // Reactively update whenever the query updates with new entities
74
74
  const rockets = useQuery(Position, Velocity)
75
- return (
76
- <>
77
- {rockets.map((entity) => <RocketView key={entity} entity={entity} />)}
78
- </>
79
- )
75
+ return rockets.map((entity) => <RocketView key={entity} entity={entity} />)
80
76
  }
81
77
 
82
78
  function RocketView({ entity }) {
@@ -84,7 +80,7 @@ function RocketView({ entity }) {
84
80
  const position = useTrait(entity, Position)
85
81
  return (
86
82
  <div style={{ position: 'absolute', left: position.x ?? 0, top: position.y ?? 0 }}>
87
- 🚀
83
+ 🚀
88
84
  </div>
89
85
  )
90
86
  }
@@ -95,10 +91,10 @@ function RocketView({ entity }) {
95
91
  Use actions to safely modify Koota from inside of React in either effects or events.
96
92
 
97
93
  ```js
98
- import { createActions } from 'koota'
94
+ import { defineActions } from 'koota'
99
95
  import { useActions } from 'koota/react'
100
96
 
101
- const actions = createActions((world) => ({
97
+ const actions = defineActions((world) => ({
102
98
  spawnShip: (position) => world.spawn(Position(position), Velocity),
103
99
  destroyAllShips: () => {
104
100
  world.query(Position, Velocity).forEach((entity) => {
@@ -205,6 +201,34 @@ hero.has(Targeting(rat)) // False
205
201
  hero.has(Targeting(goblin)) // True
206
202
  ```
207
203
 
204
+ #### Ordered relations
205
+
206
+ > ⚠️ **Experimental**<br>
207
+ > This API is experimental and may change in future versions. Please provide feedback on GitHub or Discord.
208
+
209
+ Ordered relations maintain a list of related entities with bidirectional sync.
210
+
211
+ ```js
212
+ import { relation, ordered } from 'koota'
213
+
214
+ const ChildOf = relation()
215
+ const OrderedChildren = ordered(ChildOf)
216
+
217
+ const parent = world.spawn(OrderedChildren)
218
+ const children = parent.get(OrderedChildren)
219
+
220
+ children.push(child1) // adds ChildOf(parent) to child1
221
+ children.splice(0, 1) // removes ChildOf(parent) from child1
222
+
223
+ // Bidirectional sync works both ways
224
+ child2.add(ChildOf(parent)) // child2 automatically added to list
225
+ ```
226
+
227
+ Ordered relations support array methods like `push()`, `pop()`, `shift()`, `unshift()`, and `splice()`, plus special methods `moveTo()` and `insert()` for precise control. Changes to the list automatically sync with relations, and vice versa.
228
+
229
+ > ⚠️ **Performance note**<br>
230
+ > Ordered relations are more expensive to update than regular relations but enable faster traversal when order matters. Use them only when entity order is essential.
231
+
208
232
  #### Querying relations
209
233
 
210
234
  Relations can be queried with specific targets and wildcard targets using `*`.
@@ -286,6 +310,29 @@ const parent = world.spawn()
286
310
  const changedChildren = world.query(Changed(ChildOf), ChildOf(parent))
287
311
  ```
288
312
 
313
+ #### Relation events
314
+
315
+ Relations emit events per **relation pair**. This makes it easy to know exactly which target was involved.
316
+
317
+ - `onAdd(Relation, (entity, target) => {})` triggers when `entity.add(Relation(target))` is called.
318
+ - `onRemove(Relation, (entity, target) => {})` triggers when `entity.remove(Relation(target))` is called.
319
+ - `onChange(Relation, (entity, target) => {})` triggers when relation **store data** is updated with `entity.set(Relation(target), data)` (only for relations created with a `store`).
320
+
321
+ ```js
322
+ const ChildOf = relation({ store: { priority: 0 } })
323
+
324
+ const unsubAdd = world.onAdd(ChildOf, (entity, target) => {})
325
+ const unsubRemove = world.onRemove(ChildOf, (entity, target) => {})
326
+ const unsubChange = world.onChange(ChildOf, (entity, target) => {})
327
+
328
+ const parent = world.spawn()
329
+ const child = world.spawn()
330
+
331
+ child.add(ChildOf(parent)) // onAdd(child, parent)
332
+ child.set(ChildOf(parent), { priority: 1 }) // onChange(child, parent)
333
+ child.remove(ChildOf(parent)) // onRemove(child, parent)
334
+ ```
335
+
289
336
  ### Query modifiers
290
337
 
291
338
  Modifiers are used to filter query results enabling powerful patterns. All modifiers can be mixed together.
@@ -394,9 +441,14 @@ entity.set(Position, { x: 10, y: 20 })
394
441
  entity.remove(Position)
395
442
  ```
396
443
 
444
+ When subscribing to relations, callbacks receive `(entity, target)` so you know which relation pair changed. Relation `onChange` events are triggered by `entity.set(Relation(target), data)` and only on relations with data via the store prop.
445
+
397
446
  ```js
398
- // Returns all queryable entities
399
- const allQueryableEntities = world.query()
447
+ const Likes = relation()
448
+
449
+ const unsub = world.onAdd(Likes, (entity, target) => {
450
+ console.log(`Entity ${entity} likes ${target}`)
451
+ })
400
452
  ```
401
453
 
402
454
  ### Change detection with `updateEach`
@@ -808,7 +860,7 @@ const positions = getStore(world, Position)
808
860
 
809
861
  A Koota query is a lot like a database query. Parameters define how to find entities and efficiently process them in batches. Queries are the primary way to update and transform your app state, similar to how you'd use SQL to filter and modify database records.
810
862
 
811
- #### Caching queries
863
+ #### Defining queries
812
864
 
813
865
  Inline queries are great for readability and are optimized to be as fast as possible, but there is still some small overhead in hashing the query each time it is called.
814
866
 
@@ -820,13 +872,13 @@ function updateMovement(world) {
820
872
  }
821
873
  ```
822
874
 
823
- While this is not likely to be a bottleneck in your code compared to the actual update function, if you want to save these CPU cycles you can cache the query ahead of time and use the returned key. This will have the additional effect of creating the internal query immediately on a worlds, otherwise it will get created the first time it is run.
875
+ While this is not likely to be a bottleneck in your code compared to the actual update function, if you want to save these CPU cycles you can cache the query ahead of time and use the returned ref. This will have the additional effect of creating the internal query immediately on all worlds, otherwise it will get created the first time it is run.
824
876
 
825
877
  ```js
826
878
  // The internal query is created immediately before it is invoked
827
- const movementQuery = cacheQuery(Position, Velocity)
879
+ const movementQuery = defineQuery(Position, Velocity)
828
880
 
829
- // They query key is hashed ahead of time and we just use it
881
+ // The query ref is used for fast array-based lookup
830
882
  function updateMovement(world) {
831
883
  world.query(movementQuery).updateEach(([pos, vel]) => {})
832
884
  }
@@ -834,7 +886,7 @@ function updateMovement(world) {
834
886
 
835
887
  #### Query all entities
836
888
 
837
- To get all queryable entities you simply query with no parameters.
889
+ To get all queryable entities you simply query the world with no parameters.
838
890
 
839
891
  ```js
840
892
  const allEntities = world.query()
@@ -1014,13 +1066,51 @@ useTraitEffect(world, GameState, (state) => {
1014
1066
  })
1015
1067
  ```
1016
1068
 
1069
+ ### `useTarget`
1070
+
1071
+ Observes an entity, or world, for a relation and reactively returns the first target entity. Returns `undefined` if no target exists.
1072
+
1073
+ ```js
1074
+ const ChildOf = relation()
1075
+
1076
+ function ParentDisplay({ entity }) {
1077
+ // Returns the first target of the ChildOf relation
1078
+ const parent = useTarget(entity, ChildOf)
1079
+
1080
+ if (!parent) return <div>No parent</div>
1081
+
1082
+ return <div>Parent: {parent.id()}</div>
1083
+ }
1084
+ ```
1085
+
1086
+ ### `useTargets`
1087
+
1088
+ Observes an entity, or world, for a relation and reactively returns all target entities as an array. Returns an empty array if no targets exist.
1089
+
1090
+ ```js
1091
+ const Contains = relation()
1092
+
1093
+ function InventoryDisplay({ entity }) {
1094
+ // Returns all targets of the Contains relation
1095
+ const items = useTargets(entity, Contains)
1096
+
1097
+ return (
1098
+ <ul>
1099
+ {items.map((item) => (
1100
+ <li key={item.id()}>Item {item.id()}</li>
1101
+ ))}
1102
+ </ul>
1103
+ )
1104
+ }
1105
+ ```
1106
+
1017
1107
  ### `useActions`
1018
1108
 
1019
- Returns actions bound to the world that is context. Use actions created by `createActions`.
1109
+ Returns actions bound to the world that is in context. Use actions created by `defineActions`.
1020
1110
 
1021
1111
  ```js
1022
1112
  // Create actions
1023
- const actions = createActions((world) => ({
1113
+ const actions = defineActions((world) => ({
1024
1114
  spawnPlayer: () => world.spawn(IsPlayer).
1025
1115
  destroyAllPlayers: () => {
1026
1116
  world.query(IsPlayer).forEach((player) => {