Inconsistent Streaming Out Behavior and Connection Handling with Streaming Enabled

Environment:
Platform: Roblox App/Roblox Studio
Feature: Streaming
Date Encountered: November 2, 2023

Overview:
The streaming feature on Roblox is not consistently destroying objects or handling connections when objects are streamed out, which is different from the previously observed behavior. The change in behavior was not announced and has broken some of the components in-game out of nowhere.

Previous Observed Behavior:
The behavior of how objects are streamed out is not properly explained in any of the documentation I could find. So I did some simple multiple tests in different environments with over 20-30 objects and figured out that when an instance is streamed out, it would be Destroyed as “.Destroyed” event would fire as well, along with all connections being disconnected too. When objects are streamed in, a fresh copy is sent from the server and any changes made on the client(tags, attributes, properties) would reset as well. Relying on this behavior, I continued working on a project, and about a few days ago, those specific things started breaking randomly which were relying on this particular behavior.

Current Behavior:
So at this point in writing, when objects are being streamed out, they are not being destroyed, and there’s more. They do get removed from DataModel but it’s not the same as setting .Parent to nil. So essentially, if there’s a strong reference to that object, any connections related to the object will not disconnect and the object will not be garbage collected. However, if you do not have a strong reference, it will disconnect all connections and remove the object from memory altogether(.Destroying would still not be called in either case). When the object is streamed in, it’ll still be a fresh copy from the server with everything changed on the client reset regardless of how it was streamed out. The behavior is inconsistent because:

Example:
We have 2 different parts, one of them does something when a player touches it, and one of them does something when anything touches it. What will happen is the following:

local PartA = workspace:WaitForChild("PlayerHealthPad")
local ConnectionA = PartA.Touched:Connect(function()
	-- We kept PartA as a variable to do magnitude checks when touched by a player or doing anything else with part
end)

local ConnectionB = workspace:WaitForChild("PartB").Touched:Connect(function()
	-- do something
end)

Now, when PartA is streamed out, the ConnectionA will stay connected, and when PartB is streamed out, the ConnectionB will disconnect making this entirely inconsistent.

Lastly, if you’re using CollectionService to get the instances and also using streaming, the problem becomes worse as tags changed will reset when the object is streamed back in but in some cases, the connections will reset, and in others, they won’t.

I am attaching a repo file with this with a streaming radius set to 64, and two parts with pretty much the same example as mentioned above.
streaming_repo.rbxl (54.4 KB)

Expected behavior

Tbh, I am not even sure what the expected behavior is as none of this information is outlined anywhere I could find, honestly both of the ways have their own pros and cons but I would expect it to be consistent, either disconnect and destroy the object, or don’t disconnect anything. I’ll be waiting on a response from the team on this so I can change/redo stuff accordingly on how they’ll be going forward with this. Lastly, please outline the behavior as well. Misinformation or assumptions can lead to unnecessary memory leaks and potential problems.

2 Likes

When instances are streamed out on the client it is not equivalent to them being destroyed, and it is possible for local references to keep them alive in memory. This is by design, so that if a local script has a reference to an instance and the instance is streamed out and then back in that the reference will stay linked up. You are correct that this behavior isn’t documented anywhere and we will make sure it is documented.

I am concerned that it sounds like you are saying this behavior changed recently. Can you describe exactly what is behaving differently, or what was broken? And you believe this happened on November 2nd?

Specifically, I don’t believe that destruction signals firing on clients when instances are streamed out is behavior we have had anytime in the last year or longer.

Well, I may have missed something when trying to figure it out myself as no documentation is provided, and similar questions had mixed answers as well on the forum. It may be due to the fact that I didn’t have a strong reference initially while testing and connections were disconnected instantly when an object was streamed out and assumed that to be the behavior. It did work for a while and after a few changes made in the code, the connections started building up each time the object was streamed back(as the state of the object resets when streamed back in as well). I have already changed/fixed the code but I still do believe that disconnecting connections shouldn’t be based on having a strong reference or not as that can lead to unexpected issues such as this. The state of the object will reset regardless of whether it has a reference or not, but connections may or may not reset.

Also I tested with your repro place and I continue to see the connections as being active even after stream out:

  12:14:07.041  Part2 Connection: true  -  Client - LocalScript:22
  12:14:09.057  Part1 Connection: true  -  Client - LocalScript:21
  12:14:09.057  Part2 Connection: true  -  Client - LocalScript:22
  12:14:11.073  Part1 Connection: true  -  Client - LocalScript:21
  12:14:11.073  Part2 Connection: true  -  Client - LocalScript:22
  12:14:13.090  Part1 Connection: true  -  Client - LocalScript:21
  12:14:13.090  Part2 Connection: true  -  Client - LocalScript:22

Are you seeing different behavior? Or expecting different behavior?

Apologies for the confusion, the repro had a logical error I just noticed, here’s an updated repro file.
streaming_repo.rbxl (54.3 KB)

This is the output you should get after the objects are streamed out:

13:06:42.545 Part1 Connection: true - Client - LocalScript:15
13:06:42.545 Part2 Connection: true - Client - LocalScript:16
13:06:44.561 Part1 Connection: true - Client - LocalScript:15
13:06:44.561 Part2 Connection: true - Client - LocalScript:16
13:06:46.561 Part1 Connection: true - Client - LocalScript:15
13:06:46.561 Part2 Connection: true - Client - LocalScript:16
After being streamed out:
13:06:48.577 Part1 Connection: true - Client - LocalScript:15
13:06:48.577 Part2 Connection: false - Client - LocalScript:16
13:06:50.577 Part1 Connection: true - Client - LocalScript:15
13:06:50.577 Part2 Connection: false - Client - LocalScript:16

This difference in connection state after stream out is expected behavior currently. If a reference keeps the part from fully streaming out on the client then the connection will stay alive, otherwise when the part is removed on the client the connection will be removed.

We realize this can be frustrating to have to work around and we are discussing options for improving this behavior in the future. But unfortunately I don’t anticipate a change to this behavior in the near future.