Generic cancellable timer

Use-case

Let’s say you wanted to implement your own WaitForChild, with a timeout. How would you accomplish this?

  • Listen on ChildAdded
  • Spawn a thread that is delayed for the given timeout duration
  • Use a BindableEvent to block, which will be resumed by either ChildAdded or the timeout thread

Next,

  • You call WaitForChild with a timeout of a million seconds.
  • The child is added and WaitForChild returns it as expected.

Problem

The delayed timeout thread still exists, and will not stop existing for a million seconds. The thread cannot be removed from the queue. None of the thread’s upvalues can be garbage collected. Memory inflates. Fire rains down from the heavens.

Solution: Loop it

Using a loop will either be inefficient (wait()) or inaccurate (wait(10)). It isn’t applicable where both efficiency and accuracy are needed.

Solution: Hack actual WaitForChild timer

  1. WaitForChild on an object
  2. Use blocking as timeout delay
  3. Add a child to the object to cancel
  4. Go cry in a corner for what you had to do

Solution: Timer

  • Function Timer:Start(duration): Set the timer to fire after a given duration.
  • Function Timer:Stop(): Cancel the timer, preventing it from firing.
  • Function Timer:IsRunning(): Return whether the timer is currently running.
  • Event Timer.Fired(actualDuration, gameTime): Fires after the duration has passed.
40 Likes

No words, just :heart:

1 Like

What about a hybrid solution?

local timeout = tick() + 35; --timeout 35 seconds in the future.
spawn(function()
	while waitForChildHasntBeenFired and tick() < timeout do
		wait(math.min(timeout-tick(), MAXIMUM_WAIT_TIME));
	end
	if waitForChildHasntBeenFired then
		--trigger the BindableEvent.
	end
end);

I can also think of a solution where you’d keep all timeout points in a list and you’d spawn a helper thread to determine which BindableEvents should be timing out at any given point and which timeout periods are no longer relevant.