Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
Tags

I decided to ignore the WASM problems, since clearly there are some blocking bugs for WASM builds using the GameObject-to-Entity conversion. That workflow is kind of a key convenience measure being pushed in the modern Unity ECS implementation and examples, and I don't really want to deal with working around it by writing tons of factory functions to replace the workflow.

I'll probably do a bug report if I can still reproduce it later and collect more detailed information.

So, I've written a few simple systems to handle input and tank movement, tag components to distinguish between player/enemy, and components and systems from the new ECS Unity physics package.

For movement, I have a pretty simple and naive system setup to start, where the tanks themselves are dynamic physics bodies with a physics shape, and I'm just simulating force impulses based on the direction of input across all tanks every system update in a parallel job.

    public class TankMovementSystem : JobComponentSystem
    {
        private struct TankMovementJob : IJobForEach<TankInputComponent, PhysicsVelocity, PhysicsMass>
        {
            public void Execute(
                [ReadOnly] ref TankInputComponent tankInput,
                ref PhysicsVelocity physicsVelocity,
                [ReadOnly] ref PhysicsMass physicsMass)
            {
                var movementMagnitude = math.min(1, math.distance(float2.zero, tankInput.movement));
                var movementDirection = math.normalizesafe(tankInput.movement);
                var movement = movementMagnitude/5 * movementDirection;
                physicsVelocity.ApplyLinearImpulse(
                    physicsMass,
                    new float3(movement.x, 0, movement.y));
            }
        }
        
        protected override JobHandle OnUpdate(JobHandle inputDeps)
        {
            var job = new TankMovementJob().Schedule(this, inputDeps);
            return job;
        }
    }     [UpdateBefore(typeof(TankMovementSystem))]
    public class PlayerInputSystem : ComponentSystem
    {
        protected override void OnUpdate()
        {
            Entities.ForEach((ref PlayerTankComponent playerTank, ref TankInputComponent tankInput) =>
                {
                    tankInput.movement = new float2(
                        Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
                });
        }
    }

This lead me into Yet Another set of mysterious problems:

TypeLoadException: Recursive type definition detected
Unity.Jobs.JobHandle:ScheduleBatchedJobsAndComplete(JobHandle&)
Unity.Jobs.JobHandle:Complete()
Unity.Entities.ComponentJobSafetyManager:CompleteReadAndWriteDependencyNoChecks(Int32) (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentJobManager.cs:374)
Unity.Entities.ComponentJobSafetyManager:CompleteDependenciesNoChecks(Int32*, Int32, Int32*, Int32) (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentJobManager.cs:200)
Unity.Entities.ComponentSystemBase:CompleteDependencyInternal() (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:699)
Unity.Entities.ComponentSystemBase:AddReaderWriters(EntityQuery) (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:608)
Unity.Entities.ComponentSystemBase:GetEntityQueryInternal(ComponentType*, Int32) (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:622)
Unity.Entities.ComponentSystemBase:HasSingleton() (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:549)
Unity.Physics.Systems.StepPhysicsWorld:OnUpdate(JobHandle) (at Library/PackageCache/com.unity.physics@0.2.4-preview/Unity.Physics/ECS/Systems/StepPhysicsWorld.cs:93)
IndexOutOfRangeException: Index 41 is out of range of '4' Length.
Unity.Collections.NativeArray`1[T].FailOutOfRangeError (System.Int32 index) (at <5e35e4589c1948aa8af5b8e64eea8798>:0)
Unity.Collections.NativeArray`1[T].CheckElementReadAccess (System.Int32 index) (at <5e35e4589c1948aa8af5b8e64eea8798>:0)
Unity.Collections.NativeArray`1[T].get_Item (System.Int32 index) (at <5e35e4589c1948aa8af5b8e64eea8798>:0)
Unity.Physics.BoundingVolumeHierarchy.Refit (Unity.Collections.NativeArray`1[T] aabbs, System.Int32 nodeStartIndex, System.Int32 nodeEndIndex) (at Library/PackageCache/com.unity.physics@0.2.4-preview/Unity.Physics/Collision/Geometry/BoundingVolumeHierarchyBuilder.cs:571)
Unity.Physics.BoundingVolumeHierarchy+FinalizeTreeJob.Execute () (at Library/PackageCache/com.unity.physics@0.2.4-preview/Unity.Physics/Collision/Geometry/BoundingVolumeHierarchyBuilder.cs:850)
Unity.Jobs.IJobExtensions+JobStruct`1[T].Execute (T& data, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, Unity.Jobs.LowLevel.Unsafe.JobRanges& ranges, System.Int32 jobIndex) (at <5e35e4589c1948aa8af5b8e64eea8798>:0)
Unity.Jobs.JobHandle:ScheduleBatchedJobsAndComplete(JobHandle&)
Unity.Jobs.JobHandle:Complete()
This issue appears to be identical to an issue that two different abandoned threads in the Unity forums

were referencing. around ~90% of the time when I hit play, the console is flooded with errors and the tank falls right through the floor. The other ~10% of the time, there are no errors and collision works. Because these errors continually spam each frame from start-time onwards, I'm not super sure this is actually due to a race condition. I suspected what might be happening here is that the system ordering is not really deterministic and might be executing in some hashed order for any systems which don't explicitly specify ordering rules. And so maybe there is some kind of physics initialization system that needs to run before jobs depending on physics systems, and that's not happening.

Now, removing my own physics-reliant systems doesn't fix it, nor does removing my custom step physics entity.

Taking a look at the EntityDebugger, there seem to be 4 physics-related systems running continuously... let's see if there's a problem by unpacking the system ordering constraints:

  • BuildPhysicsWorld - No order constraints
  • EndFramePhysicsSystem - [UpdateAfter(typeof(BuildPhysicsWorld)), UpdateAfter(typeof(StepPhysicsWorld)), UpdateAfter(typeof(ExportPhysicsWorld))]
  • ExportPhysicsWorld - [UpdateAfter(typeof(StepPhysicsWorld)), UpdateBefore(typeof(EndFramePhysicsSystem)), UpdateBefore(typeof(TransformSystemGroup))]
  • StepPhysicsWorld - [UpdateAfter(typeof(BuildPhysicsWorld)), UpdateBefore(typeof(ExportPhysicsWorld))]

I'm not seeing any inconsistencies or ambiguity with this ordering - it should be executing like this:

                +----------------------+
                |                      |
                |  BuildPhysicsWorld   |
                |                      |
                +------+---------------+
                       |
                       v
                +------+---------------+
                |                      |
                |  StepPhysicsWorld    |
                |                      |
                +------+---------------+
                       |
                       v
                +------+---------------+
                |                      |
         +------+  ExportPhysicsWorld  +------+
         |      |                      |      |
         |      +----------------------+      |
         |                                    |
         v                                    v
+--------+----------------+   +---------------+--------+
|                         |   |                        |
|  EndFramePhysicsSystem  |   |  TransformSystemGroup  |
|                         |   |                        |
+-------------------------+   +------------------------+

So clearly, looking at the stacktraces, most of them stem from OnUpdate calls in StepPhysicsWorld, but the exceptions are firing from within the job system itself, which unfortunately I have no visibility into other than a decompiled interface before crossing over into native compiled instructions. The ECS components may be visible to me, but the job system is a complete blackbox... And that kind of sucks, because I probably would have figured out what the bug is and fixed it by now if it wasn't. Based on the totally wonky numbers that the out-of-index is returning (huge negative and positive numbers) I'm assuming that there is either some uninitialized or corrupt memory being used as some indexing value by the job system, but I have no way of really debugging it unless I want to spend weeks trecking through instructions in IDA.

Next I tried adjusting several knobs randomly just to see if there would be any affect, like switching to standalone PC build mode, using .Net 2 instead of 4, Mono instead of IL2CPP, switching from Unity to Havok physics mode, etc. No dice.


I removed all the starter cruft from my project and tossed it in a public git repo just to make sure I can keep track of these issues in a minimal project. Tomorrow I'll probably report these bugs with repro instructions on the public Unity bug tracker.

By the way, itch.io's rich text editor seems to have severe consistency and predictability issues. Things would be a lot easier if they allowed for doing all text editing in markup-mode (markdown, BBC, HTML, whatever format they're storing this text to the database with). Especially for pasting unformatted text. Maybe this would make for a good suggestion to the itch.io team.. I think there's a forum for that, somewhere. :)