[Febuary 2024] Full ECS Stack Review

[Febuary 2024] Full ECS Stack Review

Hi everyone,

If you aren’t familiar with this format, every so often I make a big post discussing my thoughts on the Unity ECS ecosystem and all of its parts. This feedback is specifically addressed to the folks at Unity.

Very little has changed since my last review. To avoid sounding like a pessimistic broken record, I am going to limit myself to one positive and one negative of each feature. If you are looking for a more exhaustive list of critical feedback, I keep a list of items here that I periodically update.

General Feeling

Entities 1.X feels abandoned. It feels like you are all moving too slow. It feels like you are all focusing on the next big 2.0 that is going to break all of our projects and have tons of use-case oversights.

Lately, I’ve been seeing more people use asmref into packages. I’ve been seeing more people fork the packages. And I’ve seen people not willing to invest in that quit ECS altogether. And the reason is simple. The ECS packages are plagued by tons of small issues, such as missing APIs, or mistake-prone designs, or little bugs that aren’t getting fixed and in public releases in any timely manner.

If you assigned one developer each month to engage in the community, especially those who have asmref modifications or forks, and tackle exclusively the low-hanging fruits that can be fixed within a day each, you could easily do monthly releases with fat changelogs and remove all doubts that you are working on making things better while 95%+ of your team continues to do whatever it is you are doing right now. Rotate which developer is assigned to this task each month. Or make it every two weeks if one month is too long.

But if you can’t even make that work, consider having a repo out on GitHub that can accept pull requests. It doesn’t need a perfect mirror or all the fancy processes. Git is pretty good at converting full folder replacements into small diffs of the actual changes. It only takes a few minutes to sync things. But it could potentially save you some time by allowing a streamlined process to bring in user changes quickly.

Note: I am intentionally ignoring the 2023/Unity 6 compatibility, because I don’t believe it should have been that difficult. If there were significant complications, you should write about it so that us users can respect the technical debt achievements.

My Biggest Pain Point

My last biggest pain point was with AudioClip.GetData() inside a baker. This has finally been fixed! Granted, I had to find one of the couple of audio folks at Unity on the forums and get their attention, only for you all to brag about the fix in your announcement post here. Either that was an internal stress factor exacerbated by silo-ing, or there was some shady company politics being put on display. But hey, it is fixed, and I am pretty happy about that.

Of course, you had to create another major problem in the process, which was completely throw out one of your most unique features of your ECS. You had true per-platform determinism baked into your design, and now you are getting rid of it while leaving in the artifacts of its design (sortKey of ECB). Why?

It isn’t like you couldn’t solve subscene streaming a different way. Why not pre-allocate entities in the target world with shared components representing the scene being streamed in? And timing could be negotiated by a server once all players are loaded so that streaming could be made compatible with a lockstep network architecture. Why am I the one proposing this?

Mathematics

Package development has become alive again. Admittedly, I missed the update in my last review, but it is a pleasant surprise to see.

My criticism for this one is minor. There is no math.select for boolean types.

Burst

It seems like you are mostly focused on bugfixes and stability and supporting platforms. As much as I like new features, I do believe these things are important. As always, I have a crazy amount of respect for your team.

I was going to make a comment how I heard much less about problems with Burst, but then I ran into a few of my own that I had to work around. One was that the intrinsic guards throw build errors if they wrap the call site instead of directly inside the method. Another was that byte-sized enums could cause Burst to fail to compile some things, especially if they are used as function arguments to some instance method of a struct. I’ve also had a couple of situations where the cache got stale and Burst was running older versions of the job, which made debugging a little annoying.

These things are infrequent though, so I just encourage you to keep doing what you are doing, because I probably still wouldn’t be hanging out around here if it weren’t for what you do.

Collections

I benchmarked JobsUtility.ThreadIndex, and it is much faster than I originally anticipated. This opened the door to a big optimization I made recently where I needed to pool allocations per-thread.

This might be more of a job thing, but I really want to be able to have a dynamic number of containers inside some jobs. Not like thousands, mind you. But a good example of this would be an array of DynamicComponentTypeHandles.

Jobs

I’ve been seeing much better scheduling behavior of jobs recently. Maybe that’s a coincidence, but the compaction on worker threads is awesome!

Currently, I believe the job system is holding Unity back a bit. It is kinda bad at phased parallelism. I want to be able to schedule one parallel job that has multiple phases, perhaps a dynamic number of phases. I have algorithms where scheduling 40 jobs is faster than a single job, but it hits the main thread harder than I would like.

I fully acknowledge that this is a tough problem. But from the few hints you’ve provided, it sounds like you may have some ideas on how to fix this.

Entities Baking

