Advertisement

Entity Component Systems and Data Oriented Design

Started by September 25, 2018 10:12 PM
33 comments, last by Hodgman 6 years, 1 month ago
3 hours ago, Hodgman said:

Yeah that is the point. You've got some terrible "dynamic composition system" code and want to convert it to "ECS" code in a series of straightforward steps. 

Please, please write that blog post! I'd really like to read about why "dynamic composition system" is terrible, what are the alternatives (I can think of some but most of them will not be as dynamic - is any dynamic one terrible or just the one presented in the video?). I, personally, am looking into a system for lower level functionality - physics, rendering, interest management, collision, animation - and not slapping it all into a single GameObject bag. I plan to have scripting for anything regarding in-game logic, hell, I can even hide hideous interface from C++ side under neat script interface so people can use it conveniently for higher-level code, but beneath the surface is neatly engineered C++ code. 

Isn't it often the problem? That the code which performs horribly and follows bad patterns is usually easier to use for higher-level programming (so game logic etc) rather than having it well-engineered but requiring totally different way of looking at things? Less object-with-all-the-stuff-centric? I have no problem designing (I think) pretty good systems, following good practices, but where my skills fail me is designing a system for game objects that will work well - and not end up with inheritance crap, or forced everything-as-components, or any other religious crap. I'm just genuinely having a problem, because after all such object, built of all the bricks, IS EVERYTHING. It doesn't have a single responsibility, it's an object, which will become a freaking monster, player or a tree. So either there is something that totally turns it upside down, or we end up with systems we know are terrible, but they have the advantage of getting the job done, vs. ideal? To me, it's important that this system has a usable interface for end-user writing game logic, and not only follows all principles but is virtually unusable to write a logic code, which just has to do some job, and not be engineering masterpiece...


Where are we and when are we and who are we?
How many people in how many places at how many times?
22 minutes ago, noizex said:

I'd really like to read about why "dynamic composition system" is terrible, what are the alternatives (I can think of some but most of them will not be as dynamic - is any dynamic one terrible or just the one presented in the video?).

Well I got a 10x perf boost by simply deleting the base classes in that example repo (because they're OOD rule violations). So dynamic composition frameworks are fine if you're ok with that 10x perf overhead!
The only purpose they served is to re-implement existing language features anyway... Yes, this means it's not dynamic after I deleted them... but most of the time you don't need classes that can change their definition at runtime; what you need is a simple way to author new classes. If they're being authored by a programmer, then code is a perfect medium - you don't need a framework. If they're being authored by technical-non-programmers, then a unity-style Game-Object-Component GUI is still possible -- just have it generate code! Unity already implements everything in C#, and can build C# itself, so having an "Add component" GUI generating C# code should actually work fine in Unity...

I use Lua as my scripting language - mostly not for actual code that runs every frame - mostly just for code that runs during initialization of things. It's pretty easy to make some Lua code that instantiates a bunch of "components" and call that code a "game object definition". A designer-friendly GUI could also generate that bit of Lua code if you wanted.

Also, there's no reason to use the strict rules that enforce:
1) An object is either an Entity or a Component,
2) Entities have nothing but has-a relationships with components,
3) Components know which entity they're owned by,
4) Entities fulfill the service locator pattern on behalf of components (i.e. parent->GetComponent<Widget>()).
You can throw out all of those rules and get by just fine. I actually find that code is simpler when you do throw out these rules, and these frameworks.

