Skip to content

Commit

Permalink
feat(recs,store-sync): skip update stream when hydrating (#1661)
Browse files Browse the repository at this point in the history
  • Loading branch information
holic authored Oct 3, 2023
1 parent 6264008 commit c714e5d
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 34 deletions.
26 changes: 19 additions & 7 deletions packages/recs/src/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any, any, any>) {
return (
component.metadata?.componentName ??
Expand Down Expand Up @@ -80,7 +85,8 @@ export function defineComponent<S extends Schema, M extends Metadata, T = unknow
export function setComponent<S extends Schema, T = unknown>(
component: Component<S, Metadata, T>,
entity: Entity,
value: ComponentValue<S, T>
value: ComponentValue<S, T>,
options: ComponentMutationOptions = {}
) {
const entitySymbol = getEntitySymbol(entity);
const prevValue = getComponentValue(component, entity);
Expand Down Expand Up @@ -109,7 +115,9 @@ export function setComponent<S extends Schema, T = unknown>(
}
}
}
component.update$.next({ entity, value: [value, prevValue], component });
if (!options.skipUpdateStream) {
component.update$.next({ entity, value: [value, prevValue], component });
}
}

/**
Expand All @@ -132,16 +140,17 @@ export function updateComponent<S extends Schema, T = unknown>(
component: Component<S, Metadata, T>,
entity: Entity,
value: Partial<ComponentValue<S, T>>,
initialValue?: ComponentValue<S, T>
initialValue?: ComponentValue<S, T>,
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);
}
}

Expand All @@ -153,14 +162,17 @@ export function updateComponent<S extends Schema, T = unknown>(
*/
export function removeComponent<S extends Schema, M extends Metadata, T = unknown>(
component: Component<S, M, T>,
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 });
}
}

/**
Expand Down
56 changes: 39 additions & 17 deletions packages/store-sync/src/recs/recsStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type RecsStorageOptions<TConfig extends StoreConfig = StoreConfig> = {
world: RecsWorld;
// TODO: make config optional?
config: TConfig;
shouldSkipUpdateStream?: () => boolean;
};

export type RecsStorageAdapter<TConfig extends StoreConfig = StoreConfig> = {
Expand All @@ -33,6 +34,7 @@ export type RecsStorageAdapter<TConfig extends StoreConfig = StoreConfig> = {
export function recsStorage<TConfig extends StoreConfig = StoreConfig>({
world,
config,
shouldSkipUpdateStream,
}: RecsStorageOptions<TConfig>): RecsStorageAdapter<TConfig> {
world.registerEntity({ id: singletonEntity });

Expand All @@ -53,7 +55,12 @@ export function recsStorage<TConfig extends StoreConfig = StoreConfig>({
existingTable: getComponentValue(components.RegisteredTables, tableEntity)?.table,
});
} else {
setComponent(components.RegisteredTables, tableEntity, { table: newTable });
setComponent(
components.RegisteredTables,
tableEntity,
{ table: newTable },
{ skipUpdateStream: shouldSkipUpdateStream?.() }
);
}
}

Expand Down Expand Up @@ -88,12 +95,17 @@ export function recsStorage<TConfig extends StoreConfig = StoreConfig>({
entity,
value,
});
setComponent(component, entity, {
...value,
__staticData: log.args.staticData,
__encodedLengths: log.args.encodedLengths,
__dynamicData: log.args.dynamicData,
});
setComponent(
component,
entity,
{
...value,
__staticData: log.args.staticData,
__encodedLengths: log.args.encodedLengths,
__dynamicData: log.args.dynamicData,
},
{ skipUpdateStream: shouldSkipUpdateStream?.() }
);
} else if (log.eventName === "Store_SpliceStaticData") {
// TODO: add tests that this works when no record had been set before
const previousValue = getComponentValue(component, entity);
Expand All @@ -113,10 +125,15 @@ export function recsStorage<TConfig extends StoreConfig = StoreConfig>({
previousValue,
newValue,
});
setComponent(component, entity, {
...newValue,
__staticData: newStaticData,
});
setComponent(
component,
entity,
{
...newValue,
__staticData: newStaticData,
},
{ skipUpdateStream: shouldSkipUpdateStream?.() }
);
} else if (log.eventName === "Store_SpliceDynamicData") {
// TODO: add tests that this works when no record had been set before
const previousValue = getComponentValue(component, entity);
Expand All @@ -137,18 +154,23 @@ export function recsStorage<TConfig extends StoreConfig = StoreConfig>({
previousValue,
newValue,
});
setComponent(component, entity, {
...newValue,
__encodedLengths: log.args.encodedLengths,
__dynamicData: newDynamicData,
});
setComponent(
component,
entity,
{
...newValue,
__encodedLengths: log.args.encodedLengths,
__dynamicData: newDynamicData,
},
{ skipUpdateStream: shouldSkipUpdateStream?.() }
);
} else if (log.eventName === "Store_DeleteRecord") {
debug("deleting component", {
namespace: table.namespace,
name: table.name,
entity,
});
removeComponent(component, entity);
removeComponent(component, entity, { skipUpdateStream: shouldSkipUpdateStream?.() });
}
}
}
Expand Down
38 changes: 28 additions & 10 deletions packages/store-sync/src/recs/syncToRecs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { StoreConfig } from "@latticexyz/store";
import { World as RecsWorld, getComponentValue, setComponent } from "@latticexyz/recs";
import { Component as RecsComponent, World as RecsWorld, getComponentValue, setComponent } from "@latticexyz/recs";
import { SyncOptions, SyncResult } from "../common";
import { RecsStorageAdapter, recsStorage } from "./recsStorage";
import { createStoreSync } from "../createStoreSync";
Expand Down Expand Up @@ -28,7 +28,12 @@ export async function syncToRecs<TConfig extends StoreConfig = StoreConfig>({
indexerUrl,
startSync = true,
}: SyncToRecsOptions<TConfig>): Promise<SyncToRecsResult<TConfig>> {
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,
Expand All @@ -40,14 +45,27 @@ export async function syncToRecs<TConfig extends StoreConfig = StoreConfig>({
indexerUrl,
initialState,
onProgress: ({ step, percentage, latestBlockNumber, lastBlockNumberProcessed, message }) => {
if (getComponentValue(components.SyncProgress, singletonEntity)?.step !== SyncStep.LIVE) {
setComponent(components.SyncProgress, singletonEntity, {
step,
percentage,
latestBlockNumber,
lastBlockNumberProcessed,
message,
});
// already live, no need for more progress updates
if (getComponentValue(components.SyncProgress, singletonEntity)?.step === SyncStep.LIVE) return;

setComponent(components.SyncProgress, singletonEntity, {
step,
percentage,
latestBlockNumber,
lastBlockNumberProcessed,
message,
});

// when we switch to live, trigger update for all entities in all components
if (step === SyncStep.LIVE) {
for (const _component of Object.values(components)) {
// downcast component for easier calling of generic methods on all components
const component = _component as RecsComponent;
for (const entity of component.entities()) {
const value = getComponentValue(component, entity);
component.update$.next({ component, entity, value: [value, value] });
}
}
}
},
});
Expand Down

1 comment on commit c714e5d

@vercel
Copy link

@vercel vercel bot commented on c714e5d Oct 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.