[April 2025] Full ECS Stack Review

[April 2025] Full ECS Stack Review

Hello everyone. It has already been over 3 months since my last review. This year is flying by!

In case you missed my previous post back in January, I like to make posts like this discussing my thoughts on the Unity ECS ecosystem as it develops. This is mainly for the staff at Unity, but everyone is welcome to read it.

Like last time (and in general), the criticisms in this post are aimed at the technology and decisions made, not the individuals developing this technology. However, I intend my complements to be taken personally, because good things are built by smart people that should take pride in their accomplishments.

Once again, I’ve updated my wishlist if you want a list of actionable items.

General Feeling

With the latest updates at GDC, there seems to have been two different messages coming from the community in response. One group seems upset that so many things were canceled to keep Unity “backwards compatible”. The quieter group is appreciating the semblance of stability with Unity 6 and are focusing on getting stuff done.

I think I fall into that latter group. The Unite 2024 announcements were giving me flashbacks to an old DOTS roadmap chart a few years back. The course correction at GDC feels much more sustainable. I think it is important that Unity splits time between addressing the problems of today, and building better solutions for the future. Invest fully in what is out today, and the tech stagnates. But invest fully in the next generation of solutions, and tunnel vision sets in as the fixable problems of today may mask other issues the next generation may also fall victim to. There’s a little bit of ridiculousness with backwards compatibility for some parts of Unity in the GameObject world (and other events are not helping). But I think the DOTS team has found the right balance. However, there is something wrong with the DOTS release process, because I know the team is fixing issues quickly, but it takes a lot longer before those fixes show up in a release.

One thing I would like to see the DOTS team do a little bit more is creating more vertical slice proof-of-concepts that include other parts of Unity. Especially now with the cancelation of the ECS environment system, I think it would be a good time for the team to experiment with baking trees, details, and colliders of the current terrain system into entities, while leaving the base terrain rendered as a GameObject. Such an experiment may expose a lot of pain points to be addressed. And if you get something working, please share it with us as a side-sample.

While I have mixed feelings about some of the decisions that were made since my last update, I have noticed a very positive shift in how things are going with DOTS. It is honestly really awesome! There’s still more journey ahead, but I really hope this pace continues through the rest of the year.

Overall, it has been a very positive three months since my last update! Thank you for making that happen!

Alright. Time to talk about features!

Mathematics

The base mathematics package hasn’t really changed since the last update. However, I still have a positive and a negative about it.

Pro: The new numerics library is awesome (haven’t actually tried it yet, I just like the concept of its existence)!

Con: Why is the numerics library in the physics package? Please move it to the mathematics package or make it its own package.

Collections

The collections package has only received a couple of bugfixes since last time, so my comments from last time still apply.

I was going to make a comment about Allocator.Persistent being in places it shouldn’t, but when I looked for the example in the back of my mind, I realized it was gone. So yeah! Great package!

Some of the low-hanging fruits involve getting values by ref, supporting spans in the unsafe container types, and if you need a new container type, I get asked about priority queues a lot.

Jobs

Some of the job system optimizations have landed since last time. And some of them I’ve been able to notice help in a few situations. But it hasn’t been a win across the board. Shadow map culling is still a problem for me. And idiomatic foreach is still the way to go on mobile for NetCode.

Burst

With each update, Burst inches closer towards solving default interface methods. But even today, compile-time polymorphism is still not feasible.

The error messages I faced in Burst 1.8.19 were slightly different from 1.8.18, and after taking some time reading the messages and trying to make sense of what Burst could possibly be doing, I ended up finding just enough clues to work around the issues I was facing at the cost of some minor features. So overall, it is working for me.

Entities

A lot has happened here!

Let me start with the big one, which is IAspect deprecation without replacement in 1.4.0-exp.2. I have a strategy for dealing with this. And to be honest, I don’t mind the decision by itself. I could care less about whether source generators add anything to the IAspect type. Really, the core issue I see is that idiomatic foreach and IJobEntity are inextensible. I really wish I could make an API that accepts an IJobEntity instance, and then drives the IJobChunk’s Execute method from a custom parallel-for job that has special container interactions. But there’s no way to simply command an IJobEntity to populate. And similarly, I can’t extend systems with my own source generators because Unity’s will then freak out not recognizing the types, since it tries to rewrite everything. But honestly, this is only third on my list of pain points right now.

Pain point #2 is trying to avoid doing structural changes on chunks with very few entities each in a transitionary archetype (newly spawned) towards a very common archetype. If I don’t proactively avoid this, fragmentation gets really bad, especially if the structural changes are for adding chunk components or tag cleanup components.

