Add timeout parameter for Event:Wait()

add a time parameter for event:Wait(), a thread will only wait on that event for that many seconds before continuing. will return true or false based on whether the event was called before the waiting limit was reached

local WasTouched = part.Touched:Wait(3) -- waits for .Touched up to 3 seconds
if WasTouched then
  --part was touched before 3 seconds
else
  --wait timed out before part was touched
end
24 Likes

:Wait() already returns the arguments that would normally be passed into the event signal made with :Connect()

For example, this works:

local hit = part.Touched:Wait()
-- hit is the BasePart that touched part.
print("Part that touched is",tostring(hit))

Given this presumably new information, how would you solve the “check if the event timed out or fired” situation? Your current suggestion would replace existing functionality and would break existing uses.


Other than that, I like the concept.

1 Like

oh, i forgot about that
maybe, because the event isn’t fired, wait() returns nil, so

local hit = part.Touched:Wait(3)
if hit then
-- didnt timeout
else
-- timed out
end

and then the true/false return could be scrapped entirely, because at that point i think it would be more or less useless

1 Like

It should error if it times out, like waitforchild

3 Likes

I thought WaitForChild with timeout just returned nil?

I thought of that too.

Sometimes nil is a valid argument that could be passed into the event signal (or returned, in our case).

On *Value objects such as ObjectValues, the Changed event actually fires only when the Value property changes, and fires with only one argument, the new value of the Value property. This means that an ObjectValue can fire with a single argument of nil when its Value is set to nil. This is one example of how returning nil for timeout would not be reliable.

1 Like

If you manually specify a timeout, that’s the case. If you don’t specify a timeout and WaitForChild times out at the default interval, it throws a warning and kills the script. Edit: It doesn’t kill the script – it just infinitely yields.

3 Likes

there are some situations where you wouldn’t want it to error. i made this thread after i was trying to program some behavior for when a button is held down for a certain amount of time. i didn’t want to use coroutines (this would still have to use coroutines, oops) or loops, so this is what i thought of, using bindable events, and this hypothetical feature:

key.Pressed:connect(function()
  local KeyRaised = key.Raised:Wait(2) -- time key needs to be held
  if not KeyRaised then
    HeldEvent:Fire()
  end 
end
1 Like

Honestly, given the complaints other have raised, I’d have it just be a timeout option as opposed to a return value. Yes, it makes it slightly less functional, but it’d be less annoying for developers.

i realized the timeout return value is obsolete. if anything in your code relies on the return of that event, you can just check if it exists or not. if you need to know whether something like a bindable event timed out or not, you can just pass ‘true’ to it through Fire():

event:Fire(true)
-- other script
fired = event:Wait(3)
if fired then
-- do stuff
else
end

No, because returning nil means that the event did fire. If it is possible to return without firing then the wait function becomes useless.

i just showed you an example where it could be useful

There are built-in events that don’t have parameters, so your suggestion doesn’t work there. So implementing that would mean you have pretty inconsistent behaviour based on what APIs you use, which doesn’t seem good. As you point out, you’d have to add a dummy parameter to BindableEvent as well if there are no arguments to begin with. Really hacky!

Throwing an error when timing out would (seem to) be the only option to both provide backwards compatibility for all cases and also implement your feature in a clean way.

1 Like

anyone using :Wait() on events that have no return values… wouldn’t need any return values anyways?
what i think i’m really asking for here is a yield that we can cancel early with an event, not an event yield we can cancel early with a timeout

If you click on this part of a post you can see which post is being replied to:

blob.png

I was replying to your code sample just above where you suggest to return the parameters when the event fired within the timeout interval, or otherwise nil if it did not fire within the interval. In my previous post I explain why it won’t work out well.

Dumb but relevant example (since I can’t think of another event without parameters right now), what if I wanted to check if a user focuses the window in the next 3 seconds with your suggestion?

local result = game.UserInputService.WindowFocused:Wait(3)
if result then
   -- ...
end

This actually won’t work at all, because WindowFocused doesn’t have any event parameters. So this suggestion, where you return nil instead of event parameters (which may also be nil) when the timeout has expired, does not work.

Instead think something like this:


local status = pcall(
   function()
      return game.UserInputService.WindowFocused:Wait(3)
   end
)

if status then
   -- fired within 3 seconds
else
   -- did not fire
end

And something similar for an event with parameters where you also use the rest of the output of pcall.

5 Likes

The fact that one would have to wrap the Wait call in a new function in a pcall (as above) bothered me a bit…

Then I noticed that if it were implemented as timeout = error one could actually do this:

local status, hit = pcall(Workspace.part.Touched.Wait, Workspace.part.Touched, 3)
if status then
    print(hit,"touched; did not timeout.")
else
    print("nothing touched in 3 seconds; timed out.")
end

For those not aware, pcall can return in one of two ways:

  • false, error string
  • true, return values of inner function, …

In addition, its parameters are pcall(function to run, parameter 1 to function, parameter 2 to function, …)

It doesn’t look the cleanest in this case, but having minimal new wrapper functions generally bothers me more. For me, realizing the ability to get rid of the new wrapper function means that I don’t mind if this is implemented such that Wait errors when it reaches timeout. Just thought it was worth mentioning, maybe others think alike and this will sway them in their opinion.

1 Like

There’s no good way to change Event:Wait() without breaking a lot of code. Either create a new method, or just implement in Lua (using :connect() for efficiency and no memory leak):

-- Default timeout is 30 cuz WaitForChild
local function WaitEvent(ev,t)
	local res,con
	con = ev:connect(function(...)
		res = {n=select("#",...),...}
		con:disconnect()
	end) t = tick() + (t or 30)
	repeat wait() until res or tick() > t
	if not res then return false,con:disconnect() end
	return true,unpack(res,1,res.n)
end

local fired,hit = WaitEvent(part.Touched,3)
if fired then
	print(part,"got touched by",hit,"in less than 3 seconds!")
else
	print("pff,",part,"failed again")
end

But yeah, something like Event:TimedWait(seconds) would be nice.

2 Likes

Surely having :Wait( ) continue to be infinite then no previous code would break?

Well, if you want it to wait infinitely, you wouldn’t use WaitEvent() but just event:wait() instead.
If you mean for adding the timeout parameter: that could work

I actually write it out deliberately because I am confident it is easier to read and understand than calling the method directly like that for some people, so forgive me for that notation. It’s just to illustrate the idea and confusing as little people as possible at the same time. (And I just notice you point it out yourself too “it doesn’t look the cleanest in this case”)