EngoEngine / engo

Engo is an open-source 2D game engine written in Go.
https://engoengine.github.io
MIT License
1.74k stars 136 forks source link

my thoughts about engo and systems as packages #721

Open scottlawsonbc opened 4 years ago

scottlawsonbc commented 4 years ago

I recently learned about EngoEngine and had a chance to spend a few hours reading the code. It was a fun experience and I learned a lot about how EngoEngine is implemented. I also had some ideas that I would like to share and get feedback on, especially from those who know a lot more about the EngoEngine internals than myself. I'd like to use this issue as a space to share some of my thoughts about EngoEngine, and I also want to highlight some areas of the Go standard library that may be of interest to EngoEngine developers. Please share your feedback by commenting on this issue!

For context, I am not a game developer but I am interested in game engines. I am a mechatronics engineer with a background in physics; keep in mind that I am missing a lot of knowledge about game engines and have an outsider perspective.

Things I really like:

Pain points:

The awkwardness of systems leads me to wonder about different ways ECS can be modeled in Go.

In ECS, entities are data and systems are data transformations (behaviour).

In EngoEngine, entities are structs and systems are structs which implement a System interface. Systems do two things:

In Go, a struct is a sequence of fields. A field has a name and a type. Two structs are comparable if they have the same sequence of fields. Fields are components. Structs are entities. Structs with different fields are different entities. Structs with the same fields are the same kind of entity.

In Go, a type may have a method set associated with it. The method set of an interface type is its interface. The method set of a struct is the set of all methods declared with receiver type of that struct. Is a method set the behaviour part of a system? The method set associated with a struct (entity) feels to me like a really important part of systems, but another piece is missing.

Other thoughts:

In summary, key benefits of modeling systems as packages:

Noofbiz commented 4 years ago

Hello! Thank you for your interest in engo! A lot of your points are 100% spot-on. We have issues currently about optimizing systems by running Update on their own loop. That would considerably speed up the update loop.

The AddByInterface stuff makes engo act like a normal ecs setup, but the issue is that we store the entities inside the systems instead of storing them at the world level. I believe the choice was made because that was much more performant than storing all entities in the world and letting systems filter them. The CollisionAble and NotCollisionAble are necessary because by default engo does assume if it implements CollisionAble it’ll go in that system, so if you do implement everything in CollisionAble but for some reason don’t want that entity in the system (it’s kinda just for rare edge cases), then you’d use NotCollisionAble. If we wanted a more traditionall ecs setup where the world only passes on the entities to the systems, it could still be done with the way engo works relatively easily, the world would just have a map[System]entities that it uses to pass the right entities to the systems every time without the need to filter every update (yay speedy and effective!).

The Systems as Packages part is an interesting idea. I think that could be a cool way of implementing Systems! You could just call in the system’s init method something to add the system to the world, as well as set things up. I’ll definitely think on that, but that’s a pretty neat thought!.

Asday commented 4 years ago

I'm currently knee-deep in understanding DoD, but from what I understand, the most important part of ECS is that components are iterated over in L1 cache branchlessly. This means no pointers (as they generally necessitate a cache miss), and it means pre-sorting of data. Here's the book I'm reading most attentively.

Given the above, I'm surprised to hear that the way Engo suggests doing it is less performant, but then there's a lot about DoD that I'm still thinking hard about to try to understand.

Noofbiz commented 4 years ago

The way engo does it is performant, and even follows ecs (what difference is there, really, between the world maintaining a list of entities vs a system keeping them itself), it's just not standard. The only issue is doing it a true separation, where the world just passes entities based on some filter function, wasn't as performant as doing it the way it ended up.

Asday commented 4 years ago

The difference is that the System keeps only pointers to the data, in my opinion. Obviously things get confusing if they keep the actual data because, say, which System gets SpaceComponent?

My biggest sticking point on it all so far is that while a given system can guarantee linear access to one Component, how does it deal with matching components? I had an epiphany at some point and shouted out loud in real life in my bedroom "it's .select_related()!" But that didn't really get me any closer...

scottlawsonbc commented 4 years ago

Thanks for your thoughts! Really interesting to hear some of the reasons for the design decisions, it helps me understand why it is structured as it is.

Overall, I feel like this engine has a lot of potential. The ECS system as implemented here makes use of some of the unique strengths of Go, like struct composition.

I also wonder what roles goroutines might play in the future of EngoEngine. In theory, channels and goroutines should be able to do as much if not more than the current event message system. I feel this area is a bit unexplored as far as game engines in Go are concerned.

airtonix commented 2 years ago

Consider using Drawer instead of Drawable, Animator instead of Animationable, Renderer instead of Renderable. Consider inferring whether something can collide by testing whether Collider is implemented, instead of implementing two separate interfaces Collisionable and NotCollisionable. My overall impression is that interfaces are powerful but EngoEngine isn't using them to their full potential.

A Naming BikeShed !!!! this is someting i can get behind 😆

👍🏻

The AddByInterface stuff makes engo act like a normal ecs setup, but the issue is that we store the entities inside the systems instead of storing them at the world level. I believe the choice was made because that was much more performant than storing all entities in the world and letting systems filter them.

problem with this is that now we have to manually manage adding and removing entities from all the potential systems it might be a part of.

I came here from ExcaliburJs, where systems have a list of component types that entities must have in order to be operated on. Other frameworks I've used(like ecsyjs) have a more expressive query language.

If systems filtered entities based on which components they had, then we would avoid more tight coupling.