Changes to Non-ReplicatedFirst Loading Order

Hi Developers,

There are some changes coming to the non-ReplicatedFirst loading order that we want to walk you through before they go live.

ReplicatedFirst exists as a means for game creators to create loading screens and otherwise manage and optimize game loading. The order and timing of the arrival of the rest of the instances is not guaranteed, so it is expected for developers to use WaitForChild to determine when parts of the game have loaded.

If you want to read more about best practices for setting this up, here are two helpful DevHub articles about using ReplicatedFirst and WaitForChild.

The upcoming change to game join processing that will improve how these operate, but it will (as a side effect) re-order most of the instances received during join. Games that fully use WaitForChild in scripts running in ReplicatedFirst will not see any negative impacted by this change.

Before this change, the sending strategy would tend to send parents and their immediate children close together. This meant that, even though it was not officially supported or guaranteed, sometimes if you called WaitForChild on some Model/Folder/etc, that WaitForChild might return after that child plus some descendants had all loaded.

NOTE: If your game does not explicitly use WaitForChild for every instance that you need to interact with while loading then your game may encounter an issue when we modify the send order in this upcoming change.

Here’s an example of something that might break after this change:

Workspace structure

Workspace
→ Folder
—→ Part1
—→ Part2

Script in ReplicatedFirst

local f = workspace:WaitForChild("Folder")
for i,c in ipairs(f:GetChildren()) do
    trackingMap[c] = ...
end

Before this upcoming change, this script may get “lucky” and only run after Part1 and Part2 have loaded, after this upcoming change you are much less likely for this to work out. This case looks obvious, but with ModuleScripts and require() it is sometimes less obvious when situations like this might arise.

If you do use this behavior, here are two simple ways that you can resolve it:

  1. Make sure you use :WaitForChild() for every object you interact with (even implicitly through e.g. :GetChildren() ) before game.Loaded fires
  2. Add game.Loaded:Wait() in strategic places in your code to postpone work until the whole game has finished loading

To help you determine if your game has a bug like this lurking, we will be enabling this behavior for a period in Roblox Studio only before enabling for all live games. We intend to flip this on in studio during the day on Monday August 17th 2020, and to enable for all game clients at some point after that (pending results).

Thank you! And please let us know if you have any questions regarding this change.

117 Likes

This topic was automatically opened after 23 minutes.

Seems like a interesting update I’ll make sure to check my code for things. In case if I have anything that does that.

4 Likes

The studio-stage enabling sounds very convenient to me. If something goes wrong, it will be very clear why and what to resolve, and well before there could even be a problem! Thank you for this!

5 Likes

Do we now need to use WaitForChild on descendants of ReplicatedFirst from ReplicatedFirst? I believe previously they were always available.

1 Like

Is this only effective for ReplicatedFirst code trying to access items in other services? Will code that, say, runs in PlayerScripts (protected by game.Loaded) be required to use WaitForChild against instances that are placed in Studio?

Right now, I have code in PlayerScripts that does not use either FindFirstChild or WaitForChild for instances in ReplicatedStorage. PlayerScripts does not run until game.Loaded fires last I recall, so I’m not sure if I misread this announcement and that this flow would be affected or if it’s just for First code.

Current code I use that I’m concerned would be affected (non-ReplicatedFirst code):

local require = require(game:GetService("ReplicatedService").MyLoader)
1 Like

No, you don’t need to worry about this change in scripts other than ones that are in ReplicatedFirst.

When a player joins, Roblox takes a snapshot of the current game tree, and no scripts (other than ReplicatedFirst scripts) begin running on the client until every item in that snapshot has been replicated to the client.

13 Likes

This change will have no impact on the contents of ReplicatedFirst

This will only affect the initial game load (before game.Loaded). So you only need to be concerned with scripts in ReplicatedFirst interacting with content outside of ReplicatedFirst.

4 Likes

Might be nice to have some sort of boolean argument to allow WaitForChild to wait for descendants too. Like, workspace:WaitForChild(“Folder”, true) – true means we need to wait for all descendants to load in as well. WaitForChild can be tedious as is.

Also question, the OP states the GetChildren() implicitly waits for each child, but does it wait for all descendants or only immediate children of the model? Would :GetDescendants() wait for all descendants instead?

12 Likes

To clarify: WaitForChild only guarantees the specified child was present. WaitForChild does not currently guarantee anything about siblings or descendants. However, an internal implementation detail regarding how WaitForChild and initial loading interacted meant that sometimes, randomly, by luck, you might have slightly more than just that one child after waiting. I am aware of at least one game that relied on this (not guaranteed) behavior unintentionally, hence the public announcement.

