[Best practice] Improve your resource performance

Resource time warning and low fps is caused by client side resources too, so that’s exactly what you need.
You can run your server on windows and use ETW to debug server side resources though.

I believe you would need the server symbols to be imported, those are not available :o

2 Likes

Not sure what you mean by those “server symbols”, but I’ve used ETW to debug which resources cause low fps before the update with warnings was released, and I could also see the server process in WPA with ability to debug which resources cause much load on server side because the server was hosted locally on my PC.

Just tried it, and yes server debugging also works. Of course it only shows you which resources take up time, but it sadly doesn’t show you exactly which part of the resource takes up time. Though of course I wouldn’t expect that from such tool to tell me exactly which classes take up how much.

Client side debugging requires the ‘symbols’ @Syntasu was talking about.

(these symbols)

srvC:\cachepathhttps://runtime.fivem.net/client/symbols/

So I’m still slightly confused, how did you know “Intial code -> Runs at 119ms” and that your “Improved code -> Runs at 7ms”. Do i use ETW and look at duration time? (So make it go down)

Yes. In some cases it’s also possible to split the resource you want to debug into 2 or more separate resources so that you will be able to see more accurately which part of the resource takes much time.

Anyone know of a comparable tool to ETW for Linux servers? Would love to optimize server side without spinning up a Windows server locally…

2 Likes

Hi there Syntasu, I was wondering if you could help me understand the issue(s) with this resource. It’s a moneydrop resource where peds drop cash on death. I only altered the code to work with the ESX payment system so I’ll paste my code below.

The script works as expected with no warnings displayed initially. After about 15 minutes on the server, the players will see a 6 ms warning. This will slowly climb, about 1 ms every 10 minutes or so. I’ve seen it get to 30ms before logging to reset it.

I have tried altering the onticks throughout the resource and some would delay the onset of the warning and slow the climb but would never stop it from happening.

Looking at the script below, could you tell me what is most likely causing the problem described and what I can do so we can keep using the resource? All my friends absolutely love using it.

Thanks for your time!

pedindex = {}
objval = {}

Citizen.CreateThread(function()
	while true do
		Wait(0)
		for k,v in pairs(objval) do
			if DoesEntityExist(k) then
				if IsControlPressed(0,38) then -- E
					Citizen.CreateThread(function() RampTowardsPlayer(k) end)
					Wait(50)
				end
			end
		end
	end
end)

AddEventHandler("onClientMapStart", function()
    Citizen.CreateThread(function()
        while true do
            Citizen.Wait(0) -- have tried 0-20, issues persisted
            
			PopulatePedIndex()
            ResetIndexOnDeath()
            
            for k,v in pairs(pedindex) do
                if DoesEntityExist(k) then
					veh = GetVehiclePedIsIn(k, false)
					if not IsPedInVehicle(k, veh, true) then
						if IsEntityDead(k) then
							SpawnMoneyWithRandomValue(k,5,13)
							pedindex[k] = nil
							HighlightObject(k)
						end
					end
                end
            end
            
            for k,v in pairs(objval) do
                if DoesEntityExist(k) then
                    dist = DistanceBetweenCoords(PlayerPedId(-1), k)
                    if (dist.x < 0.4) and (dist.y < 0.4) and (dist.z < 1) then
                        TriggerServerEvent('getPaid', v.worth)
                        DeleteObject(k)
						PlaySoundFrontend(-1, "PICK_UP", "HUD_FRONTEND_DEFAULT_SOUNDSET")
                        objval[k] = nil
                    end
                    HighlightObject(k)
                end
            end
        end
    end)
end)

function HighlightObject(object)
    x, y, z = table.unpack(GetEntityCoords(object, true))
    SetDrawOrigin(x, y, z, 0)
    RequestStreamedTextureDict("helicopterhud", false)
    DrawSprite("helicopterhud", "hud_corner", -0.01, -0.01, 0.006, 0.006, 0.0, 255, 0, 0, 200)
    DrawSprite("helicopterhud", "hud_corner", 0.01, -0.01, 0.006, 0.006, 90.0, 255, 0, 0, 200)
    DrawSprite("helicopterhud", "hud_corner", -0.01, 0.01, 0.006, 0.006, 270.0, 255, 0, 0, 200)
    DrawSprite("helicopterhud", "hud_corner", 0.01, 0.01, 0.006, 0.006, 180.0, 255, 0, 0, 200)
    ClearDrawOrigin()
end

