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.
To sum it up nicely: Instance:WaitForChild
is only necessary when accessing
(assuming :Workspace.StreamingEnabled
is disabled*)
- Instances parented during runtime (including StarterGui to PlayerGui cloning).
- Descendants of instances replicated to the client during runtime (including StarterGui to PlayerGui cloning).
- Instances outside of ReplicatedFirst from a LocalScript inside ReplicatedFirst before
DataModel.Loaded
fires/DataModel:IsLoaded
returns true. - If network streaming is enabled, BaseParts or descendants of BaseParts inside Workspace.
*Regarding network streamingâs effect on replication behaviour: I believe, when it is enabled, WaitForChild is also necessary when accessing descendants of Workspace. I am not 100% sure if anything else is affected.
As far as StreamingEnabled goes, WaitForChild is required for BasePart descendants of the Workspace only, since streaming pertains mostly to rendering. Parts streamed out effectively do not exist.
Thanks for the feedback though! Iâll be sure to weave something in for a future edit.
Hey there! I have a question, do you need to use WaitForChild on ReplicatedStorage from local scripts for instances you explicitly place there from studio, also, if you need to use it, can you use game.Loaded to allow direct access.
I think it was mentioned that the contents of ReplicatedStorage are replicated before LocalScripts execute and game.Loaded fires, so just indexing directly shouldnât be a problem.
That is if you explicitly create the objects in Studio (for them to exist implicitly on the server), not the case for late instancing.
My source: @colbert2677
Provided the instance youâre trying to access exists there with the premade part of the game (the stuff you construct in Studio) and isnât placed there by a script using Instance.new or anything, yes itâs okay to use dot syntax on the client.
I pmâed @Anaminus and he said that you canât, base on your reply you made to another thread: Can I rely on ReplicatedStorage Instances loading before PlayerScripts run?, but maybe I asked the question incorrectly, If anyone else has an answer I would love to get your input.
Here is the question I sent:
âHi just clarifying that your post is saying that you can directly access static instances in replicated storage from a local script, aka you donât have to use WaitForChild.â
Reply:
" That would be false. Objects visible to a LocalScript will always be on the client. Objects under ReplicatedStorage on the client will never be static, because they have to be replicated in from the server.
The only way you could access an object under ReplicatedStorage directly is if the object exists on the server before the client connects, and the LocalScript is guarded by game.Loaded
."
The following thread, which was written by an engineer, better explains this dynamic in detail. Best practices around FindFirstChild and WaitForChild can be answered by this thread, but I wrote one that directly addresses those habits in respect to the DataModel.
LocalScripts do not need to use WaitForChild on instances in ReplicatedStorage that you explicitly place in from Studio, but it will need to be for objects that either the server or client creates during a running server (runtime). game.Loaded will not need to be used here since most LocalScripts are game.Loaded protected anyway (they do not execute until this fires).
Iâm still doing replication tests myself and finding a time to polish the thread since it has a few inaccuracies and information to be covered, but the answer you got back is right. The server needs to replicate instances not created by the client that are eligible for replication. Premade parts in Studio will always be guaranteed available before a client connects. Most LocalScripts other than ones in ReplicatedFirst are protected from running until game.Loaded fires.
The post where I talk about static vs dynamic instances tries to figure out when itâs safe to use direct indexing. This was really a mistake; it is never safe to use direct indexing, though for reasons unrelated to replication. The following post has more detail:
It was also a mistake because it answers a misguided question. The question was âwhen is it safe to use direct indexing?â, and the answer is âwhen the instance existsâ. The actual underlying question is âwhen does the instance exist?â, and the answer to that is âitâs complicatedâ.
Making these âstaticâ and âdynamicâ labels was also a mistake. We canât take an abstracted approach to instance lifetime. The game tree must be understood as a dynamic data structure with instances being added and removed at any point in time.
There are several contexts in which instances will be present, or not exist. For example,
- When studio opens a place file, the game tree will contain all the instances that were loaded from the file.
- A server starts in much the same way, but is more dynamic because the game is actually running.
- When a client starts, the game tree will contain basically nothing, because it has not yet received instances replicated from the server.
- Certain instances will never be replicated over, depending on where they are in the tree.
There are a number of behaviors that will add and remove instances at particular points in time. Examples:
- Replication will add and remove instances from a clientâs game tree as needed to match that of the server.
- When a client initially connects, one large snapshot of the serverâs game tree is replicated over.
- Instances can be created, destroyed, and moved by scripts, which themselves can be added and moved around.
Itâs important to identify what behaviors are possible in a given context. Such as,
- Replication does not occur in a non-running studio session.
- Scripts in the game tree are also not running at this point.
- On the other hand, plugins are running.
- On the client, CoreScripts can start running before anything has even started replicating.
- StarterScripts wont start running until the client has finished loading (
game.Loaded
).
Finally, there are a variety of APIs that are able to make certain guarantees about instances. To name a few:
- Given a correct name,
game:GetService()
will always return the service corresponding to the name. -
workspace.Terrain
will always exist (âTerrainâ is also a property of Workspace). -
ChildAdded
will always pass a newly added child. -
Players.PlayerAdded
will always pass a child that will be of the Player class. - If
Player.Character
is not nil, it is guaranteed to point to the playerâs character. -
FindFirstChild
will return the first child instance of the given name, or nil if it does not exist. -
Instance.new
has the characteristic that the returned instance has absolutely no other references to it, so it is actually âsafeâ to use. A cloned instance tree also has this characteristic. - A reference to an instance stored in a variable or table will stick around. Donât discard the reference if youâll be using the instance later.
Such guarantees should be utilized as much as possible, because they do most of the work of locating an instance in a dynamic structure. If such an API does not exist, it is important to make (or find) a feature request for it, and rally support. It seems more clear now than ever that developers need some sort of API that is as convenient as direct indexing, but also as robust as FindFirstChild. Proposals for such have been made in the past, so they should be revisited.
A useful addendum to what @Anaminus has already outlined is: never trust direct indexing to do as you wish. In Robloxâs internal indexing hierarchy, properties will always be indexed first. Iâm sure this is common knowledge but to this day (somehow) games still attempt to directly index. This is incredibly dangerous for properties you may not control. Take for example my username:
local Players = game:GetSerivce("Players")
local PlayerName = "GameAnnounce"
-- This code is fine, even though you could just fetch player from PlayerAdded
local Player = Players:FindFirstChild(PlayerName)
-- This code is not fine. This returns Roblox's event (https://developer.roblox.com/en-us/api-reference/event/Players/GameAnnounce)
local NotFinePlayer = Players[Player.Name]
-- This will always be false, even if my player is not in your game (RbxEvent == nil)
print(NotFinePlayer == Player) -- > false
A notable, yet unfortunate, example of this is Arsenal. By indexing players, and I assume other members, directly they open themselves up to bugs which they cannot foresee (within reason). I understand that this is a rare example, but a good one nevertheless to illustrate the dangers of direct indexing.
Just going to pop in here as noone has mentioned this, FindFirstChild and all forms of it can cause lag and can slow down your scripts depending on how many you are using.
Though there is a case that developers may miss which is the fact that an objectâs properties have higher precedence than a child. In the case that a child instance shares a name with an object property, whether itâs a new or existing property, direct indexing will raise problems in your code.
Well, pretty negligible if youâre good with names though.
Always wondered this, why not just use a period instead of FindFirstChild?
if workspace:FindFirstChild("Part") then
end
sure, you can do that, but why not just do this?
if workspace.Part then
end
It will error if the part doesnât exist.