Pain point #1 is entity remapping of dynamic buffers. When it comes to dynamically-sized data in ECS, dynamic buffers are all we have to work with. But having every element be a rigid structure doesn’t always work great. So several of us like to treat the dynamic buffer as just a raw memory buffer and reinterpret it how we want. The problem is that this doesn’t play well with the inspector, and it doesn’t play well with entity remapping on instantiation. Blobs and asset references are pretty easy since it just requires one custom system to patch everything from a remap-friendly dynamic buffer during subscene postprocess. But remapping entities can happen in a lot of places, and is a lot trickier to work with.

Anyways, that’s all the bad, time for some good!

Entities 1.3.14 has finally stomped out a lot of bugs and feels very solid. There’s been 30 bugfixes listed in the changelog since last time. 3 of which I reported. A lot of these were really painful. I’m down to just two reports for Entities runtime left unaddressed. One of those I only remembered to report today while writing all this.

1.4.0-exp.2 filled in some pretty big API holes with dynamic buffers. The next low-hanging holes are ref accesses to chunk components and fixing shared components not using lookup caches.

Along with all these new changes and additions, I am seeing staff be more interactive with the community again. This makes a big difference! Honestly, you all should congratulate yourselves for how much of a difference you’ve made since the start of the year. I know a lot of people don’t watch that closely to notice, but to me the difference is night and day! Kudos!

Scenes and Serialization

I don’t know when this happened, but it seems like at some point, change filtering in live worlds due to rebakes is now working correctly. I was kinda surprised when I got around to making a bug repro and it worked. That and the entity reference bugfix were really nice.

At this point, I think I understand what the rules of the live baking differ are supposed to be, and I haven’t noticed any discrepancies from those rules yet in the latest release. However, there is one thing that I am finding particularly challenging right now with the differ. Often times, I make assumptions in the runtime about rules that need to be followed, such as several components being kept in sync, enableable components being enabled when something changes, buffers being sized a certain size, ect. The differ is prone to violating these rules at runtime. Using change filters to detect these violations is expensive, and would also likely hide runtime bugs. I would really like to get a list of all the changes the differ made to the runtime world and run some dedicated editor-only systems on those changes to clean up the rule violations surgically.

Transforms

The hierarchy update race condition has finally been fixed! It took years, but it is finally done!

There’s one more bug left in the transform system, and that is that if you change existing parent components of a bunch of different entities, the order of entities in queries will be nondeterministically scrambled. I’ve struggled to figure out how to make a bug repro that demonstrates this clearly and reliably. But part of me simply doesn’t care though because I have my own system that doesn’t have this problem.

Speaking of custom systems, I’m still really curious what the new parallelism rules will be for the new unified transforms. Currently, IJobParallelForTransform uses its own parallel ordering rules, which are almost certainly going to be different from IJobChunk ordering. If I want to schedule a parallel chunk job that writes to transforms, what will happen? If I knew enough about the design, I’d consider prototyping my own version to compare against the solution I’m using today. I saw the comment in the “misconceptions” document about ECS being not the ideal storage medium for transform hierarchies, and I’d like to run some actual experiments.

Graphics

There wasn’t really anything notable in Entities Graphics I want to call out since last time. Most of what I said in my last post still stands.

There is one new thing exciting in the broader world of BRG, and that is the new Mesh LOD feature in 6.2. It is actually way nicer than I thought it was going to be! I like how it uses a formula for selection rather than manual ranges, as this makes it much more friendly for ECS. Once my framework’s community starts to get antsy for 6.2 compatibility, I look forward to adding support for it.

What is the plan is to support it in vanilla Entities Graphics? To me, this looks like it needs to be added as a factor for the binner, which is a little messy because the binner does some funky SIMD things. I hope that if you add support for it, you also fix submeshes so that they can use the full 16 bits and not just 8. The binner is one of the few parts of Entities Graphics I haven’t modified in my own solution yet, and it would be really nice if I can keep that part in sync.

Physics

I’ve been keeping a close eye on developments here. In terms of algorithm details and implementation, I’m liking the direction things are going. Motor support for multiple axes is a strong step forward. And I like the move toward substepping in the latest 1.4.0-exp.2 release. I can also appreciate the continual addition of new samples and improved documentation over time.

One area that I think could use some improvement is better documentation of the internals of the simulation model. After all, it is not uncommon for people to want to modify parts of it for their own needs. But there are not many physics engines out there that are truly stateless in the way Unity Physics is. Nearly every other physics engine uses some kind of warm-starting or other stateful attributes. While I understand a lot of the anatomy of various physics engines, my math knowledge of the algorithmic details isn’t quite where I’d like it to be to fully understand everything Unity Physics is doing in the solver just by reading the code. And this means that I struggle sometimes to identify which pieces are standard formulas, which are details specific to being stateless, which are approximations and hacks for the sake of performance, and which are bugs. Sometimes it is very clear, but here’s an example I’ve encountered in 1.4.0-exp.2 where my gut tells me I found a bug, but I can’t say with certainty:

