ब्लॉग पर वापस
Guide8 मिनट पढ़ें

FiveM डिबग तकनीकें: Lua और JS में बग्स जल्दी खोजें

FiveM स्क्रिप्ट्स को तेजी से डिबग करें। सर्वर लॉग्स, क्लाइंट प्रिंट्स, txAdmin टूल्स, प्रोफाइलर ट्रिक्स और सिद्ध वर्कफ़्लोज़ जो खिलाड़ियों के नोटिस करने से पहले समस्याओं को ठीक करते हैं।

Agency Scripts

Agency Scripts के संस्थापक और प्रमुख डेवलपर

FiveM स्क्रिप्ट्स डिबगिंग की कला

FiveM स्क्रिप्ट्स का डिबगिंग मूल रूप से मानक एप्लिकेशन के डिबगिंग से अलग है। आप एक विभाजित क्लाइंट-सर्वर आर्किटेक्चर से निपट रहे हैं जहां कोड दो अलग रनटाइम्स पर चलता है, इवेंट्स नेटवर्क के माध्यम से यात्रा करते हैं, और गेम स्टेट हर फ्रेम बदलता है। जब कुछ गलत होता है, तो त्रुटि सर्वर पर उत्पन्न हो सकती है लेकिन क्लाइंट पर प्रकट हो सकती है, या इसके विपरीत। डिबगिंग के लिए एक व्यवस्थित दृष्टिकोण विकसित करना आपको अनगिनत घंटे बचाएगा और आपके विकास वर्कफ़्लो को नाटकीय रूप से तेज़ बनाएगा। यह गाइड उन आवश्यक उपकरणों, तकनीकों और पैटर्न को कवर करता है जिन्हें पेशेवर FiveM डेवलपर्स दैनिक उपयोग करते हैं।

प्रिंट स्टेटमेंट्स का प्रभावी उपयोग

FiveM में सबसे मौलिक डिबगिंग टूल है print() function, लेकिन इसे प्रभावी ढंग से उपयोग करने के लिए केवल वेरिएबल्स को डंप करना पर्याप्त नहीं है। अपने डिबग आउटपुट को प्रीफिक्स के साथ संरचित करें जो स्क्रिप्ट, पक्ष (क्लाइंट या सर्वर), और उस function को पहचानें जहाँ प्रिंट होता है। महत्वपूर्ण संदेशों को कंसोल में अलग दिखाने के लिए रंग कोड का उपयोग करें। एक डिबग यूटिलिटी बनाएं जिसे आप ऑन और ऑफ टॉगल कर सकें बिना अपने कोड से डिबग लाइनों को हटाए।

-- shared/debug.lua
local DEBUG_ENABLED = GetConvar('myresource_debug', 'false') == 'true'
local RESOURCE_NAME = GetCurrentResourceName()

function DebugLog(module, message...)
    if not DEBUG_ENABLED then return end

    local side = IsDuplicityVersion() and 'SERVER' else 'CLIENT'
    local formatted = type(message) == 'string' and message:format(...) or tostring(message)
    local timestamp = os.date('%H:%M:%S')

    print(('[^3%s^0][^5%s^0][^2%s^0] %s'):format(
        timestamp, RESOURCE_NAME, side .. ':' .. module, formatted
    ))
end

function DebugTable(module, tbl, depth)
    if not DEBUG_ENABLED then return end
    depth = depth or 0
    local indent = string.rep('  ', depth)

    if type(tbl) ~= 'table' then
        DebugLog(module, '%s%s', indent, tostring(tbl))
        return
    end

    for k, v in pairs(tbl) do
        if type(v) == 'table' then
            DebugLog(module, '%s%s = {', indent, tostring(k))
            DebugTable(module, v, depth + 1)
            DebugLog(module, '%s}', indent)
        else
            DebugLog(module, '%s%s = %s (%s)', indent, tostring(k), tostring(v), type(v))
        end
    end
end

डिबग यूटिलिटी का उपयोग

