Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(es_extended/server/functions): improve Core.SavePlayers & Core.SavePlayer #1591

Draft
wants to merge 8 commits into
base: dev
Choose a base branch
from
1 change: 1 addition & 0 deletions [core]/es_extended/fxmanifest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ shared_scripts {
'shared/config/main.lua',
'shared/config/weapons.lua',
'shared/config/adjustments.lua',
'shared/config/compat.lua',

'shared/main.lua',
'shared/functions.lua',
Expand Down
137 changes: 93 additions & 44 deletions [core]/es_extended/server/functions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,6 @@ function ESX.RegisterCommand(name, group, cb, allowConsole, suggestion)
end
end

local function updateHealthAndArmorInMetadata(xPlayer)
local ped = GetPlayerPed(xPlayer.source)
xPlayer.setMeta("health", GetEntityHealth(ped))
xPlayer.setMeta("armor", GetPedArmour(ped))
xPlayer.setMeta("lastPlaytime", xPlayer.getPlayTime())
end

---@param xPlayer table
---@param cb? function
---@return nil
Expand All @@ -203,16 +196,22 @@ function Core.SavePlayer(xPlayer, cb)
return cb and cb()
end

updateHealthAndArmorInMetadata(xPlayer)
local playerPed = GetPlayerPed(xPlayer.source)
xPlayer.metadata.health = GetEntityHealth(playerPed)
xPlayer.metadata.armor = GetPedArmour(playerPed)

local playerCoords = GetEntityCoords(playerPed)
local playerHeading = GetEntityHeading(playerPed)

local parameters <const> = {
json.encode(xPlayer.getAccounts(true)),
xPlayer.job.name,
xPlayer.job.grade,
xPlayer.group,
json.encode(xPlayer.getCoords(false, true)),
json.encode({x = playerCoords.x, y = playerCoords.y, z = playerCoords.z, heading = playerHeading}),
json.encode(xPlayer.getInventory(true)),
json.encode(xPlayer.getLoadout(true)),
json.encode(xPlayer.getMeta()),
json.encode(xPlayer.metadata),
xPlayer.identifier,
}

Expand All @@ -231,47 +230,97 @@ function Core.SavePlayer(xPlayer, cb)
)
end

---@param cb? function
---@return nil
function Core.SavePlayers(cb)
local xPlayers <const> = ESX.Players
if not next(xPlayers) then
return
end
if Config.Compat.useV13SavePlayers then
---@param cb? function
---@return nil
function Core.SavePlayers(cb)
local xPlayers <const> = ESX.Players
if not next(xPlayers) then
return
end

local startTime <const> = os.time()
local parameters = {}
local startTime <const> = os.time()
local parameters = {}

for _, xPlayer in pairs(ESX.Players) do
updateHealthAndArmorInMetadata(xPlayer)
parameters[#parameters + 1] = {
json.encode(xPlayer.getAccounts(true)),
xPlayer.job.name,
xPlayer.job.grade,
xPlayer.group,
json.encode(xPlayer.getCoords(false, true)),
json.encode(xPlayer.getInventory(true)),
json.encode(xPlayer.getLoadout(true)),
json.encode(xPlayer.getMeta()),
xPlayer.identifier,
}
end
for _, xPlayer in pairs(ESX.Players) do
parameters[#parameters + 1] = {
json.encode(xPlayer.getAccounts(true)),
xPlayer.job.name,
xPlayer.job.grade,
xPlayer.group,
json.encode(xPlayer.getInventory(true)),
json.encode(xPlayer.getLoadout(true)),
json.encode(xPlayer.metadata),
xPlayer.identifier,
}
end