https://github.com/needle-mirror/com.unity.physics/blob/e68e39991dabb1007b1cbe7f710740876f683ddf/Unity.Physics/Dynamics/Jacobians/ContactJacobian.cs#L204

Here, centerA and centerB are passed in by ref. This method is called in two places, once for building, and once after each substep. When building, the values reference local variables after those locals have been assigned to ContactJacobian members; whereas after each substep, they come from the ContactJacobian members directly. This means on the first two substep iterations, these centerA and centerB evaluate to the same after dividing by the contacts. However, additional substeps will cause these values to slide towards zero. Maybe this is intentional similar to the mass inflation for body stacking, but it seems like a bug to me. What I ask is that links and references be added to the code around standard derivations, and that the specific details that differentiate this solver from others be called out.

Lately, I have been experimenting with something potentially very powerful that I think you might want to consider taking inspiration from. When you build out the broadphase BVH structures, you could store a ulong bitmask that corresponds to the bitwise-OR of descendant leaf entity’s EntityArchetype’s bloom mask. Then you can use an EntityQuery’s bloom mask and an EntityQueryMask together to check significantly fewer entities during spatial queries when the user wants to only find results that have a tag component or something. I know layers can already do this sort of thing, but I think being able to call out entity queries is a lot more ergonomic and intuitive for an ECS.

Controllers (Character, Camera, ect)

It is really sad to see these features perpetually sit in the “planned” phase and stagnate. Honestly, you all are really good at designing these controller features.

Otherwise, I don’t have anything to add to what I said back in January.

Audio

Nothing has happened here either, but I didn’t really expect anything to happen. There are very few people in the world who understand audio processing code. I keep procrastinating working on it because it gets kinda lonely. But I am otherwise unblocked, so all’s good here!

UI

I appreciate the new UI sample. But until unmanaged strings are a thing, I cannot take this seriously for runtime use. I should probably submit a bug report for how even the tiniest of GC per frame breaks DOTS projects horribly.

Otherwise, I’m probably going to look into a pure ECS-based UI at some point with BRG. It will be a good test of how controllable draw sorting is with BRG for world-space UIs.

AI

Did you know that AI navigation is the most-requested feature for the framework? This is not something I want to pick up. It is a very common problem though.

NetCode

I don’t have anything to add from last time, because the focus seems to be entirely on host migration. I don’t think that’s a bad thing, because that’s a big feature that I bet a lot of NetCode users want. I don’t consider myself in the NetCode market at this time.

Final Thoughts

Honestly, I hope this review gave the impression that good things are happening, and that the challenges ahead are exciting challenges. There’s a few areas that are stagnant, but for the most part, things are moving forward sustainably. Compared to the last two years, I’d say this year is off to a strong start!

Whether you work at Unity or not, feel free to reply to this topic/thread or reach out to me publicly or privately by any other means. I love technical conversations. Someone always learns something, and that’s always a win.

And finally, thanks for reading this gigantic wall of text!

Can you tell us more about this?

Thanks!

Not a technical conversation piece – just heartfelt praise for your thorough appraisals of the technology, and at such a dependable frequency, too. The continued interest of experts such as yourself in DOTS goes a long way towards providing confidence (to me, at least) in the future of the tech at Unity.

I hope unity will implement the ECS for all in the following way.

Unity suggested to use DOTS on performance heavy systems.
But with current implementation, it’s really hard to make one system to use DOTS and one to use GameObject mainly because of the different phycsic, animation, rendering, audio systems.

But if we can attach Monobehaviour and IComponentData to same object this will make the bridge disappear. Better way is to make sure the IComponentData to behave similar to mono when it comes to accessing from the script like gameobject.GetComponentData() with ref for managed code to use the data and modify the value. And the DOTS can use the normal burst compiled systems and job to get the performance. So by this a system can be performant and easy to access by other system.

But we need to make sure when accessing a IcomponentData that the other systems are not accessing it at the same time. Like by implementing a lock or something and releasing it when use is done or to wait until all the jobs accessing the component data to finish.
With managedComponentData data and managed Systems we can easly pass the calculated values back to the manged component.

So we can use existing camera controller, CharacterControllers, Animations, sound, renderers, colliders etc but still use the Dots for performance heavy systems. And making any of the above systems to work on DOTS won’t have any issue.

Just my thoughts.