With this utility in place, you can add debug logging throughout your scripts that is completely silent in production. Enable it by adding set myresource_debug true जब आपको समस्या निवारण की आवश्यकता हो तो अपने सर्वर कॉन्फ़िगरेशन को देखें। संरचित आउटपुट कंसोल लॉग्स में आसानी से खोज करने और ठीक वही खोजने में मदद करता है जो आप ढूंढ रहे हैं।

-- server/jobs.lua
RegisterNetEvent('myresource:startJob', function(jobName)
    local src = source
    DebugLog('jobs', 'Player %d attempting to start job: %s', src, jobName)

    local playerData = GetPlayerData(src)
    DebugTable('jobs', playerData)

    if not playerData then
        DebugLog('jobs', 'ERROR: No player data found for source %d', src)
        return
    end

    if playerData.job == jobName then
        DebugLog('jobs', 'Player %d already has job %s, skipping', src, jobName)
        return
    end

    DebugLog('jobs', 'Job %s assigned to player %d successfully', jobName, src)
end)

सामान्य FiveM त्रुटियाँ और समाधान

SCRIPT ERROR: nil मान को इंडेक्स करने का प्रयास

यह FiveM विकास में सबसे सामान्य Lua त्रुटि है। इसका मतलब है कि आप एक ऐसे वेरिएबल पर प्रॉपर्टी या मेथड तक पहुँचने की कोशिश कर रहे हैं जो nil. सबसे सामान्य कारण हैं खिलाड़ी डेटा को लोड होने से पहले एक्सेस करना, उस फ्रेमवर्क फ़ंक्शन का संदर्भ देना जो आपके उपयोग किए जा रहे संस्करण में मौजूद नहीं है, या कुंजी में टाइपो के साथ कॉन्फ़िग मान पढ़ना। नेस्टेड प्रॉपर्टीज़ को एक्सेस करने से पहले हमेशा nil की जांच करें।

-- BAD: Will crash if GetPlayerData returns nil
local name = GetPlayerData(src).charinfo.firstname

-- GOOD: Defensive nil checks
local playerData = GetPlayerData(src)
if not playerData then
    print('[ERROR] Player data is nil for source: ' .. src)
    return
end

local charinfo = playerData.charinfo
if not charinfo then
    print('[ERROR] charinfo missing for source: ' .. src)
    return
end

local name = charinfo.firstname or 'Unknown'

SCRIPT ERROR: nil मान को कॉल करने का प्रयास

यह त्रुटि तब होती है जब आप किसी ऐसी फ़ंक्शन को कॉल करने की कोशिश करते हैं जो मौजूद नहीं है। FiveM में, यह आमतौर पर तब होता है जब आप किसी संसाधन से एक एक्सपोर्ट कॉल करते हैं जो अभी शुरू नहीं हुआ है, किसी फ्रेमवर्क फ़ंक्शन का उपयोग करते हैं जिसे अपडेट में नाम बदला गया हो, या अपनी मैनिफेस्ट में साझा फ़ाइल लोड करना भूल जाते हैं। अपनी जांच करें fxmanifest.lua सुनिश्चित करने के लिए कि सभी आवश्यक फ़ाइलें सूचीबद्ध हैं और सही क्रम में हैं।

-- Safely calling an export that might not be available
local function SafeExport(resource, exportName...)
    local success, result = pcall(function(...)
        return exports[resource][exportName](...)
    end...)

    if not success then
        print(('[^1ERROR^0] Failed to call export %s:%s - %s'):format(
            resource, exportName, tostring(result)
        ))
        return nil
    end

    return result
end

-- Usage
local inventory = SafeExport('ox_inventory', 'GetInventory', src)

इवेंट पंजीकृत नहीं था

