Yesterday I wrote a blog post explaining, when not to use ECS (Entity Component System). Lot’s of people told me that even though, the post is interesting, it is very theoretical. So today I decided to compare the thought process of writing something in OOP style and ECS style on an example.
As an example I would like to take a log in process — before your game / app starts the user has to enter username and password, we send it to back end validate it and only than it is ok to start playing / using the app.
First let’s consider I am in SDK team, I need to write a “module” which other developers in my company, or even people from other companies will buy and use. In this case I need to create a simple to understand abstraction, which hides the complexity / implementation details. In such case it is best to follow the typical top down OOP approach. I introduce a
LogIn class, which is the main interface for the user. In best case, as a user, I will instantiate the
LogIn class and at some point, it will tell me that user is successfully logged in. It is a black box to me, it will take over UI, networking, persisting data and maybe give me some possibilities to configure it according to some special needs, but otherwise it is pretty much opaque.
Now let’s consider me an application developer. Log in process is only one of the features I need to implement. In this case I would rather follow the ECS bottom up approach, where I start thinking about the data I will need. And don’t forget — data is not only application state, it is also events and dependencies.
So, from current requirements we can assume that we will have:
- User name
- Send credentials event
- User token, which we get from backend
- Login form renderer
Now we can think about networking, if we already have some infrastructure for networking in-place, we can reuse it. If not, we can consider to keep it part of login process systems, or build a networking system which expects networking request components. Same applies to persistence.
Logic wise we could have something like:
- Show log in system
- Send login system
- Persist token system
- Load token system
- Ask to check credentials system
- Notify login succeeded system
As we are building this bottom up and for our self, we can build it flexible and testable, it would be easy to bypass the login process, or test only small parts of the process. We are not hiding the state, we don’t put abstract easy to understand things first. We decompose the problem in it’s smallest parts and let it work on a global state. This however implies that it will be harder to make generic solution out of it. We could say:
Well, we will just take those component and class definitions and put them into a separate / reusable module.
But this is not always that simple — specifically if we have a shared networking components and systems in our app. Further more, it is just a collection of things, which form something big, user will need to understand those small things anyways. We could also put something like our OOP
LogIn class on top, which will aggregate / hide the components and systems. Then we have an OOP approach with ECS underneath. This feels like a Frankenstein monster though. I guess it is ok to do in some cases, but I believe keeping to one paradigm is a better choice.
Having sad that, I just recalled building such a Frankenstein monster myself. When we were sharing code for BackEnd validation, we build a stateless worker which received game state, configuration, user events and returned new game state or an error if the events were not plausible and we suggested the player was cheating. This stateless worker was a pure function for our BackEnd. Internally it did a small re-simulation of the game, based on components and systems. So FP from the outside, ECS on the inside.
The short self reflecting episode underlines the conclusion of my previous more theoretical blog post. It is all about thought process. In FP I would concentrate on immutable state and functions which will transform it. In OOP I concentrate on abstractions, defining classes which encapsulate state and expose methods. In ECS, I concentrate at components and systems I will need to solve the given problem, elevating data representation as the key to solution not the code.