As we have discussed previously, we’re dedicated to keep improving Streaming because we believe it’s the best way to improve join time and reduce client crashes. Today, we are pleased to announce updates to Streaming Enabled that further optimize how models are sent to clients as well as how models can be streamed out when no longer needed.
At a high level enabling this “improved” model streaming behavior causes models to be sent when the client needs them, not on join, helping to reduce join times and memory requirements. Previously streaming operated mainly at the part level, resulting in models without their part descendants.
These updates are initially off by default but you can turn them on in your Workplace settings by toggling ModelStreamingBehavior to “Improved.”
Please note that the Default value is currently equivalent to Legacy, which results in the current model streaming behavior where models are sent during join unless they are Atomic/Persistent. In the future we expect to change the meaning of Default to behave the same as Improved.
Read on for more details on why we think this will help. Or, just give it a try today!
Note that knowledge of existing model streaming modes may help understand this new change. For information on the existing modes and persistent loaded signal see: New Improvements to Streaming Enabled
Currently in streaming enabled experiences all instances in Workspace that are not parts, or descendants of parts are sent during join. This results in instances that must be replicated to all clients before join is complete, and these instances cannot be garbage collected in low memory situations.
Consider an experience with a bunch of humanoid NPCs that are located far away from the player spawn point and where the parts aren’t initially sent to players. After join the client workspace will contain models for all of these NPCs, as well as humanoids, animations, and other non-part instances:
Figure 1: Traditional Streaming
When ModelStreamingBehavior is set to Improved models are no longer sent during join. When the model is sent to clients depends on if the model has any part descendants. A model that has no part descendants is considered a global model, meaning it has no spatial information. A global model is streamed to clients when its ancestor is sent to clients, unless that ancestor is sent during join. If the ancestor is sent during join then the global model is sent after join, but before the PersistentLoaded signal is fired for the player.
Figure 2: Improved Model Streaming. Only model for streamed NPC is present
Models that are spatial, meaning they have part descendants, are not sent to a client until one of their part descendants needs to be streamed to the client. When a spatial model is streamed we send the model and descendants, until we encounter a part descendant or another spatial model. Similarly when a part is streamed all non-part and non-spatial model descendants (and their descendants) are streamed. This means that models are not always sent atomically, unless they have no part descendants or all their parts belong to a single network ownership unit (NOU). This means that important use cases, like avatars will behave atomically, but if the developer creates a model for an entire city the engine will not attempt to stream the city in/out atomically.
Here is a simple example Workspace, without atomic or persistent models, and how it is replicated to clients with existing streaming behavior:
Note that everything that is not a part or a descendant of a part is sent at join. In particular the model with two part descendants is sent at join, even though its parts may never be sent to a particular client.
When Improved Model Streaming is enabled the behavior is different:
There is no change to the folder, but the two models behave differently. The model with part descendants is not sent until one of the descendant parts needs to be sent. At that time model and descendant script are sent, but not the other model, unless it also needs to be streamed at the same time.The model is streamed out when its last descendant model is streamed out.
The model without any descendant parts is not sent during join, but soon after, and before the persistent loaded signal is fired. And of course, fewer models sent during join mean faster joins!
Note: There is no change to models that have their model streaming modes set to Atomic/Persistent/PersistentPerPlayer. Those are already not sent during join and are streamed in/out atomically.
Note: Workspace itself is still sent during join even though it is technically a model instance.
Previously in streaming enabled experiences models and their non-part descendants were never streamed out, unless the model was manually set to be atomic. With improved model streaming the engine can now stream out models when they are no longer needed, potentially freeing up memory on the client and reducing what instances need property updates.
A spatial model is streamed out along with its last remaining part descendant. Just because one part descendant of a model is streamed out doesn’t necessarily mean that the model will be streamed out at that time. A model could have multiple part descendants, some near to the player/replication focus and some far away. We may be streaming away distant parts while nearby parts are still required.
A global model (no part descendants) is streamed out when its ancestor is streamed out. This is no different from behavior without improved model streaming.
With traditional replication and streaming when an instance and its descendants are replicated to a client when parented into the datamodel on the client first the ancestor is parented into the data model, and then children, and so on. This means that if a localscript wants to access some instance deep in the parented subgraph it must first WaitForChild on the parent, then child, and so on.
To address this issue when we stream models and/or part subtrees we don’t parent the root of the subtree until all descendants being sent at that time have been parented to the subtree root. This means that once a subtree arrives there is less need for localscripts to use WaitForChild to ensure that instances have arrived. As shown in Figure 1, with traditional streaming the models exist for all NPCs even when they are streamed out. If a local script wants to access the parts in the NPC then it must wait for all the parts it needs to access, possibly using WaitForChild on each part. With improved model streaming the local script would just need to wait for the model to arrive since it would not be sent to the client until needed, and when it is sent the appropriate parts will be sent along with it.
For additional information on streaming enabled please see: Instance Streaming
If you encounter a bug related to this API and are able to file bug reports please create a separate bug report for better visibility.