जब आप ऐसा इवेंट ट्रिगर करते हैं जिसके लिए कोई स्क्रिप्ट हैंडलर पंजीकृत नहीं है, तो FiveM इसे चुपचाप ड्रॉप कर देता है बिना कंसोल में कोई त्रुटि दिखाए। यह डिबग करने में पागलपन हो सकता है क्योंकि सब कुछ सही दिखता है लेकिन कुछ भी नहीं होता। इवेंट ट्रिगर करने से पहले यह सुनिश्चित करने के लिए एक हेल्पर फ़ंक्शन का उपयोग करें कि इवेंट पंजीकृत हैं, और विकास के दौरान अपने इवेंट ट्रिगर के आसपास लॉगिंग जोड़ें।

-- server/debug_events.lua
-- Wrap TriggerClientEvent to log when events fire
local originalTrigger = TriggerClientEvent

if GetConvar('myresource_debug', 'false') == 'true' then
    TriggerClientEvent = function(eventName, target...)
        print(('[^3EVENT^0] TriggerClientEvent: %s -> target: %s'):format(
            eventName, tostring(target)
        ))
        return originalTrigger(eventName, target...)
    end
end

NUI डिबगिंग विथ DevTools

NUI इंटरफेस वाली स्क्रिप्ट्स के लिए, बिल्ट-इन Chromium DevTools अमूल्य हैं। इन्हें F8 कंसोल में टाइप करके खोलें nui_devtools पूर्ण Chrome इंस्पेक्टर तक पहुँचने के लिए। यह आपको DOM संरचना का निरीक्षण करने के लिए Elements पैनल, JavaScript त्रुटियों के लिए Console, संसाधन लोडिंग के लिए Network टैब, और ब्रेकपॉइंट सेट करने के लिए Sources पैनल देता है। NUI संचार समस्याओं के लिए, संदेश ब्रिज के दोनों पक्षों को लॉग करें।

// nui/js/debug.js
// Log all incoming NUI messages
window.addEventListener('message', (event) => {
    if (event.data && event.data.action) {
        console.log(
            '%c[NUI Received]%c ' + event.data.action,
            'background: #2dd4bf; color: #000; padding: 2px 6px; border-radius: 3px;',
            'color: #94a3b8;',
            event.data
        );
    }
});

// Wrap fetch to log NUI callbacks
const originalFetch = window.fetch;
window.fetch = function(url, options) {
    const body = options?.body ? JSON.parse(options.body) : null;
    console.log(
        '%c[NUI Callback]%c ' + url,
        'background: #8b5cf6; color: #fff; padding: 2px 6px; border-radius: 3px;',
        'color: #94a3b8;',
        body
    );
    return originalFetch.apply(this, arguments);
};

Resmon और Timing के साथ प्रोफाइलिंग

मूलभूत से परे resmon मॉनिटरिंग, आप अपने स्क्रिप्ट्स में सटीक टाइमिंग इंस्ट्रूमेंटेशन बना सकते हैं। मापें कि विशिष्ट ऑपरेशन्स में कितना समय लगता है और जब वे स्वीकार्य सीमा से अधिक हो जाएं तो चेतावनी लॉग करें। यह विशेष रूप से डेटाबेस क्वेरीज, जटिल गणनाओं, और कई एंटिटीज़ को प्रोसेस करने वाले लूप्स के लिए महत्वपूर्ण है।

-- shared/profiler.lua
local Profiler = {}

function Profiler.Start(label)
    return {
        label = label,
        startTime = GetGameTimer()
    }
end

function Profiler.Stop(timer, warnThresholdMs)
    local elapsed = GetGameTimer() - timer.startTime
    warnThresholdMs = warnThresholdMs or 5

    if elapsed >= warnThresholdMs then
        print(('[^1PERF WARNING^0] %s took %dms (threshold: %dms)'):format(
            timer.label, elapsed, warnThresholdMs
        ))
    elseif GetConvar('myresource_debug', 'false') == 'true' then
        print(('[^2PERF^0] %s completed in %dms'):format(timer.label, elapsed))
    end

    return elapsed
end

