Skip to content

Improve player system memory usage and performance#1216

Open
CaseIRL wants to merge 2 commits intoqbcore-framework:mainfrom
CaseIRL:main
Open

Improve player system memory usage and performance#1216
CaseIRL wants to merge 2 commits intoqbcore-framework:mainfrom
CaseIRL:main

Conversation

@CaseIRL
Copy link

@CaseIRL CaseIRL commented Feb 19, 2026

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.

  • 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

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

  • I have personally loaded this code into an updated qbcore project and checked all of its functionality.
  • My code fits the style guidelines.
  • My PR fits the contribution guidelines.

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.
@CaseIRL
Copy link
Author

CaseIRL commented Feb 19, 2026

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant