Objects reparented to Workspace on the client can be streamed out

I wouldn’t have known about this if Roblox Studio hadn’t saturated my memory usage on a laptop I recently purchased:

My game reparents some objects (client-side only) from ReplicatedStorage to the workspace dynamically, so that it doesn’t have to copy these objects from ReplicatedStorage. These are mostly 3D scenes that appear while you’re in certain menus. However, because it’s created within ReplicatedStorage, it shouldn’t ever stream in and out of existence.

Unfortunately, when you reparent this to workspace on the client (in order to show it to the player), Roblox will think this is a streamable object (since it was replicated from the server), and in some cases unload these objects on low-memory devices.

This means I’ve had a major bug in my game due to StreamingEnabled for over a year, that I didn’t know about because my device always had enough memory to never unload objects within the Workspace.

Here’s a minimal repro showing the issue:

To reproduce the issue, run a test with the repro place (on any device with 8 GB of memory or less for more accurate results), and walk to one corner of the test place. Then wait for assets to load (you will see “Loading assets” in server output to show the progress of fetching assets via InsertService).

StreamingUnloadRepro.rbxl (23.4 KB)

Eventually, the memory will be saturated by these asset IDs until things will start to load out. When that happens, parts on all other corners of the map will be streamed out, even though there were only reparented on the client. This will delete them permenantly on that client, as walking to the spot where these instances used to be will not regenerate these instances, since they were not meant to be streamed in the first place:

Although this repro might seem contrived, it’s a very realistic scenario for any asset-heavy game for mobile and low-memory devices.

10 Likes

Thanks for the repro place, we will investigate.

4 Likes

If you need a temporary workaround for this: Given that you’re using this for menu backgrounds you could re-parent the objects to a ViewportFrame instead, though at the cost of lower rendering fidelity.

2 Likes

Can you change your scripts to clone the objects in ReplicatedStorage instead of reparenting them? Currently when a part is reparented out of replicated storage it is considered remote, but when it is cloned and parented into the workspace on the client it is considered local.

I can; however, some of these scenes are large and I would rather not have to double the amount of memory taken for these instances.

Shouldn’t it be determined through a flag set by the server whether or not a part should be streamed in and out? After all, when you reparent these parts locally, then they get streamed out, they will never get streamed back in on the client, simply because the server doesn’t agree with the client that these parts are streamable.

After you clone the object from ReplicatedStorage on the client you can delete the original one in ReplicatedStorage. This should avoid the double memory usage.

There are reasons we want reparented objects from ReplicatedStorage to behave differently from cloned objects. In the reparent case the client should still see updates to the objects if the server changes their properties since they still refer to the same object.

Sure… but shouldn’t this add to the case for why cloning isn’t always a solid workaround and that this bug is more than just an annoyance? You can’t see updates from the server if your object (which shouldn’t be streamable) gets permanently deleted on the client.