I haven’t had subscenes cause crashes for a while now, at least not without it being my own fault. I find subscenes to be somewhat pleasant to work with.

As much as I want to complain about iteration by not having Burst, I suspect you are banking on CoreCLR to fix that. So instead, I am going to discuss an issue I have seen zero progress on. Baking is very bad at inside-out relationships. If an authoring GameObject belongs to a group, and all entities baked from that group should have a component, that is really hard to do right now, if not straight-up impossible. The group could be based on the hierarchy, or a serialized list, or some other thing. One of the challenges with this is that there is no way to just bake a GameObject, independent of its components. Well, there is, but it is internal to the Entities package.

Entities Editor

The exceptions are pretty much gone! Yay!

Why are the inspectors and windows not customizable? Why do I need access to internals to write inspectors for runtime components, especially blob assets? Why can I not define a custom tree of entities that the hierarchy window can display?

Entities Runtime

I believe a lot of the powerful performance features of Entities is severely underutilized. Some of that is due to the myriad of little API gaps, but I think a lot of that simply comes from developers not diving deep enough to learn them. But the fact that every year I find new ways to use some of these features shows just how powerful this is.

I’ve already discussed my determinism concern, so instead, my criticism will be focused on type handles. I’m getting really annoyed with having to cache handles to aspect lookups or structs that bundle type handles together. These things are necessary for advanced 3rd-party libraries that need to provide a good API.

Why can’t SystemAPI work with aspect handles and lookups? Why can’t we implement an interface that SystemAPI operates on to generate custom bundles of handles? I could also argue that maybe handles shouldn’t be cached to begin with, but that’s a deep rabbit hole.

Scenes

I don’t know when this happened, but the rules around scenes and scene streaming is much more consistent and predictable than when I last had to investigate them.

With that said, they are still extremely poorly documented, and what documentation there is tends to be a bit hand-wavy on the critical details.

Transforms

The Transform Helpers are really nice when you need them.

But Transforms in general are really wasteful of chunk space and calculations. The Child buffer is especially bad.

Graphics

MaterialMeshInfo’s new range mode is actually really awesome at runtime! This was a feature I didn’t see coming but is a really nice surprise once we got it. Unfortunately, I had to completely rework the baking mess to make full use of this new feature correctly, but the runtime is pretty clean.

If you didn’t know, I have been doing a lot of innovating with what is effectively my rewrite of Entities Graphics in my framework. It is faster in every way, has many more features, and is more stable than vanilla Entities Graphics. As an outsider hearing all the news indirectly, I am only left to assume your team barely exists and you need all the help you can get. All I ask is that you do not try to reinvent the wheels I have already invented. Instead, take what I have if you want it, and if you need help understanding, then start a conversation with me!

Physics

The little hidden algorithms in this package are good. The convex hull builder and GJK algorithms are well-constructed. And recently, I’ve been able to make sense of some of the wisdom behind the contact generation algorithms.

My biggest complaint with this package was never with the algorithms, but always with the general architecture. I had always assumed this was mostly just a “me” problem of wanting something more flexible. And that has been why I have been building my own solution that borrows from Unity Physics low-level algorithms.

However, recently I have been putting together some comparisons between my framework and vanilla Unity packages. I now understand the performance complaints I keep hearing about. It is bad, like shockingly bad. And I believe the culprit is the general architecture.

I plan to bring these comparisons into the spotlight in a couple of weeks, but if you want an early look at it, here’s the commit you should check out: Add physics trigger comparison · Dreaming381/LatiosFrameworkMiniDemos@b82c007 · GitHub

Character Controller

For all the reasons that people choose to use my framework in Unity Transforms compatibility mode, this package is by far the number one reason. People really like it.

Personally, I haven’t been able to dig deep into it since it is so tightly coupled to Unity Physics. If there were some lower-level APIs or functionality that was independent, I could maybe provide more critique.

NetCode

I’ve made comments about how I don’t believe NetCode and DOTS are where they need to be where the networking architecture of NetCode makes the best use of DOTS capabilities. Despite several conversations I have had behind the scenes, I’m still not convinced it is there.

I’m convinced at this point that it is not sufficient for my own needs without significant modifications to the package that I really don’t want to invest in right now. But I’m also getting close to the point where I need some kind of networking solution to keep a project of mine moving in the right direction.

The positive is that I have had conversations directly with members of the NetCode team, which I can’t say for many of the other teams here.

DSPGraph

Despite being abandoned at this point, the package is still working. And now that the audio crash in subscenes is gone, people are using my tech built on top of it. It is one of the most-used features according to my most recent survey.

Final Thoughts

