Main/resources/[jobs]/[medic]/visn_are/script/helpers/g_callbacks.lua
2025-06-07 08:51:21 +02:00

342 lines
No EOL
12 KiB
Lua

--[[
-- Author: Tim Plate
-- Project: Advanced Roleplay Environment
-- Copyright (c) 2022 Tim Plate Solutions
--]]
-- Source: https://github.com/pitermcflebor/pmc-callbacks/
local IS_SERVER = IsDuplicityVersion()
local table_unpack = table.unpack
-- from scheduler.lua
local debug = debug
local debug_getinfo = debug.getinfo
local msgpack = msgpack
local msgpack_pack = msgpack.pack
local msgpack_unpack = msgpack.unpack
local msgpack_pack_args = msgpack.pack_args
-- from deferred.lua
local PENDING = 0
local RESOLVING = 1
local REJECTING = 2
local RESOLVED = 3
local REJECTED = 4
-- custom function to check any type
local function ensure(obj, typeof, opt_typeof, errMessage)
local objtype = type(obj)
local di = debug_getinfo(2)
local errMessage = errMessage or (opt_typeof == nil and (di.name .. ' expected %s, but got %s') or (di.name .. ' expected %s or %s, but got %s'))
if typeof ~= 'function' then
if objtype ~= typeof and objtype ~= opt_typeof then
error((errMessage):format(typeof, (opt_typeof == nil and objtype or opt_typeof), objtype))
end
else
if objtype == 'table' and not rawget(obj, '__cfx_functionReference') then
error((errMessage):format(typeof, (opt_typeof == nil and objtype or opt_typeof), objtype))
end
end
end
-- SERVER-SIDE
if IS_SERVER then
--
-- @table RegisterServerCallback
--
-- @string eventName - The name of the event to be registered
-- @function eventCallback - The function to be executed when event is fired
_G.RegisterServerCallback = function(args)
ensure(args, 'table'); ensure(args.eventName, 'string'); ensure(args.eventCallback, 'function')
-- save the callback function on this call
local eventCallback = args.eventCallback
-- save the event name on this call
local eventName = args.eventName
-- save the event data to return
local eventData = RegisterNetEvent('pmc__server_callback:'..eventName, function(packed, src, cb)
-- save the source on this call
local source = tonumber(source)
-- check if this is a simulated callback (TriggerServerCallback)
if not source then
-- return the simulated data
cb( msgpack_pack_args( eventCallback(src, table_unpack(msgpack_unpack(packed)) ) ) )
else
-- return the data
TriggerClientEvent(('pmc__client_callback_response:%s:%s'):format(eventName, source), source, msgpack_pack_args( eventCallback(source, table_unpack(msgpack_unpack(packed)) ) ))
end
end)
-- return the event data to UnregisterServerCallback
return eventData
end
--
-- @void UnregisterServerCallback
--
-- @table eventData - The data from the RegisterServerCallback
_G.UnregisterServerCallback = function(eventData)
RemoveEventHandler(eventData)
end
--
-- @any TriggerClientCallback
--
-- @string/number source - The playerId to be triggered
-- @string eventName - The name of the event to be fired
-- @table args - The arguments to be sent with the event
-- [@number timeout - Seconds to wait for response]
-- [@function timedout - The function that will be executed if timeout is reached]
-- [@function callback - Asynchronous response]
_G.TriggerClientCallback = function(args)
ensure(args, 'table'); ensure(args.source, 'string', 'number'); ensure(args.eventName, 'string'); ensure(args.args, 'table', 'nil'); ensure(args.timeout, 'number', 'nil'); ensure(args.timedout, 'function', 'nil'); ensure(args.callback, 'function', 'nil')
-- check if is a valid playerId [1-...]
if tonumber(args.source) >= 0 then
-- create a new ticket
local ticket = tostring(args.source) .. 'x' .. tostring(GetGameTimer())
-- create a new promise
local prom = promise.new()
-- save the callback function on this call
local eventCallback = args.callback
-- save the event data on this call
local eventData = RegisterNetEvent(('pmc__callback_retval:%s:%s:%s'):format(args.source, args.eventName, ticket), function(packed)
-- check if this call was async
-- & if promise wasn't rejected or resolved
if eventCallback and prom.state == PENDING then eventCallback( table_unpack(msgpack_unpack(packed)) ) end
prom:resolve( table_unpack(msgpack_unpack(packed)) )
end)
-- request the callback
TriggerClientEvent(('pmc__client_callback:%s'):format(args.eventName), args.source, msgpack_pack(args.args or {}), ticket)
-- timeout response
if args.timeout ~= nil and args.timedout then
local timedout = args.timedout
SetTimeout(args.timeout * 1000, function()
-- check if promise wasn't resolved
if
prom.state == PENDING or
prom.state == REJECTED or
prom.state == REJECTING
then
-- call the timeout callback
timedout(prom.state)
-- reject the promise
if prom.state == PENDING then prom:reject() end
-- remove the event handler
RemoveEventHandler(eventData)
end
end)
end
-- check if this call was async
if not eventCallback then
local result = Citizen.Await(prom)
-- remove the event handler
RemoveEventHandler(eventData)
return result
end
else
-- raise an error if source isn't valid
error 'source should be equal too or higher than 0'
end
end
--
-- @any TriggerServerCallback
-- Simulate a client callback
--
-- @string/number source - The simulated playerId that triggers
-- @string eventName - The name of the event to be fired
-- @table args - The arguments to be sent with the event
-- [@number timeout - Seconds to wait for response]
-- [@function timedout - The function that will be executed if timeout is reached]
-- [@function callback - Asynchronous response]
_G.TriggerServerCallback = function(args)
ensure(args, 'table'); ensure(args.source, 'string', 'number'); ensure(args.eventName, 'string'); ensure(args.args, 'table', 'nil'); ensure(args.timeout, 'number', 'nil'); ensure(args.timedout, 'function', 'nil'); ensure(args.callback, 'function', 'nil')
-- create a new promise
local prom = promise.new()
-- save the callback on this call
local eventCallback = args.callback
-- save the event name on this call
local eventName = args.eventName
TriggerEvent('pmc__server_callback:'..eventName, msgpack_pack(args.args or {}), args.source,
function(packed)
-- check if this call was async
-- & if promise wasn't rejected or resolved
if eventCallback and prom.state == PENDING then eventCallback( table_unpack(msgpack_unpack(packed)) ) end
prom:resolve( table_unpack(msgpack_unpack(packed)) )
end)
-- timeout response
if args.timeout ~= nil and args.timedout then
local timedout = args.timedout
SetTimeout(args.timeout * 1000, function()
-- check if promise wasn't resolved
if
prom.state == PENDING or
prom.state == REJECTED or
prom.state == REJECTING
then
-- call timeout callback
timedout(prom.state)
-- reject the promise
if prom.state == PENDING then prom:reject() end
end
end)
end
-- check if this call was async
if not eventCallback then
return Citizen.Await(prom)
end
end
end
-- CLIENT-SIDE
if not IS_SERVER then
local SERVER_ID = GetPlayerServerId(PlayerId())
--
-- @table RegisterClientCallback
--
-- @string eventName - The name of the event to be fired
-- @function eventCallback - The function to be executed when event is fired
_G.RegisterClientCallback = function(args)
ensure(args, 'table'); ensure(args.eventName, 'string'); ensure(args.eventCallback, 'function')
-- save the callback function on this call
local eventCallback = args.eventCallback
-- save the event name on this call
local eventName = args.eventName
-- save the event data to return
local eventData = RegisterNetEvent('pmc__client_callback:'..eventName, function(packed, ticket)
-- check if this call is simulated (TriggerClientCallback)
if type(ticket) == 'function' then
-- return the data to the simulated call
ticket( msgpack_pack_args( eventCallback( table_unpack(msgpack_unpack(packed)) ) ) )
else
-- return the data to the call
TriggerServerEvent(('pmc__callback_retval:%s:%s:%s'):format(SERVER_ID, eventName, ticket), msgpack_pack_args( eventCallback( table_unpack(msgpack_unpack(packed)) ) ))
end
end)
-- return event data so you can UnregisterClientCallback
return eventData
end
--
-- @void UnregisterClientCallback
--
-- @table eventData - The data from RegisterClientCallback
_G.UnregisterClientCallback = function(eventData)
RemoveEventHandler(eventData)
end
--
-- @any TriggerServerCallback
--
-- @string eventName - The name of the event to be fired
-- @table args - The arguments passed with the event
-- [@number timeout - Seconds to wait for response]
-- [@function timedout - The function that will be executed if timeout is reached]
-- [@function callback - Asynchronous response]
_G.TriggerServerCallback = function(args)
ensure(args, 'table'); ensure(args.args, 'table', 'nil'); ensure(args.eventName, 'string'); ensure(args.timeout, 'number', 'nil'); ensure(args.timedout, 'function', 'nil'); ensure(args.callback, 'function', 'nil')
-- create a new promise
local prom = promise.new()
-- save the callback function on this call
local eventCallback = args.callback
-- save the event data to remove it when resolved
local eventData = RegisterNetEvent(('pmc__client_callback_response:%s:%s'):format(args.eventName, SERVER_ID),
function(packed)
-- check if this call is async
-- & the promise wasn't rejected or resolved
if eventCallback and prom.state == PENDING then eventCallback( table_unpack(msgpack_unpack(packed)) ) end
prom:resolve( table_unpack(msgpack_unpack(packed)) )
end)
-- fire the callback event
TriggerServerEvent('pmc__server_callback:'..args.eventName, msgpack_pack( args.args ))
-- timeout response
if args.timeout ~= nil and args.timedout then
local timedout = args.timedout
SetTimeout(args.timeout * 1000, function()
-- check if the promise wasn't resolved yet
if
prom.state == PENDING or
prom.state == REJECTED or
prom.state == REJECTING
then
-- call the timeout callback
timedout(prom.state)
-- reject the promise if it wasn't rejected
if prom.state == PENDING then prom:reject() end
-- remove the event handler
RemoveEventHandler(eventData)
end
end)
end
-- check if this call is async
if not eventCallback then
local result = Citizen.Await(prom)
-- remove the event handler
RemoveEventHandler(eventData)
return result
end
end
--
-- @any TriggerClientCallback
-- Simulate a server callback
--
-- @string eventName - The name of the event to be fired
-- @table args - The arguments to be sent with the event
-- [@number timeout - Seconds to wait for response]
-- [@function timedout - The function that will be executed if timeout is reached]
-- [@function callback - Asynchronous response]
_G.TriggerClientCallback = function(args)
ensure(args, 'table'); ensure(args.eventName, 'string'); ensure(args.args, 'table', 'nil'); ensure(args.timeout, 'number', 'nil'); ensure(args.timedout, 'function', 'nil'); ensure(args.callback, 'function', 'nil')
-- create a new promise for this call
local prom = promise.new()
-- save the callback function on this call
local eventCallback = args.callback
-- save the event name on this call
local eventName = args.eventName
-- trigger the callback
TriggerEvent('pmc__client_callback:'..eventName, msgpack_pack(args.args or {}),
function(packed)
-- check if it was an async call
-- & if the promise wasn't rejected or already resolved
if eventCallback and prom.state == PENDING then eventCallback( table_unpack(msgpack_unpack(packed)) ) end
prom:resolve( table_unpack(msgpack_unpack(packed)) )
end)
-- timeout response
if args.timeout ~= nil and args.timedout then
local timedout = args.timedout
SetTimeout(args.timeout * 1000, function()
-- check if the promise wasn't resolved
if
prom.state == PENDING or
prom.state == REJECTED or
prom.state == REJECTING
then
-- call timeout callback
timedout(prom.state)
-- check if it's pending and reject
if prom.state == PENDING then prom:reject() end
end
end)
end
-- check if this call is async
if not eventCallback then
return Citizen.Await(prom)
end
end
end