[RELEASE] Lua Callback system

PMC-CALLBACKS SYSTEM

Installation [EN]

  • Download the latest version of pmc-callbacks
  • Unzip the file and add to your resources folder pmc-callbacks
  • Add to your .cfg file ensure pmc-callbacks

Usage [EN]

  • Add to your fxmanifest resource file shared_script '@pmc-callbacks/import.lua'

Instalación [ES]

  • Descarga la última versión de pmc-callbacks
  • Descomprime el archivo y añade a tu carpeta resources pmc-callbacks
  • Añade a tu archivo .cfg ensure pmc-callbacks

Uso [ES]

  • Añade a tu archivo fxmanifest shared_script '@pmc-callbacks/import.lua'

Methods / Métodos

server-side
global function params return
RegisterServerCallback eventName string | eventCallback function table
UnregisterServerCallback eventData table nil
TriggerClientCallback source string``number | eventName string | args table | timeout number | timedout function | callback function any
TriggerServerCallback source string``number | eventName string | args table | timeout number | timedout function | callback function any
client-side
global function params return
RegisterClientCallback eventName string | eventCallback function table
UnregisterClientCallback eventData table nil
TriggerServerCallback eventName string | args table | timeout number | timedout function | callback function any
TriggerClientCallback eventName string | args table | timeout number | timedout function | callback function any

Example / Ejemplo

client-side
-- inside the fxmanifest
-- you can @import with "shared_script '@pmc-callbacks/import.lua'"
-- or just use the provided exports

-- synchronous request
RegisterCommand('requestserver', function(s, args)
    local result = TriggerServerCallback {
        eventName = 'pmc-test:testingAwesomeCallback',
        args = {'some', 'args', 'here'}
    }
    print('gotcha', result)
end)

-- asynchronous request (same for server-side!)
RegisterCommand('requestserverasync', function(s, args)
    TriggerServerCallback {
        eventName = 'pmc-test:testingAwesomeCallback',
        args = {'some', 'args', 'here'},
        callback = function(result)
            print('gotcha', result)
        end
    }
end)

local eventdata = RegisterClientCallback {
    eventName = 'pmc-test:requestSomething',
    eventCallback = function(...)
        -- your awesome code here!
        return 'return something'
    end
}

-- want to remove the callback?
UnregisterClientCallback(eventdata) -- from RegisterClientCallback ↑
server-side
RegisterServerCallback {
    eventName = 'pmc-test:testingAwesomeCallback',
    eventCallback = function(source, ...)
        -- your awesome code here!
        return 'return something'
    end
}

RegisterCommand('requestclient', function(s, args)
    local target = tonumber(args[1])
    local result = TriggerClientCallback {
        source = target,
        eventName = 'pmc-test:requestSomething',
        args = {'some', 'args', 'here'}
    }
    print('gotcha', result)
end)

Download

Github
Download

15 Likes

Finally a way to get values from client. Nice job! :ok_hand:

This is really different to ESX callback system, and this is standalone, doesn’t need ESX to work.

Seeing as whenever I finish getting my framework rewritten for PR it will more than likely have the single most comprehensive callback system in the FiveM Ecospace, I’m gonna take a wild guess and say ESX doesn’t hold a trademark over callbacks.

…Or really anything for that matter.

Might I recommend that you write in the ability to send data via your callbacks. I’ve found it superrrrrr useful in my callback system being able to send data to the server via client to server callbacks and sending data to the client via server to client callbacks. I’ve mainly used it for checking if code on one side matches the other (mainly used to combat lua injectors editing client side code), but I’ve also found it handy doing other things as well.

[Example]

Callbacks:TriggerCallback('Mechanic:GetShopData', function(data)
    Data.CurrentShopInfo = data
end, Data.CurrentShop)

I can have 1 callback that handles getting the shop data, and I can send the current shop the player is at and pull data like that, rather than having a different callback for each shop.

I don’t really understand what you said, can you explain more?

Callbacks:TriggerCallback('Mechanic:GetShopData', function(data)
    Data.CurrentShopInfo = data
end, Data.CurrentShop)

Callbacks:RegisterCallback('Mechanic:GetShopData', function(data, cb)
    if data == 'Bennys' then
        cb(Data.Shops.Bennys)
    elseif data == 'LSCustoms' then
        cb(Data.Shops.LSCustoms)
    end
end)

So I’m sending Data.CurrentShop to the server inside the callback code. This is the shop that the player is currently at. I’m then sending back the data from that shop via the cb() and assigning it to Data.CurrentShopInfo. Instead of having a separate callback for each location, I have just one that handles them all. Data is the just table name that I use on all my resources to handle any data for the client/server (tables, variables, etc.)

