@latticexyz/recs 2.0.12-main-d7526607 → 2.0.12-main-96e7bf43

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.
@@ -1,236 +0,0 @@
1
- import { merge, Observable } from "rxjs";
2
- import { mapObject, awaitStreamValue, uuid } from "@latticexyz/utils";
3
- import { ActionState } from "./constants";
4
- import { ActionData, ActionRequest } from "./types";
5
- import { defineActionComponent } from "./defineActionComponent";
6
- import { overridableComponent, setComponent, getComponentValue, updateComponent } from "../Component";
7
- import { createEntity } from "../Entity";
8
- import { World, OverridableComponent, Metadata, Component, Components, Entity, Schema } from "../types";
9
-
10
- export type ActionSystem = ReturnType<typeof createActionSystem>;
11
-
12
- /**
13
- * @deprecated For now, we suggest using `overridableComponent(Component)` and `addOverride`/`removeOverride` to manage overrides yourself.
14
- */
15
- export function createActionSystem<M = unknown>(
16
- world: World,
17
- txReduced$: Observable<string>,
18
- waitForTransaction?: (tx: string) => Promise<void>,
19
- ) {
20
- // Action component
21
- const Action = defineActionComponent<M>(world);
22
-
23
- // Components that scheduled actions depend on including pending updates
24
- const componentsWithOptimisticUpdates: { [id: string]: OverridableComponent<Schema> } = {};
25
-
26
- // ActionData contains requirements and execute logic of scheduled actions.
27
- // We also store the relevant subset of all componentsWithOptimisticUpdates in the action data,
28
- // to recheck requirements only if relevant components updated.
29
- const actionData = new Map<string, ActionData>();
30
-
31
- // Disposers of requirement check autoruns for all pending actions
32
- const disposer = new Map<string, { dispose: () => void }>();
33
- world.registerDisposer(() => {
34
- for (const { dispose } of disposer.values()) dispose();
35
- });
36
-
37
- /**
38
- * Maps all components in a given components map to the respective components including pending updates
39
- * @param component Component to be mapped to components including pending updates
40
- * @returns Components including pending updates
41
- */
42
- function withOptimisticUpdates<S extends Schema, M extends Metadata, T>(
43
- component: Component<S, M, T>,
44
- ): OverridableComponent<S, M, T> {
45
- const optimisticComponent = componentsWithOptimisticUpdates[component.id] || overridableComponent(component);
46
-
47
- // If the component is not tracked yet, add it to the map of overridable components
48
- if (!componentsWithOptimisticUpdates[component.id]) {
49
- componentsWithOptimisticUpdates[component.id] = optimisticComponent;
50
- }
51
-
52
- // Typescript can't know that the optimistic component with this id has the same type as C
53
- return optimisticComponent as OverridableComponent<S, M, T>;
54
- }
55
-
56
- /**
57
- * Schedules an action. The action will be executed once its requirement is fulfilled.
58
- * Note: the requirement will only be rechecked automatically if the requirement is based on components
59
- * (or other mobx-observable values).
60
- * @param actionRequest Action to be scheduled
61
- * @returns index of the entity created for the action
62
- */
63
- function add<C extends Components, T>(actionRequest: ActionRequest<C, T, M>): Entity {
64
- // Prevent the same actions from being scheduled multiple times
65
- const existingAction = world.hasEntity(actionRequest.id as Entity);
66
- if (existingAction) {
67
- console.warn(`Action with id ${actionRequest.id} is already requested.`);
68
- return actionRequest.id as Entity;
69
- }
70
-
71
- // Set the action component
72
- const entity = createEntity(world, undefined, {
73
- id: actionRequest.id,
74
- });
75
-
76
- setComponent(Action, entity, {
77
- state: ActionState.Requested,
78
- on: actionRequest.on,
79
- metadata: actionRequest.metadata,
80
- overrides: undefined,
81
- txHash: undefined,
82
- });
83
-
84
- // Add components that are not tracked yet to internal overridable component map.
85
- // Pending updates will be applied to internal overridable components.
86
- for (const component of Object.values(actionRequest.components)) {
87
- if (!componentsWithOptimisticUpdates[component.id])
88
- componentsWithOptimisticUpdates[component.id] = overridableComponent(component);
89
- }
90
-
91
- // Store relevant components with pending updates along the action's requirement and execution logic
92
- const action = {
93
- ...actionRequest,
94
- entity,
95
- componentsWithOptimisticUpdates: mapObject(actionRequest.components, (c) => withOptimisticUpdates(c)),
96
- } as unknown as ActionData;
97
- actionData.set(action.id, action);
98
-
99
- // This subscriotion makes sure the action requirement is checked again every time
100
- // one of the referenced components changes or the pending updates map changes
101
- const subscription = merge(
102
- ...Object.values(action.componentsWithOptimisticUpdates).map((c) => c.update$),
103
- ).subscribe(() => checkRequirement(action));
104
- checkRequirement(action);
105
- disposer.set(action.id, { dispose: () => subscription?.unsubscribe() });
106
-
107
- return entity;
108
- }
109
-
110
- /**
111
- * Checks the requirement of a given action and executes the action if the requirement is fulfilled
112
- * @param action Action to check the requirement of
113
- * @returns void
114
- */
115
- function checkRequirement(action: ActionData) {
116
- // Only check requirements of requested actions
117
- if (getComponentValue(Action, action.entity)?.state !== ActionState.Requested) return;
118
-
119
- // Check requirement on components including pending updates
120
- const requirementResult = action.requirement(action.componentsWithOptimisticUpdates);
121
-
122
- // Execute the action if the requirements are met
123
- if (requirementResult) executeAction(action, requirementResult);
124
- }
125
-
126
- /**
127
- * Executes the given action and sets the corresponding Action component
128
- * @param action ActionData of the action to be executed
129
- * @param requirementResult Result of the action's requirement function
130
- * @returns void
131
- */
132
- async function executeAction<T>(action: ActionData, requirementResult: T) {
133
- // Only execute actions that were requested before
134
- if (getComponentValue(Action, action.entity)?.state !== ActionState.Requested) return;
135
-
136
- // Update the action state
137
- updateComponent(Action, action.entity, { state: ActionState.Executing });
138
-
139
- // Compute overrides
140
- const overrides = action
141
- .updates(action.componentsWithOptimisticUpdates, requirementResult)
142
- .map((o) => ({ ...o, id: uuid() }));
143
-
144
- // Store overrides on Action component to be able to remove when action is done
145
- updateComponent(Action, action.entity, { overrides: overrides.map((o) => `${o.id}/${o.component.id}`) });
146
-
147
- // Set all pending updates of this action
148
- for (const { component, value, entity, id } of overrides) {
149
- componentsWithOptimisticUpdates[component.id].addOverride(id, { entity, value });
150
- }
151
-
152
- try {
153
- // Execute the action
154
- const tx = await action.execute(requirementResult);
155
-
156
- // If the result includes a hash key (single tx) or hashes (multiple tx) key, wait for the transactions to complete before removing the pending actions
157
- if (tx) {
158
- // Wait for all tx events to be reduced
159
- updateComponent(Action, action.entity, { state: ActionState.WaitingForTxEvents, txHash: tx });
160
- await awaitStreamValue(txReduced$, (v) => v === tx);
161
- updateComponent(Action, action.entity, { state: ActionState.TxReduced });
162
- // TODO: extend ActionData type to be aware of whether waitForTransaction is set
163
- if (action.awaitConfirmation) {
164
- if (waitForTransaction) {
165
- await waitForTransaction(tx);
166
- } else {
167
- throw new Error("action has awaitConfirmation but no waitForTransaction specified in createActionSystem");
168
- }
169
- }
170
- }
171
-
172
- updateComponent(Action, action.entity, { state: ActionState.Complete });
173
- } catch (e) {
174
- handleError(e, action);
175
- }
176
-
177
- // After the action is done executing (failed or completed), remove its actionData and remove the Action component
178
- remove(action.id);
179
- }
180
-
181
- // Set the action's state to ActionState.Failed
182
- function handleError(e: unknown, action: ActionData) {
183
- console.error(e);
184
- updateComponent(Action, action.entity, { state: ActionState.Failed });
185
- remove(action.id);
186
- }
187
-
188
- /**
189
- * Cancels the action with the given ID if it is in the "Requested" state.
190
- * @param actionId ID of the action to be cancelled
191
- * @returns void
192
- */
193
- function cancel(actionId: string): boolean {
194
- const action = actionData.get(actionId);
195
- if (!action || getComponentValue(Action, action.entity)?.state !== ActionState.Requested) {
196
- console.warn(`Action ${actionId} was not found or is not in the "Requested" state.`);
197
- return false;
198
- }
199
- updateComponent(Action, action.entity, { state: ActionState.Cancelled });
200
- remove(actionId);
201
- return true;
202
- }
203
-
204
- /**
205
- * Removes actionData disposer of the action with the given ID and removes its pending updates.
206
- * @param actionId ID of the action to be removed
207
- */
208
- function remove(actionId: string) {
209
- const action = actionData.get(actionId);
210
- if (!action) {
211
- console.warn(`Trying to remove action ${actionId} that does not exist.`);
212
- return;
213
- }
214
-
215
- // Remove this action's pending updates
216
- const actionEntity = actionId as Entity;
217
- const overrides = (actionEntity != null && getComponentValue(Action, actionEntity)?.overrides) || [];
218
- for (const override of overrides) {
219
- const [id, componentId] = override.split("/");
220
- const component = componentsWithOptimisticUpdates[componentId];
221
- component.removeOverride(id);
222
- }
223
-
224
- // Remove this action's autorun and corresponding disposer
225
- disposer.get(actionId)?.dispose();
226
- disposer.delete(actionId);
227
-
228
- // Remove the action data
229
- actionData.delete(actionId);
230
-
231
- // Remove the action entity after some time
232
- actionEntity != null && setTimeout(() => world.deleteEntity(actionEntity), 5000);
233
- }
234
-
235
- return { add, cancel, withOptimisticUpdates, Action };
236
- }
@@ -1,18 +0,0 @@
1
- import { defineComponent } from "../Component";
2
- import { Type } from "../constants";
3
- import { World, Component, SchemaOf, Metadata } from "../types";
4
-
5
- export function defineActionComponent<T = unknown>(world: World) {
6
- const Action = defineComponent(
7
- world,
8
- {
9
- state: Type.String,
10
- on: Type.OptionalEntity,
11
- metadata: Type.OptionalT,
12
- overrides: Type.OptionalStringArray,
13
- txHash: Type.OptionalString,
14
- },
15
- { id: "Action" },
16
- );
17
- return Action as Component<SchemaOf<typeof Action>, Metadata, T>;
18
- }
@@ -1,2 +0,0 @@
1
- export * from "./constants";
2
- export * from "./createActionSystem";
@@ -1,45 +0,0 @@
1
- import { ValueOf } from "@latticexyz/utils";
2
- import { Components, Entity, Override, SchemaOf } from "../types";
3
-
4
- export type ComponentUpdate<C extends Components> = ValueOf<{
5
- [key in keyof C]: {
6
- component: C[key];
7
- entity: Entity;
8
- value: Override<SchemaOf<C[key]>>["value"];
9
- };
10
- }>;
11
-
12
- export type ActionRequest<C extends Components, T, M = unknown> = {
13
- // Identifier of this action. Will be used as entity id of the Action component.
14
- id: string;
15
-
16
- // Specify which entity this action is related to.
17
- on?: Entity;
18
-
19
- // Components this action depends on in requirement and updates
20
- components: C;
21
-
22
- // Action will be executed once requirement function returns a truthy value.
23
- // Requirement will be rechecked if any component value accessed in the requirement changes (including optimistic updates)
24
- requirement: (componentsWithOptimisticUpdates: C) => T | null;
25
-
26
- // Declare effects this action will have on components.
27
- // Used to compute component values with optimistic updates for other requested actions.
28
- updates: (componentsWithOptimisticUpdates: C, data: T) => ComponentUpdate<C>[];
29
-
30
- // Logic to be executed when the action is executed.
31
- // If txHashes are returned from the txQueue, the action will only be completed (and pending updates removed)
32
- // once all events from the given txHashes have been received and reduced.
33
- execute: (data: T) => Promise<string> | Promise<void> | void | undefined;
34
-
35
- // Flag to set if the queue should wait for the underlying transaction to be confirmed (in addition to being reduced)
36
- awaitConfirmation?: boolean;
37
-
38
- // Metadata
39
- metadata?: M;
40
- };
41
-
42
- export type ActionData<M = unknown> = ActionRequest<Components, unknown, M> & {
43
- componentsWithOptimisticUpdates: Components;
44
- entity: Entity;
45
- };
@@ -1,15 +0,0 @@
1
- import { Entity } from "../types";
2
- import { ActionState } from "./constants";
3
- import { defineActionComponent } from "./defineActionComponent";
4
- import { waitForComponentValueIn } from "./waitForComponentValueIn";
5
-
6
- export async function waitForActionCompletion(
7
- Action: ReturnType<typeof defineActionComponent>,
8
- entity: Entity,
9
- ): Promise<void> {
10
- return waitForComponentValueIn(Action, entity, [
11
- { state: ActionState.Cancelled },
12
- { state: ActionState.Failed },
13
- { state: ActionState.Complete },
14
- ]);
15
- }
@@ -1,38 +0,0 @@
1
- import { deferred } from "@latticexyz/utils";
2
- import { filter, map, startWith } from "rxjs";
3
- import { componentValueEquals, getComponentValue } from "../Component";
4
- import { Component, Metadata, Entity, ComponentValue, Schema } from "../types";
5
-
6
- export function waitForComponentValueIn<S extends Schema, T>(
7
- component: Component<S, Metadata, T>,
8
- entity: Entity,
9
- values: Partial<ComponentValue<S>>[],
10
- ): Promise<void> {
11
- const [resolve, , promise] = deferred<void>();
12
-
13
- const currentValue = getComponentValue(component, entity);
14
- if (values.find((value) => componentValueEquals(value, currentValue))) {
15
- resolve();
16
- }
17
-
18
- let dispose = resolve;
19
-
20
- const value$ = component.update$.pipe(
21
- filter((update) => update.entity === entity), // Ignore updates of other entities
22
- map((update) => update.value[0]), // Map the update to the current value
23
- );
24
-
25
- const subscription = value$
26
- .pipe(
27
- startWith(getComponentValue(component, entity)),
28
- filter((currentValue) => Boolean(values.find((searchValue) => componentValueEquals(searchValue, currentValue)))),
29
- )
30
- .subscribe(() => {
31
- resolve();
32
- dispose();
33
- });
34
-
35
- dispose = () => subscription?.unsubscribe();
36
-
37
- return promise;
38
- }
package/src/index.ts DELETED
@@ -1,9 +0,0 @@
1
- export * from "./Component";
2
- export * from "./Indexer";
3
- export * from "./Entity";
4
- export * from "./System";
5
- export * from "./World";
6
- export * from "./Query";
7
- export * from "./types";
8
- export * from "./constants";
9
- export * from "./utils";
package/src/types.ts DELETED
@@ -1,260 +0,0 @@
1
- import { Subject } from "rxjs";
2
- import { Type } from "./constants";
3
- import { Opaque } from "type-fest";
4
-
5
- /**
6
- * Entities are represented as symbols internally for memory efficiency.
7
- * To get the entity's string representation, use `getEntityString(entitySymbol)`
8
- */
9
- export type EntitySymbol = Opaque<symbol, "EntitySymbol">;
10
-
11
- export type Entity = Opaque<string, "Entity">;
12
-
13
- /**
14
- * Used to define the schema of a {@link Component}.
15
- * Uses {@link Type} enum to be able to access the component type in JavaScript as well as have TypeScript type checks.
16
- */
17
- export type Schema = {
18
- [key: string]: Type;
19
- };
20
-
21
- /**
22
- * Used to add arbitrary metadata to components.
23
- */
24
- export type Metadata =
25
- | {
26
- [key: string]: unknown;
27
- }
28
- | undefined;
29
-
30
- /**
31
- * Mapping between JavaScript {@link Type} enum and corresponding TypeScript type.
32
- */
33
- export type ValueType<T = unknown> = {
34
- [Type.Boolean]: boolean;
35
- [Type.Number]: number;
36
- [Type.BigInt]: bigint;
37
- [Type.String]: string;
38
- [Type.NumberArray]: number[];
39
- [Type.BigIntArray]: bigint[];
40
- [Type.StringArray]: string[];
41
- [Type.Entity]: Entity;
42
- [Type.EntityArray]: Entity[];
43
- [Type.OptionalNumber]: number | undefined;
44
- [Type.OptionalBigInt]: bigint | undefined;
45
- [Type.OptionalBigIntArray]: bigint[] | undefined;
46
- [Type.OptionalString]: string | undefined;
47
- [Type.OptionalNumberArray]: number[] | undefined;
48
- [Type.OptionalStringArray]: string[] | undefined;
49
- [Type.OptionalEntity]: Entity | undefined;
50
- [Type.OptionalEntityArray]: Entity[] | undefined;
51
- [Type.T]: T;
52
- [Type.OptionalT]: T | undefined;
53
- };
54
-
55
- /**
56
- * Used to infer the TypeScript type of a component value corresponding to a given {@link Schema}.
57
- */
58
- export type ComponentValue<S extends Schema = Schema, T = unknown> = {
59
- [key in keyof S]: ValueType<T>[S[key]];
60
- };
61
-
62
- /**
63
- * Type of a component update corresponding to a given {@link Schema}.
64
- */
65
- export type ComponentUpdate<S extends Schema = Schema, T = unknown> = {
66
- entity: Entity;
67
- value: [ComponentValue<S, T> | undefined, ComponentValue<S, T> | undefined];
68
- component: Component<S, Metadata, T>;
69
- };
70
-
71
- /**
72
- * Type of component returned by {@link defineComponent}.
73
- */
74
- export interface Component<S extends Schema = Schema, M extends Metadata = Metadata, T = unknown> {
75
- id: string;
76
- values: { [key in keyof S]: Map<EntitySymbol, ValueType<T>[S[key]]> };
77
- schema: S;
78
- metadata: M;
79
- entities: () => IterableIterator<Entity>;
80
- world: World;
81
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
82
- update$: Subject<ComponentUpdate<S, T>> & { observers: any };
83
- }
84
-
85
- /**
86
- * Type of indexer returned by {@link createIndexer}.
87
- */
88
- export type Indexer<S extends Schema, M extends Metadata = Metadata, T = unknown> = Component<S, M, T> & {
89
- getEntitiesWithValue: (value: ComponentValue<S, T>) => Set<Entity>;
90
- };
91
-
92
- export type Components = {
93
- [key: string]: Component;
94
- };
95
-
96
- export interface ComponentWithStream<S extends Schema, T = unknown> extends Component<S, Metadata, T> {
97
- stream$: Subject<{ entity: Entity; value: ComponentValue<S, T> | undefined }>;
98
- }
99
-
100
- export type AnyComponentValue = ComponentValue<Schema>;
101
-
102
- export type AnyComponent = Component<Schema>;
103
-
104
- /**
105
- * Type of World returned by {@link createWorld}.
106
- */
107
- export type World = {
108
- registerEntity: (options?: { id?: string; idSuffix?: string }) => Entity;
109
- registerComponent: (component: Component) => void;
110
- components: Component[];
111
- getEntities: () => IterableIterator<Entity>;
112
- dispose: () => void;
113
- registerDisposer: (disposer: () => void) => void;
114
- hasEntity: (entity: Entity) => boolean;
115
- deleteEntity: (entity: Entity) => void;
116
- entitySymbols: Set<EntitySymbol>;
117
- };
118
-
119
- export enum QueryFragmentType {
120
- Has,
121
- HasValue,
122
- Not,
123
- NotValue,
124
- ProxyRead,
125
- ProxyExpand,
126
- }
127
-
128
- export type HasQueryFragment<T extends Schema> = {
129
- type: QueryFragmentType.Has;
130
- component: Component<T>;
131
- };
132
-
133
- export type HasValueQueryFragment<T extends Schema> = {
134
- type: QueryFragmentType.HasValue;
135
- component: Component<T>;
136
- value: Partial<ComponentValue<T>>;
137
- };
138
-
139
- export type NotQueryFragment<T extends Schema> = {
140
- type: QueryFragmentType.Not;
141
- component: Component<T>;
142
- };
143
-
144
- export type NotValueQueryFragment<T extends Schema> = {
145
- type: QueryFragmentType.NotValue;
146
- component: Component<T>;
147
- value: Partial<ComponentValue<T>>;
148
- };
149
-
150
- export type ProxyReadQueryFragment = {
151
- type: QueryFragmentType.ProxyRead;
152
- component: Component<{ value: Type.Entity }>;
153
- depth: number;
154
- };
155
-
156
- export type ProxyExpandQueryFragment = {
157
- type: QueryFragmentType.ProxyExpand;
158
- component: Component<{ value: Type.Entity }>;
159
- depth: number;
160
- };
161
-
162
- export type QueryFragment<T extends Schema = Schema> =
163
- | HasQueryFragment<T>
164
- | HasValueQueryFragment<T>
165
- | NotQueryFragment<T>
166
- | NotValueQueryFragment<T>
167
- | ProxyReadQueryFragment
168
- | ProxyExpandQueryFragment;
169
-
170
- export type EntityQueryFragment<T extends Schema = Schema> =
171
- | HasQueryFragment<T>
172
- | HasValueQueryFragment<T>
173
- | NotQueryFragment<T>
174
- | NotValueQueryFragment<T>;
175
-
176
- export type SettingQueryFragment = ProxyReadQueryFragment | ProxyExpandQueryFragment;
177
-
178
- export type QueryFragments = QueryFragment<Schema>[];
179
-
180
- export type SchemaOf<C extends Component<Schema>> = C extends Component<infer S> ? S : never;
181
-
182
- export type Override<S extends Schema, T = unknown> = {
183
- entity: Entity;
184
- value: Partial<ComponentValue<S, T>> | null;
185
- };
186
-
187
- /**
188
- * Type of overridable component returned by {@link overridableComponent}.
189
- */
190
- export type OverridableComponent<S extends Schema = Schema, M extends Metadata = Metadata, T = unknown> = Component<
191
- S,
192
- M,
193
- T
194
- > & {
195
- addOverride: (overrideId: string, update: Override<S, T>) => void;
196
- removeOverride: (overrideId: string) => void;
197
- };
198
-
199
- export type OptionalType =
200
- | Type.OptionalNumber
201
- | Type.OptionalBigInt
202
- | Type.OptionalString
203
- | Type.OptionalEntity
204
- | Type.OptionalNumberArray
205
- | Type.OptionalBigIntArray
206
- | Type.OptionalStringArray
207
- | Type.OptionalEntityArray;
208
-
209
- export function isOptionalType(t: Type): t is OptionalType {
210
- return [
211
- Type.OptionalNumber,
212
- Type.OptionalBigInt,
213
- Type.OptionalString,
214
- Type.OptionalEntity,
215
- Type.OptionalEntityArray,
216
- Type.OptionalNumberArray,
217
- Type.OptionalBigIntArray,
218
- Type.OptionalStringArray,
219
- ].includes(t);
220
- }
221
-
222
- export type ArrayType =
223
- | Type.NumberArray
224
- | Type.OptionalNumberArray
225
- | Type.BigIntArray
226
- | Type.OptionalBigIntArray
227
- | Type.StringArray
228
- | Type.OptionalStringArray
229
- | Type.EntityArray
230
- | Type.OptionalEntityArray;
231
-
232
- export function isArrayType(t: Type): t is ArrayType {
233
- return [
234
- Type.NumberArray,
235
- Type.OptionalNumberArray,
236
- Type.BigIntArray,
237
- Type.OptionalBigIntArray,
238
- Type.StringArray,
239
- Type.OptionalStringArray,
240
- Type.EntityArray,
241
- Type.OptionalEntityArray,
242
- ].includes(t);
243
- }
244
-
245
- export type NumberType = Type.Number | Type.OptionalNumber;
246
- export function isNumberType(t: Type): t is NumberType {
247
- return [Type.Number, Type.OptionalNumber].includes(t);
248
- }
249
-
250
- export type EntityType = Type.Entity | Type.OptionalEntity;
251
- export function isEntityType(t: Type): t is EntityType {
252
- return [Type.Entity, Type.OptionalEntity].includes(t);
253
- }
254
-
255
- export type Layer = {
256
- world: World;
257
- components: Record<string, Component<Schema>>;
258
- };
259
-
260
- export type Layers = Record<string, Layer>;