From d8dd63e8055c603d5df41ad47765a286d800c529 Mon Sep 17 00:00:00 2001 From: alvarius <89248902+alvrs@users.noreply.github.com> Date: Tue, 11 Oct 2022 10:37:38 -0500 Subject: [PATCH] fix(solecs): only allow components to register their own updates, feat(std-client): add support for multiple overrides per component per action (#199) * fix(solecs): restrict registerComponentValue{set,remove} to be called by component * feat(std-client): add support for multiple overrides on the same component within one action --- packages/recs/src/types.ts | 4 +-- packages/solecs/src/Component.sol | 4 +-- packages/solecs/src/World.sol | 24 +++++++++++++++++ packages/solecs/src/interfaces/IWorld.sol | 4 +++ .../src/components/ActionComponent.ts | 2 +- .../ActionSystem/createActionSystem.ts | 26 +++++++++++++------ 6 files changed, 51 insertions(+), 13 deletions(-) diff --git a/packages/recs/src/types.ts b/packages/recs/src/types.ts index 7c36cd2591..9a25f78ba6 100644 --- a/packages/recs/src/types.ts +++ b/packages/recs/src/types.ts @@ -191,8 +191,8 @@ export type OverridableComponent & { - addOverride: (actionEntityId: EntityID, update: Override) => void; - removeOverride: (actionEntityId: EntityID) => void; + addOverride: (overrideId: string, update: Override) => void; + removeOverride: (overrideId: string) => void; }; export type OptionalType = diff --git a/packages/solecs/src/Component.sol b/packages/solecs/src/Component.sol index b2cfd9fd91..be1353fd84 100644 --- a/packages/solecs/src/Component.sol +++ b/packages/solecs/src/Component.sol @@ -189,7 +189,7 @@ abstract contract Component is IComponent { } // Emit global event - IWorld(world).registerComponentValueSet(address(this), entity, value); + IWorld(world).registerComponentValueSet(entity, value); } /** @@ -217,6 +217,6 @@ abstract contract Component is IComponent { } // Emit global event - IWorld(world).registerComponentValueRemoved(address(this), entity); + IWorld(world).registerComponentValueRemoved(entity); } } diff --git a/packages/solecs/src/World.sol b/packages/solecs/src/World.sol index 626973531d..0a15703ccc 100644 --- a/packages/solecs/src/World.sol +++ b/packages/solecs/src/World.sol @@ -93,6 +93,7 @@ contract World is IWorld { } /** + * Deprecated - use registerComponentValueSet(entity, data) instead * Register a component value update. * Emits the `ComponentValueSet` event for clients to reconstruct the state. */ @@ -101,11 +102,25 @@ contract World is IWorld { uint256 entity, bytes calldata data ) public requireComponentRegistered(component) { + require(msg.sender == component); Set(entities).add(entity); emit ComponentValueSet(getIdByAddress(_components, component), component, entity, data); } /** + * Register a component value update. + * Emits the `ComponentValueSet` event for clients to reconstruct the state. + */ + function registerComponentValueSet(uint256 entity, bytes calldata data) + public + requireComponentRegistered(msg.sender) + { + Set(entities).add(entity); + emit ComponentValueSet(getIdByAddress(_components, msg.sender), msg.sender, entity, data); + } + + /** + * Deprecated - use registerComponentValueRemoved(entity) instead * Register a component value removal. * Emits the `ComponentValueRemoved` event for clients to reconstruct the state. */ @@ -113,9 +128,18 @@ contract World is IWorld { public requireComponentRegistered(component) { + require(msg.sender == component); emit ComponentValueRemoved(getIdByAddress(_components, component), component, entity); } + /** + * Register a component value removal. + * Emits the `ComponentValueRemoved` event for clients to reconstruct the state. + */ + function registerComponentValueRemoved(uint256 entity) public requireComponentRegistered(msg.sender) { + emit ComponentValueRemoved(getIdByAddress(_components, msg.sender), msg.sender, entity); + } + /** Deprecated, but left here for backward compatibility. TODO: refactor all consumers. */ function getComponent(uint256 id) external view returns (address) { return getAddressById(_components, id); diff --git a/packages/solecs/src/interfaces/IWorld.sol b/packages/solecs/src/interfaces/IWorld.sol index 7ffd25b34c..89ffbd6e46 100644 --- a/packages/solecs/src/interfaces/IWorld.sol +++ b/packages/solecs/src/interfaces/IWorld.sol @@ -31,8 +31,12 @@ interface IWorld { bytes calldata data ) external; + function registerComponentValueSet(uint256 entity, bytes calldata data) external; + function registerComponentValueRemoved(address component, uint256 entity) external; + function registerComponentValueRemoved(uint256 entity) external; + function getNumEntities() external view returns (uint256); function hasEntity(uint256 entity) external view returns (bool); diff --git a/packages/std-client/src/components/ActionComponent.ts b/packages/std-client/src/components/ActionComponent.ts index 7f62314cba..9559a80cfc 100644 --- a/packages/std-client/src/components/ActionComponent.ts +++ b/packages/std-client/src/components/ActionComponent.ts @@ -3,7 +3,7 @@ import { defineComponent, World, Type, Component, Metadata, SchemaOf } from "@la export function defineActionComponent(world: World) { const Action = defineComponent( world, - { state: Type.Number, on: Type.OptionalEntity, metadata: Type.OptionalT }, + { state: Type.Number, on: Type.OptionalEntity, metadata: Type.OptionalT, overrides: Type.OptionalStringArray }, { id: "Action" } ); return Action as Component, Metadata, T>; diff --git a/packages/std-client/src/systems/ActionSystem/createActionSystem.ts b/packages/std-client/src/systems/ActionSystem/createActionSystem.ts index 5fd5dcb117..ade25c8d4f 100644 --- a/packages/std-client/src/systems/ActionSystem/createActionSystem.ts +++ b/packages/std-client/src/systems/ActionSystem/createActionSystem.ts @@ -14,7 +14,7 @@ import { setComponent, Metadata, } from "@latticexyz/recs"; -import { mapObject, awaitStreamValue } from "@latticexyz/utils"; +import { mapObject, awaitStreamValue, uuid } from "@latticexyz/utils"; import { ActionState } from "./constants"; import { ActionData, ActionRequest } from "./types"; import { defineActionComponent } from "../../components"; @@ -83,6 +83,7 @@ export function createActionSystem(world: World, txReduced$: Obse state: ActionState.Requested, on: actionRequest.on ? world.entities[actionRequest.on] : undefined, metadata: actionRequest.metadata, + overrides: undefined, }); // Add components that are not tracked yet to internal overridable component map. @@ -139,12 +140,17 @@ export function createActionSystem(world: World, txReduced$: Obse // Update the action state updateComponent(Action, action.entityIndex, { state: ActionState.Executing }); + // Compute overrides + const overrides = action + .updates(action.componentsWithOptimisticUpdates, requirementResult) + .map((o) => ({ ...o, id: uuid() })); + + // Store overrides on Action component to be able to remove when action is done + updateComponent(Action, action.entityIndex, { overrides: overrides.map((o) => `${o.id}/${o.component}`) }); + // Set all pending updates of this action - for (const { component, value, entity } of action.updates( - action.componentsWithOptimisticUpdates, - requirementResult - )) { - componentsWithOptimisticUpdates[component as string].addOverride(action.id, { entity, value }); + for (const { component, value, entity, id } of overrides) { + componentsWithOptimisticUpdates[component as string].addOverride(id, { entity, value }); } try { @@ -201,8 +207,12 @@ export function createActionSystem(world: World, txReduced$: Obse if (!action) throw new Error("Trying to remove an action that does not exist."); // Remove this action's pending updates - for (const component of Object.values(componentsWithOptimisticUpdates)) { - component.removeOverride(actionId); + const actionEntityIndex = world.entityToIndex.get(actionId); + const overrides = (actionEntityIndex != null && getComponentValue(Action, actionEntityIndex)?.overrides) || []; + for (const override of overrides) { + const [id, componentKey] = override.split("/"); + const component = componentsWithOptimisticUpdates[componentKey]; + component.removeOverride(id); } // Remove this action's autorun and corresponding disposer