Skip to content

Commit

Permalink
feat(queue): add timeouts
Browse files Browse the repository at this point in the history
  • Loading branch information
D4isDAVID committed Dec 17, 2023
1 parent 8930090 commit 77dd947
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 13 deletions.
8 changes: 8 additions & 0 deletions config/queue.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
return {
---Amount of seconds to wait before removing a player from the queue after disconnecting while waiting.
timeoutSeconds = 30,

---Amount of seconds to wait before removing a player from the queue after disconnecting while installing server data.
---Notice that an additional ~2 minutes will be waited due to limitations with how FiveM handles joining players.
joiningTimeoutSeconds = 0,
}
5 changes: 4 additions & 1 deletion server/events.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ AddEventHandler('chatMessage', function(_, _, message)
end)

AddEventHandler('playerJoining', function()
if not serverConfig.checkDuplicateLicense then return end
local src = source --[[@as string]]
local license = GetPlayerIdentifierByType(src, 'license2') or GetPlayerIdentifierByType(src, 'license')
if not license then return end
if queue then
queue.removePlayerJoining(license)
end
if not serverConfig.checkDuplicateLicense then return end
if usedLicenses[license] then
Wait(0) -- mandatory wait for the drop reason to show up
DropPlayer(src, Lang:t('error.duplicate_license'))
Expand Down
82 changes: 70 additions & 12 deletions server/queue.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ end

-- Queue code

local config = require 'config.queue'
local maxPlayers = GlobalState.MaxPlayers

---Player license to queue position map.
Expand Down Expand Up @@ -47,16 +48,17 @@ end

---Map of player licenses that passed the queue and are downloading server content.
---Needs to be saved because these players won't be part of regular player counts such as `GetNumPlayerIndices`.
---@type table<string, true>
---@type table<string, { source: Source, timestamp: integer }>
local joiningPlayers = {}
local joiningPlayerCount = 0

---@param source Source
---@param license string
local function addPlayerJoining(license)
local function updatePlayerJoining(source, license)
if not joiningPlayers[license] then
joiningPlayerCount += 1
end
joiningPlayers[license] = true
joiningPlayers[license] = { source = source, timestamp = os.time() }
end

---@param license string
Expand All @@ -67,16 +69,54 @@ local function removePlayerJoining(license)
joiningPlayers[license] = nil
end

---@type table<string, true>
local timingOut = {}

---@param license string
---@return boolean shouldDequeue
local function awaitPlayerTimeout(license)
timingOut[license] = true

Wait(config.timeoutSeconds * 1000)

-- if timeout data wasn't consumed then the player hasn't reconnected
if timingOut[license] then
timingOut[license] = nil
return true
end

return false
end

---@param license string
---@return boolean playerTimingOut
local function isPlayerTimingOut(license)
local playerTimingOut = timingOut[license] or false
timingOut[license] = nil
return playerTimingOut
end

---@param source Source
---@param license string
---@param deferrals Deferrals
local function awaitPlayerQueue(source, license, deferrals)
if playerPositions[license] then
if joiningPlayers[license] then
-- the player was in the middle of joining, so let them in
updatePlayerJoining(source, license)
deferrals.done()
return
end

local playerTimingOut = isPlayerTimingOut(license)

if playerPositions[license] and not playerTimingOut then
deferrals.done(Lang:t('error.already_in_queue'))
return
end

enqueue(license)
if not playerTimingOut then
enqueue(license)
end

-- wait until the player disconnected or until there are available slots and the player is first in queue
while DoesPlayerExist(source --[[@as string]]) and ((GetNumPlayerIndices() + joiningPlayerCount) >= maxPlayers or playerPositions[license] > 1) do
Expand All @@ -90,22 +130,40 @@ local function awaitPlayerQueue(source, license, deferrals)

-- if the player disconnected while waiting in queue
if not DoesPlayerExist(source --[[@as string]]) then
dequeue(license)
if awaitPlayerTimeout(license) then
dequeue(license)
end
return
end

addPlayerJoining(license)
updatePlayerJoining(source, license)
dequeue(license)
deferrals.done()

-- wait until the player finally joins or disconnects while installing server content
-- this may result in waiting ~2 additional minutes if the player disconnects as FXServer will think that the player exists
while DoesPlayerExist(source --[[@as string]]) do
Wait(1000)
local joiningData
while true do
joiningData = joiningPlayers[license]

-- wait until the player finally joins or disconnects while installing server content
-- this may result in waiting ~2 additional minutes if the player disconnects as FXServer will think that the player exists
while DoesPlayerExist(source --[[@as string]]) do
Wait(1000)
end

-- wait until either the player reconnects or was disconnected for too long
while joiningPlayers[license] and joiningPlayers[license].source == joiningData.source and (os.time() - joiningData.timestamp) < config.joiningTimeoutSeconds do
Wait(1000)
end

-- if the player disconnected for too long stop waiting for them
if joiningPlayers[license] and joiningPlayers[license].source == joiningData.source then
removePlayerJoining(license)
break
end
end
removePlayerJoining(license)
end

return {
awaitPlayerQueue = awaitPlayerQueue,
removePlayerJoining = removePlayerJoining,
}

0 comments on commit 77dd947

Please sign in to comment.