feat(init): cache.__call metamethod for caching function results

See #385.
Included formatting changes (tabs->spaces).
This commit is contained in:
Linden
2023-08-02 15:16:37 +10:00
parent 05c9d335e8
commit b68da917be

205
init.lua
View File

@@ -1,16 +1,17 @@
-- ox_lib <https://github.com/overextended/ox_lib> ---@meta
-- Copyright (C) 2021 Linden <https://github.com/thelindat> ---ox_lib <https://github.com/overextended/ox_lib>
-- LGPL-3.0-or-later <https://www.gnu.org/licenses/lgpl-3.0.en.html> ---Copyright (C) 2021 Linden <https://github.com/thelindat>
---LGPL-3.0-or-later <https://www.gnu.org/licenses/lgpl-3.0.en.html>
if not _VERSION:find('5.4') then if not _VERSION:find('5.4') then
error('^1Lua 5.4 must be enabled in the resource manifest!^0', 2) error('^1Lua 5.4 must be enabled in the resource manifest!^0', 2)
end end
local ox_lib = 'ox_lib' local ox_lib = 'ox_lib'
local export = exports[ox_lib] local export = exports[ox_lib]
if not GetResourceState(ox_lib):find('start') then if not GetResourceState(ox_lib):find('start') then
error('^1ox_lib should be started before this resource.^0', 2) error('^1ox_lib should be started before this resource.^0', 2)
end end
local status = export.hasLoaded() local status = export.hasLoaded()
@@ -30,25 +31,25 @@ local context = IsDuplicityVersion() and 'server' or 'client'
function noop() end function noop() end
local function loadModule(self, module) local function loadModule(self, module)
local dir = ('imports/%s'):format(module) local dir = ('imports/%s'):format(module)
local chunk = LoadResourceFile(ox_lib, ('%s/%s.lua'):format(dir, context)) local chunk = LoadResourceFile(ox_lib, ('%s/%s.lua'):format(dir, context))
local shared = LoadResourceFile(ox_lib, ('%s/shared.lua'):format(dir)) local shared = LoadResourceFile(ox_lib, ('%s/shared.lua'):format(dir))
if shared then if shared then
chunk = (chunk and ('%s\n%s'):format(shared, chunk)) or shared chunk = (chunk and ('%s\n%s'):format(shared, chunk)) or shared
end end
if chunk then if chunk then
local fn, err = load(chunk, ('@@ox_lib/%s/%s.lua'):format(module, context)) local fn, err = load(chunk, ('@@ox_lib/%s/%s.lua'):format(module, context))
if not fn or err then if not fn or err then
return error(('\n^1Error importing module (%s): %s^0'):format(dir, err), 3) return error(('\n^1Error importing module (%s): %s^0'):format(dir, err), 3)
end end
local result = fn() local result = fn()
self[module] = result or noop self[module] = result or noop
return self[module] return self[module]
end end
end end
----------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------
@@ -56,37 +57,37 @@ end
----------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------
local function call(self, index, ...) local function call(self, index, ...)
local module = rawget(self, index) local module = rawget(self, index)
if not module then if not module then
self[index] = noop self[index] = noop
module = loadModule(self, index) module = loadModule(self, index)
if not module then if not module then
local function method(...) local function method(...)
return export[index](nil, ...) return export[index](nil, ...)
end end
if not ... then if not ... then
self[index] = method self[index] = method
end end
return method return method
end end
end end
return module return module
end end
lib = setmetatable({ lib = setmetatable({
name = ox_lib, name = ox_lib,
context = context, context = context,
onCache = function(key, cb) onCache = function(key, cb)
AddEventHandler(('ox_lib:cache:%s'):format(key), cb) AddEventHandler(('ox_lib:cache:%s'):format(key), cb)
end end
}, { }, {
__index = call, __index = call,
__call = call, __call = call,
}) })
-- Override standard Lua require with our own. -- Override standard Lua require with our own.
@@ -98,86 +99,112 @@ local intervals = {}
---@param interval? number ---@param interval? number
---@param ... any ---@param ... any
function SetInterval(callback, interval, ...) function SetInterval(callback, interval, ...)
interval = interval or 0 interval = interval or 0
if type(interval) ~= 'number' then if type(interval) ~= 'number' then
return error(('Interval must be a number. Received %s'):format(json.encode(interval --[[@as unknown]]))) return error(('Interval must be a number. Received %s'):format(json.encode(interval --[[@as unknown]])))
end end
local cbType = type(callback) local cbType = type(callback)
if cbType == 'number' and intervals[callback] then if cbType == 'number' and intervals[callback] then
intervals[callback] = interval or 0 intervals[callback] = interval or 0
return return
end end
if cbType ~= 'function' then if cbType ~= 'function' then
return error(('Callback must be a function. Received %s'):format(cbType)) return error(('Callback must be a function. Received %s'):format(cbType))
end end
local args, id = { ... } local args, id = { ... }
Citizen.CreateThreadNow(function(ref) Citizen.CreateThreadNow(function(ref)
id = ref id = ref
intervals[id] = interval or 0 intervals[id] = interval or 0
repeat repeat
interval = intervals[id] interval = intervals[id]
Wait(interval) Wait(interval)
callback(table.unpack(args)) callback(table.unpack(args))
until interval < 0 until interval < 0
intervals[id] = nil intervals[id] = nil
end) end)
return id return id
end end
---@param id number ---@param id number
function ClearInterval(id) function ClearInterval(id)
if type(id) ~= 'number' then if type(id) ~= 'number' then
return error(('Interval id must be a number. Received %s'):format(json.encode(id --[[@as unknown]]))) return error(('Interval id must be a number. Received %s'):format(json.encode(id --[[@as unknown]])))
end end
if not intervals[id] then if not intervals[id] then
return error(('No interval exists with id %s'):format(id)) return error(('No interval exists with id %s'):format(id))
end end
intervals[id] = -1 intervals[id] = -1
end end
----------------------------------------------------------------------------------------------- --[[
-- Cache lua language server doesn't support generics when using @overload
----------------------------------------------------------------------------------------------- see https://github.com/LuaLS/lua-language-server/issues/723
this function stub allows the following to work
local key = cache('key', function() return 'abc' end) -- fff: 'abc'
local game = cache.game -- game: string
]]
cache = { game = GetGameName(), resource = GetCurrentResourceName() } ---@generic T
local notify = ('__ox_notify_%s'):format(cache.resource) ---@param key string
---@param func fun(...: any): T
---@param timeout? number
---@return T
---Caches the result of a function, optionally clearing it after timeout ms.
function cache(key, func, timeout) end
cache = setmetatable({ game = GetGameName(), resource = GetCurrentResourceName() }, {
__index = context == 'client' and function(self, key)
AddEventHandler(('ox_lib:cache:%s'):format(key), function(value)
self[key] = value
end)
return rawset(self, key, export.cache(nil, key) or false)[key]
end or nil,
__call = function(self, key, func, timeout)
local value = rawget(self, key)
if not value then
value = func()
rawset(self, key, value)
if timeout then SetTimeout(timeout, function() self[key] = nil end) end
end
return value
end,
})
local notifyEvent = ('__ox_notify_%s'):format(cache.resource)
if context == 'client' then if context == 'client' then
setmetatable(cache, { RegisterNetEvent(notifyEvent, function(data)
__index = function(self, key) if locale then
AddEventHandler(('ox_lib:cache:%s'):format(key), function(value) if data.title then
self[key] = value data.title = locale(data.title) or data.title
end) end
return rawset(self, key, export.cache(nil, key) or false)[key] if data.description then
end, data.description = locale(data.description) or data.description
}) end
end
RegisterNetEvent(notify, function(data) return export:notify(data)
if locale then end)
if data.title then
data.title = locale(data.title) or data.title
end
if data.description then cache.playerId = PlayerId()
data.description = locale(data.description) or data.description cache.serverId = GetPlayerServerId(cache.playerId)
end
end
return export:notify(data)
end)
cache.playerId = PlayerId()
cache.serverId = GetPlayerServerId(cache.playerId)
else else
---`server`\ ---`server`\
---Trigger a notification on the target playerId from the server.\ ---Trigger a notification on the target playerId from the server.\
@@ -187,7 +214,7 @@ else
---@param data NotifyProps ---@param data NotifyProps
---@deprecated ---@deprecated
---@diagnostic disable-next-line: duplicate-set-field ---@diagnostic disable-next-line: duplicate-set-field
function lib.notify(playerId, data) function lib.notify(playerId, data)
TriggerClientEvent(notify, playerId, data) TriggerClientEvent(notifyEvent, playerId, data)
end end
end end