JRS CORE文档
开始使用

Best Practices

Habits that keep a RedM resource fast, safe and easy to maintain

These rules come from the problems we hit while building and running JRS Core. Follow them in your own scripts and your server will stay smooth.

Performance#

Sleep while nothing happens. A loop with Wait(0) runs every frame. Use it only while something visible needs it.

Good
lua
CreateThread(function()
    while true do
        local sleep = 1000                       -- idle: check once per second
        local dist = #(GetEntityCoords(PlayerPedId()) - spot)
        if dist < 5.0 then
            sleep = 0                            -- close: react every frame
            DrawPrompt()
        end
        Wait(sleep)
    end
end)
Bad
lua
CreateThread(function()
    while true do
        Wait(0)                                  -- always every frame
        if #(GetEntityCoords(PlayerPedId()) - spot) < 5.0 then DrawPrompt() end
    end
end)

Cache what you reuse. PlayerPedId() and GetEntityCoords() are natives: call them once per loop turn, not once per check.

Do not create garbage in hot loops. New tables and string concatenations inside a per-frame loop make the garbage collector work all the time. Build them once outside the loop.

Tables and arrays#

Tables with named keys are looked up directly. A list has to be searched one element at a time, so use a table as a set for membership tests:

Good
lua
local police = { police = true, sheriff = true }
local isLaw = police[job]                       -- one lookup
Bad
lua
local police = { 'police', 'sheriff' }
local isLaw = false
for i = 1, #police do
    if police[i] == job then isLaw = true break end
end

Use named fields for structured data ({ grade = 0, job = 'police' }) instead of positional ones ({ 0, 'police' }): they read better and survive changes.

Security#

Everything a client sends can be forged. The server decides.

Warning

Never accept a price, amount, reward, item name or coordinates from the client as the truth. Look them up on the server.

lua
RegisterNetEvent('myres:buy', function(item, count)
    local src = source
    count = math.floor(tonumber(count) or 0)
    local def = Config.Items[item]                       -- price comes from the server config
    if not def or count < 1 or count > 20 then return end

    local ped = GetPlayerPed(src)
    if #(GetEntityCoords(ped) - Config.Shop.coords) > 6.0 then return end   -- distance check

    local JRS = exports.jrs_core:getCoreApi()
    if not JRS.RemoveMoney(src, def.price * count) then return end          -- pay first
    exports.jrs_inventory:addItem(src, item, count)
end)
  • Add a cooldown per player to every event that gives money or items.
  • Check permission (job, group, level) on the server, never only in the menu.
  • Give rewards with JRS.AddReward(...) so the economy multipliers apply.
  • Do not send secrets (webhooks, API keys) to the client or put them in a shared file.

Database#

Always use parameters. Never build SQL by joining strings.

Good
lua
local rows = MySQL.query.await('SELECT * FROM jrs_items WHERE item = ?', { item })
Bad
lua
local rows = MySQL.query.await("SELECT * FROM jrs_items WHERE item = '" .. item .. "'")
  • Call MySQL.*.await inside a thread or an event handler, not at file load.
  • Select only the columns you need and add an index for the column you filter on.
  • Save in batches (on interval and on drop), not on every small change.

Structure#

  • One resource, one job. Put shared tables in config.lua and texts in locales.
  • Name events resource:action (jrs_mining:sell) so they never collide.
  • Prefix every print with your resource name and hide debug output behind Config.Debug.
  • Keep numbers readable: write hashes and flag ids as named constants with a comment.
lua
local FLAG = { NO_RAGDOLL = 0 }    -- id found by testing on build 1491

Clean up after yourself#

Everything you create must be removed when the resource stops, or players keep ghost props, blips and prompts after a restart.

lua
AddEventHandler('onResourceStop', function(res)
    if res ~= GetCurrentResourceName() then return end
    for _, blip in ipairs(blips) do RemoveBlip(blip) end
    for _, obj in ipairs(props) do DeleteEntity(obj) end
    SetNuiFocus(false, false)
end)

Release assets too: RemoveAnimDict, SetModelAsNoLongerNeeded, SetStreamedTextureDictAsNoLongerNeeded.

NUI (web interfaces)#

  • Show player text with textContent, never innerHTML, so a name cannot inject code.
  • Always give a way out (Esc and a close button) and call SetNuiFocus(false, false) when you close.
  • Keep the page small: one script file, no external CDN (the browser inside the game may be offline or blocked).

Handle failures#

Wrap code that depends on outside input (JSON, database rows, another resource) in pcall and log the error. Do not hide your own bugs with it.

lua
local ok, data = pcall(json.decode, raw)
if not ok or type(data) ~= 'table' then
    print(('[myres] bad data: %s'):format(tostring(data)))
    return
end

Before you release#

  1. Restart the resource twice and check the console is clean.
  2. Test with two players: one doing the action, one watching.
  3. Try to break your own events: wrong item, huge count, far away, spamming.
  4. Check the idle cost with the resource monitor (resmon in F8): an idle script should stay near 0.00 ms.
JRS Core · 免费开源的 RedM 框架 · JRS TEAM 出品