How Can I Make A Robust Client-Server Cooldown System?

Currently I am trying to make a cooldown system for a power up I am working on. I understand how debounces work, but the biggest problem is getting these debounces and checks to link up with the server. Right now my cooldown system…
IN CLIENT:
does :FireServer() and then waits 3 seconds until debounce is set back to false

IN SERVER:
connects with OnServerEvent and waits 2.5 seconds until its own debounce is set back to false.

The reason I am using 2.5 in the server is because of the possible latency that can occur sometimes. This system does work, but it isn’t robust nor ideal. How can I improve this?

-- CLIENT
local db = false
tool.Activated:Connect(function()
    if db then return end
    db = true
    re:FireServer()
    wait(3)
    db = false
end)

-- SERVER
local db  =false
re.OnServerEvent:Connect(function()
    if db then return end
    db = true
    -- do powerup here.
    wait(2.5)
    db = false
end)

This is the template of my current cooldown system. How can I make this more robust?

Your system is already relatively robust and exactly how I, maybe even a number of other developers, would handle cooldowns. One thing: I would opt to completely remove waits from your code.

You can use RunService to create a Heartbeat-based wait for the client if you really need it (you really won’t). As for the server, instead of using a debounce flag and wait, I would record the last time the event was successfully fired. If the remote is fired again and a certain threshold hasn’t been exceeded yet, then I reject the request immediately.

Often at times I put this check directly into the remote but for visibility and salvaging’s sake, I’m writing this as a function that returns a boolean value: therefore, once ran, returning that true the threshold has been met/passed and should continue or false that it hasn’t and shouldn’t do anything.

local lastFire = 0
local waitThreshold = 3

local function thresholdMet()
    local currentTick = tick()
    -- Needs to be an if statement because I also update lastFire if successful
    if currentTick >= lastFire + waitThreshold then
        lastFire = currentTick
        return true
    end
    return false
end
4 Likes

This looks clean! I make client-server cooldown like OP as well. Do you mind elaborating why you prefer this approach? Is it because it simply looks better or does this have a performance advantage and is more reliable than wait? :grin:

Believe it or not, wait is evil. I would write any amount of code to avoid using wait in my code. The only exception is the wait method of RBXScriptSignal because that’s still fine in event-driven systems. I do this because it looks better and it is better.

So, why is wait evil? It’s a hold over from the legacy 30Hz pipeline. I have no clue what that means, though I believe it has something to do with rates, considering the hertz unit. BUT! Wait has a minimum (EW) of ~0.03 seconds when yielding threads AND it runs for an additional amount of seconds to find an open slot in the task scheduler! SO!!! Wait never actually waits the amount of time YOU WANT, but it waits some more! Like a queue for a carnival ride! >:(

Now, pair it up with its evil family members, spawn and delay. They… THEY ALSO! Incorporate WAIT! Also: if you’ve got an intensive system that uses a lot of this… now you have the issue of your threads not even running. Check out this thread - it’s mainly Coroutines vs Spawn, but it’s also got some useful pointers about why spawn is bad. You’ll notice I walked in there cluelessly saying they’re all good and you can use whatever, but now I’m a firm believer in how evil wait/spawn/delay are.

okay sorry thats all. basically: more accuracy with this and you aren’t yielding threads for debounces but checking for actual time elapsing instead. better performance, better looks, better everything.

8 Likes

Aiwa for sure, thanks for the explanation! Will definitely do this moving forward for critical stuff that relies on a delay or cooldown :stuck_out_tongue:

2 Likes

So my whole life has been a lie?! wait() me:BreakJoints()

2 Likes