ERC-7509: Entity Component System

Represent entities comprising components of data, with systems which operate on entities' components.


Metadata
Status: DraftStandards Track: ERCCreated: 2023-09-05
Authors
Rickey (@HelloRickey)

Abstract


This proposal defines a minimal Entity Component System (ECS). Entities are unique identities that are assigned to multiple components (data) and then processed using the system (logic). This proposal standardizes the interface specification for using ECS in smart contracts, providing a set of basic functions that allow users to freely combine and manage multi-contract applications.

Motivation


ECS is a design pattern that improves code reusability by separating data from behavior. It is often used in game development. A minimal ECS consists of
Entity: a unique identifier.
Component: a reusable data container attached to an entity.
System: the logic for operating entity components.
World: a container for an entity component system.
This proposal uses smart contracts to implement an easy-to-use minimal ECS, eliminates unnecessary complexity, and makes some functional improvements that are consistent with contract interaction behavior. You can combine components and systems easily and freely. As a smart contract developer, the benefits of adopting ECS include:

  • It adopts a simple design of decoupling, encapsulation, and modularization, which makes the architecture design of your game or application easier.
  • It has flexible composition ability, each entity can combine different components. You can also define different systems for manipulating the data of these new entities.
  • It is conducive to expansion, and two games or applications can interact by defining new components and systems.
  • It can help your application add new features or upgrades, because data and behavior are separated, new features will not affect your old data.
  • It is easy to manage. When your application consists of multiple contracts, it will help you effectively manage the status of each contract.
  • Its components are reusable, and you can share your components with the community to help others improve development efficiency.

Specification


The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

World contracts are containers for entities, component contracts, and system contracts. Its core principle is to establish the relationship between entities and component contracts, where different entities will attach different components, and use system contracts to dynamically change the data of the entity in the component.

Usual workflow when building ECS-based programs:

  1. Implement the IWorld interface to create a world contract.
  2. Call createEntity() of the world contract to create an entity.
  3. Implement the IComponent interface to create a Component contract.
  4. Call registerComponent() of the world contract to register the component contract.
  5. Call addComponent() of the world contract to attach the component to the entity.
  6. Create a system contract, which is a contract without interface restrictions, and you can define any function in the system contract.
  7. Call registerSystem() of the world contract to register the system contract.
  8. Run the system.

Interfaces

IWorld.sol


IComponent.sol


Library

The library Types.sol contains an enumeration of Solidity types used in the above interfaces.

Rationale


Why include type information instead of simple byte arrays?

This is to ensure the correctness of types when using components, in order to avoid potential errors and inconsistencies. External developers can clearly set and get based on the type.

Why differentiate between a non-existent entity and an entity with false state?

We cannot judge whether an entity actually exists based on its state alone. External contributors can create components based on entities. If the entities he uses don't exist, the components he creates may not make sense. Component creators should first check if the entity exists, and if the entity does exist, it makes sense even if the entity's state is false. Because he can wait for the entity state to be true before attaching the component to the entity.

Why getEntityComponents function returns all addresses of components instead of all component ids?

There are two designs for getEntityComponents. The other design is to add an additional mapping for the storage of component id and component address. Every time we call addComponent, the parameters of the function are the entity id and component id. When the user calls getEntityComponents, it will returning an array of component ids, they query the component address with each component id, and then query the data based on each component address. Because a entity may contain many component ids, this will cause the user to request the component address multiple times. In the end, we chose to use getEntityComponents directly for all addresses owned by the entity.

Can registerComponent and registerSystem provide external permissions?

It depends on the openness of your application or game. If you encourage developers to participate, the state of the component and system they submit for registration should be false, and you need to check whether they have submitted malicious code before using setComponentState and setSystemState to enable them .

When to use get with extra parameters in component?

The component provides two get functions. One get function only needs to pass in the entity id, and the other has more _params parameters, which will be used as additional parameters for obtaining data. For example, you define a component that stores the HP corresponding to the level of an entity. If you want to get the HP of an entity that matches its level, then you call the get function with the entity level as _params.

Reference Implementation


See Ethereum ECS Example

Security Considerations


Unless you want to implement special functions, do not provide the following methods directly to ordinary users, they should be set by the contract owner.
createEntity(), setEntityState(), addComponent(), removeComponent(), registerComponent(), setComponentState(), registerSystem(), setSystemState()

Do not provide functions that modify entities other than set() in the component contract. And add a check in set() to check whether the entity is available and whether the operating system is available.

After the system is registered in the world, it will be able to operate the component data of all entities in the world. It is necessary to check and audit the code security of all system contracts before registering it in the world.

If the new version has deprecated some entities, component contracts and system contracts. They need to be disabled in time using setEntityState(), setComponentState(), and setSystemState().

Copyright


Copyright and related rights waived via CC0.