User trust is serious concern right now. I’d like to see Unity make strides to invest in trust both short and long term. I’m currently working on a comparison project between my framework and vanilla Unity ECS, and the results so far make Unity look far worse than I expected.

Please feel free to reach out to me via these forums, Discord, or publicly in this thread. I want to discuss. I want this tech to move in a direction where I don’t feel like I am being further and further isolated as if you all are secretly plotting to get rid of me. It has been a rough past few weeks. Things can get better, but it starts with communication. We need more of it!

For everyone who did decide to read this wall of text (unity or not), thanks for making it all the way to the bottom. If you have comments or questions, feel free to reply to this thread and share them!

Sorry but I have to ask :

“especially those who have asmref modifications or forks” Do you have any links ? references ? github of that ?

I would love to have a look to what the comunity is doing around that :slight_smile:

As always, great write up! I can agree with pretty much every point you are making.

Very much agree on that. And it’s not that I’m waiting impatiently on the next big thing. I’m annoyed by the long stretch of small bugs in something like Hierarchy that we have to fix ourselves. Sometimes it’s just 1 line of code. Why does it take months to do so? In addition, some communication would be appreciated what’s the focus right now.

Also about the .asmref stuff. All it would really take is a scan of either your, Tertles or my Core library and what we need to access that is internal but should be public.
Last thing I needed was changing the enabled mask on a chunk level. Nothing is provided for end-users but it’s really all there, in ChunkDataUtility.GetEnabledRefRW.

Entities is in a weird place and doesn’t really know what it wants to be. Is it for beginners and has safety rails in every place or for experts, letting them do all kinds of stuff on pointer level. Right now, it’s only for experts with internal access. 70% of the stuff I’m doing would not be possible without it.

I have to expose Unity.Entities.Editor and Unity.Entities.UI.Editor just to make a some drawers for my data. As I perceive, people I know only do this for very small stuffs, nothing big.

I can confirm that a lot of us have been working on various future entities somethings for quite a while now; I’ve gotten mixed messages about when we’ll be allowed to say what about them, so I won’t expound for now.

That’s mostly why I haven’t been talking that much here or in discord for a while. That said, I am basically very into the somethings and I genuinely think and hope y’all will be too.

My overall advice to people using entities is that it’s extremely not going away in the future, and I think it’s a great idea to build stuff on it now for that reason. I think the concerns about needing internal access are quite valid, I can confirm we all care about them a lot, and I hope we can significantly reduce that in the future.

This is probably what I needed to hear the most. I love entities and I hope its adoption only grows. I really hope the somethings include missing peripheral solutions like audio and animation etc. Companion GameObjects make me feel dirty haha.

@DreamingImLatios - amazing feedback and great that you are still hanging in…same for @tertle and a few others…

I belong to the group which lost interest, due to slow progress, poor communication and overall lack of direction. Hard to believe how little tangible progress Unity made in the last 6 years…

I still hope that it somehow turns around…but things like ‘cannot say’, ‘working on something’s’ don’t give me too much excitement…let’s hope 2024 has more exciting news waiting…

There are several big concerns with this, that I would really appreciate you or anyone else make an attempt to elaborate on and try to clear up:

  1. Are these new things going to break my project? Historically, you’ve been very good at breaking projects that don’t entirely conform to the workflow that you use for your internal projects. Do you ever test with the various public repos I have out there? That’s one of the reasons I develop publicly, is so that you can test stuff and critically evaluate potential ways you may break things.

  2. Once this new big wave of updates arrive, how long are we going to have to wait to get all the bugfixes and incremental improvements for it? Historically, you’ve shown that you would rather spend the time chasing the next big idea. There will be another big idea that appears once you have this out. This is why Unity has the reputation of not finishing things. You never leave a developer behind to fill all the missing gaps when you move on to the next big thing.

  3. How much of what you are doing is reinventing what I and others have already done, but in an inferior way?

  4. Are you going to provide more value than the work required for me to migrate?

  5. Do you understand that not addressing those four concerns is what I mean by “abandoned” and not that your team has stopped working on DOTS things altogether?

I’ll be honest, I’m so far ahead of Unity with these things that I am really skeptical that their solutions will match in both performance and feature parity. Except for maybe FMOD, right now I provide the only solutions that consistently outperform GameObjects by a substantial margin in these areas.

The alternative for me would be building my own engine at this point. And that’s probably what I would do if I had to start over. If I constantly feel like I am developing solo, I’d rather have more ownership of the full stack.

Update for 1.2.0-pre.12
The new update dropped, and while the vast majority of my concerns remains valid, there are a few notable things.
First off, Entities Runtime uses a bloom filter for faster query evaluation. This is an excellent addition!

