Return of Changes to Non-ReplicatedFirst Loading Order

Hi Developers,

Previously, in August 2020, we introduced some changes to the non-ReplicatedFirst loading order in this post: Changes to Non-ReplicatedFirst Loading Order. Due to issues we discovered with this change, we disabled it temporarily. We are excited to inform you that we are ready for these changes to go live once more!

Information from the post above is copied and pasted below for your convenience:

Previous Post

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.
ReplicatedFirst
WaitForChild

The upcoming change to game join processing 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:

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

Again, 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 January 19th , 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.

Questions asked Previously:

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

This change will have no impact on the contents of ReplicatedFirst

  • 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?

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.

Important Reminders:

Functions such as GetChildren() or GetDescendents() DO NOT AUTOMATICALLY CALL WaitForChild().

The presence of one child DOES NOT GUARANTEE the presence of any of its siblings. You must call WaitForChild() on each child you require for a given script.

Again, for visibility: We intend to flip this on in studio during the day on January 19th , and to enable for all game clients at some point after that (pending results).

Jan 27th Update:

80 Likes

This topic was automatically opened after 16 minutes.

To those looking to find the DevHub articles on ReplicatedFirst and WaitForChild, I’ve linked them below for your convenience. (So that you don’t have to search on the Developer Hub yourself!)

ReplicatedFirst
WaitForChild

Anyways, love these changes!

8 Likes

It’s nice to have a reminder beyond just editing the other post. But, the new date of it being enabled in Studio (Jan 19) is within the “old information” section with the same wording, which I almost missed reading. It’d be great if that was moved to the end.

Also, the “copied” part of that previous topic here is a bit jumbled up in the actual new information (i.e. the introduction and the “questions asked previously”). To account for quick and easy readability, can you store all the info in a collapsible menu like “previous post” or something? That way devs don’t miss any new info thinking the rest is just the old stuff.

Edit: It has been done, thank you!

5 Likes

Thanks for the links, they must’ve gotten lost in the copy paste process. I’ve added them to the post!

4 Likes

I believe the code should be formatted here.


This update is great! Thanks to the engineers who rolled this out!

2 Likes

Seems like a great change to me!

Just one question, if a certain script is negatively affected by this change, what kind of a warning should we expect in the output section to receive? I’m not familiar with ReplicatedFirst, however, if I do start to use it prior to January 19th, I would like to know.

The most likely error you would see is the same error you would see if you used . to access something that doesn’t exist on the client (yet):

  15:29:57.399  > print(workspace.NotAChild)  -  Studio
  15:29:57.400  NotAChild is not a valid member of Workspace "Workspace"  -  Edit
  15:29:57.400  Stack Begin  -  Studio
  15:29:57.400  Script 'print(workspace.NotAChild)', Line 1  -  Studio
  15:29:57.400  Stack End  -  Studio
5 Likes

noice looking good :slight_smile: this solved many problems lol

What are the exact benefits of this? Will this change introduce faster loading in general, or maybe less thread hanging from WaitForChild?

(This is simply based on my understanding of everything)

This change first rolled out for improved game joining process, or in other words (as you said) faster loading in general.

However, as said above in the quote from this article, there were multiple issues when changes rolled out the first time.

The link above leads to the point in the thread for the changes made (back in August) where users started reporting issues. There were several UI errors and multiple ping spikes when players joined certain games. Eventually, they did disable the changes so that they could fix the errors, as already stated.

Now that these changes are back and improved, it should help with a faster game loading process and not cause errors in the clients.

I do have just one last question.

Could it be possible to implement this in a way where it would be optional to turn this on or off (for now)? Therefore, if there did happen to be any errors we could report it, and then turn it off so that our games wouldn’t be affected.

This is part of a group of changes we are making targeting all aspects of the join. Most of the other changes are fully internal/transparent to game developers, but we couldn’t find a way to do that for this particular change.

In our internal testing, we saw ~4x improvement in join snapshot processing for our core test game (this number will vary for different games, but should be a significant improvement in all cases)
Before:
o1join_off
After:
o1join_on

Additionally, this resolves some other downstream server performance impacts of joins. Here we have the summary view from the microprofiler. We perfomed periodic groups of 5 joins. Before the change you can see clear performance spikes/lag in the frames where we process joins, after those spikes are almost entirely gone

Before:


After:

We also anticipate some benefits in total bytes needed to join games, which should have a direct (positive) impact on join times, but in our tests that improvement varied between games so I don’t want to quote a specific improvement number.

Not all of the gains will be realized from this individual change, but there will be some improvement, and this change is necessary for continuing on this path.

9 Likes

It would be nice to have a method argument that allows us to reliably obtain the descendants of the instance we are waiting on (similar to how the old system worked). WaitForChild is a pretty long thing to type for every child object and sometimes it’s impossible to know just how many child objects there should be.

object:WaitForDescendantsLoaded() may be a good option or object:WaitForChild(“abcd”, true) (true being the argument telling the method to use the old way of handling this) would be great for improving the quality of life for developers.

Edit: I like this update because it more clearly achieves what you’d expect using WaitForChild, but I don’t see any good patches and alternatives for devs who still rely on the old system and don’t have the patience to call wait for child over and over and over again.

2 Likes

Yeah- I get what you mean. I’ve been having that problem too.

So for clarification, would it be ideal for me to put the example code block at the top of the load script I have in ReplicatedFirst for my custom load screen to make sure all the UI elements the script is indexing from a different GUI all exist? (I use :GetDescendants() to preload them all.)

1 Like

You should make sure that there’s some guarantee in place that all of the Instances your load screen script is working with exists before you use them. Generally I think this is done by calling WaitForChild. Calling GetDescendants may not be safe in this use case if the script doesn’t do anything to guarantee that the descendants it’s getting have been replicated by the time GetDescendants is called. Please remember that GetDescendants does not automatically call WaitForChild

That seems like an impossibility though. The UI I have has hundreds of UI elements, am I really going to have to write individual declarations for each one? :grimacing:

I think they misunderstood your question (a lot of Roblox engineers are not as familiar with Roblox from a developer POV).

It is totally valid to check if #something:GetDescendants() exceeds a certain amount to check how many things have been replicated under something.

4 Likes

Yes, that should also work. Sorry C_Sharper, didn’t know this was what you meant, and thanks buildthomas for the answer!

3 Likes