└── README.md /README.md: -------------------------------------------------------------------------------- 1 | Various Nothke's DOTS gotchas. These are random and in no particular order, also used as a little cheatsheet. Hopefully, someone will find it useful. 2 | 3 | Correct as of: 9/19/2019 4 | 5 | ## Be careful about obsolete resources 6 | 7 | As DOTS is still in development, it is susceptible to frequent changes. The biggest problem however is that there are loads of resources and tutorials online showing no longer valid ways of doing things. Check [this page on Unity forums for a list of some of deprecated features](https://forum.unity.com/threads/api-deprecation-faq-0-0-23.636994/). If you encounter any of these, you are likely looking at an old tutorial. 8 | 9 | ## My VS DOTS snippets 10 | 11 | (Warning: They are not updated to latest version of ECS! Will do that soon!) 12 | 13 | Check out my snippets https://github.com/nothke/Unity-VS-Code-Snippets, which include quick-creating templates for systems, jobs, parallel jobs and more.. 14 | 15 | ## Mathematics as a using static 16 | 17 | It is useful to include Mathematics library as 18 | ``` 19 | using Unity.Mathematics; 20 | using static Unity.Mathematics.math; 21 | ``` 22 | Because we can then create data by calling creation methods like `float3 position = float3(0,0,0)` without using the "new" keyword. This is beneficial because it is very similar to how it is written in shader code. As a matter of fact you can literally just copy the code to a HLSL function and it will behave in the same way. 23 | 24 | ## Mathematics swizzling 25 | 26 | There is a hidden feature (doesn't show up in VS auto complete) in Mathematics library commonly referred to as "swizzling", where you can swap or convert values very easily, similar to how it's done in HLSL code. 27 | 28 | A common swizzling example is when you want to convert 3D position into a horizontal 2D vector, so you want to put x and z into a float2's x and y: 29 | ``` 30 | float3 pos3d = float3(1, 2, 3); 31 | float2 pos2d = pos3d.xz; 32 | // pos2d is now (1, 3); 33 | 34 | float4 vector = float4(1, 2, 3, 4); 35 | vector.xw = vector.yz; 36 | // vector is now (2, 2, 3, 3); 37 | ``` 38 | 39 | ## Creating a renderable mesh entity 40 | 41 | The entity archetype must at least contain: 42 | - RenderMesh (from Unity.Rendering, found in "Hybrid Rendering" package) 43 | - LocalToWorld (from Unity.Transforms) 44 | 45 | Optionally, you can use Translation, Rotation, Scale or other transform component to place the entity. 46 | 47 | Note: MeshRender is a ISharedComponentData and must be set with SetSharedComponentData 48 | 49 | Important!: Material provided to the MeshRender must be GPU instanced (tick GPU instancing in the material inspector) 50 | 51 | #### Example: 52 | 53 | Create the archetype and RenderMesh at start: 54 | ``` 55 | var manager = World.Active.EntityManager; 56 | 57 | EntityArchetype myRenderableArchetype = manager.CreateArchetype( 58 | typeof(RenderMesh), 59 | typeof(LocalToWorld), 60 | 61 | typeof(Translation)); 62 | 63 | RenderMesh myRenderMesh = new RenderMesh() 64 | { 65 | mesh = myMesh, 66 | material = myInstancedMaterial 67 | }; 68 | ``` 69 | 70 | Then, create the entity and add components: 71 | 72 | ``` 73 | var entity = manager.CreateEntity(particleArch); 74 | 75 | manager.SetSharedComponentData(entity, myRenderMesh); 76 | manager.SetComponentData(entity, new Translation() { Value = myPosition }); 77 | ``` 78 | 79 | You should now have a visible object in game! 80 | 81 | ## Creating or destroying Entities from within a JobComponentSystem 82 | 83 | Since entities can only be created/destroyed on the main thread, their construction/destruction must be deferred until the job completes. You can issue a command to destroy an entity using an EntityCommandBuffer. The EntityCommandBuffer can be obtained from one of the EntityCommandBufferSystems (start typing EntityCommandBufferSystems, and you will get a bunch). You can obtain the system from World in OnCreateManager. 84 | 85 | ``` 86 | EndSimulationEntityCommandBufferSystem commandBufferSystem; 87 | 88 | protected override void OnCreate() 89 | { 90 | commandBufferSystem = World.GetOrCreateSystem(); 91 | } 92 | ``` 93 | 94 | Then, pass the command buffer to the job in OnUpdate. EDIT: Also, we need to tell the barrier system which job is using the command buffer so it can wait for it to finish. 95 | 96 | ``` 97 | var handle = new SystemJob() 98 | { 99 | ecb = commandBufferSystem.CreateCommandBuffer().ToConcurrent(); 100 | }.Schedule(this, inputDeps); 101 | 102 | // Tell the barrier system which job is using the ecb so it can complete it. 103 | commandBufferSystem.AddJobHandleForProducer(handle); 104 | ``` 105 | 106 | The job must be IJobForEachWithEntity (since we need the entity) and should have: 107 | 108 | ``` 109 | public EntityCommandBuffer.Concurrent ecb; 110 | ``` 111 | 112 | In Job's Execute, if you wish to destroy the entity, use 113 | 114 | ``` 115 | ecb.DestroyEntity(index, entity); 116 | ``` 117 | 118 | Note: In the Unity example, the "index" provided to the EntityCommandBuffer is the same as the entity, but in the docs it says that it just needs to be a unique number as to not write to the same data 119 | 120 | [Example from Unity samples](https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/ECSSamples/Assets/HelloCube/7.%20SpawnAndRemove/SpawnerSystem_SpawnAndRemove.cs) 121 | 122 | ## Running a system only on entities that contain a component, aka tagging 123 | 124 | Since a certain Unity.Entities update, it is no longer recommended to include a component into a system if you are not using its data, that is, just for the sake of "tagging". 125 | 126 | Instead, put `[RequireComponentTag(typeof(SomeComponentIRequire))]` above the system's job. 127 | 128 | Alternatively, you can still pass data as tag, but use `[ReadOnly] ref SomeComponentIRequire` in Execute parameter. 129 | 130 | ## Don't automatically start a system on start 131 | 132 | If you wish to prevent SOME systems from automatically starting, you can add `[DisableAutoCreation]` to the top of the system class. 133 | 134 | If you wish to prevent ALL systems from automatically starting, you can add `UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP` to your Project Settings > Player > Scripting Defines Symbols. 135 | 136 | ## Manual system start 137 | 138 | If you wish to add systems to your world manually (considering [they are not auto-started](#dont-automatically-start-a-system-on-start)), first you need to put them in a system group, and then add them to the update list. 139 | 140 | Example: 141 | 142 | ``` 143 | group = World.Active.GetOrCreateSystem(); 144 | var mySystem = World.Active.GetOrCreateSystem(typeof(MySystem)); 145 | group.AddSystemToUpdateList(mySystem); 146 | ``` 147 | 148 | This will now run at every invocation of the group's Update, respecting the [UpdateAfter/Before] sorting, just as if it was automatically started. 149 | 150 | ## Parallel writing to a NativeArray 151 | 152 | Parallel writing is by default not allowed because of the race conditions of writing to the same index and Unity safety system will warn if you try to parallel write and will instead force single-threading. 153 | 154 | BUT, you CAN write to an array in parallel if you make sure that you don't write to the same index. To do that you need to add `[NativeDisableParallelForRestriction]` in front of the NativeArray. Same goes for ComponentDataFromEntity and other collections. Note that you will now not be warned even if you are writing to the same spot, so, be very careful. 155 | 156 | ## Transferring data from entity to entity 157 | 158 | Lets say we want to transfer some data from one entity to the other. First, we need to reference an entity in another entity. Then, you can get a `ComponentDataFromEntity<>` which is an array of component datas indexed by entity in a system using `GetComponentDataFromEntity()`. You can use this to take data, pointed to by our stored Entity, from one entity to the other. 159 | 160 | For example, we have a component data: 161 | 162 | ``` 163 | public struct MyData : IComponentData 164 | { 165 | public float value; 166 | } 167 | ``` 168 | 169 | ..and we want to add to it's value from another entity. We create another component data that stores the linked entity and the amount we want to transfer: 170 | 171 | ``` 172 | public struct MyTransferingData : IComponentData 173 | { 174 | public Entity entity; 175 | public float transferAmount; 176 | } 177 | ``` 178 | 179 | In system OnUpdate we can fetch `ComponentDataFromEntity`: 180 | ``` 181 | protected override JobHandle OnUpdate(JobHandle inputDeps) 182 | { 183 | var myDatas = GetComponentDataFromEntity(); 184 | 185 | var job = new SystemJob() 186 | { 187 | myDatas = myDatas 188 | }; 189 | 190 | return job.Schedule(this, inputDeps); 191 | } 192 | ``` 193 | 194 | In the System's Job, we can now get data by indexing the `ComponentDataFromEntity`: 195 | ``` 196 | [BurstCompile] 197 | struct SystemJob : IJobForEach 198 | { 199 | [NativeDisableParallelForRestriction] // So we can parallel write 200 | public ComponentDataFromEntity myDatas; 201 | 202 | public void Execute(ref MyTransferingData transfer) 203 | { 204 | var myDatas = tileDatas[transfer.entity]; 205 | myDatas.value += transfer.dataToTransfer; // Where we add the value 206 | myDatas[transfer.entity] = tileData; 207 | } 208 | } 209 | ``` 210 | 211 | You can also mark as ReadOnly if you want to only read, making the job potentially faster. 212 | 213 | ## Tuples with auto layout are not supported by Burst 214 | 215 | I've encountered this error when trying to return a tuple, for example: 216 | ``` 217 | public static (int, int) GetCoord(int index) 218 | ``` 219 | Instead, I had to use the out parameter to accomplish the same: 220 | ``` 221 | public static void GetCoord(int index, out int x, out int y) 222 | ``` 223 | I am not sure if it works by explicitly setting the layout (if that can even be done with C# tuples?). Alternatively you can always use a custom struct. 224 | 225 | ## Force ForEach system to run on a single thread 226 | 227 | Use `job.ScheduleSingle(this, inputDeps);` 228 | 229 | ## Slowdown on start (Burst compilation) 230 | 231 | You may notice that when starting the game in editor with ECS systems you encounter a few stutters that can last up to several seconds until the game starts running smoothly. This is because Unity compiles Burst code asynchronously every time you enter the play mode. Until the burst native code is ready, jobs will be run without burst. 232 | 233 | You can force Unity to compile Burst code ahead of time by using `[BurstCompile(CompileSynchronously = true)]`, but I personally recommend not doing that as it is better to have a shorter time of entering the play mode. 234 | 235 | But note that this only happens in editor! Rest assured, this will not happen in build, all burst code is compiled during the build process. 236 | 237 | ## Plenty of GC Allocations each frame when using ECS/Jobs 238 | 239 | When using jobs or ECS you may notice a high amount of GC Allocations. There could be several reasons for that: 240 | - Unity uses a managed object called a [DisposeSentinel](https://docs.unity3d.com/ScriptReference/Unity.Collections.LowLevel.Unsafe.DisposeSentinel.html) to track the native collection's lifetime. It is producing GC allocations when creating/disposing native collections. That is the expected behavior as the DisposeSentinel helps you track issues and memory leaks. Leak checks are by default not being tracked in build, therefore there won't be any DisposeSentinels nor GC allocations. You can also set Jobs > Leak Detection to off to turn it off in editor. 241 | 242 | ## Returning a single value from a Job 243 | 244 | The only thing that "survives" a job are native collections, so if you want to return a value from a job, even if it's a single one, you must wrap it in a NativeArray. 245 | 246 | For example, job for calculating a mean value might look like this: 247 | ``` 248 | public struct CalculateMeanValue : IJob 249 | { 250 | [ReadOnly] public NativeArray values; 251 | // This will be the NativeArray with a single element: 252 | [WriteOnly] public NativeArray output; 253 | 254 | public void Execute() 255 | { 256 | float total = 0; 257 | for (int i = 0; i < values.Length; i++) 258 | total += values[i]; 259 | 260 | output[0] = total / values.Length; 261 | } 262 | } 263 | ``` 264 | 265 | You can even wrap BlobAssetReference into a NativeArray to return it, for example to build a Unity.Physics.Collider inside a job. 266 | 267 | ## Game fails to build with CreateEntity/AddComponent EntityCommandBuffer job 268 | 269 | Creating entities/adding components is currently not supported by the Burst compiler. If you wish to add CreateEntity/AddComponent commands to the EntityCommandBuffer in a job, you should not `[BurstCompile]` it. 270 | 271 | ^ To be changed in the next Burst version. 272 | 273 | ## Dispose collections after the job has completed 274 | 275 | Lets say we have a NativeArray with `Allocator.TempJob`, and we wish to dispose it when the job has completed, you can add a `[DeallocateOnJobCompletion]` on collection's definition inside a job. This is especially useful in systems since we don't know when the system's job will end. 276 | 277 | Example: 278 | ``` 279 | public struct MyJob : IJobParallelFor 280 | { 281 | [DeallocateOnJobCompletion] 282 | [ReadOnly] NativeArray readFromThisArray; 283 | 284 | public void Execute(int i) 285 | { 286 | float value = readFromThisArray[i]; 287 | // Do something... 288 | } 289 | } 290 | ``` 291 | 292 | ## Unity Physics causes an InvalidCastException in Entity Debugger 293 | 294 | Will probably be fixed, but in the meantime.. 295 | 296 | Add `"com.unity.properties": "0.6.4-preview"` (or [later versions](https://bintray.com/unity/unity/com.unity.properties) depending on your Unity version) to your packages file dependencies. 297 | 298 | Solution [found in this post](https://forum.unity.com/threads/entity-debugger-feedback.522893/page-3#post-4853669) --------------------------------------------------------------------------------