function DistanceBetweenCoords(ent1, ent2)
    local x1,y1,z1 = table.unpack(GetEntityCoords(ent1, true))
    local x2,y2,z2 = table.unpack(GetEntityCoords(ent2, true))
    local deltax = x1 - x2
    local deltay = y1 - y2
    local deltaz = y1 - y2
    
    dist = math.sqrt((deltax * deltax) + (deltay * deltay) + (deltaz * deltaz))
    xout = math.abs(deltax)
    yout = math.abs(deltay)
    zout = math.abs(deltaz)
    result = {distance = dist, x = xout, y = yout, z = zout}
    
    return result
end
    
function ResetIndexOnDeath()
    if IsEntityDead(GetPlayerPed(-1)) then
        for k,v in pairs(objval) do
            objval[k] = nil
        end
    end
end

function PopulatePedIndex()
    local handle, ped = FindFirstPed()
    local finished = false -- FindNextPed will turn the first variable to false when it fails to find another ped in the index
    repeat
        if not IsEntityDead(ped) then
                pedindex[ped] = {}
        end
        finished, ped = FindNextPed(handle) -- first param returns true while entities are found
    until not finished
    EndFindPed(handle)
end

function SpawnMoneyWithRandomValue(ped, lowlimit, upperlimit)
    value = math.random(lowlimit, upperlimit)
    money, quantity = MoneyVariance(value)
    
    x, y, z = table.unpack(GetEntityCoords(ped, true))
	z = z + 1.3

    i = 0
    while i < quantity do
        

        x2 = math.random() + math.random(-2,2)
        y2 = math.random() + math.random(-2,2)
	    z2 = math.random() + math.random(6,9)
		
        i = i + 1
        
        tempobject = CreateObject(GetCashHash((RoundNumber((money / quantity), 0))), x, y, z, true, false, true)
		SetActivateObjectPhysicsAsSoonAsItIsUnfrozen(tempobject, true)
		SetEntityDynamic(tempobject, true)
        ApplyForceToEntity(tempobject, 1, x2, y2, z2, 0.0, 3.0, 0.0, 0, 0, 1, 1, 0, 1)
        objval[tempobject] = { worth = (RoundNumber((money / quantity), 0)) }
    end
end

function RampTowardsPlayer(entity)
	local t = 0.0
	
	while t < 1.0 do
		Wait(25)
		t = t + 0.01
		vec3 = VectorLerp(GetEntityCoords(entity, true), GetEntityCoords(PlayerPedId(), true), t)
		vehicle = GetRandomVehicleInSphere(vec3,2.0,0,0)

		if DoesEntityExist(entity) and (vehicle < 2) then -- The reason we choose two is because sometimes it returns 1 and I don't know why. I assume it also returns 2 sometimes just incase.
			SetEntityCoords(entity, vec3)			
		elseif not DoesEntityExist(entity) then
			t = 1.0
		end
	end
end

function VectorLerp(vec1, vec2, t)
	vecOut = vec1 - (t * (vec1 - vec2))
	return vecOut
end

function GetCashHash(money)
    local propA = "prop_anim_cash_note"
    local propB = "prop_anim_cash_pile_01"
    local propC = "prop_cash_envelope_01"
    local propD = "prop_cash_pile_02" --smaller prop
    local propE = "prop_cash_pile_01" -- bigger wad of cash
    local propF = "prop_money_bag_01" 
    local model = 0
    
    if (money >= 0) then
        model = propA
    end
    if (money >= 20) then
        model = propB
    end
    if (money >= 100) then
        model = propC
    end    
    if (money >= 250) then
        model = propD
    end    
    if (money >= 500) then
        model = propE
    end    
    if (money >= 1500) then
        model = PropF
    end
   
    return GetHashKey(model)
end

function MoneyVariance(value)
    local RNG = math.random()
    local basevalue = value
    local multiplier = 1.0
    local quantity = math.random(5,6)
    
    
    if (RNG <= 0.75) then
        multiplier = 0.85
        quantity = math.random(2,4)
    end
    if (RNG <= 0.45) then
        multiplier = 1.1
        quantity = math.random(1,2)
    end
    if (RNG >= 0.35) then
        multiplier = 1.3
        quantity = math.random(1,2)
    end
    if (RNG >= 0.20) then
        multiplier = 1.6
        quantity = math.random(1,2)
    end
    if (RNG >= 0.04) then
        multiplier = 2.3
        quantity = math.random(1,2)
    end
    if (RNG >= 0.02) then
        multiplier = 4.0
        quantity = 1
    end
    if(RNG >= 0.009) then
        multiplier = 5.0
        quantity = math.random(1,3)
    end
    
    finalvalue = basevalue * multiplier
    return finalvalue, quantity
end

