Project Necromancy: How to Revive a Dead Rust Project
How do you plan an effective refactor? How do you know if the changes you’re introducing won’t just introduce new problems? The answer isn’t so simple because every project is different.
This talk identifies approaches that have been helpful when refactoring a game made with the Amethyst game engine. It looks at having a beginner’s mindset that allows developers to take a step back and re-examine the fundamentals of entity, component, system (ECS). It also explores other approaches outside of the code that can help with guiding a refactor.
Presented by
Micah is a software engineer. She is interested in learning about Rust and game development.
I am a welder who also runs an electronics and programming education company, micronote.tech. In my free time, I have been learning Rust through working on space_shooter_rs, a space shooter game made with the Amethyst game engine.
Resources
Recordings
Transcript
Project Necromancy: How to Revive a Dead Rust Project
Bard:
Carlo Supina and Micah strive
left for dead Rust projects to revive
by making it dress
up with an ECS
now it's perfectly looking alive.
Carlo:
Hi, everyone, welcome to our talk on how to revive a dead Rust project. My name is Carlo, and I will be presenting together with Micah, talking about a 2D shooter game made with the Amethyst engine. I'm a welder, pursuing AWS certification. And I run a micronote, I talk about microcontrollers with Python. In my free time, enjoy tinkering with electronics, designing and printing did�3D parts. I dove into embedded Rust with a remote controlled rover. And then wanted more conventional experience, I started working on Kibrarian for managing schematics and footprints for Kicad. It's an electronics suite. I scattered working on space_shooter_rs, made with the Amethyst.
Micah:
Hey, I'm Micah, a software engineer at Mozilla. My background is focused on front end web development. Currently I work on the Firefox web browser on the UI. And I like to write blog posts about experiences in software development. They are a way to deliver complex topics in a way that are simple and easy to follow. I got started with Rust two years ago as an on and off hobby. I have contributed to open source Rust projects and a few toy projects. I started learning about game development using the Amethyst game engine. I started learning about a common game development architecture called ECS and how it was used by Amethyst. And we'll be explaining what ECS is later in this talk. Don't worry about knowing what it is right now. And so, I decided to share my learnings at Rust Conf this year where it helped solidify my knowledge about the topic. I write about it on blog posts. And now to Carlo, where he will explain the origin of space_shooter_rs.
Carlo:
In 2019, I was honing Rust skills. I want I decided to make a game. And found arewegameyet.rs. They created games with the Rust language. Here I discovered a game engine, Amethyst, that used an unfamiliar architecture, ECS. I read through the Amethyst book and followed tutorials and started working on my own game, space_shooter_rs. It's a space shooter game with enemies from the top of the screen. It was chosen�to be an official game for the Amethyst engine. It was initially a project for learning Rust. Which meant it contained mistakes that a person new to Rust would make. And it was unorganized due to me being new to the ECS architecture of Amethyst. The game contained large components with redundant data. It was difficult for me to continue contributing to, because with a couple of hours to work on a feature, I would need to reorient myself to working on something new. For this reason, it was difficult for others to contribute to too. I was aware that refactoring was needed but wasn't sure where to start. Because of this, it slowed to a halt until 2020 when I watched Micah's Rust Conf talk about experiences with Amethyst. This talk inspired me to work on the game again. I ended up encountering the same problems. This is when I invited Micah to collaborate on space_shooter_rs. She had industry practices through work at Mozilla. And I reached occupant out... determining if I had time and if I had a plan and what needed to be worked on.
Micah:
I was interested in collaborating because I wanted to expand my knowledge by working on an existing game. At first, I wasn't sure if I would be able to help out meaningfully since I was learning Rust and Amethyst. And I wasn't sure if I would make good suggestions on how to make the existing codebase due to my experience. But I realized that the concerns that I was having, Carlo knew the code and could guide me through the problems the project was having. It was clear there were areas of Amethyst Carlo had explored that I haven't. So, I knew I would be learning my concepts as well. And another nice thing about the collaboration, we were coming into this knowing we could learn something from each other. And since it was the two of us, we could make mistakes and learn from them. But as someone not familiar with the code, it was hard to know where to begin with refactoring the codebase.
In this section our talk, we're going to provide some background for the space_shooter game. A little bit of knowledge will provide background into the refactoring decisions we made and lead us into discussions of how we refactored the ECS code. And finally, explaining collaboration strategies to plan and execute the refactors we discussed. And now a to Carlo who will be giving a summary of space_shooter.
Carlo:
Here's background. The goal of the game is to survive levels by destroying enemies, collecting consumables, buying items and defeating the final boss. It's the binding of Isaac. And items that synergize, randomly generated levels and satisfying controls with physics. Currently, three enemy types. They have a distinct behavior for unique challenges. There are 13 unique items. Purchased by the player and when acquired change the rules of the game to benefit the player. This could range from changing the speed of the player to changing the damage the player does to enemies or even changing the prices of items in the store. The game has four consumable drops. Consumables are dropped by enemies when destroyed. So far, health and money. Which in this game is represented by bright green space rocks. The money is used to purchase items from the store. Items are listed for a price. When purchased by the player, the item drops from the top and the player collects it. There are animations, 3D backgrounds and a work in progress boss in the game. And now to Micah talk about the entity structure.
Micah:
We have been throwing around the early ECS. It's entity component system. It's a common pattern used in game development that makes it easy to compose objects in a game because components can be arbitrarily added to entities. This makes development much more data driven since entities are defined by the components that are attached to them. So, in ceremony, an entity often represented with a single ID can be composed of a number of components. And a component acts like a container for data that can describe an aspect of an object. And finally, a system is a piece of logic that can operate on one or more entities in ha game. I find it easier to understand what ECS is through a series of examples.
This slide shows a screenshot of the game with five circled objects. These are entities. They are sprite renders for the enemy, spaceship, drone ship, and enemy projectile. Enemies are rented with unique IDs. Which is why they have labels with one through five. Attaching names such adds spaceship or enemy make it easier to describe the purpose to someone else. Next, now the entity's characteristics required for them to function in a game. Entities have a number of components attached to them. These components define pieces of data that help describe behaviors that we expect an entity to have. And it helps to think of the collections as ground together under one ID. I have grouped them together with the entity they are associated with. Entities has SpriteRender, attack, health and movement attached. And entity 2 has similar components as well. If we needed to access a health component for the player entity, we need an expressive way to do this. One way is to have the player tag attached to the entity to differentiate a health component associated with the player entity from the others. Of course, having something like a health component attached to an entity doesn't do much on its own. We need a way for the game to update the values of a health component when damage is dealt to the entity that it's attached to. And this is where the system part of ECS comes in. But before we talk about the system part of ECS. We should address component storages. This is how Amethyst updates collections of them. Giving components their own storages allows for faster access to data needed when updating an entity's component state. This is important when a system needs to operate on hundreds of components at a time. Because of this, it's important that component storages are responsible for containing and managing components of one type. And for every component in a storage, they have an entity ID that they are associated with. Now that we have briefly explained component storages, we can quickly go over the system part of ECS.
In this diagram, we have an example of an animation system implemented using the Amethyst game engine. The system is a way to know what sprite is in each game cycle. This shows that the animation system needs to read from a time resource. Resources in Amethyst are containers of data not associated with an entity. The other two storages it reads from are the animation and SpriteRender component storages. Using these resources, the animation system writes to an entity SpriteRender component. In particular, a SpriteRender component can tell Amethyst which should be drawn. This is related to the time resource, resulting in the animation sequence. The animation component serves as data the entity should be using when determining what to rendering the SpriteRender component with. And now the problems that we were having. One was that the spaceship and enemy component had a lot of redundant data. We took a step back and examined the basics of ECS. We wanted to avoid components that were bloated, redundant data and as a result difficult to use. And we found these problems made some of the existing systems overly complex. The first two points, having components that were bloated and redundant data made it difficult to keep track of what data a component was responsible for. This caused some scenarios where it wasn't clear what the system was supposed to do. Since the problem made it difficult to reuse, it made the properties specific to the component they were associated with. This meant modifying the health stat of an entity would need to specifically access the component containing it rather than a generic health component. To break them down in a way that makes them reusable and concise, it helps to define a set of requirements. We should define what the expected behavior of an entity should be. This would help conceptualize how the entity components work together. And instead of as a whole. To illustrate the point, we can list the expected behaviors of the spaceship and item entities as components. It might seem like a lot of components for only three entities it. But keep in mind every one is a small piece of data with a functionality for an entity, it's easier to have a holistic view of the entity's behavior. If we look at the spaceship and enemy entities, they have a number of similar behaviors, motion, animation and health. These are pieces of data in the form of their own generic components. Having components reusable between entities is what ECS strives to do. You might also notice that the spaceship, enemy and item entities have a component with their same name. For the space_shooter game, we made the decision that functionality that is specific to an entity should be campaigned to the specific component. Now that we have behaviors defined, let's examine the older revision of the spaceship and enemy components. In particular, take a look at the motion component as one of the required behaviors for these two. In these code snippets, spaceship and enemy have data required for the motion behavior. But we wanted to avoid repeating this data and instead extracting them into their own generic motion component attached to any other entity that requires motion. The images here highlight the properties that define motion behavior, and we can now move them into their own component. This motion component applies to entity motion within a 2 dimensional space. Motion and shooter refers to the acceleration, deceleration and maximum speed values. We also simplified some of the properties to be represented as vectors. Such as velocity to store the X and Y values. Now to Carlo to explain how the components allow for more designable systems.
Carlo:
Before I talked about items in space_shooter. Items fuel the progression of the player. They're purchased from the store and modify the rules. As the player destroys enemies, they can collect currency, allowing them to destroy enemies more efficiently and collect more currency and items and so on. How to keep the system lean and focused while at the same time allowing them to detect if an item is collected? For example, there's an item wall called frequency augmenter. This sharply increases the rate of fire for the play. Collisions are detected in a system, spaceship item collision system. All have a HashMap of data about what traits they detect. For the frequency augmenter, this data is an increase in the fire rate for the player. This data needs to find its way to a system called spaceship system which is a system for managing the spaceship attributes. How do we get the data from the collision detection system into the spaceship system? The solution is through the use of an event channel. They function as communication lines between systems. A good analogy for event channels is a radio broadcast. A system that needs to send data to other systems, it can initialize with the data that needs to be sent. Then written to the event channel in the source system. This event channel can be thought of as a radio tower broadcasting a message. Other messages can tune into this channel by setting up an event reader to look for events of a certain type. Then the system can read the data from the event message and use it in the system. Going back to the frequency augmenter item, when it's collected in the spaceship item collision system, it initializes an event and sent to the event channel to the spaceship system where the attributes can be added to the spaceship that collected it. This can be extended for items that affect any system in the game. In this next sex, how we collaborated together to implement the refactors we discussed previously. One of the most important parts of trying to revive the space_shooter project was to establish a workflow for the both of us. We needed... to discuss and prioritize tasks to get the code in a better place. We are exploring four approaches which are collaborative coding practices, writing documentation together, communication and weekly meetings.
The first factor that made our collaboration successful is our collaborative coding practices. When I started this in 2019, GitHub was a place to safely store and distribute files. I was using branch requests, but not to the extent that I need be. I didn't know to, because there were infrequent contributors besides me alone. Part of the reason I reached out to Micah to collaborate on the project, I knew she likely had a lot more experience using these tools through her work at Mozilla. After a week, I gave her enough trust to give maintainer privileges to not go through me. Allowing her to look at issues, branches, pull requests and others in the repository. I followed her lead and learned for myself how to use the collaborative tools. My last point is even though we are communicating through direct communication lines, we still make sure to do public code reviews through GitHub, even if it is just a small change. That makes our decisions transparent which is good for the growth of the project and established a formal process for reviewing code. Now over to Micah to talk about the importance of documentation for the project.
Micah:
Since we were both learning new things as we refactored space_shooter, we wanted to work on documentation as a way to record thought processes. Including tasks, updating the README be an entry point. Anyone who wanted to learn about the game or contributing, go to the mdBook. Where more information is found. It's a tool in Rust that's modern online book it is�markdown files. We decided to create a book for space_shooter as a central location for documentation. Right now the most important content is helping them make contributions to the project. We needed to have a contributing guide for those interested in adding code. We needed to have a code of conduct to establish a safe environment to contribute and learn. While dedication about the architecture and code have been the main focus, Carlo has been actively working to provide contribution guidelines around adding new items to the game as well as artwork. The goal is to have more than one way to contribute, code, artwork, ideas for items and even documentation. The online work book for space_shooter is a work in progress. But anyone interested in previewing it can check it out Amethyst.GitHub.io/space_shooter. And now to weekly meetings.
Carlo:
They were critical to the progress so far. We established short term goals. Refactor a bloated component into smaller components. Long term goals, an example, adding a ... to the game and what kind of components and systems it would require. And the last time are the larger project goals that relate to space_shooter_rs as a project. What kind of documentation do we have? And do we plan on selling a version of the game? And sharing ideas. Our main guiding principle is that while some ideas are certainly bad, all ideas are worth sharing. This is important because we have to not be afraid of sharing ideas to make sure that we are on the same page with the project.
Some examples are character abilities, items and the structure of the game.
Micah:
Having a way to track discussions around refactoring decisions was one of the best ways to capture thought processes. We did this by having discussions on GitHub issues and code reviews. If we had any questions or ideas about an issue we were working on, we would post them to the issue they were relevant to. Ideally, these discussions would involve any pre implementation work, such as clarifying the problem before writing any code. Then once a pull request has been submitted as a potential fix for that issue, we could move the implementation discussions to there. While these discussions can be done privately, information that is easily available would allow us to revisit the reasoning for why we decided to make certain implementation decisions. This can make it easier for when documentation is created to address this section of the code. Or if a new architecture problem arises as a result of fixing that one issue. At the end of the day, making these discussions public on GitHub will help create an environment that encourages open discussion and questions with others. Sometimes discussions around implementation details can be less relevant when addressing an issue. Which is when we discussed project ideas through director messaging. It's difficult to define a balance to what's at the task at hand and unnecessary information. In general, project ideas not ready for the public are kept in direct message. This is features that are not relevant to the current goal such as future project ideas and implementation ideas that are off topic. And now to Carlo who will explain how creating informal documentation helped with communicating the state of the space_shooter project to help onboard me on to the project.
Carlo:
I made two informal documents when Micah joined. Current state and ideal state. Current state document described the game as it was, and the ideal state described the game as I thought it should be after refactor. The intent of the documents was for Micah to use as a reference getting familiar with the game. But at the same time explaining my current intentions for the game. As Micah was more comfortable, we were able to use the ideal state document to have in depth discussions about what the ideal state of the game should look like. This turned into a more informal ideas document where we throw ideas for discussion as we think of them. It came from inspiration from other games. After a few weeks of consistent collaboration on this project, I started working on a few more formal documents to put the longer term ideas in my head on to paper.
The first document was a large flowchart explaining my ideas for what progression through the game looked like. The progress of levels, unlocking characters, bosses. And levels. And the next were the MVPs for the project. I say MVPs because I wrote two based on the two didn't scopes for the project. The first MVP was for space_shooter as a showcase game. Includes how many items, characters, bosses and levels you want in the game. The general rule for this MVP was things only could be in the showcase game to the point where they made sense for the game to show off the Amethyst engine. The second MVP was for space_shooter as a fully released game. The only difference between this MVP and the showcase game MVP is the general rule for this document is more content to make the game as fun as possible rather than just showing off the engine. This could be a story or even a secret ending. And now to conclude the talk.
Micah:
In summary, the main takeaways on the collaboration we used were engaging regularly in open discussion is beneficial to capturing project progress. Documentation is important for solidifying knowledge about project architecture decisions. And sharing ideas regularly keeps everyone on the same page. And this is what we found to be the most helpful for reviving a dead Rust project. If you're interested in learning Rust or want to contribute to an open source game, then we would be happy to help. Working on space_shooter is a project that aims to be a fun and informative learning experience. Whether it's with code, arts, and/or documentation, feel free to reach out and we would be excited to meet you. And thank you for attending our talk. We are now open to taking any questions.
Inaki:
Great talk!
Carlo:
Glad you liked it.
Inaki:
So, first question is, why is item and spaceship collision a separate system from spaceship and enemy collision?
Micah:
So, we organized our systems to listen for specific event types. Like in this example, spaceship to enemy and spaceship to items were their own even types. And because of that, we decided to make the decision to create separate systems for them, so it was easier to maintain the logic that was specific to those particular event types. But yeah.
Inaki:
Gotcha. Do you feel there is a difference between starting a game... a game design with ECS and moving one from something else to ECS?
Carlo:
Yeah. I can take this one. Yes. So, I have... I've tried a few smaller games. I've done like a game jam in the past. And yeah. ECS was very unique to me because of the way it organizes the data. It's completely different. So, I think the most developed one I have done in the past is something with Py Game and I was sort of using the object oriented tools there to make... to best represent the data in that way and it wasn't... it's very easy to get organized. And ECS is very good at constraining the data and systems into being organized which is really nice.
But as you can see, we need to refactor anyway. So, you can still mess it up.
Inaki:
Of course, always. Always, always, always. It seems like ECS is a popular solution for games. But are there any situations where you would not use it?
Carlo:
I can't think of any. But I don't think... we're still learning ECS. And I... yeah. I can't think of a situation where I wouldn't. But I don't think I'm... I know enough to speak fully to that.
Inaki:
Also, it seems that the Amethyst game engine in recent versions, I'm not quite sure if it's released yet or not, they're moving on to a new ECS system. Can you comment on that or know anything about that?
Carlo:
I know that, yeah, they're switching from library called specs to legion. And I don't think I'm qualified to talk about it. But yeah. If you are interested in learning about that, there... you should join the Amethyst Discord. That's where we learn a lot about it. It's something we will probably need to do some refactoring to adjust for that in the future. But, yeah, join the Amethyst Discord�to learn more about that.
Inaki:
Another talk. And another user asks whether you've had a look at the Bevy engine which also is quite recent and whether you can comment on it from the user perspective, an Amethyst user.
Carlo:
I can't comment on it. But because I haven't tried it yet. It does look interesting. I know it uses like the same design philosophy. And I know that Amethyst, there is a post that was made that Amethyst is working with Bevy. And so, yeah, I think it's a good... it's a... it's a great engine that people should try too.
Inaki:
Great.
Carlo:
We're not in competition.
Inaki:
All righty, then. I think we're out of questions. So, just one second, see if anyone came in. Oh, no, we're good. Micah, Carlo, thank you so much
Carlo:
Yep. Great to be here.
Inaki:
Until next time.
Carlo:
Yep, see ya!