Improve player system memory usage and performance#1216
Improve player system memory usage and performance#1216CaseIRL wants to merge 3 commits intoqbcore-framework:mainfrom
Conversation
Rewrites player.lua to use a class-based prototype pattern with shared methods via __index and weak-keyed private state to reduce memory usage and improve performance at scale. All existing scripts continue to work without any changes needed, QBCore.Functions.GetPlayer() acts as an API layer. Each GetPlayer call still creates closures for the proxy but these are short-lived rather than permanently stored on the player object still a net improvement over the original. - player.lua: Virtually a full rewrite to class methods - functions.lua: New helper added, all player lookups updated - events.lua: Internal player handling cleaned up
Title says it all. I forgot to swap the Player.Functions calls in paycheck interval to call class methods.
|
I forgot to do the PaycheckInterval in this function PaycheckInterval()
if not next(QBCore.Players) then
SetTimeout(QBCore.Config.Money.PayCheckTimeOut * (60 * 1000), PaycheckInterval) -- Prevent paychecks from stopping forever once 0 players
return
end
for _, Player in pairs(QBCore.Players) do
if not Player then return end
local payment = QBShared.Jobs[Player.PlayerData.job.name]['grades'][tostring(Player.PlayerData.job.grade.level)].payment
if not payment then payment = Player.PlayerData.job.payment end
if Player.PlayerData.job and payment > 0 and (QBShared.Jobs[Player.PlayerData.job.name].offDutyPay or Player.PlayerData.job.onduty) then
if QBCore.Config.Money.PayCheckSociety then
local account = exports['qb-banking']:GetAccountBalance(Player.PlayerData.job.name)
if account ~= 0 then
if account < payment then
TriggerClientEvent('QBCore:Notify', Player.PlayerData.source, Lang:t('error.company_too_poor'), 'error')
else
Player:AddMoney('bank', payment, 'paycheck')
exports['qb-banking']:RemoveMoney(Player.PlayerData.job.name, payment, 'Employee Paycheck')
TriggerClientEvent('QBCore:Notify', Player.PlayerData.source, Lang:t('info.received_paycheck', { value = payment }))
end
else
Player:AddMoney('bank', payment, 'paycheck')
TriggerClientEvent('QBCore:Notify', Player.PlayerData.source, Lang:t('info.received_paycheck', { value = payment }))
end
else
Player:AddMoney('bank', payment, 'paycheck')
TriggerClientEvent('QBCore:Notify', Player.PlayerData.source, Lang:t('info.received_paycheck', { value = payment }))
end
end
end
SetTimeout(QBCore.Config.Money.PayCheckTimeOut * (60 * 1000), PaycheckInterval)
end |
| } | ||
|
|
||
| for name, fn in pairs(player:GetAllExtraMethods()) do | ||
| functions[name] = function(...) return fn(player, ...) end |
There was a problem hiding this comment.
I believe I noticed an issue with this whilst testing. Extra methods are defined by resources that call AddMethod, so they typically won't need the player object passed as the first parameter. An example you can test with is GetItemByName from qb-inventory.
Could you test this out on your end and confirm? Thank you
There was a problem hiding this comment.
For sure, was mostly done pretty quick to be honest as a test if it was even do-able, I am still running the modified version on my dev server atm and i did have to make one or 2 other changes I never pushed back here. Only just noticing this though at 4:50am for me so bare with, i'll take a note of it now, and have a test after sleep ❤️
There was a problem hiding this comment.
No worries. Thank you for working on these changes 😃
Qwerty1Verified
left a comment
There was a problem hiding this comment.
Apologies for the review gap.
I think with these we could save on even more memory, as well as fix a potential future issue on internal calls via the metamethod.
Feel free to let me know what you find 😄
| local self = setmetatable({}, { | ||
| __index = function(t, key) | ||
| local priv = Private[t] | ||
| if priv and priv.extra_methods[key] then | ||
| return function(...) return priv.extra_methods[key](t, ...) end | ||
| end | ||
| return QBPlayer[key] | ||
| end | ||
| }) |
There was a problem hiding this comment.
I think we could even move the metatable and closure creation out of here, to re-use the same metatable and closures per player instance. It saves extra allocations per player, and saves on garbage collection.
Also, the closure on line 23 may possibly give us the same issue we had previously where it's passing itself into external "extra_methods". From what I can tell, the only path that this would at any time occur on is internal player object calls, which isn't entirely the intended path anyways, and likely wouldn't work with this call flow unless . syntax is used.
It may be best to eliminate the closure if there's no other need for it, and just return the handler.
| __index = function(t, key) | ||
| local priv = Private[t] | ||
| if priv and priv.extra_methods[key] then | ||
| return function(...) return priv.extra_methods[key](t, ...) end |
There was a problem hiding this comment.
| return function(...) return priv.extra_methods[key](t, ...) end | |
| return priv.extra_methods[key] |
There was a problem hiding this comment.
This would allow for the same style of call external to occur internally, rather than having either:
Player:Test(arg1) - function(player, player, arg1)
or
Player.Test(arg1) - function(player, arg1)
It could be used as:
Player.Test(arg1) - function(arg1)

Description
Rewrites player.lua to use a class-based prototype pattern with shared methods via __index and weak-keyed private state to reduce memory usage and improve performance at scale.
All existing scripts continue to work without any changes needed, QBCore.Functions.GetPlayer() acts as an API layer. Each GetPlayer call still creates closures for the proxy but these are short-lived rather than permanently stored on the player object still a net improvement over the original.
Mock Object Benchmark
32 players | Old: 87.99 KB | New: 43.43 KB | Saved: 44.56 KB
64 players | Old: 188.37 KB | New: 86.87 KB | Saved: 101.50 KB
128 players | Old: 376.77 KB | New: 173.77 KB | Saved: 203.00 KB
256 players | Old: 753.64 KB | New: 347.64 KB | Saved: 406.00 KB
512 players | Old: 1507.39 KB | New: 695.39 KB | Saved: 812.00 KB
1024 players | Old: 3014.92 KB | New: 1390.92 KB | Saved: 1624.00 KB
2048 players | Old: 6030.92 KB | New: 2782.92 KB | Saved: 3248.00 KB
Checklist