My preference for maintainable programs is for the flow of control and the flow of data to be as easy to follow as possible. "Typical OOP bullshit" completely fails at both of those points (virtual calls everywhere means the flow of control is spaghetti). But EC frameworks and ECS frameworks that rely on the above rules (especially #3 and #4) encourage their users to form implicit channels of communication via the service locator pattern, and implicit communication == obfuscating the data flow, so building a large program around this is an anti-pattern IMHO.

Once you've deleted the useless base classes and hard-coded the entity types, you can actually then remove the entity as a communication hub altogether, and instead have the components directly communicate with each other. At that point you're actually just writing "normal OO" code without a bloated framework and unnecessary "entity/component" restrictions.

22 minutes ago, noizex said:

Isn't it often the problem? That the code which performs horribly and follows bad patterns is usually easier to use for higher-level programming (so game logic etc) rather than having it well-engineered but requiring totally different way of looking at things? Less object-with-all-the-stuff-centric?

For this, I actually prefer to lean towards a functional style of programming (no hidden side effects), where you write the "main loop" procedure as if it was acting on a single object of every type that exists in your game. This makes the flow of data obvious, and also makes the required order of operations (and opportunities for parallel execution) obvious too, because it's defined by that functional-procedure. Instead of there actually being a single object of each type, and a single result value from those function calls, there's arrays of objects and arrays of results. The structure of the main loop looks the same though....
I guess I'll have to port Aras' code to this style too :D 

Advertisement
3 hours ago, Hodgman said:

Once you've deleted the useless base classes and hard-coded the entity types, [...]

Could you maybe explain what you mean by hard-coding the entity types? Defining concrete c++ classes in code, rather than having a fully dynamic/flexible SoA style entity that can be anything and everything?

devstropo.blogspot.com - Random stuff about my gamedev hobby

16 hours ago, Strewya said:

Could you maybe explain what you mean by hard-coding the entity types? Defining concrete c++ classes in code, rather than having a fully dynamic/flexible SoA style entity that can be anything and everything?

Exactly. The "EC" framework that he starts with (base class for components, entity that is just a vector of components) is basically an implementation of a new Language (+ a VM for that language) that allows runtime definition of classes. The alternative to building a language in C++ and then writing your classes in that new language, is to just write your classes in C++ to start with... and as it turns out, this "VM" adds a 10x performance overhead to the game, so it really isn't a great idea :) Just write normal code.

1 hour ago, Hodgman said:

Exactly. The "EC" framework that he starts with (base class for components, entity that is just a vector of components) is basically an implementation of a new Language (+ a VM for that language) that allows runtime definition of classes. The alternative to building a language in C++ and then writing your classes in that new language, is to just write your classes in C++ to start with... and as it turns out, this "VM" adds a 10x performance overhead to the game, so it really isn't a great idea :) Just write normal code.

Well, that's true and, for he most part, this will involve rather limited set of components, because if they were to cover a gameplay needs it seems like a job for scripting (where we have more dynamic situation), not C++ -. So not having the whole "crust" of generic component that can be anything would not be a problem. Robert Nystrom in his book mentions the version which is just normal composition (http://gameprogrammingpatterns.com/component.html), and then he shows other approaches, getting more close to what usual person thinks of component-based approach (the "terrible one"). By the way, Bob's way of going through the topic is insanely useful, even if he doesn't dig very deep into the subjects and keep them "appraochable", I love how he starts small, with a rather simple example and builds on top of it, while mentioning all pros and cons - no religious warfare, no opinionated stuff (or if any, he does it as some side-notes) - I wish more people would write books or articles on such topics in that manner.

Anyway, what I would like to learn myself is how to make a system that:

  • doesn't need to be overly generic (I'll have limited set of "modules" building the low level game object that I may need - transform, appearance, physics, skeleton, animation controller etc) 
  • allow me to not have some of the "component" on a given object - if half of the objects in the world are static I don't want them to have skeleton or animation controller needlessly allocated 
  • good on performance side (would rather avoid paying costs of magnitude 10x or more...)
  • have a good interface to access given functionality (though as I mentioned previously this would be alleviated by a scripting layer where I can hide certain design decisions - I'm a strong believer that game logic should not be so low level to make people think of performance all the time, something they're pushing in Unity now, I'd like the low level layer to be able to cope with whatever high level game logic throws at it)
  • (nice to have) would be ability to substitute certain functions, like being able to add different kind of beaviour in place, like Physics and WeirdPhysics - but this gets us into virtual land, and requires some common interface to be shared by classes implementing this behaviour (not so common as Component, but something like IPhysicsController etc.)

And about your suggestion of

Quote

"Once you've deleted the useless base classes and hard-coded the entity types, you can actually then remove the entity as a communication hub altogether, and instead have the components directly communicate with each other. At that point you're actually just writing "normal OO" code without a bloated framework and unnecessary "entity/component" restrictions."

I'm missing something here... components may communicate with each other to perform their functions, but there has to be some entry point to what they do, which will be used in game logic? Say we want to move our gameobject forward and start walking animation, what you mean to do? We're at game logic level, we're thinking "objects", not "some transform component" because that would be too abstract... I want to obj->setAnimation("walking"), obj->setVelocity(123) and have it perform what I need, and on the low level this can be whatever, separate systems made of components stored as SoA or AoS or ASUS even... If that makes sense? So the Entity/Object will have to be somehow there because we need some entry point to use the whole thing in a convenient manner - without the entry point you have components thrown around into various places and a bunch of them refer to an object - but how do you actually USE it?


PS. Am I the only one who's missing Slack's or other communicators way of formatting inline code (single backtick ``) inside the text? Whenever I try to use preformatted it adds a paragraph and destroys the whole thing :( Unless there is some other way to pre-format only part of an inline text?


Where are we and when are we and who are we?
How many people in how many places at how many times?

@Hodgman So i'm a bit confused, because i read (or understood) a few conflicting ideas from your post. These parts are conflicting in my mind:

On 9/28/2018 at 11:22 AM, Hodgman said:

hard-coded the entity types

 

On 9/28/2018 at 11:22 AM, Hodgman said:

then remove the entity as a communication hub altogether

 

On 9/28/2018 at 11:22 AM, Hodgman said:

where you write the "main loop" procedure as if it was acting on a single object of every type that exists in your game

The last quote i understand is a main loop that updates arrays of objects.

So how does it work? Do you updates arrays of Player, Monsters, Projectiles, CondumableItem etc, or do you update arrays of PhysicsData, InputData, CollisionData, RenderData, etc?

devstropo.blogspot.com - Random stuff about my gamedev hobby

Advertisement

:(


Where are we and when are we and who are we?
How many people in how many places at how many times?

A really good coverage of game objects can be found in the Game Engine book by J. Gregory. The book also covers the approach where you have a shallow entity hierarchy with a couple of specific derived entity types which assemble specific components. He calls this a hybrid hierarchy / component approach which is similar to what Hodgman is talking about I think. I personally like this approach as well since it much nicer to have some specific entity types for debugging.

Here is a good discussion if you don't have the book. I like that it covers the difference between tool-time and run-time object models:

https://www.gameenginebook.com/resources/GEA2_GameObjectSystems.pdf

 

 

Ah, thanks for reminding me. I've got the book and I learned a lot from it (my whole animation system is based on ideas from this book). For some reason I never checked the chapter on game objects as I had my own idea settled already back then... which didn't really age that well :)


Where are we and when are we and who are we?
How many people in how many places at how many times?

@Hodgman So how's the blog post/code sample coming along? I've been eager to see it :)

This topic is closed to new replies.

Advertisement