Greetings Developers,
Coming soon, calls to :Destroy()
on the server will replicate to clients. What exactly does this mean?
TL;DR: Try changing the ReplicateInstanceDestroySetting
under Workspace to Enabled for your experiences and see how they are affected, as we plan to enable it by default in the future.
Current Behavior
You may have noticed that currently when you call :Destroy()
on an Instance, that Instance will be destroyed, meaning that it and all its descendants:
- Will have their parent set to nil.
- Will have their parent locked, meaning they cannot be reparented to anything else.
- Any connections made on them will be disconnected for you.
However, what you may not have noticed before is that the same is not true on the clients connected to that server. Roblox’ automatic replication will remove the Instances on the clients as well (setting their Parent
to nil), but it will not destroy those Instances. That means that on the client, the Instances:
- Can still be reparented to another object if you kept a reference to them.
- Will not have their connections automatically disconnected.
The Planned Change
That all changes soon, we’ve upgraded the automatic network replication code such that when you call :Destroy()
on the server, the destroyed Instances will actually be destroyed on the client rather than just being removed.
In addition to the new replication behavior, since the server will now Destroy
objects on the client, it may be important for code on the client to interact with Destroy
in more subtle ways, in particular performing some specialized cleanup on an object before it gets destroyed. In order to facilitate this, we’ve added a dedicated Destroying
event on Instance which allows you to jump in and do such cleanup.
We recognize that this change could potentially break some experiences, especially those containing custom replication code. That’s why we’re rolling out this change carefully in a way that gives you time to adapt your code if it would be impacted…
Rollout Schedule
Now: The Destroying
event will be available in all experiences as it will not impact existing code.
Also now: You will see a ReplicateInstanceDestroySetting
under Workspace. You may optionally turn on the new replication behavior by changing this setting to Enabled. Please give this a try and let us know how your experience is affected!
A few months from now: We will change the default for ReplicateInstanceDestroySetting
to Enabled, but you will still be able to opt out by manually changing to Disabled if you need more time to adapt your experiences. Before we do this we will make another DevForum post to keep everyone informed, so please stay tuned!
Later on: We would like to ultimately end up at a place where we can remove the setting and enable Destroy()
replication for all Roblox experiences to make memory clean-up easier for all Devs. Please help us get there!
New Behavior Technical Details
How it will work
When Destroy()
gets called on the Server, an explicit “destroy” message will also get replicated so that the same Destroy()
gets called on Instances on the client.
Before each Instance is destroyed, the Instance.Destroying
event will be fired. This gives you a chance to run additional cleanup code on the client before the Instances are removed from the hierarchy (Note that Destroying
handler cannot “cancel” the Destroy()
, so make sure you only call Destroy()
on things you’re sure you want gone). Timing may change with Deferred Lua enabled - see Deeper Technical Details for more info.
Destroy()
will disconnect all connections on Instance, so you don’t have to worry about manually disconnecting connections.
Let’s go through an example!
-- Experience contents
Workspace
→ Folder X
→ Folder Y
→ Part Z
X:Destroy() --> Called by a ServerScript
Then the following events will occur:
First, on the server
-
X.Parent
property gets locked (So thatDestroying
handlers can’t attempt to “rescue” it) -
X.Destroying
event gets fired -
X.Parent
gets set tonil
-
Y.Parent
property gets locked -
Y.Destroying
event gets fired -
Y.Parent
gets set tonil
-
Z.Parent
property gets locked -
Z.Destroying
event gets fired -
Z.Parent
gets set tonil
- X, Y, and Z are “locked-in” as the set of Instances to be destroyed, and replicated to the client
Then, on the client
- Client receives X, Y, and Z as a set of Instances to
Destroy()
. - Events 1-9 happen on client-side exactly as they did on server-side even if the client hierarchy is arranged differently due to LocalScript modifications.
Deeper Technical Details
Destroy replication is server → client
As with all property replication, Destroy()
will only be replicated from server → client and not the other way around as that would create security issues.
Non-destroying parent changes
If you’re writing server scripts that specifically set X.Parent = nil
instead of calling Destroy()
, your code won’t be affected. Only code called Destroy()
is affected… however you should consider migrating your code to use Destroy()
if it doesn’t already.
Interaction with Deferred Lua
If an Instance X gets destroyed, the DataModel will fire the Instance.Destroying
event. If Deferred Lua is enabled, the listeners of Instance.Destroying
will not run immediately, but after X and its subtree have been fully removed from the hierarchy.
Interaction with Streaming Enabled
If an Instance X gets destroyed on the server, X:Destroy()
will get replicated to all clients which the server has ever replicated X to. This logic helps ensure that the Destroy
call frees as much memory as possible.
This matters in a streaming-enabled experience: Imagine the client receives part X under Workspace, hooks up a bunch of signal listeners to X, and then sets X.Parent = nil
. If the server does not replicate X:Destroy()
to the client because X isn’t currently in the client’s hierarchy, then the client will never be able to clean up those listeners without separate handling code.
Interaction with additional client-local content
Only the specific Instances that were destroyed on the server will be destroyed on the client, additional client-local Instances parented to the destroyed Instances will be left alone to avoid unnecessarily interfering with client logic. Imagine a situation where (thanks to local modifications) the DataModels look different on the client and server:
Server Workspace
→ Folder X --> Server Script destroys this
→ Folder Y
Client Workspace
→ Folder X
→ Folder Y
→ Part Z --> A LocalScript has created this
Now, a ServerScript calls X:Destroy()
, recursively destroying X and Y on the server-side. Only the Destroy()
of X and Y will be replicated, thus part Z will not be destroyed on the client-side.
Note that since Part Z is a descendant of the destroyed Folder Y, and Folder Y was destroyed, Part Z will still end up with a nil
parent (but it won’t have been destroyed so it can be reparented). You may hook up a listener to the Destroying
event of Folder Y to handle cleanup or reuse of Part Z if needed.
Interaction with client-local hierarchy changes
All instances that were destroyed on the server-side will also be destroyed on the client-side, even if a LocalScript moved them somewhere else other than the parent which was destroyed on the server.
E.g.: If you have a situation where the DataModels are:
Server Workspace
→ Folder X --> Server Script destroys this
→ Folder Y
→ Part Z
Client Workspace
→ Part Z --> has been reparented to Workspace by a LocalScript
→ Folder X
→ Folder Y
If a server Script calls X:Destroy()
(recursively destroying the X, Y, and Z on the server), then X, Y, and Z will all be destroyed on the client-side upon replication even though Z isn’t a descendant of X anymore on the client.
Wrapup
Please let us know how the change affects your code.