koota 0.1.6 → 0.1.8

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
@@ -71,12 +71,12 @@ function RocketRenderer() {
71
71
  const rockets = useQuery(Position, Velocity)
72
72
  return (
73
73
  <>
74
- {rockets.map((entity) => <Rocket key={entity} entity={entity} />)}
74
+ {rockets.map((entity) => <RocketView key={entity} entity={entity} />)}
75
75
  </>
76
76
  )
77
77
  }
78
78
 
79
- function Rocket({ entity }) {
79
+ function RocketView({ entity }) {
80
80
  // Observes this entity's position trait and reactively updates when it changes
81
81
  const position = useTrait(entity, Position)
82
82
  return (
@@ -322,10 +322,18 @@ world.set(Time, { current: performance.now() });
322
322
  ```
323
323
 
324
324
  ### Select traits on queries for updates
325
- Query filters entity results and `select` is used to choose what traits are fetched for `updateEach` and `useStore`.
325
+ Query filters entity results and `select` is used to choose what traits are fetched for `updateEach` and `useStore`. This can be useful if your query is wider than the data you want to modify.
326
326
 
327
327
  ```js
328
- // Add example when I get the energy
328
+ // The query finds all entities with Position, Velocity and Mass
329
+ world.query(Position, Velocity, Mass)
330
+ // And then select only Mass for updates
331
+ .select(Mass)
332
+ // Only mass will be used in the loop
333
+ .updateEach([mass] => {
334
+ // We are going blackhole
335
+ mass.value += 1
336
+ });
329
337
  ```
330
338
 
331
339
  ### Modifying trait stores direclty
@@ -473,7 +481,7 @@ Both schema-based and callback-based traits are used similarly, but they have di
473
481
 
474
482
  [Learn more about AoS and SoA here](https://en.wikipedia.org/wiki/AoS_and_SoA).
475
483
 
476
- ### Structure of Arrays (SoA) - Schema-based traits
484
+ #### Structure of Arrays (SoA) - Schema-based traits
477
485
 
478
486
  When using a schema, each property is stored in its own array. This can lead to better cache locality when accessing a single property across many entities. This is always the fastest option for data that has intensive operations.
479
487
 
@@ -488,7 +496,7 @@ const store = {
488
496
  };
489
497
  ```
490
498
 
491
- ### Array of Structures (AoS) - Callback-based traits
499
+ #### Array of Structures (AoS) - Callback-based traits
492
500
 
493
501
  When using a callback, each entity's trait data is stored as an object in an array. This is best used for compatibiilty with third party libraries like Three, or class instnaces in general.
494
502
 
@@ -507,6 +515,47 @@ const store = [
507
515
  const Mesh = trait(() => THREE.Mesh())
508
516
  ```
509
517
 
518
+ #### Typing traits
519
+
520
+ Traits can have a schema type passed into its generic. This can be useful if the inferred type is not good enough.
521
+
522
+ ```js
523
+ type AttackerSchema = {
524
+ continueCombo: boolean | null,
525
+ currentStageIndex: number | null,
526
+ stages: Array<AttackStage> | null,
527
+ startedAt: number | null,
528
+ }
529
+
530
+ const Attacker = trait<AttackerSchema>({
531
+ continueCombo: null,
532
+ currentStageIndex: null,
533
+ stages: null,
534
+ startedAt: null,
535
+ })
536
+ ```
537
+
538
+ However, this will not work with interfaces without a workaround due to intended behavior in TypeScript: https://github.com/microsoft/TypeScript/issues/15300
539
+ Interfaces can be used with `Pick` to convert the key signatures into something our type code can understand.
540
+
541
+ ```js
542
+ interface AttackerSchema {
543
+ continueCombo: boolean | null,
544
+ currentStageIndex: number | null,
545
+ stages: Array<AttackStage> | null,
546
+ startedAt: number | null,
547
+ }
548
+
549
+ // Pick is required to not get type errors
550
+ const Attacker = trait<Pick<AttackerSchema, keyof AttackerSchema>>({
551
+ continueCombo: null,
552
+ currentStageIndex: null,
553
+ stages: null,
554
+ startedAt: null,
555
+ })
556
+ ```
557
+
558
+
510
559
  ### React
511
560
 
512
561
  ### `useQuery`
@@ -517,10 +566,10 @@ Reactively updates when entities matching the query changes. Returns a `QueryRes
517
566
  // Get all entities with Position and Velocity traits
518
567
  const entities = useQuery(Position, Velocity);
519
568
 
520
- // Render them
569
+ // Render a view
521
570
  return (
522
571
  <>
523
- {entities.map(entity => <Renderer key={entity.id()} entity={entity} />)}
572
+ {entities.map(entity => <View key={entity.id()} entity={entity} />)}
524
573
  </>
525
574
  );
526
575
  ```
@@ -533,9 +582,9 @@ Works like `useQuery` but only returns the first result. Can either be an entity
533
582
  // Get the first entity with Player and Position traits
534
583
  const player = useQueryFirst(Player, Position);
535
584
 
536
- // Render it if found
585
+ // Render a view if an entity is found
537
586
  return player ? (
538
- <Renderer entity={player} />
587
+ <View entity={player} />
539
588
  ) : null;
540
589
 
541
590
  ```
@@ -577,11 +626,10 @@ function App() {
577
626
 
578
627
  ### `useTrait`
579
628
 
580
- Observes an entity, or world, for a given trait and reactively updates when it is added, removed or changes value.
629
+ Observes an entity, or world, for a given trait and reactively updates when it is added, removed or changes value. The returned trait snapshot maybe `undefined` if the trait is no longer on the target. This can be used to conditionally render.
581
630
 
582
631
  ```js
583
- // Get the position trait from an entity and reactively updates
584
- // when it changes
632
+ // Get the position trait from an entity and reactively updates when it changes
585
633
  const position = useTrait(entity, Position);
586
634
 
587
635
  // If position is removed from entity then it will be undefined
@@ -593,7 +641,28 @@ return (
593
641
  Position: {position.x}, {position.y}
594
642
  </div>
595
643
  );
644
+ ```
645
+
646
+ The entity passed into `useTrait` can be `undefined` or `null`. This helps with situations where `useTrait` is combined with queries in the same component since hooks cannot be conditionally called. However, this means that result can be `undefined` if the trait is not on the entity or if the target is itself `undefined`. In most cases the distinction will not matter, but if it does you can disambiguate by testing the target.
596
647
 
648
+ ```js
649
+ // The entity may be undefined if there is no valid result
650
+ const entity = useQueryFirst(Position, Velocity)
651
+ // useTrait handles this by returned undefined if the target passed in does not exist
652
+ const position = useTrait(entity, Position);
653
+
654
+ // However, undefined here can mean no entity or no component on entity
655
+ // To make the outcome no longer ambiguous you have to test the entity
656
+ if (!entity) return <div>No entity found!</div>
657
+
658
+ // Now this is narrowed to Position no longer being on the component
659
+ if (!position) return null
660
+
661
+ return (
662
+ <div>
663
+ Position: {position.x}, {position.y}
664
+ </div>
665
+ );
597
666
  ```
598
667
 
599
668
  ### `useTraitEffect`
@@ -601,8 +670,7 @@ return (
601
670
  Subscribes a callback to a trait on an entity. This callback fires as an effect whenenver it is added, removed or changes value without rerendering.
602
671
 
603
672
  ```js
604
- // Subscribe to position changes on an entity and update a ref
605
- // without causing a rerender
673
+ // Subscribe to position changes on an entity and update a ref without causing a rerender
606
674
  useTraitEffect(entity, Position, (position) => {
607
675
  if (!position) return;
608
676
  meshRef.current.position.copy(position);