I'm an ex-long time "AAA" game programmer, now solo indie developer making GearBlocks, a game all about creative building, interactive machines, and gears - lots of gears!

Report RSS Yet more optimisations

Posted by on

OK, it seems I spoke too soon when I said in the last blog post that I was done with optimisations to the construction modification code! When working on the damage system, I found that detaching parts off large constructions with lots of parts could still be really slow, so over the last few weeks I've been working to resolve this.

Setting transform parents

When modifying a construction (i.e. attaching or detaching parts), I need to change transform parents in order to manipulate the construction's transform hierarchy, and by far the biggest performance cost I found was with this re-parenting. Even when setting worldPositionStays to false when calling SetParent() so that Unity doesn't have to recalculate world transforms, it's still really slow when you call SetParent() a lot, due to Unity internally updating the physics colliders. When modifying a large construction, in the profiler I was seeing Physics.HandleColliderHierarchyChanges and Physics.SyncColliderTransform costing many tens, sometimes hundreds of ms!

So now I've done everything I can to get rid of unnecessary re-parenting, thereby minimising the number of SetParent() calls, specifically:-

  • When fixedly attaching parts together, all the parts have to be re-parented from their current rigidbodies to a single rigidbody. Now, parts from the rigidbody with the smaller number of parts always get re-parented to the rigidbody with the larger number (without re-parenting the larger number of parts).
  • Similarly, when deleting fixed attachments, parts need to be re-parented to separate rigidbodies. Now, after determining the groupings of parts left after attachment deletion, the largest group always stays under their original rigidbody, and the rest get re-parented to other new rigidbodies.
  • Lastly; I was parenting rigidbodies that were part of the same construction to a container gameobject, this was handy for clarity and debugging purposes, but not strictly necessary. I changed the code to maintain the rigidbody-to-parent-construction relationship a different way, rather than relying on the transform hierarchy for this. After that I was able to eliminate setting of the rigidbodies transform parents entirely.

Other optimisations

I was using List to hold temporary lists of parts and rigidbodies when determining how to reorganise a construction hierarchy after deleting attachments. If there were a large number of things in these lists (e.g. parts), then calling Contains() or Remove() on them would be noticeably slow because these are O(n) operations (a linear search). So I switched over to using a HashSet instead, for which these operations are O(1).

After a construction is modified, its rigidbodies bounds and mass properties (e.g. centre of mass, inertia tensor, etc.) need to be recalculated. I've now optimised the code that does this, mostly by caching data that doesn't change (e.g. for parts that haven't been re-parented to a new rigidbody).

Also after a construction is modified, a few GetComponentsInChildren() calls were being used to cache references to rigidbodies and parts. These calls were quite slow (and also caused some pretty sizable GC allocs), but after restructuring the code a bit, I was able to eliminate the need for them.

Results

All of these optimisations added together have made huge gains, at least in the test case I was using (a construction with over 2000 parts). It used to be that detaching a single part in this test could take well over 300ms(!) which caused a noticeable frame rate hitch, now it takes less than 37ms.

Around 22ms of this remaining time is taken by updating rigidbody mass properties (assigning to mass, centerOfMass, inertiaTensor, and intertiaTensorRotation), which there’s not much I can do about. I can’t understand why this would be so slow, something odd seems to be happening under the hood in Unity. Maybe this issue is fixed in Unity 2018, but for now I’m stuck on 2017.4, due to the Networking API issues I’ve discussed in previous posts. Another 12ms out of the ~37ms total is taken by Unity in Physics.UpdateBodies, which I don’t think I can do anything about either unfortunately.

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: