Great tutorial! I’ve always been an avid user of WaitForChild, especially in some unnecessary cases, and this will help me optimize my game further.
Good read, as always!
I’ve seen different people using WaitForChild or FindFirstChild when it comes to Remotes and Bindables in ReplicatedStorage. Wondering if ReplicatedStorage loads before ServerScriptService - if it does so, I’ll stick with my FindFirstChilds.
Yeah. Typically in the case of a Tool’s LocalScript, it is an object that also exists with the other children of the tool. To prevent any potentially unwanted cases, you only need to call WaitForChild on top parents.
The only necessary cases of WaitForChild in your code are for the Handle and the Neon variables. The rest are all unnecessary cases.
When WaitForChild completes on the handle, we know our LocalScript acknowledges the Handle’s existence. And since cloning and replication are synchronous, the children of Handle will be available when Handle itself is.
The same would go for an outside script accessing your tool. That script would only need to use WaitForChild on the tool, then every other case becomes unnecessary.
EDIT: Nearly this whole post is misinformation. See this post:
This much is fine, since the contents of ReplicatedStorage aren’t implicitly available to the client, the server replicates this at the beginning of a session. It’s necessary to wait only for direct children of ReplicatedStorage, everything else can be indexed directly.
Don’t rely on the order of replication to help you determine what’s appropriate because you may run into cases where that dependence on order presents you with edge cases. Your code should always strive not to have dependencies where not required.
As for ServerScriptService, it has no business replicating to the client and anything you put there is implicitly available to the server when it starts. In short: it’s instantaneously available. So to put it into concept, technically ReplicatedStorage does not load before ServerScriptService.
The thing for ServerScriptService is that if you don’t include a script there in Roblox Studio, then it’s not implicitly available. For example, the Lua Chat System - in your Explorer, you don’t see ChatServiceRunner. This is why most code you see waits for ChatServiceRunner but doesn’t for a child module of it, ChatService.
local ServerScriptService = game:GetService("ServerScriptService")
local ChatServiceRunner = ServerScriptService:WaitForChild("ChatServiceRunner")
local ChatService = require(ChatServiceRunner.ChatService)
If ChatServiceRunner was already available in your game’s hierarchy and didn’t need to be cloned or reparented at run time (current method for injecting CoreScripts), WaitForChild would not need to be used on any of this.
Another example - suppose your game had a Bindables folder in ServerStorage. The server does not need to call WaitForChild to access it, it can do so directly by using dot syntax on it’s name.
local Bindables = ServerStorage.Bindables
FindFirst and WaitForChild are heavily case-dependent methods to use. The circumstances and context of your code will almost always determine whether or not these functions are appropriate to use. Especially of WaitForChild.
So for clarification sake, say you had a folder dedicated to Remotes and Bindables that was directly parented under ReplicatedStorage, you’d only need to use WaitForChild with that instance and for the instances parented under the folder, you only need to do Folder.ChildName?
Exactly. On the server however, ReplicatedStorage’s contents exist implicitly, meaning you don’t need to use WaitForChild at all on the server. You can use dot syntax for items in ReplicatedStorage however, since they’re replicated and available to LocalScripts by the time they execute.
Thanks for the detail. I’ve just seemed to develop this habit as I got too many errors from directly indexing in the PlayerGui, and carried it through to most of my scripts.
This is a great resource that I will definitely come back to in the future
If this principle was to be true, wouldn’t it require that all children of the Tool, including Handle
must be available when said Tool is replicated?
The main catch is where you’re calling the method from. A script’s place in hierarchy, especially that of a LocalScript since that’s what you’re likely working with when it comes to a Tool, changes the requirements.
I may require some clarification as to what you’re asking if the following does not answer your question, but I have done a bit of testing around with this theory and have produced some results.
Tool LocalScript
As expected, when a LocalScript is a child of the tool, it is also part of the items on the replication queue for the Tool. Therefore, you still need to call WaitForChild on any of the Tool’s immediate children, as they may be replicated later than the LocalScript.
In a quick test that was ran in Roblox Studio, part of this could be debunked. A Tool’s Handle is implicit to a LocalScript, as are its children. However, any other item part of the Tool is not implicit. Therefore, direct indexing will produce an error. Observe the following image from a repro:
As you can see, Event does exist in our tool and I attempted to index it with dot syntax. However, it produced the error that the Event isn’t a valid member. Therefore, Event has not been replicated at the time that the LocalScript was executed, which is why it’s not recognised as a member.
This proves the need to wait for top parents in a tool, aside from the Handle. For the sake of consistent conventions though, I personally recommend also waiting for the handle. Just like I mentioned in the post below it: you should not rely on the order of replication to save you from an index error.
External LocalScript
For an external LocalScript, I simply put a quick script in StarterPlayerScripts to test this. My repro produced results as expected and I will explain.
In the repro, all that’s necessary is to wait for the Backpack. Since my tool is placed into StarterGear, it gets cloned into the player’s Backpack when they spawn. As cloning is synchronous, the Tool’s children are immediately available when the Tool is parented.
Given that Backpack isn’t immediately added to a Player after their object has been instanced, I call WaitForChild on the Backpack. However, this gets rid of the necessity to use WaitForChild on the tool, so that’s an even further bonus. Observe how no error is produced in either occasion:
So essentially; the Tool instance itself may be replicated, but its children not at the same time. Therefore once you’ve waited for and retrieved a top parent, all of the Tool’s children will be available, including the Tool’s Handle post-replication or post-cloning.
What I was trying to note is that the fundamental assertion of your previous post, where if a child is replicated, so must it’s descendants is untrue.
Nevertheless, there is another point that must made. There is no issue with using WaitForChild
if you are only calling it once to preload a variable. To expand on this, it’s completely fine to use WaitForChild
to retrieve and store references to descendants of your tool. It is, however, very problematic when you use it repeatedly to obtain a an Instance that you’ve interacted with previously.
I find this to be contradictory to what’s been tested and how either replication or cloning works. Given the tests created, the children of an object are available in the case of an object being replicated. The code’s placement in hierarchy is fairly important to understanding this behaviour.
I’ve included above examples that also show this behaviour. In our case, we only call WaitForChild on a single parent to where an object is held and then any subsequent indexing via a method or dot syntax does not throw an error.
Do you have a source, reference or repro of some kind that’s able to disprove what’s been said or tested, or am I still not understanding your stance of disagreement?
On this one, while I do agree, it’s not something I recommend to others or practice myself. In the case of the presented code example to which I initially responded to, the removal of WaitForChild would result in a negligible performance gain and only truly has any relevance in readability and organisation.
I use WaitForChild as little as possible and only in cases where I would legitimately need it, hence unnecessary. You can do it, but it’s fairly pointless. To bring back an example,
local ServerScriptService = game:GetService("ServerScriptService")
local ChatServiceRunner = ServerScriptService:WaitForChild("ChatServiceRunner")
local ChatService = require(ChatServiceRunner.ChatService)
-- vs
local ServerScriptService = game:GetService("ServerScriptService")
local ChatServiceRunner = ServerScriptService:WaitForChild("ChatServiceRunner")
local ChatService = require(ChatServiceRunner:WaitForChild("ChatService"))
-- vs
local ChatService = require(game:GetService("ServerScriptService"):WaitForChild("ChatServiceRunner").ChatService)
--vs
local ChatService = require(game:GetService("ServerScriptService"):WaitForChild("ChatServiceRunner"):WaitForChild("ChatService"))
All of these accomplish the same thing and the only relevance between them is how they look in your code. I choose the one that uses less cases of WaitForChild since it looks more readable to me and I know that ChatService will be available when its parent, ChatServiceRunner, has been sent to the ServerScriptService.
This isn’t a question of being readable, if the code is prone to failure. Server-client replication is subject to massive behavioral changes when under conditions such as:
- Poor client performance.
- Poor server performance.
- Connection latency.
- High instance count.
Furthermore, it’s imprudent at best to rely on behavior which is not documented, and is subject to changes in the future.
Wow, this is really useful, thanks!
Huh. I might be starting to understand your point of disagreement, though I’m still not quite following. I may require further explanation or a rundown in a DM.
-
Considering I mentioned two things, I’m unsure of which one you’re talking about specifically.
-
Code failure potential was addressed in the thread itself. Was there something I said that contradicted it? I’m thinking that it’s based on what I said about replication and child availability and how that matter is incorrect, but I don’t fully follow.
-
Which undocumented behaviour did I list?
Nonetheless, I’ll try finding a way to incorporate these points (changes on replication) into the thread where appropriate, since this isn’t quite a resource on replication though the stance is relevant.
The child replication for objects under a tool is not officially documented in any way.
As for replication, I know for a fact that replication times vary depending on game performance, and that the behavior your refered is not always factual.
Hm… I’ll be sure to have a look and investigate. The point has been made clear to me now, it seems; thank you for elaborating.
It definitely is an oversight to have mentioned any assertions about replication without taking in the process and the factors that affect them. I’ll have to do some tests under light and heavy load environments and look up what I can.
Loved this thread!
Personally, I would never use :WaitForChild() especially with the new upcoming VM.
This is because for example, if your waiting for the root of a player’s character, properties like the CFrame are not loaded yet and if your teleporting the player when waiting for the root, it will error because the CFrame hasn’t even loaded.
I do use :FindFirstChild() wherever I can though, I do this because it prevents me from using a pcall if it errors when I index it directly, instead it can return a Boolean if it even exists. How lovely it is right?
The behaviour you’re reporting is likely a bug, but I’ll certainly do some tests to confirm it.
Addressing an earlier comment I made. Thanks for all the feedback up to now, I’m still in the process of testing around. Seems I have some misplaced information and new information.
There’s still a lot of threads out there on FindFirstChild and WaitForChild. This one is essentially a compilation of my own knowledge, information and testing. Still have a ways to go.
Added a bad habit for WaitForChild, 4. This one relates to the attempt of microoptimisation by indirectly using the DataModel’s WaitForChild (inherited from Instance), which is often confused for a regular optimisation. In short: you save barely anything noticeable, may miss bad uses of either function and may potentially affect readability.
After some lengthy discussion with @devSparkle above, I’m inclined to review the post as well as a few responses I’ve offered and correct them as such. I’d also like to find a better format for the thread if at all possible. I’ll see what I can manage soon.
This post:
No good. Dot syntax can be used on contents of ReplicatedStorage. This is because ReplicatedStorage’s contents are among objects that are replicated before DataModel.Loaded fires, which is when non-ReplicatedFirst scripts run.
There’s also a pretty good post you can reference that talks about avoiding WaitForChild by waiting for the game to load, even though LocalScripts don’t fire until that’s done anyway. Essentially: implicit (or more properly, static) objects can avoid the use of WFC, but instances added at run time (or more properly, dynamic objects) should incorporate WFC. See that here:
@Dandystan gave me this pointer.1
1: But doesn’t want to post.