Running code at an interval without loops with waits

Hello,

Is there a way to run code at an interval without the use of waits spawn? I don’t want to use threads is that spawning a new thread is expensive (around 0.3 seconds for me).

Thanks,
rad

1 Like

I’m not sure I understand?
We may be able to better help you if you explain specifically the situation you’re facing that’s making you want to avoid using loops with waits.

You could use the :Wait() part of events to yield until an event fired. For example,

local PartThatTouched = Part.Touched:Wait()

print(PartThatTouched.Name, 'touched!')

will yield until another part touched it, and return said other part. Unless you mean something similar to wait?

2 Likes

I’m asking for something similar to setInterval in javascript. It runs a specific function at specified intervals.

1 Like

A loop in a coroutine such as

local loop = coroutine.create(function()
	while wait(10) do 
		-- Stuff
	end
end)

coroutine.resume(loop)

is functionally identical to setInterval. JavaScript is single-threaded. It schedules tasks to achieve concurrent execution between threads. Lua does the same thing. I don’t believe there is any other way to do this.

As @Wingz_P has mentioned below, spawn() has a brief delay before running which is what you may be seeing. Coroutines do not have this delay.

8 Likes

I am not presently aware of anyway to do this without Loops in Lua.

Using spawn() has a 0.3 wait time built into it before the thread executes. Have you tried using coroutines? Coroutine executions are instantaneous, I’d suggest reading more up on them if you haven’t already.

4 Likes

Sorry, I think I turned this into an XY problem. I was saying that I didn’t want to use loops and waits because I thought there was no other way than using spawn to get two things to run simultaneously but now I know that I can use coroutines.

Anyway, thanks for the help.

2 Likes

I had to do some experimenting, but I got something that might be used instead of a wait().

local runService = game:GetService("RunService")
local bindable = Instance.new("BindableEvent")

local function waitforinterval()
	
	local x = 0
	
	while runService.RenderStepped:Wait() do
	
	x = x + 1
	
		if x == 178 then --approx 3 seconds
		
			bindable:Fire()
			
			break
		
		end
			
	end
	
end


local function code()
	
	--code
	
	waitforinterval()
	
end

event.Event:Connect(code)

waitforinterval()

However, one thing is that this is very hard to manipulate, and you would need to experiment again and again to get the right number.

I think you may have confused things a bit by also saying you wanted something like JavaScript’s setInterval, which isn’t really part of JavaScript and generally uses whatever the host environment’s event scheduler is, the most obvious Roblox analog of which would be a coroutine and wait(t) to use the task scheduler. A coroutine is not a proper thread, but it basically serves the same purpose as a worker in JS.

The only other obvious way, free of all waits and thread-like thingies, would be to poll time yourself in a fast loop. A construct like this:

local INTERVAL = 3
local elapsed = 0
RunService.Heartbeat:Connect(function(dt)
    elapsed = elapsed + dt
    if elapsed >= INTERVAL then
        elapsed = elapsed - INTERVAL
        -- do your approximately every 3 seconds thing here
    end
end)

This particular example errs on the side of not firing early (it waits at least 3 seconds), and also tries to preserve the average rate by keeping the remainder (subtracting 3 from elapsed rather than setting elapsed to 0, an OK thing to do as long as INTERVAL >> dt). But you don’t have to do it like this, you could for example also check something like math.abs(elapsed - INTERVAL) < EPSILON if firing within some time window is desired, with or without skipping off-beat updates. Depends on your exact usage…

You can also use timestamps in place of accumulated elapsed time, using tick() or os.time(), etc… if you want events happening at specific known times, or even as an alternative way to specify the interval.

4 Likes

You could use delay(number seconds, callback func). It also has a minimum time of 30ms (0.03 seconds). If you want a shorter wait, only RunService.RenderStepped:Wait() can do it. Lua only runs every other game loop unless it is bound to the game loop using the RunService. There isn’t a way to run a function after all the other Lua code finishes in the same game loop.

2 Likes

I was doing this in one of my scripts, and wondering if it would cause any performance issues?

That still uses the wait() call, as stated in this blog. And as the OP stated, he wanted to get away from this. But I am aware you have admitted this yourself.

1 Like

Nope. One addition and one compare is completely negligible.

1 Like

My implementation might be a little more complicated than that - but that should suffice as to not derail the thread. Thank you!

Yes, it does. And it’s pretty much a drop-in replacement for setTimeout you get with most JS environments. Hence my confusion about what sort of solution is acceptable. Basically, if you want to do something periodically, your options are to use the task scheduler (always involving some flavor of wait), or roll your own timing loop.

I feel like without literally hardcoding approximately 3 seconds into the code, there is no other way to properly answer this question.

Other than using wait() itself, of course.

I most often write my own timing loops inside heartbeat in server code, not for performance reasons but more for organization and synchronization reasons, but I’ve also used coroutines exactly like what @ qqtt991 suggests, particularly for stuff on that runs on client code.

1 Like

This can be used to do without loops but with a wait. (BindableEvent)

This is effectively a more expensive while-loop. It also needs to start a separate thread for the event handler. I would refrain from using that construct, it provides no benefits over a loop in a thread of its own.

5 Likes