Run systems concurrently
It would be cool to run our Updates concurrently, so we can. Something like https://github.com/Workiva/go-datastructures might be useful for concurrent-safe data structures.
Would this require that all Components have to be adjusted for this change? A simple (maybe inferior) solution could be to use a double buffer for all systems that need to run on the primary thread.
I'd prefer not to use a library for this. Concurrent data structures are trivial to implement in Go and I'd prefer just adding locks where needed vs pulling in an entirely new dependency.
Thinking about this more @Noofbiz is my understanding correct that the OpenGL stuff must be executed on the main thread?
If so, it seems there could be three different goroutines here for parallelization:
- The main goroutine that handles rendering
- The "update" goroutine that handles updating the scene (this is mostly just updating the
ecs.World, no?) - The "physics" goroutine which handles collision detection and any other physics work (such as box2d the box2d extension).
We need to be careful though because some things do need to be synchronized. I don't think this is as simple as just spawning off goroutines for each processing type.
I know Godot is multithreaded and does something similar. Can probably look to it for inspiration here.
I'd prefer not to use a library for this. Concurrent data structures are trivial to implement in Go and I'd prefer just adding locks where needed vs pulling in an entirely new dependency.
my experience with 'lets use this executor concurrency library' has always resulted in 'lets remove that stupid library'.
The way I'm thinking about it would rely heavily on interfaces. We'd redo systems so that they just accept an interface, then we just implement the interface on the entities. For example, the RenderSystem right now uses SpaceComponent and RenderComponent. But all we really use on it are View(), Texture(), Height(), Width(), and Position(), and even these are just Reads in the case of the RenderComponent, we don't even need to alter values. So why do we need the whole SpaceComponent? We could just have an interface with all the stuff the RenderSystem needs for it's update, and accept that interface. Then in Update, we'd only call those interface functions. No need for casting or much runtime costs at all. The other great part of doing it this way is that the Read-Write interface functions, like Move() could contain locks within them already, making race conditions easier to account for and fix in one place. It would definitely be a 2.0 change, however.