Content Streaming

In-experience content streaming allows the Roblox engine to dynamically load and unload 3D content and related instances in regions of the world. This can improve the overall player experience in several ways, for example:

  • Faster join times, as players can start playing in one part of the world while more of the world loads in the background.
  • Experiences can be played on devices with less memory since content is dynamically streamed in and out.
  • Distant terrain landmarks are visible through lower-detail meshes even before they are fully streamed in.

Enabling Content Streaming

Content streaming can be enabled through the StreamingEnabled property of the Workspace object in Studio (this property cannot be set in a script).

Once enabled, it's recommended that you adhere to the following practices:

  • Because clients will not typically have the entire Workspace available locally, use Instance:WaitForChild() for objects that are needed within a LocalScript. Note that some objects may never be streamed to a given client, so you should also specify a timeout if the script cannot afford to wait indefinitely, for example WaitForChild("Object", 2).
  • Minimize placement of 3D content outside of Workspace. Content in containers such as ReplicatedStorage or ReplicatedFirst is ineligible for streaming and may negatively impact join time and memory usage.
  • If you move a player's character by setting its CFrame, do so from a server-side Script and use streaming requests to more quickly load data around the character's new location.
  • Avoid parenting a part to another part, especially when the parent is far away from the child. Instead, group them as a Model.
  • Manually set the player's ReplicationFocus only in unique situations such as in experiences that don't use a Player.Character. In these cases, make sure the focus is near the object(s) that the player controls to ensure content continues to stream in around the player's interaction point.

Technical Behavior

Streaming In

Content eligible for streaming must be a Part or MeshPart (BasePart) or a descendant of a BasePart, and must be under Workspace in the hierarchy.

When a player joins, all instances in the Workspace are sent to the client, excluding BaseParts and descendants of those instances. Then, during gameplay, the server streams in BaseParts and their descendants to the client.

Streaming Out

