diff --git a/packages/recs/src/Component.ts b/packages/recs/src/Component.ts index dc4be8750c..58a56e8047 100644 --- a/packages/recs/src/Component.ts +++ b/packages/recs/src/Component.ts @@ -18,6 +18,11 @@ import { import { isFullComponentValue, isIndexer } from "./utils"; import { getEntityString, getEntitySymbol } from "./Entity"; +export type ComponentMutationOptions = { + /** Skip publishing this mutation to the component's update stream. Mostly used internally during initial hydration. */ + skipUpdateStream?: boolean; +}; + function getComponentName(component: Component) { return ( component.metadata?.componentName ?? @@ -80,7 +85,8 @@ export function defineComponent( component: Component, entity: Entity, - value: ComponentValue + value: ComponentValue, + options: ComponentMutationOptions = {} ) { const entitySymbol = getEntitySymbol(entity); const prevValue = getComponentValue(component, entity); @@ -109,7 +115,9 @@ export function setComponent( } } } - component.update$.next({ entity, value: [value, prevValue], component }); + if (!options.skipUpdateStream) { + component.update$.next({ entity, value: [value, prevValue], component }); + } } /** @@ -132,16 +140,17 @@ export function updateComponent( component: Component, entity: Entity, value: Partial>, - initialValue?: ComponentValue + initialValue?: ComponentValue, + options: ComponentMutationOptions = {} ) { const currentValue = getComponentValue(component, entity); if (currentValue === undefined) { if (initialValue === undefined) { throw new Error(`Can't update component ${getComponentName(component)} without a current value or initial value`); } - setComponent(component, entity, { ...initialValue, ...value }); + setComponent(component, entity, { ...initialValue, ...value }, options); } else { - setComponent(component, entity, { ...currentValue, ...value }); + setComponent(component, entity, { ...currentValue, ...value }, options); } } @@ -153,14 +162,17 @@ export function updateComponent( */ export function removeComponent( component: Component, - entity: Entity + entity: Entity, + options: ComponentMutationOptions = {} ) { const entitySymbol = getEntitySymbol(entity); const prevValue = getComponentValue(component, entity); for (const key of Object.keys(component.values)) { component.values[key].delete(entitySymbol); } - component.update$.next({ entity, value: [undefined, prevValue], component }); + if (!options.skipUpdateStream) { + component.update$.next({ entity, value: [undefined, prevValue], component }); + } } /** diff --git a/packages/store-sync/src/recs/recsStorage.ts b/packages/store-sync/src/recs/recsStorage.ts index 9debe2652f..d8fab3446e 100644 --- a/packages/store-sync/src/recs/recsStorage.ts +++ b/packages/store-sync/src/recs/recsStorage.ts @@ -24,6 +24,7 @@ import worldConfig from "@latticexyz/world/mud.config"; export type RecsStorageOptions = { world: RecsWorld; config: TConfig; + shouldSkipUpdateStream?: () => boolean; }; export type RecsStorageAdapter = { @@ -38,6 +39,7 @@ export type RecsStorageAdapter = { export function recsStorage({ world, config, + shouldSkipUpdateStream, }: RecsStorageOptions): RecsStorageAdapter { world.registerEntity({ id: singletonEntity }); @@ -52,7 +54,12 @@ export function recsStorage({ async registerTables({ tables }) { for (const table of tables) { // TODO: check if table exists already and skip/warn? - setComponent(components.RegisteredTables, getTableEntity(table), { table }); + setComponent( + components.RegisteredTables, + getTableEntity(table), + { table }, + { skipUpdateStream: shouldSkipUpdateStream?.() } + ); } }, async getTables({ tables }) { @@ -87,7 +94,9 @@ export function recsStorage({ if (operation.type === "SetRecord") { debug("setting component", tableId, entity, operation.value); - setComponent(component, entity, operation.value as ComponentValue); + setComponent(component, entity, operation.value as ComponentValue, { + skipUpdateStream: shouldSkipUpdateStream?.(), + }); } else if (operation.type === "SetField") { debug("updating component", tableId, entity, { [operation.fieldName]: operation.fieldValue, @@ -96,11 +105,12 @@ export function recsStorage({ component, entity, { [operation.fieldName]: operation.fieldValue } as ComponentValue, - schemaToDefaults(table.valueSchema) as ComponentValue + schemaToDefaults(table.valueSchema) as ComponentValue, + { skipUpdateStream: shouldSkipUpdateStream?.() } ); } else if (operation.type === "DeleteRecord") { debug("deleting component", tableId, entity); - removeComponent(component, entity); + removeComponent(component, entity, { skipUpdateStream: shouldSkipUpdateStream?.() }); } } }, diff --git a/packages/store-sync/src/recs/syncToRecs.ts b/packages/store-sync/src/recs/syncToRecs.ts index 8321102b4c..ceb9735a58 100644 --- a/packages/store-sync/src/recs/syncToRecs.ts +++ b/packages/store-sync/src/recs/syncToRecs.ts @@ -30,7 +30,12 @@ export async function syncToRecs({ tableIds, matchId, }: SyncToRecsOptions): Promise> { - const { storageAdapter, components } = recsStorage({ world, config }); + const { storageAdapter, components } = recsStorage({ + world, + config, + shouldSkipUpdateStream: (): boolean => + getComponentValue(components.SyncProgress, singletonEntity)?.step !== SyncStep.LIVE, + }); const storeSync = await createStoreSync({ storageAdapter,