the symptom, in your words

"GameObject.Destroy() is deferred — a destroyed object still holds its exclusive claims for the rest of the frame"

✓ verified on 26.07.22
lane Writing gameplayposted

▸ SYMPTOM

  • You destroy an object and immediately hand its exclusive role to a replacement — and for the rest of that frame both objects report holding the role. A camera swap leaves two cameras both reporting IsMainCamera == true, the exact ambiguous state the swap was written to avoid.
  • A "is anything leaked?" instrument that counts scene objects by name reports a leak that is not onerigs=1 with the rig reported OFF — right after a teardown, costing a full investigation into a phantom.

▸ CAUSE

GameObject.Destroy() is deferred to the end of the frame. The object you just destroyed still exists, still answers queries, and still holds any exclusive engine claim it owns until the frame ends.

Two distinct failures fall out of that one fact:

  1. Exclusive claims outlive the destroy call. A debug fly camera handed IsMainCamera back to the player camera by destroying its own camera object and then setting the player's flag true. Because the destroy is deferred, the debug camera's flag was still set for the remainder of the frame, so both cameras claimed IsMainCamera — "exactly one camera claims this, or the engine settles it arbitrarily" was violated for a full frame.
  2. A same-frame census lies. A leak instrument counting objects by name (built to catch a camera object outliving its component) printed rigs=1 with the rig reported off — which under its own rule read as a leak. The toggle command had called Deactivate and the state print in the same frame, and the object it had just destroyed was still counted.

The teardown habit of "destroy the thing, then fix up whoever is left" is only safe for claims the destroy itself releases — and a deferred destroy releases nothing until the frame ends.

▸ FIX

For any claim that is exclusive by contractIsMainCamera, a singleton input/cursor claim, a registry slot — clear it on the outgoing owner before calling Destroy(), in the same breath the incoming owner takes it:

snippet
// WRONG — deferred destroy leaves the old claim set this frame
oldCamera.GameObject.Destroy();
playerCamera.IsMainCamera = true;   // now BOTH are true until frame end

// RIGHT — release the exclusive claim before destroying
oldCamera.IsMainCamera = false;     // clear the outgoing owner first
playerCamera.IsMainCamera = true;   // incoming owner takes it
oldCamera.GameObject.Destroy();     // deferred teardown of an object that no longer claims anything

For a leak census, never read it in the same frame as the teardown it checks. Distinguish a real leak from a pending destroy by re-reading on any later frame — a real leak survives that, a pending destroy does not — and say so in the instrument's own comment, because the same-frame reading is otherwise indistinguishable from the failure the instrument exists to catch.

▸ WHY IT WORKS

The deferral only bites within the frame that calls Destroy(). Clearing the exclusive claim explicitly means the invariant ("exactly one owner") holds continuously, regardless of when the object's memory is actually reclaimed. And moving any leak assertion to a later frame lets the engine's end-of-frame destruction actually run first, so the census counts the world as it will be, not as it is mid-teardown.

Verified on engine 26.07.22: seen in a real project.
s&box moves fast; an undated fix is a liability. Spot a stale detail?
changelog
  • Published.

Want to know when new guides or fixes drop? Join the community to help build this out. Report gotchas, flag outdated fixes, or just lurk.

Join the Discord