Entities Graphics also rewrote the baking which now looks a bit closer to what I did two months ago. It sill doesn’t handle incremental baking correctly. And it bakes entities with mixtures of opaque and transparent materials suboptimally. But it is still a big improvement.

I don’t know what the Entities Graphics team plans to do regarding material properties, culling, and mesh deformation. That whole process is where I’m leagues ahead in terms of performance, up to 50X in benchmarks. However, while I have some ideas about a new LOD design that would reduce the data payload (plus cross-fades), I haven’t started implementing it yet. Feel free to ping me if you want to chat about it.

Overall, I can at least say there are no negatives with this version compared to the previous 1.2 version. It still doesn’t provide enough value to compensate for completely breaking my debugging tooling and workflows, but it is a step closer.

I have only one question:
Version 1.2
How tf I should make a game without animations, audio, ai navigation?
I mean no one in unity even though about games need that or what?
Why there are plenty paid assets made by solo devs but multibillion company cant do the same in span of 6 years?
I dont want to spend 100$ just to make my cube rotating via animator :smile:

If you are willing to put up with Entities 1.1 for a while longer, I offer free solutions to both animations and audio.

Based on how much my transforms, physics (the triggers parts), and rendering is outperforming vanilla Unity ECS, I suspect it is less that they don’t think we need animation and audio and more that they don’t know how to build these things performantly in an ECS. What I don’t see often discussed is that while for game code the “performance by default” may ring true, for engine code, the bar is much higher, because it is competing against battle-proven C++. Optimizing engine-level features while delivering an intuitive API requires a level of discipline and attention to detail that I just don’t see out of Unity. I’m not saying that all the developers at Unity are incapable of this. I can definitely name two off the top of my head that operate at that level. But I also have had conversations with others that demonstrated frustrating levels of tunnel vision. And that’s not even factoring how much I suspect leadership interferes with this.

I’m not saying I’m perfect either. I know there are areas in my tech that are slacking. But I compensate for that by being very open to requests and having fast turnaround times. Unity seems incapable of doing this due to systemic issues, which probably doom Entities from ever really catching on with the masses.

Edit: To be clear, I’m not trying to put down any of the Unity developers. They are all nice people who often outclass most developers in the world. I’m just trying to point out the reasons why I believe things to be the way they are.

Hi, just out of curiosity, can you go a little bit in depth on what Unity is doing inefficiently in these systems (Transforms, Renderer, Physics). I’ve been rolling my own Transform system and Renderer in my spare time (just for practice though, not to beat Unity.), and I’d like to hear about the biggest performance gains.

I’d like to hear about Culling too. I’ve been reading AAA engines are now switching to Culling on GPU.

He has already written down his thought and techniques about those topics
https://github.com/Dreaming381/Latios-Framework-Documentation

There’s far too many technical details to fit into a single post. But here are some quick tips:

  1. Use as little ECS chunk memory as you need for transforms.
  2. At large hierarchy scales, use a breadth-first algorithm iterating over child ECS chunks instead of a depth-first for the first 16-or-so depth levels. Most chunks tend to hold the same descendant entity relative to their prefab, so you get pretty good cache coherency. I have a much more detailed article on this in my “Optimization Adventures” series which you can find in the framework docs.
  3. Most objects don’t need matrices on the CPU. Vectors are the same amount of data, so upload that to the GPU and let the GPU convert it into a matrix. Often the conversion is “free” as the upload is bandwidth-bound anyways. The winding order calculation also gets cheaper.
  4. Upload GPU data during BRG’s OnPerformCullingCallback after evaluating culling and only for the entities that are visible. You need to make this incremental, but this one has huge payoffs for complex things (like skinning). For simpler data, chunk-granularity is good enough and is what I use for material properties.

GPU culling is really good for static environment, but what I often see not discussed is that it is not-so-great for dynamic objects. You really don’t want to be syncing the entire game state with the GPU every frame (which is unfortunately what Entities Graphics does and is one of the things I fixed to outperform it).

I’ve been saying for quite a while they should get rid of LocalToWorld. Maybe one day. :smile: Unity uses LocalTransform as the simulation position and LocalToWorld as the render position which could differ because of inter/extrapolation. How do you deal with it?

Unity Transforms are local space centric, whereas QVVS Transforms (my solution) are world-space centric. In QVVS, if the entity does not have a parent, then it doesn’t even have a LocalTransform. Only the WorldTransform, bringing its transforms chunk usage down to just 48 bytes. Children cache their parent world transforms and try to keep the world and local transforms roughly in sync. There’s a TransformAspect that manages all of this complexity.

If you need to split simulation and rendering world transforms, it would be trivial for me to add a dedicated presentation transform component that the renderer would pick up instead when present. But no one has asked for that yet.