During gameplay, a client may stream out (remove from the player's Workspace) regions and the BaseParts contained within them, based on the behavior set by StreamOutBehavior. The process begins with regions furthest away from the player's character (or ReplicationFocus) and moves in closer as needed. Regions inside the StreamingMinRadius range never stream out.

To further anticipate stream out, examine these scenarios:

Scenario Example Streaming Behavior
A part is created locally through Instance.new() in a LocalScript. In a "capture the flag" game, you create and attach blue helmet parts to all players on the blue team through a LocalScript. The part is not replicated to the server, and it is exempt from streaming out unless you make it a descendant of a part that exists on the server, such as a part within a player's character model.
A part is cloned locally from ReplicatedStorage through Instance:Clone() in a LocalScript. A wizard character casts a spell by activating a Tool, upon which an object including several special effects is cloned from ReplicatedStorage and parented to the workspace at the wizard's position. The part is not replicated to the server, and it is exempt from streaming out unless you make it a descendant of a part that exists on the server.
A part is reparented from ReplicatedStorage to the workspace via a LocalScript. A "wizard's hat" is stored in ReplicatedStorage. When a player chooses to play on the wizards team, the hat is moved into their character model via a LocalScript. The part remains eligible for streaming out since it came from the server and was replicated to ReplicatedStorage. Avoid this pattern as it causes a desync between the client and server, and the part may stream out; instead, clone the part.

Assemblies and Mechanisms

When at least one part of an assembly is eligible for streaming in, all of the assembly's parts also stream in. However, an assembly will not stream out until all of its parts are eligible for streaming out. On both stream in and stream out, all of the Constraints and Attachments involved with physical mechanics also stream in/out, helping to ensure consistent physics updates on clients.

Note that assemblies with anchored parts are treated slightly differently than assemblies with only unanchored parts:

Assembly Composition Streaming Behavior
Unanchored parts only Entire assembly is sent as an atomic unit.
Anchored root part Only the parts, attachments, and constraints needed to link the streamed parts to the root part are streamed in together.

Timing Delay

There may be a slight delay of ~10 milliseconds between when a part is created on the server and when it gets sent to clients. In each of the following scenarios, you may need to use Instance:WaitForChild() and other techniques rather than assuming that events and property updates always occur at the same time as part streaming.

Scenario Example Streaming Behavior
A LocalScript makes a RemoteFunction call to the server to create a part. A player activates a Tool locally to spawn a part on the server that all players can see and interact with. When the remote function returns to the client, the part may not yet exist, even though the part is close to the client focus and within a streamed area.
A part is added to a character model on the server via a Script and a RemoteEvent is fired to a client. When a player joins the police team, a "police badge" part stored in ServerStorage is cloned and attached to the player's character model. A RemoteEvent is fired and received by that player's client in order to update a local UI element. Although the client receives the event signal, there is no guarantee that the part has already streamed to that client.
A part collides with an invisible region on the server and triggers a RemoteEvent on the client. A player kicks a soccer ball into a goal, triggering a "goal scored" event. Other players that are close to the goal may see the "goal scored" event before the ball has been streamed to them.

Streaming Properties

The following properties control how content streaming applies to your experience. All of these properties are non-scriptable and must be set on the Workspace object in Studio.

StreamingMinRadius

The StreamingMinRadius property indicates the radius around the player's character (or ReplicationFocus) in which content streams in at the highest priority. Care should be taken when increasing the default, as doing so will require more memory and more server bandwidth at the expense of other components.

StreamingTargetRadius

The StreamingTargetRadius property controls the maximum distance away from the player's character (or ReplicationFocus) in which content streams in. Note that the engine is allowed to retain previously loaded content beyond the target radius, memory permitting.

StreamingIntegrityMode

Your experience may behave in unintended ways if a player moves into a region of the world that hasn't been streamed to them. The Streaming Integrity feature offers a way to avoid those potentially problematic situations. Please see the StreamingIntegrityMode documentation for more details.

StreamOutBehavior

The StreamOutBehavior property sets the streaming out behavior according to one of the following values:

Setting Streaming Behavior
Default Default behavior, currently the same as LowMemory.
LowMemory The client only streams out parts in a low memory situation and may remove content until only the minimum radius is present.
Opportunistic Regions beyond StreamingTargetRadius can be removed on the client even when there is no memory pressure. In this mode, the client never removes content that is closer than the target radius, except in low memory situations.

Requesting Area Streaming

If you set the CFrame of a player character to a region which isn't currently loaded, streaming pause occurs, if enabled. If you know the character will be moving to a specific area, you can call Player:RequestStreamAroundAsync() to request that the server sends regions around that location to the client.

The following scripts show how to fire a client-to-server remote event to teleport a player within a place, yielding at the streaming request before moving the character to a new CFrame.

Script - Teleport Player Character

1local ReplicatedStorage = game:GetService("ReplicatedStorage")
2
3local teleportEvent = ReplicatedStorage:WaitForChild("TeleportEvent")
4
5local function teleportPlayer(player, teleportTarget)
6 -- Request streaming around target location
7 player.RequestStreamAroundAsync(teleportTarget)
8 -- Teleport character
9 player.Character:SetPrimaryPartCFrame(teleportTarget)
10end
11
12-- Call teleport function when the client fires the remote event
13teleportEvent.OnServerEvent:Connect(teleportPlayer)
14
LocalScript - Fire Remote Event

1local ReplicatedStorage = game:GetService("ReplicatedStorage")
2
3local teleportEvent = ReplicatedStorage:WaitForChild("TeleportEvent")
4local teleportTarget = CFrame.new(50, 2, 120)
5
6-- Fire the remote event
7teleportEvent:FireServer(teleportTarget)
8

Detecting Instance Streaming

In some cases, it's necessary to detect when an object streams in or out and react to that event. A useful pattern for streaming detection is as follows:

  1. Assign a CollectionService tag to all of the affected objects (the Tag Editor plugin may be helpful).

  2. From a single LocalScript, detect when a tagged object streams in or out through CollectionService:GetInstanceAddedSignal() and CollectionService:GetInstanceRemovedSignal(), then handle the object accordingly. For example, the following code adds tagged Light objects into a "flicker" loop when they stream in and removes them when they stream out.

    LocalScript - CollectionService Streaming Detection

    1local CollectionService = game:GetService("CollectionService")
    2local RunService = game:GetService("RunService")
    3
    4local random = Random.new(tick())
    5local heartbeatCount = 0
    6local flickerSources = {}
    7
    8-- Detect tagged parts streaming in or out
    9if workspace.StreamingEnabled then
    10 CollectionService:GetInstanceAddedSignal("FlickerLightSource"):Connect(function(light)
    11 flickerSources[light] = true
    12 end)
    13 CollectionService:GetInstanceRemovedSignal("FlickerLightSource"):Connect(function(light)
    14 flickerSources[light] = nil
    15 end)
    16end
    17
    18-- Flicker loop
    19RunService.Heartbeat:Connect(function()
    20 heartbeatCount += 1
    21 if heartbeatCount > 5 then
    22 heartbeatCount = 0
    23 for light, _ in pairs(flickerSources) do
    24 light.Brightness = 8 + random:NextNumber(-0.4, 0.4)
    25 end
    26 end
    27end)
    28

Customizing the Pause Screen

The Player.GameplayPaused property indicates the player's current pause state. This property can be used with a Instance:GetPropertyChangedSignal() connection to show or hide a custom GUI.

LocalScript

1local GuiService = game:GetService("GuiService")
2local player = game.Players.LocalPlayer
3
4-- Disable default pause modal
5GuiService:SetGameplayPausedNotificationEnabled(false)
6
7local function onPauseStateChanged()
8 if player.GameplayPaused then
9 -- Show custom GUI
10 else
11 -- Hide custom GUI
12 end
13end
14
15player:GetPropertyChangedSignal("GameplayPaused"):Connect(onPauseStateChanged)
16

Model Level-of-Detail

When streaming is enabled, Models outside of the currently streamed area will not be visible. However, you can tell the engine to create approximate "imposter meshes" for models that are not present on clients. Level-of-detail for these meshes works on a per-model basis and is selectable via the model's LevelOfDetail property.

Model Setting Streaming Behavior
StreamingMesh Activates the asynchronous generation of an imposter mesh to display when the model is not present on clients.
Disabled or Automatic The model disappears when outside the streaming radius.

When using imposter meshes, note the following precautions:

  • Imposter meshes are designed to be seen at 1024 studs away from the camera or further. If you've reduced StreamingTargetRadius to a much smaller value like 256, imposter meshes may not be visually acceptable for the model they replace.
  • Textures are not supported; imposter meshes are rendered as smooth meshes.
  • If a model and its descendant models are all set to StreamingMesh, only the top-level ancestor model is rendered as an imposter mesh, wrapping all geometries under the ancestor model as well as its descendant models. For better performance, it's recommended that you use Disabled for descendant models.