The Rust’s Stories: Rust, ECS, and WebAssembly
“The Bit Knight, the king calls you to serve. If you get the princess back, you will be rewarded.”
This article will focus on turn-based roguelike games’ design/architecture level. For implementation details, please visit my repository or repo of the author of the book titled “Hands-on Rust” (link to the repo). The game is based on the proprietary bracket-lib library, but the knowledge contained in the book can be transferred to another engine, e.g., bevy (work in progress :)).
Chapter 1: The Idea
From an early age, I was fascinated by video games. After mastering the art of getting the bits, one of my first thoughts was, “I’m going to be a game dev programmer.” However, I’ve ended up “web dev,” mainly dealing with the front-end. However, my mind wandered to the time when I was reading “C ++ language . The School of Programming” book from cover to cover.
Three years ago, I found out about the Rust language. It is perfect for me because it combines many concepts I know from functional programming in JS and the power of a low-level language. I immediately thought about making a game. However, I put off the idea, wanting first to bring my Rust skills to the right level. I did just that when I found the book titled “Hands-on Rust: Effective Learning through 2D Game Development and Play.” Roguelike + Rust? Only the RPG + Rust combination could have been more beautiful. I heard a voice in my head telling me to try it.
“The knight visited the all-powerful oracle and asked:
What kind of monsters will I face?
I see I can see clearly these are the most powerful beings on earth, Rust, ECS, and WebAssembly.”
Chapter 2: Rust
Rust is a relatively new language. Its first official version was released in 2010. It is a multi-paradigm and general-purpose language. The fascinating fact about Rust is that it is not reinventing the wheel. On the contrary, it borrows many concepts from different languages, e.g.,
The main features of the language:
- memory safety,
- memory management through Resource Acquisition Is Initialization,
- security of concurrency in the concept of Ownership and Borrowing,
- a type system called features supports a mechanism like type classes, inspired by the Haskell language.
Rust is not a simple language. A lot of time is needed to learn to use Rust’s sword. We will not find a “garbage collector” here (I will use the version of this word later in the below :)). There is a concept of a ‘static garbage collector’ (for curious ones, see here), and in a way, Rust is equipped with such a mechanism as well. It is nothing but a compiler that cares and helps the programmer understand common mistakes (dangling pointer, anyone?).
When we first use Rust, the compiler is our enemy. I advise you to make friends with it as soon as possible because this tool is an invaluable guide to the world of good programming practice.
Chapter 3: ECS
While working as a programmer, I repeatedly read code in a language I did not know.
Learning Rust was already difficult. The ECS turned out to be a monster I had never heard of before. I learned that the ECS is an architectural pattern that relies on modeling through composition. I also learned that inheritance is a big problem in the game dev world. Here my mental model slowly began to clear up. As a web developer, I have often dealt with inheritance and know how challenging it can be. Especially, C ++ allows inheritance from many base classes, leading to a “diamond problem.”
To better understand the concept of ECS, I reached into my toolbox, and I used the visualization technique (I used Miro and plain colored cards). I find it an excellent tool for understanding new concepts because images speak much more to us than the text itself. The result of my work can be seen below:
- Entity: has no logic. It is made of components. A good analogy is a database entity or front-end store that stores data about, e.g., the player, opponents, and items. An example of the registration function is as follows:
- Component: I think the example will say more than a thousand words, so here it is:
As you can see, a component is nothing but a programmer-defined data type. Rust has no classes. Instead, we have structures available. A good source is here if you haven’t figured out what a macro “derive” is.
- System: the logic of the game is included here. The system creates a query to “ECS” about components, then returns a list of entities that can be modified, e.g., change the location.
Here again, macros come in handy. From lines 4 to 7, the components to which the function “entity_render” is to have access are defined. It is worth noting that as the function’s first argument, we get an object of type “SubWorld.” The name indicates that it is a part of the world (entity database) that is limited to types defined by macros. In line 9, the function “query” is called, which will return an entity list containing the components “Point” and “Render,” e.g., the entity “Player” is composed of the components “Point” and “Render,” so the entity “Player” will be returned from the inquiry. An analogy is pseudo SQL code:
1. Select * front Entities where entity.type includes Point and Render
- Resources: It is an additional pool of entities that should be available everywhere, regardless of the “ECS world.” Such a resource is the “Camera” type from the screenshot above. It is worth noting that objects registered as “resources” are passed by the argument of a function, not by calling the function “query.”
With the basic concepts of “ECS” explained, the game development algorithm looks like this:
Chapter 4: WebAssembly
WebAssembly is a new technology for me that I have not dealt with so far. So, I decided to try to understand the very concept of compiling and running the project in Rust. However, I left the understanding of WebAssembly in the browser for later.
We start work on WebAssembly with the finished game from chapter 3, which looks like this:
The Rust ecosystem is constantly evolving. One of the things that immediately caught the attention of the Rust community was the ability to compile code into WebAssembly. It is because Rust does not have a virtual machine (which makes it easier to support a new architecture) or its runtime environment (NodeJS).
The environment itself is not an obstacle. C # can also run in WebAssembly, but the resulting code must contain net environment code. Rust is a language compiled into machine code. Generally, WebAssembly is a kind of machine code built into browsers (more here). The libraries you will be using must also support building to WebAssembly. In other words, a library with dependencies related to a specific architecture or system (e.g., one that works only on Linux) will not compile to WebAssembly without prior preparation.
So the first thing you need to do is install the appropriate compilation support:
1. rustup target add wasm32-unknown-unknown
Next, you install:
1. cargo install wasm-bindgen-cli
Of course, the WebAssembly code runs in the browser, so we need the HTML file. We build the application with the command:
1. cargo build --target wasm32-unknown-unknown –release
And next, you tight the object code:
1. wasm-bindgen target/wasm32-unknown-unknown/release/dungeoncrawl.wasm --out-dir ./docs --no-modules --no-typescript
The “docs” folder prepared in this way can be put on a static file server. I used Github pages (https://rengare.github.io/dungeoncrawler_rs/ ).
We made it to the end, and the brave knight saved the princess. Our hero defeated Rust, ECS, and WebAssembly. However, this is not the end, as the adventure is still ongoing, and dragons and monsters are alive and well. The brave hero dedicated his treasure to upgrading his equipment. Armed with “Bevy and Takio” he is ready for new adventures.
The adventure of writing the game was not the easiest. Many questions popped up in my head, e.g.,:
- is my knowledge of Rust enough,
- what is ECS,
- how to write ECS-compliant code in Rust,
- what the file structure should look like,
- how WebAssembly works and how to compile a project.
Fortunately, the book mentioned above helped to walk me through the process. However, it wasn’t always easy, and I had to put in a lot of work to understand the new concepts. For this purpose, I have often used visualization. With my article, I would like to encourage you, the reader, to test the Rust language of the WebAssembly technology and explore unknown topics 😉.
About the Author:
Frontend Developer at Synergy Codes
An engineer specializing in information systems, mainly the Internet. He graduated from the State Vocational College in Nysa and the Wrocław University of Technology in Poland. He started his professional career 8 years ago, but he has been interested in IT itself from an early age. His hobbies are computer science and science in general.