I had problem with resumable while wait do check, so I created this module. (P.s: I think RunService is expensive sometimes)
This module creates updateObject that works like coroutine.
Example:
local PlayersService = game:GetService("Players")
local KeepUpdate = require(game:GetService("ReplicatedStorage")["Modules"]["KeepUpdate"])
PlayersService.PlayerAdded:Connect(function(player)
local function isHumanoidDead(player)
if player.Character == nil then return false end
if player.Character.Humanoid.Health <= 0 then
return true
else
return false
end
end
player.CharacterAdded:Wait() -- wait for character
wait(2.5) -- fix for humanoid.health = 0 problem
local UpdateObject = KeepUpdate.new(isHumanoidDead, {[1] = player}, 0.01) -- return Update Object
UpdateObject:Start() -- start check
print("not yield!")
print(UpdateObject["_status"]) -- active
player.Character.Humanoid:TakeDamage(100)
wait(0.01)
print(UpdateObject["_status"]) -- sleep
UpdateObject:Start()
print(UpdateObject["_status"]) -- active
UpdateObject:Destroy()
print(UpdateObject["_status"]) -- dead, so we may remove now
UpdateObject = nil
end)
Arguments for KeepUpdate.new is : checkFunc, arguments, timePerCycle, callbackFunc
checkFunc - function that will be called each cycle (function should return false or true)
arguments - nil or table like {[1] = “hi”,[2] = 54} or {“hi”,54}
timePerCycle - just time that will be waited per cycle, like: while wait(timePerCycle) do end (may be nil)
callbackFunc – nil or function that will be called when return from checkFunc is true
Methods for UpdateObject is: Start,Stop,Destroy (example: UpdateObject:Stop() )
Realistically, you shouldn’t use while wait in the first place. If there are event-based methods for your specific system then you should look to use those instead. Polling should be the absolute last option you look to for resolving a problem, not the first.
Additionally, RunService signals are not expensive… they’re signals. You can connect to these signals to allow something to happen every time that point in a frame is reached. What’s really expensive when you use RunService signals is what kind of code you’re running in them.
RenderStepped should never be used except for modifying computationally inexpensive properties such as CFrame or transparency or for things that should be set before the frame renders. You will scarcely need things to update before a frame renders.
Stepped should only be used in cases where you need a higher run priority (e.g. animations, tweening) or for continuous systems that should be modified before physics simulation occurs.
Heartbeat for everything else. Good for all cases.
For the example in the OP, there are already four native ways to check for Humanoid deaths:
Humanoid.Died (canonical)
Humanoid.HealthChanged
Humanoid:GetPropertyChangedSignal(“Health”)
Humanoid.Changed (checking for Health as the passed argument)
There’s no reason why you should be polling to check if the Humanoid dies. It’s not a good example to use because it doesn’t help show why your module is helpful, given that there are better ways to do what you’re suggesting. You should instead come up with an example where polling is necessary to accomplish your system and where event-based solutions, whether natively or handcrafted, do not work. An example is fetching data from a website that is subject to change during a session’s run.
Heartbeat is just a signal that the engine fires whenever it reaches a certain point in a frame, specifically when physics simulation has concluded. Signal firing is computationally inexpensive and there are plans to introduce improvements to code with deferred signals.
Both of these are computationally inexpensive but what’s more important to look at is the practice implications of these methods. Abusing the conditional of a wait based on the fact that it returns values is bad practice and the reasons are outlined in the post I linked in my post above.
If there’s an event-based solution, you always want to use that instead. Your code won’t and shouldn’t spend any unnecessary time waiting. With signals, your code will run as in when they’re fired, allowing you to make your code a whole lot more predictable about when it will handle fired signals and actions in your experience. Polling (while x do) should be your last option.
Also: in your specific example, wait runs on a legacy frequency (30hz pipeline) and will be deprecated, so it was never ideal to begin with. It waits a minimum of 0.03 seconds, so using any lower value doesn’t actually go through. Heartbeat is fired every frame once physics simulation finishes for the current frame. As for task.wait, it introduces the same practice problems as using legacy wait as the conditional even though it runs on Heartbeat frequency.
Not expensive to fire signals every frame. Everything you’ve ever seen on Roblox mostly (or all) runs on a frame-by-frame basis, from rendering to handling all the code you write and the engine internals.