I expect script.AncestryChanged connections to still run when script.Parent becomes nil. script.Destroying works in that situation, for example.
My reasoning is that it will be a lot easier to run cleanup logic if it was this way. Imagine there’s a script that inside the character that manages projectiles. When the character gets removed for whatever reason, the script will be able to clean up all the projectiles so they don’t remain stuck mid-air. To achieve this kind of logic right now, I would need to have an external watcher script to delete those projectiles which makes things a lot less nice!
As far as I know, this occurs because Destroying runs before the object is destroyed. Notice how the function name uses the present tense to show this.
AncestryChanged on the other hand runs after the ancestor has changed, notice the use of the past tense in the function name.
The ordering of these events appears to be intentional and any change in behaviour could cause confusion or broken code.
I don’t propose any changes to the order the events run in. What you’ve mentioned doesn’t affect this, because scripts continue running even when parented to nil, so it doesn’t matter whether AncestryChanged runs before or after!
I spent some time investigating the repro place file you attached. Thanks for the repro, it is very help when investigating issues to have a concrete example of the behavior.
I’ll do my best to describe what is happening in this example. Hopefully this will at least clear up why the scripts behave as they do.
There are multiple operations that happen when a script is unparented.
The script instance is “removed” from the data model.
Various internal operations occur as a side-effect of the script being removed.
Events associated with the script being removed are invoked which may call Luau functions which are connected to the events.
It turns out that one of the side-effects of removing a script is to “disable” Luau callbacks associated with the script. In general we don’t want callbacks to continue to be fired for a script that is no longer in a runnable state. For example we wouldn’t want a heartbeat callback to continue to execute after the script is removed from the data model.
Of course, there are also events like AncestryChanged which are invoked when the script is unparented. However, the signal doesn’t actually get invoked until after the callbacks for the script are disabled. That is why the script which is not unparented receives the event (it is still active), but the script that was just unparented doesn’t receive the event (it’s callbacks were disabled just prior to the event being fired).