CaptainZ

CaptainZ

Prompt Engineer. Focusing on AI, ZKP and Onchain Game. 每周一篇严肃/深度长文。专注于AI,零知识证明,全链游戏,还有心理学。
twitter

Has MUDv2 deprecated the ECS architecture?

Lattice (@latticexyz) MUD is the most established and well-known full-chain game engine in the Web3 space. In its previous first-generation version, it clearly stated that MUDv1 is a framework based on ECS. I also wrote a related introductory article titled “In-depth Analysis of the Full-Chain Game Engine MUD.” A few weeks ago, Lattice announced the V2 version, which made significant changes to the overall architecture of MUD, weakening the connection with ECS and introducing a new on-chain database called “Store,” which is based on a “table” data structure. So how should we understand the relationship between “ECS” and “table”? How does the new component “Store” work? Is “ECS” suitable for making full-chain games? We will explore these questions in this article.
 

Overview of MUDv2#

quick-start-mud-cli

MUDv2 is a framework for building Ethereum applications. It compresses the complexity of building EVM applications through a tightly integrated software stack. MUDv2 includes: Store (an on-chain database), World (an entry point framework that provides standardized access control, upgrades, and modules), Foundry-based rapid development tools, client data storage that reflects on-chain state, and MODE (a Postgres database that can use SQL queries and reflect your on-chain state).
 
MUDv2 is not a rollup or a chain; it is a set of libraries and tools that can work together to build on-chain applications. MUDv2 is not limited to the Ethereum mainnet; it can work on any EVM-compatible chain. MUDv2 is not only suitable for autonomous worlds and on-chain games, but it also does not impose a data model on developers. You can do anything with MUD that you can do with flat Solidity mappings and arrays. MUDv2's data availability solution is on par with conventional applications deployed on the Ethereum mainnet, such as ENS and Uniswap.
 
The main idea of MUDv2 is that all on-chain state is stored in the Store (MUD on-chain database). The way we currently handle smart contract states leads to some major issues, such as the coupling of state and logic, which makes upgrading logic very difficult. With MUD, you will never use data storage driven by the Solidity compiler. All states are saved and retrieved using the Store, which is an efficient on-chain database. The Store is like SQLite: it is an embedded database manually optimized in Yul. It has tables, columns, and rows.
 
MUDv2 logic is stateless and has custom permissions that can be called across contracts. MUD recommends using World: an entry point kernel responsible for mediating access to the Store from different contracts. World creates a Store upon deployment. The tables in each Store are registered under a namespace, represented by a name, just like a flat file system path.
 
MUDv2 does not require indexers or subgraphs; the frontend synchronizes itself: when using the Store (and the extended World), your on-chain data self-checks, and any changes are broadcast through standard events. These events and patterns are utilized by MODE: MODE transforms your on-chain state into an SQL database and maintains millisecond-level latency updates.

What is the Essence of ECS#

Snip20230713_8

Through literature review, I personally believe (the author @hicaptainz is not a developer, please correct any errors) that the ECS (Entity-Component-System) pattern is essentially a way of modeling data structures, with its core focusing on how to store and organize data.
 

  1. Entity: In the ECS pattern, an entity is an abstract concept that does not directly hold data but associates data through components. An entity can be seen as a container for one or more components, primarily serving to provide a unique identifier for the components.

  2. Component: Components are carriers of data. In the ECS pattern, all data is encapsulated in components. Each component represents a specific attribute or behavior, such as position, speed, color, etc. Components contain only data and no logic or behavior.

  3. System: Systems are where data is processed. Systems determine how to process these entities based on the components of the entities. Each system has one or more specific tasks, such as rendering, physics simulation, AI logic, etc.

 
From this perspective, the ECS pattern is indeed a way of modeling data structures. It separates data (components) from behavior (systems), making data storage and processing more flexible and efficient. The advantages of this pattern include:

 

  • Composability: By combining different components, entities with various attributes and behaviors can be created without needing to create a large number of classes or structures.

  • Data Locality: Since components contain only data, related data can be stored closely together, improving cache utilization and thus enhancing performance.

  • Reusability: Systems only care about data and not about which entity the data comes from, allowing the same system to be reused across multiple entities.

  • Parallelism: The separation of data and behavior makes parallel processing of data in a multithreaded environment much easier.

Types of ECS Data Structure Modeling#

