The "Awaiting" Problem

Recently I’ve been curious on how to effectively solve the “awaiting” problem. For awaiting problem, it means an action will execute after a certain amount of time only if it fulfill some criterias, let me give an example:

Let’s say I have a custom Health Bar. I want to replicate exactly what the default Roblox humanoid health bar does. There’s a property called HealthDisplayType and it’s DisplayWhenDamaged by default. Let’s say I want to recreate such effect. When a humanoid is damaged, it will show the Health Billboard UI, then after 5 seconds, it will return to invisible. However, if someone damage the humanoid during that 5 seconds, it will reset the countdown to 5 seconds again.

Normally we will do:

HealthChanged:Connect(function()
     UI.Enabled = true
     wait(5)
     UI.Enabled = false
end)

However this will still disable the UI even if someone damage the humanoid within that 5 seconds!

I know there should be some hacky solution to it, such as using a repeat wait() or while loop, but is there any other efficient ways without such polling? Like setting a boolean variable?

1 Like

The way I do it is by creating an intValue and every time the player is hit, the intValue will go up by 1, so the code would look like:

HealthChanged:Connect(function()
     intValue.Value = intValue.Value + 1
     UI.Enabled = true
     wait(5)
     intValue.Value = intValue.Value - 1
     
    --if the intValue is not 0, the player has been hit another time and we won't disable the UI
    --if the intValue is 0, the player has not been hit while the UI was showing so we disable it
     if intValue.Value == 0 then
           UI.Enabled = false
     end
end)
3 Likes

The way I do this is by tracking a unique “tag” for each event. I get this unique tag by simply calling the tick() function.

local healthTag = 0
HealthChanged:Connect(function()
   local tag = tick()
   healthTag = tag
   UI.Enabled = true
   wait(5)
   if (tag == healthTag) then
      UI.Enabled = false
   end
end)

Every time the event is fired, it changes the healthTag. After the 5 seconds has elapsed, it checks to see if its own local version of tag still matches with the higher-scope healthTag. If it does, then you know that it hasn’t been fired again. And therefore you know that this was the most recent firing of the event.

It’s not a stateless approach, but I’ve been doing this for years and it works great.

13 Likes

Not that it’s likely to happen, but tick() has a certain resolution depending on the platform, so it wont always return a unique value between calls (see print(tick()==tick())). A more robust solution would be to increment the current value (local tag = healthTag + 1).

3 Likes

You can use promises for this, but it’s a maybe a bit over-engineered. However, this is how I would solve this problem.

local currentPromise = nil
HealthChanged:Connect(function()
	if currentPromise then
		currentPromise:Reject()
		currentPromise = nil
	end

    UI.Enabled = true
	currentPromise = promiseWait(5)
	currentPromise:Then(function()
		UI.Enabled = false
	end)
end)
4 Likes

Thanks everyone’s solution, all of them works but I think I’ll stick with Crazyman32’s/speeddecoolste’s one as they both require the least amount of work and they are the most efficient one in my opinion.

Cool! Is it possible to just use the “promises” part of your engine without all the rest?

@Headstackk

As for how I'd solve it:

using some kind of timer library, because any game I make is likely to need one anyway. Every time the health changes the timer is reset to 5 seconds, and once it finally runs out it emits a signal that the GUI script can listen for. There are some timer libraries for Love2D which also uses Lua, but I don’t actually know if those would work with Roblox.

Here’s what that might look like:

local healthBarTimer = Timer.new()
local healthBarFadeDelay = 5

function onHealthChanged()
	healthBarTimer:SetTimeLeft( healthBarFadeDelay )
	healthBarTimer:Start() --might be unncessesary depending on the timer lib implementation
            showHealthBar()
end
    
    function showHealthBar()
           UI.Enabled = true
    end        

function hideHealthBar( )
	UI.Enabled = false
end

healthBarTimer.Ended:Connect(hideHealthBar)

I think this is a nicer abstraction than having to deal with promises or tags. It’d also be easy to make it more complicated, like having it poll the time that’s left every frame for the last second of the timer to make a nice fade-out effect.

If no timer library exists, I’d love to make and publish one :smiley:

If it’s just the resetting that’s an issue I’d of gone with something simple like:

Please note I am on mobile.
Using this code means the UI will only appear and begin it’s countdown if the UIs Enabled was set to fale on the time of damage.

HealthChanged:Connect(function()
if UI.Enabled == false then
UI.Enabled = true
wait(5)
UI.Enabled = false
end
end)

Yup! Just grab the relevant file and change the requires to be not using Nevermore!

1 Like