MySQL.prepare(
"UPDATE `users` SET `accounts` = ?, `job` = ?, `job_grade` = ?, `group` = ?, `position` = ?, `inventory` = ?, `loadout` = ?, `metadata` = ? WHERE `identifier` = ?",
parameters,
function(results)
if not results then
return
end
MySQL.prepare(
"UPDATE `users` SET `accounts` = ?, `job` = ?, `job_grade` = ?, `group` = ?, `inventory` = ?, `loadout` = ?, `metadata` = ? WHERE `identifier` = ?",
parameters,
function(results)
if not results then
return
end

if type(cb) == "function" then
return cb()
if type(cb) == "function" then
return cb()
end

print(("[^2INFO^7] Saved ^5%s^7 %s over ^5%s^7 ms"):format(#parameters, #parameters > 1 and "players" or "player", ESX.Math.Round((os.time() - startTime) / 1000000, 2)))
end
)
end

print(("[^2INFO^7] Saved ^5%s^7 %s over ^5%s^7 ms"):format(#parameters, #parameters > 1 and "players" or "player", ESX.Math.Round((os.time() - startTime) / 1000000, 2)))
else
---@param cb? function
---@return nil
function Core.SavePlayers(cb)
local xPlayers <const> = ESX.Players
if not next(xPlayers) then
return
end
)

local startTime <const> = os.time()
local parameters = {}

for _, xPlayer in pairs(ESX.Players) do
local playerPed = GetPlayerPed(xPlayer.source)
xPlayer.metadata.health = GetEntityHealth(playerPed)
xPlayer.metadata.armor = GetPedArmour(playerPed)

local playerCoords = GetEntityCoords(playerPed)
local playerHeading = GetEntityHeading(playerPed)

parameters[#parameters + 1] = {
json.encode(xPlayer.getAccounts(true)),
xPlayer.job.name,
xPlayer.job.grade,
xPlayer.group,
json.encode({ x = playerCoords.x, y = playerCoords.y, z = playerCoords.z, heading = playerHeading }),
json.encode(xPlayer.getInventory(true)),
json.encode(xPlayer.getLoadout(true)),
json.encode(xPlayer.metadata),
xPlayer.identifier,
}
end

MySQL.prepare(
"UPDATE `users` SET `accounts` = ?, `job` = ?, `job_grade` = ?, `group` = ?, `position` = ?, `inventory` = ?, `loadout` = ?, `metadata` = ? WHERE `identifier` = ?",
parameters,
function(results)
if not results then
return
end

if type(cb) == "function" then
return cb()
end

print(("[^2INFO^7] Saved ^5%s^7 %s over ^5%s^7 ms"):format(#parameters, #parameters > 1 and "players" or "player", ESX.Math.Round((os.time() - startTime) / 1000000, 2)))
end
)
end
end

ESX.GetPlayers = GetPlayers
Expand Down
3 changes: 3 additions & 0 deletions [core]/es_extended/server/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ AddEventHandler("playerDropped", function(reason)

if xPlayer then
TriggerEvent("esx:playerDropped", playerId, reason)
xPlayer.setMeta("lastPlaytime", xPlayer.getPlayTime())

local job = xPlayer.getJob().name
local currentJob = Core.JobsPlayerCount[job]
Core.JobsPlayerCount[job] = ((currentJob and currentJob > 0) and currentJob or 1) - 1
Expand Down Expand Up @@ -355,6 +357,7 @@ AddEventHandler("esx:playerLogout", function(playerId, cb)
local xPlayer = ESX.GetPlayerFromId(playerId)
if xPlayer then
TriggerEvent("esx:playerDropped", playerId)
xPlayer.setMeta("lastPlaytime", xPlayer.getPlayTime())

Core.playersByIdentifier[xPlayer.identifier] = nil
Core.SavePlayer(xPlayer, function()
Expand Down
18 changes: 18 additions & 0 deletions [core]/es_extended/shared/config/compat.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Config.Compat = {
-- Use the v1.13.0 Core.SavePlayers function.
-- The new Core.SavePlayers function introduces significant performance improvements by avoiding native calls,
-- reducing thread hitches commonly experienced on larger servers during player saves.
--
-- While this doesn't break compatibility with any user scripts, it may still lead to different behaviour than you would expect.
--
-- There are trade-offs to consider:
-- 1. If a player or the server crashes, their health, armor, and position may not be saved.
-- - Client crashes may lead to unsaved data.
-- - Server crashes, however, will most definitely result in data loss for affected players.
--
-- For larger servers, the performance benefits might outweigh these risks,
-- especially if frequent thread hitches are an issue.
--
-- Note: Do not enable this setting unless you fully understand the implications.
useV13SavePlayers = false,
}
Loading