-- Usage in a server event
RegisterNetEvent('myresource:heavyOperation', function(data)
    local timer = Profiler.Start('heavyOperation')

    -- ... expensive processing ...
    local result = ProcessLargeDataSet(data)

    Profiler.Stop(timer, 10) -- warn if over 10ms
end)

स्टेट बैग डिबगिंग

स्टेट बैग्स एक शक्तिशाली लेकिन कभी-कभी भ्रमित करने वाली सुविधा हैं। जब स्टेट बैग मान अपेक्षित रूप से अपडेट नहीं होते हैं, तो आमतौर पर इसका कारण यह होता है कि आप उन्हें गलत एंटिटी पर सेट कर रहे हैं, हैंडलर सही बैग नाम पकड़ नहीं रहा है, या प्रतिकृति में देरी हो रही है। एक स्टेट बैग इंस्पेक्टर कमांड बनाएं जो किसी दिए गए एंटिटी के लिए सभी स्टेट डंप करता है।

-- server/debug_statebags.lua
RegisterCommand('debugstate', function(source, args)
    local targetId = tonumber(args[1])
    if not targetId then
        print('Usage: debugstate [playerId]')
        return
    end

    local playerPed = GetPlayerPed(targetId)
    if playerPed == 0 then
        print('Player not found: ' .. targetId)
        return
    end

    local entityState = Player(targetId).state
    print(('[^3STATE BAGS^0] Player %d:'):format(targetId))

    -- Print known state keys (state bags don't have an iterator)
    local keysToCheck = {'job', 'gang', 'duty', 'dead', 'phone', 'inventory'}
    for _, key in ipairs(keysToCheck) do
        local val = entityState[key]
        if val ~= nil then
            print(('  %s = %s (%s)'):format(key, tostring(val), type(val)))
        end
    end
end, true)

आवश्यक डिबगिंग चेकलिस्ट

  • दोनों कंसोल जांचें। हमेशा सर्वर कंसोल (txAdmin या टर्मिनल) और क्लाइंट कंसोल (F8) दोनों में त्रुटियों को देखें। एक पक्ष की त्रुटि अक्सर दूसरे पक्ष पर टूटे हुए व्यवहार की व्याख्या करती है।
  • संसाधन स्थिति सत्यापित करें। उपयोग करें ensure अपने संसाधन को पुनः आरंभ करने के लिए और restart एकल संसाधन को पुनः आरंभ करने के लिए। जांचें resmon सुनिश्चित करने के लिए कि संसाधन वास्तव में चल रहा है।
  • साफ़ वातावरण के साथ परीक्षण करें। उसी सिस्टम के साथ इंटरैक्ट करने वाले अन्य स्क्रिप्ट्स को अक्षम करें। कई बग संसाधनों के बीच संघर्षों से आते हैं न कि एकल स्क्रिप्ट के भीतर बग से।
  • त्रुटि स्टैक ट्रेस को ध्यान से पढ़ें। Lua स्टैक ट्रेस आपको सही फ़ाइल और लाइन नंबर दिखाते हैं। त्रुटि तक पहुँचने वाली कॉल चेन को समझने के लिए इन्हें नीचे से ऊपर पढ़ें।
  • जोखिम भरे ऑपरेशनों के लिए pcall का उपयोग करें। डेटाबेस क्वेरी, एक्सपोर्ट कॉल, और JSON डिकोडिंग को wrap करें pcall त्रुटियों को सहजता से पकड़ने के लिए ताकि वे आपकी स्क्रिप्ट को क्रैश न करें।
  • अपनी कॉन्फ़िग्स का संस्करण बनाएं। जब खिलाड़ी बग रिपोर्ट करते हैं, तो पूछें कि वे कौन सा संस्करण चला रहे हैं। कई समस्याएं अपडेट के बाद पुराने कॉन्फ़िगरेशन फ़ाइलों से उत्पन्न होती हैं।

शुरू करने के लिए तैयार?

हमारी शॉप से स्क्रिप्ट लें, या सपोर्ट, अपडेट और आगे आने वाली चीज़ों की झलक के लिए Discord से जुड़ें।