12/21/19 update here: My personal feedback of the full DOTS spectrum
I was hoping that more Unite talks or the new DOTS packages would be out before I wrote this, but unfortunately, they are not. Since I’m not going to have a lot of time the next few weekends, I figured it best to post this now and update it if I learn anything new. I’m trying to keep this post focused on things that have not been frequently discussed or addressed in the Unite videos but directly affect my development and workflows.
This is a long post so I have broken things up by sections that can mostly be read independently of each other.
About me and my perspective
Major disclaimer, I am not a full time game developer working on production projects. I am merely a hardcore hobbyist. I am actually a C++ developer for embedded commercial applications. Before Unity, I used to do all my hobbyist game dev using raw C++ and no engine, so I learned a lot about different game engine architectures. Consequentially, the technical features that DOTS provide makes me feel right at home, and opens up a real potential for dozens of game ideas I had set aside due to technical feasibility. Currently I am participating in weekend game jams using DOTS and building up a framework of utilities until I have enough ideas and technologies in place to commit my free time to one of the dozens of game projects I have in mind. I am still debating whether I want to open-source my framework.
As an example of the kinds of stupid things I do, here is one of my weekend game jam projects that managed to make it to the internet (most don’t): Sheep or Fall by Dreaming381
DOTS Documentation
My general policy is that I don’t expect anything in preview to be fully documented, and the released DOTS packages are actually quite well-documented compared to most things I come across. But one of the things that has kept me using Unity has been the excellent documentation and scripting reference pages. While the pages are finicky on mobile, they are superior in many ways to package manager documentation, and that is something I hope gets addressed since DOTS documentation is package manager documentation.
First, the scripting reference brings to the forefront what is meant to be the public API, whereas the package manager documentation auto-generates documentation for everything. Second, the scripting reference collapses all overloads into a single page with detailed descriptions and sometimes even a code example to help me gauge if what I am looking at might fit my use case. Meanwhile, the mathematics documentation takes my web browser way too many seconds to load and I have to use my web browser’s search tool to find what I am looking for.
Mathematics
First off, Mathematics has become my new favorite math library. Basing it off of HLSL (which I consider superior to GLSL) was a great decision. In fact, I like it so much that for a recent project I made as an example project in classical Unity where I tried to avoid almost everything DOTS, I still ended up using Mathematics. And while I have difficulty with the package manager documentation format, the actual descriptions provided in the documentation have been very helpful.
As for where I would like to see some improvement, give some conditional logic a little more love! Right now, bitmask outperforms math.any/all(bool4). It would also be nice to have predefined bitmask constants so that I don’t have to define my own. Example: math.bitmaskFFTF. Having bitmask work with bool2 and bool3 would be cool. Also I sometimes run into situations where I need to compare 2v2 floats with greater than and 2v2 floats with less than and I would like to get better vectorization out of those operations. One way to do that is to shuffle the arguments and then decrement two of the floats by a single machine unit. That latter part could be a useful math function. cmin and cmax are awesome, but sometimes I want to get the first index of the min and max value and use that to select from a different variable. And lastly, Physics uses a FourTransformedPoints struct and I made a similar simdFloat3 data type which does similar things and some extras. I made simdFloat3 use a, b, c, and d to index the four float3s while x, y, and z indexes the components of all four float3s. Both have swizzling thanks to T4. While I have gotten really good results with this, I would love to see an official version in Mathematics with intrinsics.
Burst
Burst is probably the most original technology in the DOTS tech stack, and it solves what was Unity’s biggest weakness and made it Unity’s biggest strength. For 99% of the use cases, it is as simple as adding [BurstCompile]. Most questions are answered in the solo documentation page. Simplicity at its finest! The docs are missing function pointers and shared statics, though I haven’t found a real use case for either of those yet.
I have three major pain points with Burst right now.
First, generic jobs are not supported in AOT. Normally the solution for this is to use a custom job type instead, but that doesn’t work for my use case (at least to my level of understanding). My use case is that I have several complex acceleration structures that I want to iterate over and process the results of the iteration with custom logic. That custom logic comes packaged in a generic struct with NativeContainers and ComponentDataFromEntity and the struct implements an interface. Now in order to iterate over all the generated results, I need multiple phases where the results within a phase can be dispatched in a thread-safe manner but results of one phase are not thread-safe with the results of another phase. (If you need a more specific example, I can provide one.) Anyways, my solution to this is to have a generic method that takes the custom logic struct and then schedules the jobs in a chain. When testing in the editor, this works amazingly and I am really happy with the performance of my clean and shiny code. But then I can’t ship it. I’m hoping that with the new 2019.3 compiler hooks that this is an easy problem to solve. Ideally, it either just works or I can make it work with some [BurstCompileForeach] or something.
Second, Burst is not supported for WebGL yet. I don’t need hand-optimized math intrinsics. Just having LLVM go aggro on my code will probably give me enough performance for my game jam games.
Third, I’m in play mode and a suddenly see a weird behavior that I can reproduce pretty easily. I pause and then want to set a breakpoint in one particular job I suspect to be the culprit. The job is like many of my other jobs in that I am using Burst without debug = true because otherwise my CPU would cry. But in order to temporarily turn off Burst for that one job to set a breakpoint, I need to stop the game, edit the attribute, recompile the code, then enter playmode again.
Besides my request that the pains go away, I wish you the best of luck with supporting more platforms and more compilation modes!
Collections
Collections are great! But writing custom containers is a pain when trying to get the safety sentinels correct. Most of the time, I need a custom data structure composed of several already existing NativeContainers. So instead of writing a custom container, I just make a struct with those other NativeContainers as members. The problem I run into is when trying to add safety attributes. Sometimes, the attributes work when put on the struct instance in a job. Sometimes I have to put the attributes inside the struct definition instead. It’s confusing.
Being able to specify in the struct definition [AlwaysReadOnly], [AlwaysDisableParallelForRestriction], [NeverDisableParallelForRestriction], and [InheritFromStruct] or similar attributes could solve this problem.
Jobs
I have very little negative to say about the job system. You guys did an awesome job with this! I don’t really use the extra job types other than IJobParallelForDefer. Only pain points with multithreading that isn’t Burst-related are how annoying it is to return a single result when I Run() an IJobForEach and the lack of an out-of-the-box non-deterministic multithreaded RNG for presentation and logic where I don’t care about determinism. These issues are relatively minor for me as I can easily create a workaround solution.
Entities
I love this ECS! It has reached the point where it gives me everything I had with every other C++ ECS I have worked with except in a way cleaner format! If I want to use it to batch-reason about my MonoBehaviours, I can do that. If I want to run all my systems totally serial but still have the algorithms go wide, I can do that. If I want to use fewer fat components, I can do that. If I want a full reactive system that only processes a change per entity, I can do that (requires storing the version number per component for anyone wondering). If I want to treat my logic more like an ES (one giant component per entity) and still get Burst and threading, I can do that. If I want to have a binary tree per entity, I can do that. Blobs make awesome static asset tables, and dynamic asset tables can be implemented with entity references. My code is cleaner than ever and more reusable. I have more control over execution order and have less code paths because of it. It was way easier for me to come up with a naming convention scheme that made sense. And I now build games faster with DOTS than without, even though I often cheat and use some MonoBehaviours for certain things, although that’s becoming less frequent with every project.
But this solution is not perfect, and there’s some room for improvement that can help people like me out.
I know there are some changes to how Worlds work in that next release, so I am going to spare my complaints about having to duplicate code to add systems to ComponentSystemGroups. I will just mention that being able to subclass Worlds to give them more globalized data is super useful for framework writers like me.
My biggest complaint of Entities is the performance of systems. Now I know that this has been discussed and optimizations are coming. But I haven’t seen any discussions about what are the real roots of this problem, and one root in particular I believe is a design flaw that I hope can be fixed before Entities leaves preview. The issue I have is with the check to run or not run a system. Every system checks a bunch of EntityQueries for entity count and does some and|or combinational processing to determine if the system should run. If the result differs from the previous frame, the system also has OnStart/StopRunning called. While I agree that the default version of this method is a good idea and should be optimized, the truth is that this code is making decisions when I as the gameplay developer can make better ones. Whether a system needs to run might be dependent on only one EntityQuery, but also be dependent on a value of a singleton and what scene is currently active. I may also not want OnStartRunning and OnStopRunning to be called based on my criteria, but only when the Enabled property changes. And I want all of this visible to the EntityDebugger. So what is my proposed solution to this? Make ShouldRunSystem virtual and return 2 bools, the second bool being for calling OnStart/StopRunning.
In my custom framework, I have two dictionaries of typed dictionaries<Entity, T> where I store structs that implement either an IComponent or an ICollectionComponent. IComponent can store ref types. ICollectionComponent is just like IComponent except that it can also store NativeContainers, the dictionary keeps track of Reader/Writer JobHandles, and it has special hooks into my abstract JobComponentSystem class to automatically update the handles if they aren’t updated explicitly. I don’t use these very often, but they are nice for some specific game logic. My annoyance is that I can’t make these part of the archetype and get them to work with Entities.Foreach.
Transforms
For the most part Transforms just works. The code is pretty optimized. My only complaint is that the transform conversion system automatically assumes that anything with a non-unit scale is a NonUniformScale when it might actually be a uniform Scale. I have to add the scale components back anyways for static entities for physics so it is not a huge deal for me. It’s just a little annoying.
Scenes
I haven’t messed around much with scenes as I am not really much of a level designer and end up procedurally generating everything. But for a recent game jam where I was collaborating with someone, we tried it out and I ran into this weird issue where the subscene icon was wrong and then eventually the scene cache got corrupted. I deleted the cache files and reloaded Unity and I haven’t reproduced the issue since. I am greatly looking forward to the new live link features in 2019.3! I am very curious, are those features going to work over internet and/or allow collaborative editing sessions?
Hybrid Renderer
0.1.1 broke my HDRP test project, so I use 0.1.0 sometimes. Besides that, most of my qualms with graphics and rendering are with the SRP and Shader Graph team, so I won’t discuss that here.
Physics
Unity made this rather interesting decision to use Blobs for colliders, essentially making colliders immutable. I immediately questioned this decision as I knew it was going to cause a lot of problems with procedural generation, scaling, morphing, and query logic. I decided to write my own solution borrowing pieces from Unity but using a union of collider types as an IComponentData in which some collider types could have a BlobAssetRef and be partially immutable while other collider types can have Entity refs instead for mutable meshes. This fundamental change has helped me avoid a lot of issues at the cost of eating up a lot of my free time. I have no idea how the physics team is going to make skinned meshes and cloth work, but that team is smart and will figure something out. I just expect it is going to take them a while.
As for simulation, that part seems like an improvement over PhysX. I don’t really know. I don’t do a whole lot of realistic physics in games. I usually just want triggers and bump-and-slides and queries.
I did discover one stupid trick that I think Unity could easily copy. Change the namespace from Unity.Physics to something like Unity.PhysicsEngine. This lets you use “Physics” as a name of a static class to hold all your query API. It gets rid of the need to do all the ICollidable nonsense and makes the API much more intuitive.
Audio
I haven’t used this and do not plan on using this until some out-of-the-box ECS API comes along. I may update this section once that happens.
Animation
TBD
Netcode
Also TBD, though I do have some general comments about my view of this. In the past, I have always avoided making networked games. There is just too much up-front investment. I have to write all the game code with networking in mind. Then I need a networking infrastructure and likely choose my hosting solution before I can even verify if my game is still fun over a network. If the new Netcode allows me to set up a server and client on my local machine and have a remote buddy connect as a client over the internet and we can test the fun-factor of the game before ever choosing my hosting solution, that would be pretty huge for me!
DOTS in General
This isn’t a problem at the moment, but I suspect it to become a problem as more things transition to DOTS. People build lots of unique and original games in Unity. There is no way every DOTS package Unity releases is going to meet everyone’s needs, and people are going to want to rip stuff out and replace it with custom solutions. I ask that this be kept in mind when making packages, so that someone like me can replace the physics package with my own, and be able to make it play nice with Animation and Cinemachine without having to modify those packages. This doesn’t have to be easy. It just has to be doable.
Thanks for reading!
Hopefully you found this helpful and insightful!