Skip to content

Commit

Permalink
fix(solecs): only allow components to register their own updates, fea…
Browse files Browse the repository at this point in the history
…t(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
  • Loading branch information
alvrs authored Oct 11, 2022
1 parent 33e8959 commit d8dd63e
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 13 deletions.
4 changes: 2 additions & 2 deletions packages/recs/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ export type OverridableComponent<S extends Schema = Schema, M extends Metadata =
M,
T
> & {
addOverride: (actionEntityId: EntityID, update: Override<S, T>) => void;
removeOverride: (actionEntityId: EntityID) => void;
addOverride: (overrideId: string, update: Override<S, T>) => void;
removeOverride: (overrideId: string) => void;
};

export type OptionalType =
Expand Down
4 changes: 2 additions & 2 deletions packages/solecs/src/Component.sol
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ abstract contract Component is IComponent {
}

// Emit global event
IWorld(world).registerComponentValueSet(address(this), entity, value);
IWorld(world).registerComponentValueSet(entity, value);
}

/**
Expand Down Expand Up @@ -217,6 +217,6 @@ abstract contract Component is IComponent {
}

// Emit global event
IWorld(world).registerComponentValueRemoved(address(this), entity);
IWorld(world).registerComponentValueRemoved(entity);
}
}
24 changes: 24 additions & 0 deletions packages/solecs/src/World.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -101,21 +102,44 @@ 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.
*/
function registerComponentValueRemoved(address component, uint256 entity)
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);
Expand Down
4 changes: 4 additions & 0 deletions packages/solecs/src/interfaces/IWorld.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion packages/std-client/src/components/ActionComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { defineComponent, World, Type, Component, Metadata, SchemaOf } from "@la
export function defineActionComponent<T = undefined>(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<SchemaOf<typeof Action>, Metadata, T>;
Expand Down
26 changes: 18 additions & 8 deletions packages/std-client/src/systems/ActionSystem/createActionSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -83,6 +83,7 @@ export function createActionSystem<M = undefined>(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.
Expand Down Expand Up @@ -139,12 +140,17 @@ export function createActionSystem<M = undefined>(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 {
Expand Down Expand Up @@ -201,8 +207,12 @@ export function createActionSystem<M = undefined>(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
Expand Down

0 comments on commit d8dd63e

Please sign in to comment.