So what are the ways to implement ECS data structures? Generally speaking, the two more popular methods are Archetype and Sparse Set, while others, such as Bitset and Reactive ECS, are less common. Each has its advantages and applicable scenarios.
 

  1. Archetype (also known as Table-based ECS): Archetype is a storage method optimized for data locality, storing entities with the same component set in a table where components are columns and entities are rows. Unity's ECS system adopts this method. The advantage of Archetype is that since the same component set is contiguous in memory, it can greatly improve cache efficiency and data access speed. Additionally, since each Archetype knows the components it contains, entities can be created and destroyed dynamically at runtime without extensive memory operations.

  2. Sparse Set (also known as Sparse ECS): Sparse Set is an efficient method for data storage and access that combines the advantages of arrays and hash tables. In Sparse Set-based ECS, each component is stored in its own sparse set, with the set keyed by entity ID. The advantage of this method is that it can effectively utilize cache while also saving space. However, the downside is that it is more complex to implement.

 
As for Bitset and Reactive ECS, they also have their unique advantages but may not be suitable for all application scenarios.
 

  • Bitset: Bitset is a simple and efficient method for checking whether an entity has a specific component. However, since Bitset itself cannot store component data, it usually needs to be combined with other storage methods.

  • Reactive ECS: Reactive ECS is a more advanced pattern that allows systems to respond to the addition, deletion, and modification of components. This pattern can make the code clearer and easier to understand but may increase implementation complexity.
     
    If any readers have seen my article “How Curio Built ECS Game Engine into OPStack?”, they will immediately notice that Curio adopts the Sparse Set method for storing and operating ECS data. So what about MUDv2? Clearly, it is “Archetype,” or “table-based ECS.”

How the Store Component Works#

The data model provided by MUDv2's Store component can well support the ECS pattern. In the Store, you can create a table to represent entities, each with a unique key. You can add various components to each entity, which can be fields in the table. Then, you can write systems to process these components, which can be functions in smart contracts.
 
For example, you could create a "Player" table with "position" and "health" fields. Then, you could write a "move" function to change the player's position and a "damage" function to reduce the player's health. So, while the Store component of MUD does not directly implement the ECS pattern, its data model can effectively support the implementation of the ECS pattern.
 
Some may wonder if this table-based on-chain database also supports OOP (Object-Oriented Programming) patterns.
 
The Store component provides a table-based data model, which is closer to a relational database or data-driven design rather than an object-oriented programming (OOP) model. In OOP, data and the methods that operate on that data are encapsulated in objects, while in the Store model, data is stored in tables, and the logic for operating on the data is handled by functions in smart contracts.
 
However, this does not mean you cannot use object-oriented design patterns in smart contracts. You can create a smart contract to represent an object, which can have state variables to represent the object's properties and functions to represent the object's methods. Then, you can use the Store to persist the state of these objects.
 
For example, you could create a "Player" smart contract with "position" and "health" state variables, as well as "move" and "damage" functions. Then, you could use the Store to store the "position" and "health" of each "Player" instance. So, while the data model of the Store does not directly support object-oriented programming, you can use object-oriented design patterns in smart contracts and use the Store to persist the state of the objects.
 
But as we have emphasized, for full-chain games, the ECS pattern is still more suitable because one significant advantage here is that new systems can be introduced at any stage of development and will automatically match any existing and new entities with the correct components. This promotes a design where systems are developed as single-responsibility, small functional units that can be easily deployed across different projects, achieving the “composability” of full-chain games.

Storage of On-Chain Data#

Since the Store component establishes an on-chain database, where exactly is this on-chain data stored? Let's recall the storage method of the EVM.
 
In the Ethereum Virtual Machine (EVM), on-chain data is primarily stored in two data structures: Storage and Memory.

  1. Storage: This is the persistent storage of each smart contract, where data is persistent between transactions. Storage consists of key-value pairs, where both keys and values are 256 bits. In Solidity, the state variables of contracts are stored here. For example, if you define a uint256 public counter; in a contract, this counter variable is stored in storage. Each time you call a function that changes the counter, it will update in storage.

  2. Memory: This is temporary storage for each function call, where data is cleared after the function call ends. Memory is linear and can be imagined as a byte array. In Solidity, local variables of functions are stored here.

Additionally, there is a data structure called Stack, which is used to store function call parameters and return values, as well as some temporary computation results. The size of the stack is limited, containing a maximum of 1024 elements.
 
In the EVM, the cost of reading and writing to storage is very high because it consumes gas. Therefore, smart contract developers usually try to optimize their code to reduce storage operations. In contrast, the cost of reading and writing to memory is much lower, but since the data in memory is cleared after the function call ends, it is only suitable for storing temporary data.
 
MUDv2's Store component stores data in the EVM's storage. Each smart contract has its own storage space in the EVM, which is persistent, meaning that even between transactions, the data in storage remains unchanged.
 
