RBXScriptSignal:Wait([int TimeOut])

It would be nice if you could set a timeout value on signal waits such that if it’s not fired within a certain period of time, it will continue.

print("Start")
workspace.Part.Touched:wait(10)
print("Finish")

If the brick is not touched within 10 seconds, finish anyways.

15 Likes

Good idea! I’m surprised this isn’t a thing already.

:wait returns whatever you would receive as arguments to the connected function.
For example, partTouched = Touched:wait()

On events that return nil anyways, how will we differentiate between the event firing and the timeout being reached?

  • :waitTimeout(timeout) returns didFire, ...
  • :wait(timeout) errors on timeout
  • Not an important case

0 voters

Or something else?

Im not a huge supporter of this idea, but the only solution that would be viable would be returning a specific enum dedicated to this (other than erroring which is equally gross)

6 Likes

Code that can be used instead to achieve this:

local RbxUtility = LoadLibrary("RbxUtility")

local function waitUntilTimeout(event, timeout)
    local signal = RbxUtility.CreateSignal()
    local conn = nil
    conn = event:Connect(function(...)
        conn:Disconnect()
        signal:fire(...)
    end)

    delay(timeout, function()
        if (conn ~= nil) then
            conn:Disconnect()
            conn = nil
            signal:fire(nil)
        end
    end)

    return signal:wait()
end

-- Example usage
local child = waitUntilTimeout(game.Workspace.ChildAdded, 5)
if (child == nil) then
    print("ChildAdded did not fire before timeout")
else
    print("New child " .. tostring(child) .. " added")
end

Credit to @Merely for writing this code: Timeout argument for Event:Wait()

3 Likes

This also makes me wish that we could cancel “delay” functions before they fire. Similar to how you can cancel things in JavaScript (setTimeout, clearTimeout)

2 Likes

I wrote a cancellable delay, with no leaky threads.

local function Delay(duration, func, ...)
	if duration <= 0 then
		error("duration must be greater than 0", 2)
	end

	local done = false
	local args = {n = select('#', ...), ...}
	local container = Instance.new("BoolValue")
	local t = tick()
	spawn(function()
		local v = container:WaitForChild("Cancel", duration)
		done = true
		if v ~= nil then
			v.Parent = nil
		else
			func(tick()-t, unpack(args, 1, args.n))
		end
	end)

	return function()
		if done then
			return false
		end
		done = true
		local cancel = Instance.new("BoolValue")
		cancel.Name = "Cancel"
		cancel.Parent = container
		return true, tick()-t
	end
end

And there happens to be a signal-timeout function right below that.

local function SignalTimeout(signal, timeout)
	local block = Instance.new("BoolValue")
	local timedout, results = false, nil
	local cancel
	local conn = signal:Connect(function(...)
		results = results = {n = select("#", ...), ...}
		if not cancelTimeout() then
			return
		end
		block.Value = not block.Value
	end)
	cancelTimeout = Delay(timeout, function()
		timedout = true
		block.Value = not block.Value
	end)
	block:GetPropertyChangedSignal("Value"):Wait()
	conn:Disconnect()
	if timedout then
		return false
	end
	return true, unpack(results, 1, results.n)
end
4 Likes

That’s awesome! Great use of the timeout for WaitForChild.