12 ms·
Object-Oriented Entity-Component-System Design
- reidjs 5y agoI'm curious if anyone has used ECS in a business application? Our application has a lot of business rules between our different entities. Currently we use something like MVC and the rules mainly live in the models themselves. This can be a messy when describing rules between objects.
- codr7 5y agoThis is pretty much a solved problem in languages with multiple dispatch (Common Lisp, Julia, Raku etc), the behavior lives in the methods which don't belong to a specific class. You can sort of, kind of get there with free functions and overloading as long as you're not doing anything fancy with the methods.
- NovaX 5y agoentity = instance id component = metadata + data system = metadata processors This way you can decouple business rules from the model by using metadata to instruct how the entity should be processed. At work we use json schema as our type system to describe our entities, where every instance includes the schemas that it implements. The metadata allows us to render an entity, process it via server-side triggers, store and search, use generic crud routes, etc. In a language like Java, this is the same as using reflection to inspect interfaces and annotations for processing an object.
- peterthehacker 5y agoThat chart with “Weak Foundation” vs “Solid Foundation” reminds me of Martin Fowler’s chart in his Refactoring book that compares “good design” vs “no design”[0]. [0] https://martinfowler.com/bliki/DesignStaminaHypothesis.html https://martinfowler.com/bliki/DesignStaminaHypothesis.html
- jayd16 5y agoSure ok. Callbacks violate what makes ECS performant but they like the pattern anyway, so why not.
- de_keyboard 5y agoWhere ECS gets muddy for me is when you have systems working on entities with multiple types of components at once. * physics - position component + physics component * rendering - position component + animation component * etc.. How do we now store these components? How do we create / access aggregates efficiently? If we have two arrays then there is lots of hopping around: PositionComponent[] PhysicsComponent[] Maybe we need some kind of grouping class? struct EntityData { PositionComponent position; // Null if not a position entity PhysicsComponent physics; // Null if not a physics entity } Interfaces have their issues too, but at least it's fairly clear what to do: class BouncyBall : IHasPosition, IHasPhysics { IPosition getPosition() { // ... } IPhysics getPhysics() { // ... } } Anyone solved this before?
- royjacobs 5y agoWhat's wrong with iterating across multiple components? You seem to imply that this is "hopping around" and therefore bad, but it's perfectly acceptable to do so. Of course, if your system needs to iterate across 20 components to do its job then maybe you need to check if you've factored your components correctly.
- munificent 5y ago> What's wrong with iterating across multiple components? It's bad for spatial locality. You end up with many more CPU cache misses, which significantly slows down execution. Using the CPU cache effectively is one of the primary reasons to use ECS.
- Twisol 5y agoRecognizing your username, I'd probably best not argue, but wouldn't iterating across (say) two component arrays only cost you two pages in the cache at any given time, since you're doing a sequential scan? You should have the same number of cache misses overall, unless you're doing something very complicated for each entity to cause the cache to vacate one of the pages. Of course, if you access N components you need N pages in the cache concurrently, which is going to fall over for a not-too-large N. But N=2 or N=3 seems unlikely to kill spatial locality. I can imagine it gets a little more complicated with prefetch, but you're still using the prefetched pages -- you just need to prefetch pages for two separate arrays (potentially at different rates based on component size) rather than one. Do these details end up snowballing in a way I'm not seeing, or are there details I'm just missing outright?
- royjacobs 5y agoHmm, I don't get it. Isn't one of the major reasons to use ECS to exploit mechanical sympathy, i.e. you make sure that cache lines are always full, you can use SIMD for calculations, etc. All of this is pretty much out of the window for this design, so what is the benefit of the ECS here?
- ScoobleDoodle 5y agoI think the component instances per type are in the cache coherent memory location. So all physics components are cache coherent with each other. And the rendering instances with each other. At the individual object level the different components are not cache coherent. So the rendering and physics instances of one object are not in any memory coherent location. Because the physics will do it’s SIMD to resolve the mutual state. And then rendering will do the SIMD for their aggregate.
- nodivbyzero 5y agoCheck this out. https://github.com/skypjack/entt https://github.com/skypjack/entt EnTT is a header-only, tiny and easy to use library for game programming and much more written in modern C++. Among others, it's used in Minecraft by Mojang, the ArcGIS Runtime SDKs by Esri and the amazing Ragdoll.
- elteto 5y agoTiny? In what world is this tiny? entt.hpp: #include "config/version.h" #include "core/algorithm.hpp" #include "core/any.hpp" #include "core/attribute.h" #include "core/family.hpp" #include "core/hashed_string.hpp" #include "core/ident.hpp" #include "core/monostate.hpp" #include "core/type_info.hpp" #include "core/type_traits.hpp" #include "core/utility.hpp" #include "entity/component.hpp" #include "entity/entity.hpp" #include "entity/group.hpp" #include "entity/handle.hpp" #include "entity/helper.hpp" #include "entity/observer.hpp" #include "entity/organizer.hpp" #include "entity/poly_storage.hpp" #include "entity/registry.hpp" #include "entity/runtime_view.hpp" #include "entity/snapshot.hpp" #include "entity/sparse_set.hpp" #include "entity/storage.hpp" #include "entity/utility.hpp" #include "entity/view.hpp" #include "locator/locator.hpp" #include "meta/adl_pointer.hpp" #include "meta/container.hpp" #include "meta/ctx.hpp" #include "meta/factory.hpp" #include "meta/meta.hpp" #include "meta/node.hpp" #include "meta/pointer.hpp" #include "meta/policy.hpp" #include "meta/range.hpp" #include "meta/resolve.hpp" #include "meta/template.hpp" #include "meta/type_traits.hpp" #include "meta/utility.hpp" #include "platform/android-ndk-r17.hpp" #include "poly/poly.hpp" #include "process/process.hpp" #include "process/scheduler.hpp" #include "resource/cache.hpp" #include "resource/handle.hpp" #include "resource/loader.hpp" #include "signal/delegate.hpp" #include "signal/dispatcher.hpp" #include "signal/emitter.hpp" #include "signal/sigh.hpp"
- linkdd 5y agoSince it's a header-only library, with a lot of templates, only the used code will be compiled. So yes, this is tiny. Tinier than Unity, CryEngine, Unreal Engine, or other huge frameworks of that kind.
- 5y ago
- jeremycw 5y agoI think the focus on ECS when talking about data-oriented design largely misses the point of what data-oriented design is all about. Focusing on a _code_ design pattern is the antithesis of _data_-oriented design. Data-oriented is about breaking away from the obsession with taxonomy, abstraction and world-modeling and moving towards the understanding that all software problems are data transformation problems. It's that all games essentially (and most software in general) boil down to: transform(input, current_state) -> output, new_state Then, for some finite set of platforms and hardware there will be an optimal transform to accomplish this and it is our job as engineers to make "the code" approach this optimal transform.
- BobbyJo 5y ago> Focusing on a _code_ design pattern is the antithesis of _data_-oriented design Doesn't the former enable the latter? Ideally, language (both human and machine) would have the semantics needed to represent all transforms, but that's not the case. Code you rely on, since none of it is written in isolation, needs to enable you to implement data-oriented design should you so choose. Also, I don't think pointing out that 'all games are essentially...' is particularly useful. It's true, no question, but that doesn't mean it's the most useful mental model for people to use when developing software. Our job as engineers is to make software that functions according to some set of desires, and those desires may directly conflict with approaching an optimal transform.
- jeremycw 5y ago> Doesn't the former enable the latter? Not necessarily. ECS is a local maxima when developing a general purpose game engine. Since it's general purpose it can do nothing more than provide a lowest common denominator interface that can be used to make any game. If you are building a game from scratch why would you limit yourself to a lowest common denominator interface when there's no need? Just write the exact concrete code that needs to be there to solve the problem. > Our job as engineers is to make software that functions according to some set of desires, and those desires may directly conflict with approaching an optimal transform. All runtime desires of the software must be encoded in the transform. So no software functionality should get in the way of approaching the optimal transform. What does get in the way of approaching the optimal transform is code organization, architecture and abstraction that is non-essential to performing the transform.
- gh123man 5y agoPossibly related - this is the same person who is working on a very impressive voxel game/engine. I didn't see an explicit mention of it in the blog, but the Youtube videos speak for themselves: https://www.youtube.com/channel/UCM2RhfMLoLqG24e_DYgTQeA https://www.youtube.com/channel/UCM2RhfMLoLqG24e_DYgTQeA
- echohack5 5y agoPersonal anecdote: Habitat was developed as a sort of application deployment / configuration management tool in Rust, and the architecture there is roughly equivalent to an ECS. I found it a joy to work with and work on. Not sure if it's fundamentally a better software pattern, but it at least meshes with my brain better than how most OO-style software is laid out. https://github.com/habitat-sh/habitat https://github.com/habitat-sh/habitat
- wlamartin 5y agoWe also use an ECS in Rust (shipyard) to power a bunch of the editing experience in Story.ai After some serious initial difficulty getting my head away from "this is a block" to "this entity has a block" I think it's turned out to make a lot of sense. For example, checking whether a value shared by a "when there is a new tweet" is in scope when someone wants to use it (i.e. in nested block) is as simple as having a few components to represent a pointer from value to the ID of the block that shares it, and a list of blocks that share scope for this location and then compare the two. https://story.ai https://story.ai
- adamnemecek 5y agoI was really into ECS a while back and tried to implement a GUI framework in it. It was not a good match since your single widgets contain wildly different types of data.
- nikki93 5y agoThe performance / cache-orientation point gets talked about a lot re: ECS, but IME there are also (and maybe moreso) other important benefits: - Combined with an inspector UI, you can explore gameplay by adding and removing components from entities and arrive at design emergently. One way to look at this is also that you write components and systems to handle the main gameplay path you start out thinking about, but your queries encode many other codepaths than just that (a combinatorial explosion of component membership in entity is possible). This lets you get a kind of "knob crawl" that you see in eg. sound design when tweaking parameters live with synths too. It lets artists and designers using the editor explore many gameplay possibilities. - The way I see the component data is it's kind of an interface / source of truth, but some subsystems may end up storing transient data elsewhere at runtime (eg. a spatial octree or contact graph for physics). However as components are added or removed or component properties updated, the caches should be updated accordingly. You get a single focal point for scene state. Once some state is expressed as a component you get undo and redo, saving to scene files, saving an invidual (or group of) entity as a blueprint to reuse, ... The cache thing feels like a minor point to me, inside a larger category of allowing you to massage your data based on access patterns by decoupling the logic acting on it. With performance being one of the goals of said massaging along with many others. I also find myself not really focusing on the "system" aspect as much as the entity / component; esp. re: embedding constructs for that into a library. I've found you can get far just having one large "updateGame()" function that does queries and then performs the relevant mutations in the query bodies, and you can then separate code into more functions (usually just simple free functions without parameters) from there that become your systems. There's a bit of a rabbit hole designing reusable scheduling and event systems and whatnot but I feel like just simple calls to top level / free functions like this on a per-game basis seems a lot clearer and ultimately more flexible (it's just regular procedural code and you're in control of what happens when). I like seeing the backtrace / callstack and being the owner of things and then being explicit all the way up vs. entering from some emergently scheduled event dispatch system.
- kvark 5y agoWhat I found in practice is that many people start using the ECS for speed, and then draw themselves into a corner of the design space. Now they have to weight every step on "how would it work in ECS?", and dedicate effort to fight the ECS paradigm. For example, a mesh may contain multiple materials. Is each material chunk a separate entity? Or maybe each bone in a skeleton is a separate entity with its own "parent" and "transform" plus other component. One of the different approaches is component graph systems [1]. It lacks the ability to mix and match components, but provides a more natural (and simpler) model to program for. [1] https://github.com/kvark/froggy/wiki/Component-Graph-System https://github.com/kvark/froggy/wiki/Component-Graph-System
- meheleventyone 5y agoInteresting, this is the same approach as Godot (a tree of nodes) by the looks of things?
- deleted 5y ago[deleted]
- serverholic 5y agoI saw this project awhile ago and think its really interesting. I hope people explore this more in the future.
- ksaho 5y agoThis reminds me on scene-graphs back in the day.
- runald 5y agoIt seems to be good in theory, but I'm not really buying into any of the implementations of ECS, CGS or whatnot until they have a working, non-trivial example. If they have really thought it far enough, then they should have at least have a complex game as a solution to a general problem they are addressing. The demo in that repo you linked can be trivially implemented, CGS or no. Someone could rebut with: it's only a demonstration, but it's easy to scale any game of any size. In theory yes, but theory and practice aren't always friends. The actual complex game could serve as a proof, and the author can enumerate the pain points and limitations of the framework. Wouldn't that take too long? Not really, just copy an actual released game, but using shitty (I mean, free) assets and make up some random content. It should cut the total development time from a week to a month at most. By copying, I mean everything, from the main menu, to in game-menu, to the little bits of logic that makes a game fun and engaging.
- TinkersW 5y agoC# and C/C++ Interop where either can be used or combined for nearly everything Just no! Pick one and stick with it.
- insaneisnotfree 5y agoI don't get it. What's the motive to create another layered file structure project organization?