The Store component provides a higher-level abstraction that allows developers to more conveniently store and read data in smart contracts. Developers can define their own tables, each with a specific schema that defines the fields and their types in the table. Then, developers can use the API provided by the Store, such as set, get, etc., to operate on the data in these tables.
 
These operations will ultimately be converted into read and write operations on the EVM storage. For example, when you call the MyTable.set function to set a record, this function will encode the data into bytes and then write it to the EVM's storage. When you call the MyTable.get function to retrieve a record, this function will read data from the EVM's storage and then decode the data into the type you defined.

The Overlooked State Machine Problem#

In our previous analysis, we seem to see only the advantages of the ECS pattern. So, are there really no disadvantages? Here we need to start with the EVM itself and state machines.
 
A state machine, also known as a state automaton, is a model that describes the state changes of an object and how it responds to inputs (such as events or conditions). In a state machine, the system is in a certain state at any given time and will transition from one state to another under the influence of inputs. In traditional game engines, state machines are often used to manage the behavioral states of game objects (such as characters, enemies, AI, etc.). These states may include walking, running, jumping, attacking, defending, dying, etc. State machines can help us clearly define and control the transitions between different states of game objects.
 
For the EVM (Ethereum Virtual Machine), we can think of it as a globally distributed state machine. In the Ethereum network, each block contains a series of transactions, and these transactions are the inputs. The EVM executes these transactions and updates the global state of Ethereum based on the content of the transactions. The global state of Ethereum includes the balance information of all accounts, the code of smart contracts, and the data stored in smart contracts, etc. When a transaction is executed, it may transfer funds, call contracts, or change the storage state of contracts, all of which will lead to changes in Ethereum's global state.
 
Therefore, for full-chain games, storing the state machine in the game engine is a necessary task. However, storing the state machine (especially a distributed state machine) in the ECS pattern presents a series of problems.
 
The problem of storing state machines in ECS: When developing games using ECS, one may encounter the question of whether to store the state machine in ECS. This question is much more complex than expected for many reasons. For example, changing the state of an entity, allowing an entity to participate in multiple state machines, retrieving the current state of an entity, changing the state list of the state machine, etc., all present challenges in ECS.
 
The problem of label states: An intuitive way to implement a state machine is to create a label for each state. This method performs well when querying all entities in a given state, as ECS implementations typically excel at finding all entities with a given component/label. However, other operations, such as changing states, require adding one label and removing another, which may introduce complexity and performance issues.
 
Let's analyze this specifically with the Archetype data structure.
 
The implementation of Archetype involves gathering all entities with the same component set into one table. Skipping the specific details, this method facilitates efficient iterative caching of components, as multiple components can be iterated contiguously. However, this also comes with some costs.
 
To maintain the aggregation of entities with the same component set, every time a component is added or removed, entities need to be moved between different tables. This also applies to labels. Therefore, frequently adding or removing state-related labels can become very costly, as all components of the entity need to be copied each time the state changes (in fact, due to the storage method, each component needs to be copied twice).
 
To address the above issues, the authors of Bevy ECS proposed a new method to implement storage variants: separating Archetype from tables. In this method, tables only store the (data) components of entities, while Archetype stores components + labels. Multiple Archetypes can point to the same table. With this, applications can add and remove labels (and states) in constant time, which is a significant improvement over having to copy all components.
 
Perhaps MUDv3 will improve in this direction; let's see if it will add a new Archetype storage component outside of the table in the future.

Conclusion#

Now let's try to answer the initial question. The table is a modeling method for implementing “Archetype ECS.” MUDv2 has not abandoned ECS; it just happens that the table component can also implement OOP architecture. This table is generated through the Store component and stored in the EVM's Storage, so all read and write operations on the table will be converted into read and write operations on Storage. Although the ECS architecture has many difficulties in storing the EVM state machine, there are still many ways to help solve these issues. If you are interested in these optimization methods, you can follow the author's Twitter (@hiCaptainZ) for the latest information. The author is currently the Head of Research at Gametaverse (@GametaverseDAO), holds a Master's degree in Optical Engineering from the University of North Carolina and a Master's degree in Finance from the Chinese University of Hong Kong, and is currently researching Web3 Gaming, AI, and ZKP. The research topics expected in the coming weeks are as follows:
 

  • Comparative analysis of traditional game engines and Web3 game engines
  • How to determine the playability of a game
  • The rise of gambling games and GambleFi in Web3
  • What are the composability aspects of full-chain games
  • Economic system design for Web3 games
  • How different game genres affect the design of Tokenomics
  • Is the ECS architecture a paradigm (advantages and disadvantages)
  • How to design an ECS from scratch
  • How AI can assist in chain game production
  • Overview of ZK zero-knowledge proofs across all tracks
  • ZK technology and machine learning ZK-ML
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.