angleint = 0
function CapsuleCheckForNearbyPed(inputped)
    x, y, z = table.unpack(GetEntityCoords(inputped, true))
    flag = 12
    radius = 60
    Wait(7)
    for i = angleint, 72 do     
        angleint = angleint + 1
        AdjustAngleInt()
        
        local angle = math.rad(i * 5)

        local startX = (60.0 * math.cos(angle)) + x;
        local startY = (60.0 * math.sin(angle)) + y;
                    
        local endX = x - (startX - x)
        local endY = y - (startY - y)
        
        ray = StartShapeTestCapsule(startX,startY,z,endX,endY,z,radius,flag,inputped,7)
        _, _, _, _, result = GetShapeTestResult(ray)

        return result
    end
end

function AdjustAngleInt()    
    if angleint > 72 then
        angleint = 1
    end
end

function DetectNpcByAiming()
    local aiming = false
    local entity
    
    if IsPlayerFreeAiming(PlayerId()) then
        aiming, entity = GetEntityPlayerIsFreeAimingAt(PlayerId())
        if (aiming) then
            if IsEntityAPed(entity) then
                return entity
            end
        end
    end
end

function GetTableLength(temptable)
    local count = 0
    for _ in pairs(temptable) do
        count = count+1
    end
    
    return count
end

function RoundNumber(num, numDecimalPlaces)
    if numDecimalPlaces and numDecimalPlaces>0 then
        local mult = 10^numDecimalPlaces
        return math.floor(num * mult + 0.5) / mult
    end
    
    return math.floor(num + 0.5)
end

And you are sure the pedIndex and objVal are cleaned up correctly. Maybe try printing the count in these tables (foreach -> entry ~= nil -> count++) like each minute. I suspect that the tables might be incrementally growing… which seems to fit your descriptions.

I’m currently busy, I will take a more thorough look another time :slight_smile:

Thanks very much Sytasu, I’ll start to try and see if I can figure out which variable might be causing the problem. Could you tell me how I should properly empty or unset a variable(objval, for example)? I’m searching but not finding a way to handle that.

Thanks for your time!

1 Like

I guess just setting it to nil should be fine. But I could be wrong as where the table will keep growing and thus iterating over the nil index as well.

In that case you could try to find the nearest nil value in the table and write it to that index. This should keep the table from having a million nil index. I do not know if this is done for you or not.

Hi there Syntasu,

I think I figured out what variable is causing the issue by placing each one in the ondeath event one at a time:

function ResetIndexOnDeath()
    if IsEntityDead(GetPlayerPed(-1)) then
        for k,v in pairs(objval) do
            objval[k] = nil
            objval[tempobject] = nil
            pedindex[k] = nil
            pedindex[ped] = nil
        end
    end
end

And then letting the ms warning grow some before killing myself. The warning disappears when I add the pedindex[k] and pedindex[ped] in the loop.

This gets rid of the ms warning but breaks the resource in that you can’t get any more money once you die.

The problem I now face is controlling that variable in the event that someone doesn’t die. I don’t understand the code well enough to know when I can safely set it to nil or if that should even be how I handle it. Perhaps the table should be truncated somehow, keeping only the latest x entries in the table? I don’t know if that can be done in lua.

I’d very much welcome your input in the matter. Thanks for your time!

1 Like

I can’t seem to resolve the lag issue without stopping the resource from working. I can get the ms warnings to go away but can’t seem to get it to keep that variable working.

I’ve tried a bunch of variations. The commented lines have all been tried both separately and together.

function ResetIndexOnDeath()
    if IsEntityDead(GetPlayerPed(-1)) then
        for k,v in pairs(objval) do
            objval[k] = nil
            --objval[tempobject] = nil
            --pedindex[k] = nil
            --pedindex[ped] = nil
            --pedindex = {}
        end
    end
end

Do you happen to have an idea of what I can do to resolve this issue?

I think you could safely increase your delay to one second :thinking:

Did you even read the topic? Or are you just posting it here and waiting for someone to fix it for you?

Seriously… the last example is the exact same code.

You dont…

sorry if i ask but, the hitch frame time warnings in console… are they related to server code or client code? (my server starts to get hitch frametime warnings after 25 / 26 players)

hitch frame time warnings in the server console indicate server code issues. Like: heavy operations or a lot of mysql operations per second (which is the case with esx/vrp)

So it’s normal using ESX to have such hitch warnings?

Yeah, this is 100% related to server code. The key here is to find out which resource causes these hitches. As @d0p3t said, this is mainly due to doing heavy operations.

Mainly you want to run hitch free, since hitches can cause people to lose connection from your server if the hitches are too severe.