1 Like

Okay, I understand what you mean.

  1. I can add that behavior but callbacks are made for cross side not same side, if you use it for same side you can create conflicts or it can be less safe because you can make fake server callbacks.
  2. This is the same as just using AddEventHandler
    Example:
function GetShopData(data, cb)
    if cb then cb(Data.Shops[data]) end
    return Data.Shops[data]
    -- adding those if-statement was inefficient and a waste of lines
end

AddEventHandler('Mechanic:GetShopData', GetShopData)
RegisterServerCallback('Mechanic:GetShopData', GetShopData)

Idk what you mean by same side and cross side. I assume cross side = client to server or vice versa by the sounds of it.

In the example I posted above, Callbacks:RegisterCallback is done serverside and Callbacks:TriggerCallback is clientside. It’s not possible to fake code thats written on the server…so I’m a little confused by what you’re saying.

Okay, sorry I didn’t understand what you are saying, i thought you wanted to call the callback from the server to a server callback.

If I understand what you mean, my script is currently doing that, where did you see that it cannot be done?
What you did but on my script:

-- client-side
Data.CurrentShopInfo = TriggerServerCallback('Mechanic:GetShopData', Data.CurrentShop)
-- this is synchronous

-- server-side
RegisterServerCallback('Mechanic:GetShopData', function(source, data)
    return Data.Shops[data]
end)

This is what you mean?

When did lua promises become a thing!?

1 Like

What do you mean?
It’s mentioned in docs from years ago probably.

Guess I’m blind. Sorry about that, and thanks! <3

Basically yeah.

I callback for createvehicle, vehicle created 2x.

  • What do you mean with that ‘created 2x’?
  • What code did you use?

Get a little bit explained before adding a reply! Thanks.

SERVER =======================

self.spawn = function(playerId)
		if self.vSpawn == 0 then
			if self.vProprio == ESX.Players[playerId].identifier or ESX.Players[playerId].pAdmin > 7 then

				local entity = TriggerClientCallback(playerId, 'spawnSonVehicle', self);

				if entity then
					self.vEntity = entity;
					self.vSpawn = 1;

					--ESX.Players[playerId].chatMsg(self.vSqlID.." - ".. self.vData.name.." spawn /v ranger", ESX.colors.VALID)
					ESX.Players[playerId].notifyPF(self.vData.name.." ~g~spawn ~y~/v ranger", 1);
				end
			else ESX.Players[playerId].chatMsg("Véhicule ID "..self.vSqlID .." ne vous appartient pas", ESX.colors.STOP) end
		else ESX.Players[playerId].chatMsg(self.vData.name.." déjà spawn /v localiser", ESX.colors.STOP) end
	end

CLIENT =======================

local spawnActive = 0;
RegisterClientCallback('spawnSonVehicle', function(data, ...)

	local vehicle = nil;
	if spawnActive == 0 then
		spawnActive = 1;

		local vehicleName = data.vData.name;
		local model = (type(vehicleName) == 'number' and vehicleName or GetHashKey(vehicleName))
		local coords = data.vPos;

		if IsModelInCdimage(model) then

			ESX.RequestModel(model)
			vehicle = CreateVehicle(model, coords.x, coords.y, coords.z, coords.heading, true, false)
			
			local networkId = NetworkGetNetworkIdFromEntity(vehicle)		

			SetNetworkIdCanMigrate(networkId, true)
			SetEntityAsMissionEntity(vehicle, true, false)
			SetVehicleHasBeenOwnedByPlayer(vehicle, true)
			SetVehicleNeedsToBeHotwired(vehicle, false)
			SetVehRadioStation(vehicle, 'OFF')
			SetModelAsNoLongerNeeded(model)
		
			ESX.Game.SetVehicleProperties(vehicle, data.vData);
			SetVehicleEngineOn(vehicle, false, false, true);
			Citizen.SetTimeout(5000, function()
				spawnActive = 0;
			end)
		else TriggerEvent('chat:addMessage', {args = {"Véhicule ID ".. data.vSqlID .." non identifié."}}) end	
	end
	while not vehicle do
		Wait(10)
	end
	return vehicle;
end)

i added
**while not entity do Wait(10) end**

it works ! lol

Good job man i love this game

No, its not work realy now

This is random bug

Isn’t there a problem with using GetGameTimer() as an identifier for the callback? Won’t there be any issues when the same event gets triggered twice?