KeepUpdate module

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() )

Module link: KeepUpdate - Roblox

1 Like

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.

But why RunService.Heartbeat event not expensive?

I thought that while wait(0.01) do will be better than RunService.Heartbeat in many cases

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.

2 Likes