DEV Community

Cover image for What I learned about code while creating an ECS
Winston Puckett
Winston Puckett Subscriber

Posted on

What I learned about code while creating an ECS

Backstory

I've been a very opinionated developer from the beginning. My passion has been to figure out what actually makes development easy and promote/do that.

After working in an enterprise scale application for a couple years, I found a pattern that was really excellent for making that code simple and named it ExtendedIPO. While my team agreed that my code was incredibly readable, easy to extend, and easy to debug, there were members who had their own styles and thoughts about how to structure the code in this code base. I gave up and wrote this post on the subject instead.

I knew that I was right. That this was the way to code, and if everyone just followed the examples, there would be less bugs and things.

Then I wrote an ECS.

Everything changed in one method...

If you don't know what an ECS is, Catherine West explains it really well.

This was the first "large" project I worked on after I discovered ExtendedIPO. There are a couple code choices I had adopted since ExtendedIPO such as:

  • Complete immutability
  • list.ForEach() over foreach(var item in list)
  • Composition over Inheritance
  • No tunnelling. You can only go 3 scopes deep

All of these go beyond ExtendedIPO to create consistent, readable code.

When I made the ECS World class, all of these choices came into question in one method.

When I call, "World.Start()", it iterates through all of the systems and calls the Execute method on each to mutate the components there. Each Execute method returns a "SystemResult" which exits the foreach loop when SystemResult = SystemResult.Exit.

Here's how it looks:

public void Start() 
{
    var systemResult = SystemResult.Continue;
    // Continue until a system asks to stop.
    while (systemResult == SystemResult.Continue)
    {
        // Perform one loop.
        foreach (var system in _systems)
        {
            systemResult = system.Execute();
            if (systemResult != SystemResult.Continue)
                break;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Here's every reason this goes against my list of "good" code choices:

  • systemResult is mutable, but there's not a great way to make this readable otherwise (open for discussion)
  • foreach gave 100ms better performance than .Foreach()
  • _systems holds a list of type BaseSystem. This is inherited because each BaseSystem holds a bit of information about itself and must be constructed the same way each time. Of course, this is supposed to be shallow inheritance. Nothing should need to derive from something deriving from BaseSystem.
  • I've put a foreach loop inside of a while loop.

"Every methodology becomes religion"

This is a quote from a programmer I really respect. What it means is that everything from OOP to Functional to TDD to DDD eventually becomes something people believe has to be the only way to program.

At the point when a methodology has a following that refuses to program any other way, it has become a "religion." I've been hesitant to subscribe completely to any methodology because of this, but I've been searching for the best way to program nevertheless.

I realized that I had done what others have done before me when I created ExtendedIPO. While it's a great way to structure things in my codebase, declaring that it's the only way to program is just not right. All of these other things which I built on top of it is not the only way to program either.

Instead...

We need to realize that all coding strategies are second to the domain. This is less structured than DDD. It's, explore and know your domain and write code which describes that domain well.

Top comments (0)