"GameObject.Destroy() is deferred — a destroyed object still holds its exclusive claims for the rest of the frame"
▸ 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 one —
rigs=1with 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:
- Exclusive claims outlive the destroy call. A debug fly camera handed
IsMainCameraback 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 claimedIsMainCamera— "exactly one camera claims this, or the engine settles it arbitrarily" was violated for a full frame. - A same-frame census lies. A leak instrument counting objects by name (built to catch a camera object outliving its component) printed
rigs=1with the rig reported off — which under its own rule read as a leak. The toggle command had calledDeactivateand 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 contract — IsMainCamera, 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:
// 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 anythingFor 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.
- Published.