Function that waits for an object that meets one of the names specified

Assuming you are searching for a particular object in an Instance, you may want to check the name. If said name varies based on scenario, you can use a function written by me to assist you.

local function WaitForChild(parent: Instance, ...): Instance
	local t = { ... }
	local l = t[#t]
	local c
	if typeof(l) == 'number' and l > 0 then
		c = l
	else
		c = math.huge
	end
	local time = tick()
	local n = {}
	for i, v in pairs(t) do
		if typeof(v) == 'string' then
			table.insert(n, v)
		end
	end
	for i, v in pairs(parent:GetChildren()) do
		if table.find(n, v.Name) then
			return v
		end
	end
	local r
	local connection; connection = parent.ChildAdded:Connect(function(child)
		if table.find(n, child.Name) and typeof(r) ~= 'Instance' then
			r = child
			connection:Disconnect()
		end
	end)
	local w
	repeat
		task.wait(0)
		if tick() > time + 5 and typeof(parent) == 'Instance' and not w then
			w = true
			warn('Infinite yield possible on ' .. parent:GetFullName() .. ':WaitForChild(...)\n' .. debug.traceback())
		end
	until typeof(r) == 'Instance' or tick() > time + c
	if typeof(connection) == 'RBXScriptConnection' then
		connection:Disconnect()
	end
	return r
end

One example would be Humanoid.RootPart["Root Hip"], the name may otherwise be RootJoint, so you could use this function as such:

local RootJoint = WaitForChild(Humanoid.RootPart, "Root Hip", "RootJoint", 5)
-- Last argument means 5 seconds until yield is forced to complete, returning nil.
local function M_WaitForChild(parent: Instance, ...): Instance
	local names = {...}
	local object = nil
	
	local thread = coroutine.running()
	
	for _, v in names do
		if object then
			break
		end
		
		object = parent:FindFirstChild(v)
	end
	
	if object == nil then
		local connection
		connection = parent.ChildAdded:Connect(function(child)
			if table.find(names, child.Name) and object == nil then
				object = child
				task.spawn(thread)
				
				connection:Disconnect()
			end
		end)
		
		coroutine.yield(thread)
	end
	
	return object
end

Improved, you’re just long polling as @twinqle said.

2 Likes

What’s the purpose of a coroutine in this situation? Why isn’t an event listener enough?

Im not sure where exactly do you mean by coroutine, but if it’s about the coroutine.yield, the purpose of that is to actually stop the thread that called the function. If you don’t and object is still nil, the ChildAdded event will fire without it returning the actual object because the function is already done, I hope i cleared this up. Edit: this logic also applies to SignalModules:Wait()

A better solution is: (Similar to the one of @ATrashScripter)

local function WaitForChildWithName(Object, ...)
	local Names = {...}
	for i, Name in Names do
		local Item = Object:FindFirstChild(Name)
		if Item then
			return Item
		end
	end
    ----------------------------------------------------------------------
	local Thread = coroutine.running()
	local Connection; Connection = Object.ChildAdded:Connect(function(Item)
		if table.find(Names, Item.Name) then
			Connection:Disconnect()
			task.spawn(Thread, Item)
		end
	end)
	return coroutine.yield()
end
2 Likes

I think I understand. Then, would this thread yield indefinitely until the correct child we’re looking for has been added?

How would we incorporate :WaitForChild()'s timeout, which causes it to return nil after 5 seconds, by default?

local function M_WaitForChild(parent, timeout, ...)
	local names = {...}
	timeout = timeout or 5 -- feel free to edit
	local object = nil
	
	for _, v in names do
		object = parent:FindFirstChild(v)
		if object then
			return object
		end
	end
	
	local thread = coroutine.running()
	
	local connection
	connection = parent.ChildAdded:Connect(function(child)
		if table.find(names, child.Name) then
			object = child
			task.spawn(thread, child)

			connection:Disconnect()
		end
	end)
	
	task.delay(timeout, function()
		if object == nil then
			connection:Disconnect()
			task.spawn(thread)
		end
	end)

	return coroutine.yield()
end

task.delay(6, function()
	local c = Instance.new("Part", workspace)
end)

local obj = M_WaitForChild(workspace, nil, "Part", "Whatever")
warn(obj) -- prints nil

Ok, I pretty much combined @focasds’s and my solutions (I didn’t know that you could send in other arguments in task.spawn when resuming thread) and I added a task.delay function which checks if the object is nil. If it is, it disconnects the ChildAdded function and resumes the current thread. You can check my example above.

1 Like

Thank you so much. I’m not very familiar with working with the task scheduler. Learning more about this will definitely help with improving game performance! :smiley:

1 Like

What exactly do you mean by trivial?

the client side doesn’t tend to listen to ChildAdded correctly, typically when there’s an object inserted from the server.

regardless of that, I rewrote the code to be more efficient than the proposed solution.

1 Like