Search Unity

What has been the most useful for you regarding data management?

Discussion in 'Entity Component System' started by Nyanpas, Sep 19, 2020.

  1. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
    Apparently I have a lot to read up on about what functionality is implemented to store and manage all the data. I have looked into the blobs, the various Natives, what can and cannot be static, as well as lifetime and struct usage and its limitations. Yet, I keep seeing new functionality I never thought about already implemented, and due to time constraints, it is hard for me to upkeep with the expansions of the API-documentation.

    So I would like to hear it from you if you have any insights on this you would like to share. :3c
     
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    For keeping up with new additions, I find it helpful to look at diffs of packages whenever a new release comes out. It makes it much more obvious what was added. I keep a personal git repo that tracks the changes. However, I recall a company hosting public git repos of each package to track changes. If you cannot keep up with that, then it is best you focus on your game and if you find yourself doing something in a way that feels ugly, ask on these forums if there is a better way.
     
    Enzi and nyanpath like this.
  3. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    My day job involves writing complex financial algorithms in SQL, so I know first-hand just how horrible data-oriented-design can be when used over a complex domain and large team. It makes it very hard to generalize and isolate your algorithms, and easy to introduce strange data dependencies or implicit, undocumented behaviour (and let's not get started on testing...). What seems great in a small example blows up when the code gets complex. OO isn't a panacea, but it provides a lot more room for choosing good design principles.

    I can't tell you if that applies to game programming too, but it makes me skeptical of going too far into ECS proper for managing everything in your game. I'm using it a bit for some of the more performance-critical pieces, and might end up sticking some static object/props in there too once they figure out the subscene offset workflow.

    However, the other pieces of DOTS are really solid. Jobs are an incredible way to isolate a piece of functionality (and if they can be Bursted, all the better). When you're putting together jobs that don't use entities, native collections (NativeArray, NativeList, NativeHashMap, NativeQueue) are a must. Blobs can be a bit awkward at times, but they open up a lot of possibilities (eg multiclassing and complex nested data structures). Blobs are the most flexible in regards to what you can represent, but also the most challenging to manage properly.

    Ultimately, I'd find the problem you want to solve first, then try to find the data structure, not the other way around. You may never end up using a NativeMultiHashMap or SharedStatic. If you're finding difficulty in representing what you need to represent, then it's time to start looking. IMO, it helps having a grasp of complexity analysis (eg big-oh), and also where these things sit at a low level -- if you understand pointers, memory layout, buffer pool allocation, and cache-efficient memory access, then it's much easier to grasp which of the high-level data structures is going to get you what you need.
     
    Nyanpas, Singtaa and FakeByte like this.
  4. Singtaa

    Singtaa

    Joined:
    Dec 14, 2010
    Posts:
    492
    Agree with everything @burningmime said. In my experience, folks who worked a lot with relational databases tend to have a better eye for the shortcomings of ECS. Behavioral richness is not its strength.

    Now that the hype on DOTS has cooled down considerably, I hope more people can see this just as an alternative useful tech stack instead of something that will replace the existing tech. Ofc, I also wish Unity can spend more time working on better hybrid workflows.
     
    Nyanpas likes this.
  5. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
    You probably meant that: https://github.com/needle-mirror
     
  6. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
    Interesting findings. I have found that using traits in the form of components to build up objects (or entities as they are now called) has helped with reuse as well as for looking up as the traits work well for filtering (which seems to be what ECS is all about). However, my project is rather data heavy with regards to creation of meshes and the world, and for that since it is hard to pre-calculate in detail how many vertices the meshes will have, it seems I have to create data structures that accommodate this uncertain-tee.
     
  7. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    The idea of building entities from a combination of components is awesome for game dev, but MonoBehaviour gives you that too. My concerns are founded in tying together your algorithms and data representation in such an innate way. Of course, that's what you have to do to get decent performance. The more abstracted you are from your data, the less the programmers knows about its representation (good!), and the less the computer knows about its representation (bad!). ECS, assembly, 2000-line SQL queries full of join hints, low-level C++ (TMP aside), NumPy, compute shaders, etc -- the fastest code is all tied very closely to the representation of its data.

    ECS - especially if you're trying to jobify/Burst everything - is a tradeoff. It's trading more programmer time for less runtime. There might be some trivial examples which are easier in ECS than MB, but IME most things are much simpler to reason about and write in OO land.
     
    nyanpath likes this.
  8. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    967
    @burningmime I do think you know what you're talking about but
    is such an ignorant statement to make. When you don't have good design, nothing is easy to reason about, no matter in what style it is written. There's just no accounting for weird human abstractions. When things get entangled all over the place and interact with each other you'll have a harder time developing in a team, that's just the course.

    And I do think that ECS code can be locked down easier and has less code than most manager classes in OOP.

    On topic:
    Your bread and butter will be IComponentData and IBufferElementData.
    When it comes to optimization, system caches and lookups, NativeArrays, NativeHashmaps, etc... will be great.
    For loading, creating bigger data that's always the same, Blobdata will be useful.

    If you start working with ECS. Focus on the bread and butter. The rest will come naturally to you, I believe, when it's time to use them.
     
  9. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    That's like saying you can dig a hole with a spoon or an excavator, so they're equally effective. Yes, you can have bad design patterns with MB, and bad design patterns with ECS. Yes, you can have more complex code with MB or with ECS. But "IME" stands for "in my experience". My experiences so far (limited to a one-man 2.5D RPG) is that ECS code is almost always more long, confusing and tightly coupled than the equivalent MB code. If nothing else, you're working with about 1/4th the features of the C# language, 1/8th the features of Unity.

    Simple stuff like pausing the game, anything UI, running a sequence of events, procedural generation, making custom editors, etc, etc. Some of this is straight up impossible in ECS (therefore requires awkward and error-prone code bridge code), others can be worked around, but in a way that's more complex.

    Even if it's just logic that could be implemented in pure ECS, it's very often not a good match. For a concrete example, a lot of my scripts are basically coroutines. Think cutscenes in Final Fantasy 7-9. Eg,
    Steve says something to Sally, then Sally jumps off a chimney while Steve runs over to her, then when both of them have finished their animations, Steve says something else, and the player chooses Sally's response, and based on that...
    . I can't imagine writing something like that in ECS without basically building some hairball of a DSL.

    Again, I'm not saying that MB is better than ECS, just that it offers more possibilities for good design. If your manager classes are huge and entangled, there's a very clear, tried-and-true solution for that: dependency injection/IoC. What ECS brings to the table is performance, and for the 20% of your game that needs it, it's an excellent tool.
     
    NotaNaN and nyanpath like this.
  10. nyanpath

    nyanpath

    Joined:
    Feb 9, 2018
    Posts:
    77
    That has been exactly my experience with this so far, however Joachim Ante replied to me in another thread that making the DOTS-API easier and less time-consuming to use (as you are sort of coding on a level the game engine should provide for you to call itself a game engine) is something they are working on and will come going forward. I hope to see the results of this.
     
    NotaNaN and burningmime like this.
  11. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    I think it is important to not conflate experience with absolutes. Different people have different ways of thinking, reasoning, and working. I struggle with dynamic-typed languages like JavaScript or Python to reason about what I want to accomplish. And naturally, any project I am a part of that involves working with those typically ends up being a disaster. But for the reasons I struggle with those languages, there are many people who find those aspects intuitive and efficient. They create some amazing stuff. And then they look at me and wonder how I do what I do so efficiently in strongly-typed languages like C++ and C#.

    I think it is also important to not conflate ECS as an architecture and the current feature-set of Unity.Entities-compatible modules. ECS as an architecture is tried and proven in game development and has powered several well-known AAA titles. It is more than just performance. It solves issues with regards to logical responsibilities and execution order.
    Pausing can be solved with hierarchical system disabling/enabling. UI is just a very static 2D game overlayed on top of the real game. Sequences of events can be solved with a command buffer, often with a priority queue. Procedural generation is actually one of the easiest things to do in an ECS.
    However, these things are more difficult to do in Unity's ECS than they need to be, mostly because only a small part of the engine is ready. That doesn't mean that Game Objects will forever be faster to work in compared to an ECS. What it does mean is that unless you are the type of person who likes, wants, or needs to do custom things, Unity's ECS is probably not ready for you. For me, no game engine currently exists that does what I actually want to do. DOTS is open enough that it is the clearest path for me to build what I want to build without me having to rewrite the entire engine from scratch (specifically the serializer, authoring workflows, and asset management). So that is why I am here.

    None of this is to say there is anything wrong with you preferring OOP with GameObjects over the new ECS stuff. You are one of the few people who have correctly identified where the new DOTS tech fits in the GameObject world. I applaud you for that. It is a valuable perspective and I hope you continue to provide feedback for how that can be improved further.

    All I am asking is that you consider these different perspectives before using absolutes so that people don't feel the need to call your opinions "ignorant". :D

    Is this something you are struggling with and hence this thread? I find it easier to evaluate the best tool for each specific problem rather than try to identify all the problems a given tool can solve.
     
    NotaNaN, RaL, charleshendry and 2 others like this.
  12. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    I've had a pretty different experience with it

    It does take longer to actually physically write DOTS code than MB code, but it takes waaay less time to reason about architecture, performance & scalability in DOTS. In the end, I'm almost certain DOTS brought a massive time save in my project. And the benefits are even greater when working with a team, because DOTS gives you way less ways of doing terrible design choices than in OOP. Simple/straightforward implementations of things in DOTS often coincide with the right way to do things for both scalability and performance. On the other hand; OOP in a team context will almost always result in spaghetti unless some heavy policing/reviewing is done by the lead. And even then, it's often not enough to prevent a messy architecture

    I especially have to disagree with the "ECS code is almost always more long, confusing and tightly coupled than the equivalent MB code." statement. The code is more long for sure, but much clearer due to the reliance on entity queries and very explicit update orders. As for coupling, I gotta say I'm surprised you're raising this as an issue. The quasi-absence of coupling is one of the biggest strengths of ECS
     
    Last edited: Sep 21, 2020
  13. nyanpath

    nyanpath

    Joined:
    Feb 9, 2018
    Posts:
    77
    Oh, my, where do I even begin.

    I've done goofed so hard with DOTS so much I had straight Monobehaviour-implementations which were faster because I had no idea about overhead when using the Job Systeme. I've had systems that I thought were simple, clean and efficient perform terribly compared to where I just had promise chains as if I explicitly typed out the branching choices. Point in case, my triangulator, which currently performs over 100 times better than Triangle.NET and is faster than anything else I have tried so far. It looks like a complete mess but that's simply because I can just add more cases to cover shortcuts and the faster it becomes. I didn't think it would work that way but it did.

    I wish it wasn't the case, but sometimes it seems that one can actually pick up on patterns in the ugliest way possible and make it work in the best way possible.
     
  14. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Sure. Everything I have said here should have a big flashing "in my opinion" above it.

    In the context of this forum though, we need to consider ECS as to what can be done with its current implementation. Using the full feature set of C# is not on the table - you will always be using structs and not classes, lack access to coroutines, etc.

    And don't get me wrong - in places where ECS is needed, it absolutely shines. My contention is that it leads to poor architecture and tight implicit coupling when used extensively. Kdb+ is an incredibly fast database, but I don't think anyone is writing huge parts of their stack in Q.

    And I would contend that for each of those cases (except maybe pausing), the equivalent MB code is easier to write, read, test, fix bugs with, modify, onboard new team members, etc. And this problem is inherent to using pure ECS for those tasks.

    For UI, this gets into the idea of separating algorithms from data representation. A flexbox layout shouldn't care what controls are inside it, but it does need to easily iterate through those controls and calculate their sizes. Unity approach right now is focusing on UI Toolkit (looks like HTML+CSS) which seems like a good approach to empower designers.

    For event sequences, I gave the example above. Yes, it could be done with command buffers of some sort, but that would be like cutting grass with scissors. This weekend I popped off like 30 "yield return" style coroutines; I can't imagine how hard it would have been to translate them to ECS.

    Procedural generation is an implementation issue; I've posted elsewhere about difficulties in managing/spawning prefab sets. In time this will be better; looking foreward to the offset-subscene thing.

    It's ready for a hybrid approach. As they develop more systems based on it, it will be possible to bring over AI, etc. But I don't expect them to ever bring over UI (side note: the new HTML+CSS stuff looks promising).

    Sorry if it came off a bit strong. Of course people have different perspectives. I don't have a background in game development, aside from some hobby projects. My perspective is largely from backend software engineering on distributed systems.
     
    Nyanpas likes this.
  15. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
    The sad fact that so little is done with regards to this that is available that I can find online and I am left to trial-and-error until I have something working. Run-time triangulation of non-monotone polygonal shapes with holes in them is seemingly easy in theory, but optimising this for performance is the real challenge. I have tried a lot of libraries and they all seem to be made with next-year async performance in mind. So I spent half a year coming up with something that could at least perform well, but at the expense of portability. Now I am in the process of porting it to the Job system, and while doing so I beheld a pale horse, and hell followed with him.

    However, I was mostly curious to see what others could share of their experiences.
     
  16. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    Here you are talking about Unity's feature-set.
    And here you are talking about the architecture.

    Keep these separate. Stating that Unity's ECS needs more time to mature and things are easier to do in MonoBehaviour right now (something a lot of people would agree with) is a different conversation from stating that Data-Oriented Design and ECS architectures are inferior when performance is not required (which a lot of people will argue against, the smart ones bringing up Entitas as a counter-example). While I understand what you are trying to say, it is confusing for other people when you don't keep these conversations separate.
    No worries. I'm just trying to help you articulate a little more cleanly since your perspective is a good one but too many people have misinterpreted your posts (I can tell from their responses).

    There's actually a 1-to-1 translation of coroutines to ECS. I plan on writing a codegen tool for it at some point assuming Unity doesn't beat me to it.

    I see what you are asking now. The truth is I've ran into my fair share of pain too. There's no RAII in C#, and ECS is lacking the missing pieces to compensate. Specifically, we need a special kind of struct IComponentData which is deep-copyable and disposable. We also need a way to detect whether a blob asset lives in deserialized storage or not so we can conditionally dispose it.

    Part of the issue is that DOTS is still kind of new. People haven't had very long to build up a library of resources for it yet. If you feel that is important to change, publicize what you are up to with git links so that people can give you feedback and suggestions. That's what I do, granted I don't get very much feedback. :p
     
  17. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Any chance you could share or open-source it? That sounds awesome.

    ------------------------

    I'm talking about ECS as an architecture. "Data-oriented design" is big, and "the current feature set of ECS" is small, but "the eventual steady-state of ECS, based on the architectural decisions already made" is what concerns me (or rather, what I think is a poor solution to many problems).

    I started on something like that: https://gitlab.com/burningmime/nati...ativeEnumerablePostprocess/ConvertToStruct.cs

    However, it still turned out to be a poor match to my use case. The particular one is this: say you have a scene at an art gallery. 6 characters are wandering around doing different things in the background. A couple is moving between paintings discussing them, a student is making a sketch, a janitor is cleaning the floor, and a security guard is walking between rooms looking for suspicious behaviour, and a robber is laying out a heist. The are all looping "idle" behaviours until the player interrupts them.

    Now, the player character walks up to one of them to talk. That character needs to (cleanly) pause whatever they were doing, turn to face the player, and start a conversation. Then when the conversation is over, the character resumes their task.

    When the player hits the interact button, two "levels" of algorithm need to happen. From the player-character perspective, the algorithm is something like this:
    Code (csharp):
    1.  
    2. Disable player movement.
    3. Simultaneously...
    4.     (1) Turn character towards the target.
    5.     (2) Interrupt the target, and turn them towards the player.
    6. Once (1) and (2) are both complete, then begin the conversation subroutine.
    7.  
    The implementation of (2) is going to depend on the current state of the target. Object-oriented provides a clear path here. An "Activity" is an (expandable) set of possible tasks. Some of these can be interrupted immediately, some can be pause-resumed, and some can't be interrupted, so we wait until the "idle" coroutine yields something else. For example, if the janitor is cleaning, he might set down his mop before turning, and pick it back up before resuming. If he's taking a smoke break, he might just turn right away. If he's transitioning between cleaning and taking a smoke break, he will finish that animation before engaging the player.

    Object-oriented provided a very clear way to design each level of the algorithm and synchronize the components. The clean ECS way would probably be to build some set of hierarchical behaviour trees to control this mess. You'd end up with a great deal of code and complexity.
     
  18. FakeByte

    FakeByte

    Joined:
    Dec 8, 2015
    Posts:
    147
    Wouldn't job dependencies work with this problem?
     
  19. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    See? Even I misunderstood your viewpoint. You've complained about the architecture but haven't provided any evidence other than missing features or your day-job project which I don't know enough about to know if it was poorly implemented or if it was the wrong tool for the job. What I do know is that ECS architectures are especially good at realtime and interactive applications when you know how to use it properly. At a multi-person team design level that involves drawing up transformation graphs and defining data as an interface for the different independent modules.

    I would represent these activities as entities. I would then define components which represent things like whether the active activity is done, or a writable flag requesting the activity gets interrupted (which some activity systems can ignore). Anything that get be defined as an interface method can also usually be defined as a component in ECS. It is backwards to the way most programmers are used to reasoning about things. But once it "clicks" (and for some it may never because different people reason about things differently), it scales in complexity really well.
     
  20. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    No, they don't as these are actions that persist over multiple frames.
     
  21. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    I've provided examples directly from game dev. At no point do I see any iteration of ECS being superior for UI development to the alternative in OO world. At no point do I see any iteration of entities ECS an activity workflow such as the one I've described. This is because of the architecture of ECS, caused by the tying together of data and algorithms that work on that data. Again, I'm not complaining about ECS itself, it has a very good use case (performance). I'm saying that it's not great for everything. Unity seems to agree with me, at least in the first part, since their new UI system isn't ECS.

    The problem is that the activity has data of its own, and different activities have their own ways of pausing. So now you represent the class hierarchy as a bunch of separate components (
    Activity
    ,
    ActivityAnimationState
    ,
    CleanFloorsActivity
    ,
    SmokeBreakActivity
    , etc). Now the higher-level algorithm ("pausing an activity in progress") needs to consult the state of the underlying activity. Maybe you send out a
    PauseCurrentActivityEvent
    event component, and then the
    CleanFloorsPausingSystem
    can find entities with
    CleanFloorsActivity AND PauseCurrentActivityEvent
    , which will properly pause it, and send back a
    PauseCompleteEvent
    . And a
    SmokeBreakPausingSystem
    (or maybe just
    GenericAnimationPausingSystem
    ).
    You're just layering complexity on complexity, often with a one-frame delay, to solve a problem that abstract classes have already solved.

    And what if you add a new activity type, but forget to implement all the underlying pause infrastructure correctly? -- you've gone from a "method missing" compile time error to hopefully some sort of system at runtime that can catch that state.

    It's not that you can't do this in ECS, it's that ECS is the wrong tool for the job.
     
  22. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    I don't understand this argument. To me, this is a problem of OOP, not ECS.

    Why not just let a system which processes the underlying activity handle that?
    Events are completely unnecessary for this problem. All you need is for a system which reads the underlying event and a PauseRequest component to write to other components whatever it needs to write to implement its specific pausing behavior.
    In ECS you abstract using data, not systems. You write components which provide an abstracted view of the complexity and expose those, such as the PauseRequest component which might contain some fields regarding who is making the request and how urgent the request might be.
    Not implementing the direct logic in a single system for the activity type (EntityQuery) in question would be identical to if you decided that activity type would not be pausable. That means your game is still in a working, testable state, including the other functionality of the activity.

    I can continue to contend this, but the reality is I don't think I am going to be able to convince you anytime soon. From how you reasoned about converting your example to ECS, you are thinking about action sequences and abstractions rather than data, state, and transformations. Your mind is still thinking in OOP philosophy. Until you have been bit hard enough by execution order issues, infinite event chains, baggage operations, logic ownership arguments, single-inheritance restrictions causing code duplication, god objects, serialization propagation madness, ect. it will be very difficult for you to see the benfits of ECS aside from performance.

    The thread topic is about patterns of working with data, and I have done my best to hint at how I reason about data in these examples as counter-arguments. I am struggling to figure out what else I could add that would bring knowledge to the topic, but I would like to stick to it and not let this architectural philosophy get any more out of hand.
     
    SenseEater and RaL like this.
  23. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    OOP (and functional, and dynamic, and metaprogramming) allow you to write a generic algorithm without knowing what the data type is. ECS requires that systems know the exact data type of what they're working with (you can sometimes get away with using generics + constraints in a limited capacity to share code).

    I converted in the same way you suggested, I just called it "Event". It's adding some set of additional (temporary) data into a database, querying for that data in a separate system, doing the transformation, and writing it back. That's not simpler, nor is it more performant (all those archetype changes and frame delays!) than a virtual method call.

    I've been bitten by a fair bit of that outside game programming. ECS solves some of this, but introduces a whole heap of other problems.

    ECS is not a panacea; it's a tool whereby you can trade your programmer time for faster runtime. The only argument that kinda convinces me so far is @PhilSA's suggestion that by limiting the feature set, you can rely on your team members to make fewer mistakes. Which is kinda the same philosophy as Google's Go. I disagree -- I've always been on the side of "hire good people and give them powerful tools" instead of "hire more/cheaper people and give them tools they won't mess up". However, I'm not a manager ;-P

    I quite enjoy debating software architecture. I know I won't convince you, or anyone else, of anything, but it's always good to see different perspectives and how to approach problems in different ways. My day right now is a lot of waiting for tests to run, so I have time to waste. But if you have real work to do, we can end this conversation.[/CODE]
     
    NotaNaN likes this.
  24. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    Fair enough. I can continue to provide perspective as long as it feels somewhat relevant to the topic.

    If you write your algorithms as stateless static methods, generics + constraints is sufficient far more often than not.

    I made the mistake of not asking what exactly the "job" is. I suspect that for your use cases, the "job" is a game with many varied, unique, and individually sparse "events" which may invoke additional "events" and create complex logical sequences. I would typically describe this as "event-driven programming". You are using virtual methods, abstraction, and encapsulation via OOP to do this. That makes sense. It is good at that. And consequently, it is something that GameObjects and MonoBehaviours are exceptionally good at.

    But you know what event-driven programming is really bad at? Simulation. For a simulation, you often start at a state where you can make aggressive assumptions. But as soon as an event chain is unleashed, the assumptions you can make on that state are invalidated. If your simulation logic is to tick each object via virtual method, the implementation of each object needs to defensively account for changes in state by a previous object.
    Example:
    If each soldier updates the orders it receives from the commander on each tick, and one of the soldiers is actually a spy who kills the commander the moment he receives an order. Any soldier who updates after the spy will not receive the order because the commander is now dead. Suddenly half your soldiers received the order and half didn't.

    This may seem like a simple case that you might be able to work around. But usually this kind of issue comes up when manipulating transforms and performing spatial queries where lots of math is involved.

    You know what is really good at simulation? ECS. In an ECS, assumptions of state can remain intact because event processing is deferred. This allows for much simpler algorithms to run, directly solving the problem at hand with far fewer edge cases. Which brings me to this:
    Actually, if you are writing simulation or engine-level code, ECS saves you time (assuming competence). You get to solve the problem much more directly, without having to account for side effects and whatnot. For the simulation-heavy games I typically tackle, the productivity speedup has been a factor of 5x in C++ (yes I measure this). In Unity I am breaking about even due to the missing features in Unity's ECS, but the types of games I am making are very different now. I expect that factor to go up to the 5x as things mature (both from Unity and my own custom utilities).

    The reality is, most ECS architectures I have worked with in C++ have a small number of main thread-exclusive event-driven systems. Entities can have special types of polymorphic components (there's an array of pointers to indices of backing buffers of concrete types) which subscribe to system events. The systems when executed iterates through a buffer of events generated by a previous ECS system and invokes virtual methods which then can cascade into event chains in a very OOP style except that something like an EntityManager is also used so that data-only components can also be modified. This effectively takes the best of both worlds, but requires the OOP context to run inside the ECS architecture.

    Unity's ECS doesn't really have this. It almost does in a couple of different ways, one of them being Hybrid, but it is missing a few pieces that make it cumbersome.
     
    NotaNaN likes this.
  25. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    This brings up a good point (that's maybe closer to the OP topic). What's the best way to use runtime dispatch in DOTS? There's function pointers -- which work, but are quite awkward to use, and don't allow context (eg closures). You can do BlobAssetReference or void* plus a function pointer, which is basically how virtual methods are implemented, but you're just heaping in more awkward. You can also do this with switch statements (eg how Unity.Physics does it), that might be more Burst-friendly, but could be error prone.

    It's possible to use Cecil to generate switching methods. For example:

    Code (CSharp):
    1. // You'd just write a regular interface
    2. interface IActivity { int start(); }
    3.  
    4. // ...and implement it normally, except with a special "wrap" method in each struct
    5. struct CleanFloorsActivity : IActivity {
    6.     int numFloors;
    7.     int start() { return numFloors + 8; }
    8.     [CompilerGenerated] extern SActivity wrap(); }
    9. struct SmokeBreakActivity : IActivity {
    10.     float4 cigarettesByHour;
    11.     int start() { return math.Round(cigarettesByHour.x); }
    12.         [CompilerGenerated] extern SActivity wrap(); }
    13.  
    14. // to be able to code against it you'd also need to manually add this piece
    15. [GenerateWrapInterface(typeof(IActivity))]
    16. struct SActivity { [CompilerGenerated] extern int start(); }
    17.  
    18. // And you could go ahead and use it like this (even in a bursted method)
    19. SActivity activity = new CleanFloorsActivity().wrap();
    20.  
    21. // And then call its methods directly
    22. activity.start();
    23.  
    24. // ==========================================
    25. // The generated struct would look something like this
    26. // (you would write the part above the line and cecil would write this part)
    27. struct SActivity {
    28.     /* possibly some alignment bits; need to look into that part */
    29.     fixed byte data[16]; // set to the largest size
    30.     int typeId;
    31.  
    32.     int start() {
    33.         switch(typeId) {
    34.             case 1: return ((CleanFloorsActivity*) (void*) data)->start();
    35.             case 2: return ((SmokeBreakActivity*) (void*) data)->start();
    36.             default: throw new Exception($"Type id {typeId} out of range."); } } }
    37.  
    I don't know if that's something worth codifying into an actual framework of some sort, of if it's just too hacky. I could toss together a prototype if it's something people would be interested in.
     
    Last edited: Sep 23, 2020
    NotaNaN and nyanpath like this.
  26. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    I can think of two different ways to do what I described. However, they both would require modifications to the Entities package. One would use classes with pooling. The other would use structs with interfaces and Burst. Personally, I like the classes with pooling approach better because
    1) Burst isn't going to be able to optimize this use case much better than .Net 5
    2) It allows for better integration with 3rd party libraries
    3) It allows for more flexibility with the language

    The class type approach would likely have a base type that would look like this:
    Code (CSharp):
    1. public abstract class ClassComponent<T> where T : ClassComponent<T>
    2. {
    3.     public Entity entity { get; internal set; }
    4.  
    5.     protected abstract void OnAdded(EntityManager entityManager);
    6.     protected abstract void OnRemoved(EntityManager entityManager);
    7. }
    I can go into quite a bit of detail about how these would be stored and pooled and operate with the ComponentType system (hint: The generic arg dictates the ComponentType so only the base type can be queried). I could also go into a similar level of detail with the structs and Burst approach.

    However, one thing I am struggling with (using either approach) is: What would the expected behavior be for callbacks with regards to prefabs, disabled entities, instantiations, prefab cloning, ect?

    If you react immediately, you might subscribe a prefab to runtime events. However, if you use reactive systems, multiple systems might try to initialize the component. And then there's instantiation, where you may want to clone references for some members but generate new instances for others.
     
    NotaNaN likes this.
  27. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Hmmm... I don't really understand exactly how that would work, or what this would add. Is the idea that there's some associated framework-internal generic struct you can query for, which then will let you access the underlying class?
     
  28. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    Unity' archetype system has no knowledge of type trees for performance reasons. However, we need to present objects as a base type in order to make use of virtual functions and polymorphism. For classes, the solution I am proposing is to only use a base type as the ComponentType.
    Code (CSharp):
    1. public class Dog : ClassComponent<Dog>
    2. {
    3.     //...
    4. }
    5.  
    6. public class Corgi : Dog
    7. {
    8.     //...
    9. }
    If you had two empty entities and you added a Dog to one but a Corgi to the other, they would have the same archetype of Dog. Therefore you can iterate over all Dog instances and invoke methods polymorphically. But you would not be able to iterate over just Corgis because the archetype system doesn't know what a Corgi is. Internally the code would always cast a Corgi to a Dog before adding it. The pooling system would still know about the specialized types though. It would use an indirect buffer to perform the lookup.