To safeguard your game, you should use WaitForChild in your scripts running in ReplicatedFirst when those scripts need to interact with instances outside of ReplicatedFirst. When doing so, you should only assume that the specific instance you used WaitForChild for is loaded (and not any of its siblings or descendants). I agree that WaitForChild use can be tedious, there are some discussions about options for making this flow easier to use, but for the time being it is the tool we have & support.

3 Likes

Due to internal delays we did not enable this on August 17th, but we did enable this on August 18th.

4 Likes

Thanks for the reply and clarification, but I’m not sure you understood my question.

Would this mean that internally roblox does a wait for child inside the GetChildren() function? Instead of saying

game.ReplicatedStorage:WaitForChild("RemoteFunctions") -- one of these lines for every instance in rep store

could I do

game.ReplicatedStorage:GetChildren()

and still rely on the remote functions folder being there because GetChildren() internally does a waitforchild() call (or something of the sort)?

My second question was if GetDescendants() did the same internal waitforchild() call that GetChildren() does. I wanted to know if I could run ReplicatedStorage:GetDescendants() and rely on all the remote events and functions inside the folder (nested) to be loaded as well.

Would this mean that internally roblox does a wait for child inside the GetChildren() function?

No, this is meant as a clarification of what your scripts need to do. GetChildren() and GetDescendants() do not call WaitForChild() automatically, so if your code is running in some context where the children are not already loaded you need to explicitly add WaitForChild() calls to your code.

could I do game.ReplicatedStorage:GetChildren()
and still rely on the remote functions folder being there because GetChildren() internally does a waitforchild() call (or something of the sort)?

No, if you need to wait for children or descendents, you need to directly call WaitForChild() from your scripts. It will not happen automatically by calling GetChildren() nor by calling GetDescendants().

2 Likes

Does this mean that I’ve been using WaitForChild incorrectly all these years? I use it for top-level instances (such as ScreenGuis) in the PlayerGui and Folders etc. in ReplicatedStorage. Then I index the descendants normally. According to the wiki…

WaitForChild is extremely important when working on code ran by the client (in a LocalScript ). Roblox does not guarantee the time or order in which objects are replicated from the server to the client. This can cause scripts to break when indexing objects that do not exist yet.

Additionally, does this change how I should approach waiting for Folders etc. created by the server with their descendants contained within and then parented to ReplicatedStorage? I always used to assume that the descendants would be replicated along with the folder.

Note that I am talking about LocalScripts/ModuleScripts located in StarterCharacterScripts and StarterPlayerScripts (but primarily the latter).

Perhaps not wrong, but unnecessary might be the word there. Using WaitForChild when the instance is already there just returns the instance immediately with no yield.

Not sure why the DevHub says that. That seems inaccurate. By the time your non-ReplicatedFirst code starts running, the entire game is available to use, even items created by scripts before the player joined the game.

If the server creates an instance when the player is already there (or moves one into a replicated container), ChildAdded can fire before all of its children have replicated. You can make sure that an instance has all of its children replicated by having the server use a RemoteEvent and FireClient with that instance as an argument. The OnClientEvent won’t fire on the client until all of that instance’s children are ready and available.

1 Like

Though there’s no way to reliably use this information because you can’t tell the difference between an object that was there before the player joined the game and one that was just created. If something gets created at runtime by a script, you need to be using WaitForChild to get a reference to anything you’re accessing inside of it.

This doesn’t change anything regarding that, but you’ve just been getting lucky. The bottom line is that if you dynamically create an object using a script, you have to be using WaitForChild to access stuff within it on the other side of the network.

Yes, to clarify: I would advocate for using WaitForChild on all script-generated objects. If you don’t, it’s pretty common for the first player to join to have timing issues. If you don’t want to name every child something unique though, you can always use the RemoteEvent trick.

With that in mind, the excerpt from the DevHub is still wrong in the more common case of accessing instances that are available in Studio.

1 Like

Unfortunately yes, you do need to explicitly invoke WaitForChild for each individual descendant that you want to access, not just the root. We do not guarantee atomicity of sending currently. In practice, the instances frequently end up in the same bundle of messages, but it is not a guarantee. You can try this out locally by cloning some huge model (e.g. with 10k+ parts) into replicated storage, using WaitForChild on the root, and then as soon as that returns examining the contents of the clone.

1 Like

I wrote a post related to instance lifetimes that may be helpful:

The gist is to treat the game tree as a dynamic structure, identify when instances are added, removed, and the conditions involved, and make use of APIs with guarantees as much as possible.