Remove the " Something unexpectedly tried to set the parent of X to NULL while trying to set the parent of X"

t=tick()spawn(function()print(tick()-t)end)
0.01389741897583

?

waiting 0 yields 1/30th of a second. Spawning yields until the next resume at < 1/60th of a second, its expected behavior.

I hate wait(0) as much as the next guy but this has nothing to do with the OP.

However, how this argument started is a good idea - you could avoid this error by simply doing this:

~~~
spawn(function() part:Destroy() end)
~~~
6 Likes

Why are we even talking about wait? The problem here is logical. If you change the parent while you’re executing an AncestryChanged event then what do you do with the connections that haven’t been fired yet?

part.AncestryChanged:connect(function()
   part.Parent = workspace
end)

This will error because you’re trying to change the data that fired the event while the event is still firing. wait() isn’t a magic solution that makes the error not happen, it yields the thread and allows the event to continue executing so that your code will change the data after the event finishes.

10 Likes

If this is the reason why this happens then can’t there be a check to see if any unfired connections exist? If so, finish them then destroy? If theres no connections (As in most cases) just destroy.

2 Likes

That would basically turn Destroy into a yieldfunction and thus changes the behaviour/expectations that you should have of the method, I don’t think that’s a good idea. Yielding yourself before calling Destroy seems a lot cleaner, then you are actively aware of the yielding, instead of it being a potential side effect.

3 Likes

Because no one expects Destroy to execute other lua code that changes the state of the game. What we really need is a way to manually yield the thread so that those other connections can execute.

Ah, wait()

1 Like

coroutine.yield() would work better in this case. It’s basically wait() with no resume delay.

1 Like

To be specific, the code everyone is looking for here is:

coroutine.yield(coroutine.running())

Which will add the current thread back into the “execution queue”, but still for the current frame, to be executed after all other currently queued threads are executed. Basically, it will “wait” until all other threads for the current frame have completed, but not actually “wait” any frames (as will normally be the case if you use the “wait()” function). You would use it something like this:

game.Workspace.ChildAdded:connect(function(child) 
    coroutine.yield(coroutine.running()) 
    child:Destroy()
end)

Which would successfully destroy the part without it existing for any frames.

4 Likes

running() isn’t necessary, though. yield() already pauses the calling thread in its own, and the scheduler doesn’t do anything with the arguments passed to yield().

Zeuxcg described this as a really ugly bug that shouldn’t be utilized.
I wouldn’t advise using this @PlaceRebuilder, as this might get patched eventually.

1 Like

He meant that you at least shouldn’t use it for loops, because it means you execute several times per frame until the time quantum of the Lua scheduler runs out (so you literally fill up the remainder of the time quantum with your task, several times).

It’s not as ugly if you just call it once to put the coroutine back into the queue for the current frame temporarily. Not sure if the fact that it processes it again in the same cycle is the intended behaviour though.

This scheduling issue should be fixed btw.

3 Likes