ECS & Unity.Physics order diagram


Unity3D ECS & new Unity.Physics systems ordering

<-PREV | NEXT->

A diagram and notes on ECS "System update order" and the Unity.Physics "Simulation phases" including the latter's callbacks.

Background: I wished to correlate these to make it easier to visualise where my `PostCreateContacts` and `ICollisionJob` were run relative to each other and what I need to do to share data between them.

The arrows are reverse dependencies.  It makes 'common sense' to think of thing 1 leading to thing 2 happening (e.g. `CopyTransformToGameObjectSystem` before `EndFrameLocalToParentSystem` in the box on the left) but really thing 2 depends on thing 1 having completed.

The dotted boxes are only conceptual encapsulations -- in this case parts of the new Unity.Physics system.

Solid boxes represent `ComponentSystemGroup`s which contain the `JobComponentSystem`s, placed there by the `[UpdateInGroup]` attribute.  Ordering within is achieved with `[UpdateAfter]` and `[UpdateBefore]`.  (Aside:  `ComponentSystemGroup` is also a `JobComponentSystem` subclass.)

As stated, this ordering affects the Systems ... but they then often schedule `IJob`s whose order is determined by dependencies which therefore form another tree of things that happen.  These dependencies are `JobHandle`s and can be combined to create trees of dependencies.  A tree of these jobs (e.g. 3 depends on 2 which depends on 1) is then required to complete by a call to `Complete()` (e.g. requiring 3 to complete means 1 is run first, then 2, then 3).

The Systems are run on the main thread whereas the jobs are run in worker threads.

That `EndFramePhysicsSystem` calls `Complete()` but on the frame's jobs.  Jobs can be added to this list by stashing the result of `World.Active.GetOrCreateSystem<EndFramePhysicsSystem>()` then `endFramePhysicsSystem.HandlesToWaitFor.Add(jobHandleToDoAfterPhysics)`. 

Consequently I found good results from:

  1. In `UpdateBefore(typeof(StepPhysicsWorld)]`: Allocating my Native storage then scheduling my contacts callbacks ,
  2. In `UpdateAfter(typeof(StepPhysicsWorld)]`: scheduling my collisions, then
  3. recording the final JobHandle into the `EndFramePhysicsSystem.HandlesToWaitFor` mentioned above
  4. In `UpdateAfter(typeof(EndFramePhysicsSystem)]`: freeing my native containers (e.g. `NativeArray`)

When you register a Physics callback with `StepPhysicsWorld.EnqueueCallback()` the calls come out of the 'system' concept and are on the main thread.  Obviously you might then wish to schedule some jobs to work on the masses of data now available.  Idiomatic code for this:

public sealed class StickyCollisionSystem : JobComponentSystem {
    // ...
    protected override JobHandle OnUpdate(JobHandle inputDeps) {
        // ...
        // Local function rather than delegate reduces GC!
        // https://forum.unity.com/threads/foreach-creates-garbage-doesnt-work-with-componentgroup-interrupts-method-execution.646351/#post-4341718
        // http://mustoverride.com/local_functions/
        JobHandle onPostCreateContacts(ref ISimulation simulation, ref PhysicsWorld world, JobHandle inDeps) { // TODO: Reduce alloc here
            var gatherContactsJobs = new GatherContactsJob {
                stickCauses = GetComponentDataFromEntity<stickcause>(true),
            };
            inDeps = gatherContactsJobs.Schedule(simulation, ref world, inDeps);
            return inDeps;
        }
        stepPhysicsWorld.EnqueueCallback(SimulationCallbacks.Phase.PostCreateContacts, onPostCreateContacts);
    }
    // [BurstCompile] // TODO: Restore once finished not using ref types like string in logging!
    private struct GatherContactsJob : IContactsJob {
        [ReadOnly] public ComponentDataFromEntity<stickcause> stickCauses;
        public void Execute(ref ModifiableContactHeader header, ref ModifiableContactPoint contact) {
            var aIsSticky = stickCauses.Exists(header.Entities.EntityA);
            var bIsSticky = stickCauses.Exists(header.Entities.EntityB);
            if (!(aIsSticky || bIsSticky))
                return;
            log($"Contact(A:{header.Entities.EntityA}, B:{header.Entities.EntityB}, at:{contact.Position} dist:{contact.Distance}");
        }
    }
}


GraphViz source file is in this gist and can be rendered to the image below with:

dot -Tpng ECS-Unity.Physics.dot -o ECS-Unity.Physics.png

The graph source was created by hand but I suspect it'd be viable to produce similar version through code analysis (e.g. Roslyn).  I'd hazard an equivalent for jobs could only be done at runtime.

<-PREV | NEXT->

Leave a comment

Log in with itch.io to leave a comment.