74 lines
2.0 KiB
Plaintext
74 lines
2.0 KiB
Plaintext
--!strict
|
|
|
|
--[[
|
|
Time-based contextual values, to allow for transparently passing values down
|
|
the call stack.
|
|
]]
|
|
|
|
local Types = require "../Types"
|
|
-- Logging
|
|
local logError = require "../Logging/logError"
|
|
local parseError = require "../Logging/parseError"
|
|
|
|
local class = {}
|
|
|
|
local CLASS_METATABLE = { __index = class }
|
|
local WEAK_KEYS_METATABLE = { __mode = "k" }
|
|
|
|
--[[
|
|
Returns the current value of this contextual.
|
|
]]
|
|
function class:now(): unknown
|
|
local thread = coroutine.running()
|
|
local value = self._valuesNow[thread]
|
|
if typeof(value) ~= "table" then
|
|
return self._defaultValue
|
|
else
|
|
return value.value :: unknown
|
|
end
|
|
end
|
|
|
|
--[[
|
|
Temporarily assigns a value to this contextual.
|
|
]]
|
|
function class:is(newValue: unknown)
|
|
local methods = {}
|
|
-- Methods use colon `:` syntax for consistency and autocomplete but we
|
|
-- actually want them to operate on the `self` from this outer lexical scope
|
|
local contextual = self
|
|
|
|
function methods:during<T, A...>(callback: (A...) -> T, ...: A...): T
|
|
local thread = coroutine.running()
|
|
local prevValue = contextual._valuesNow[thread]
|
|
-- Storing the value in this format allows us to distinguish storing
|
|
-- `nil` from not calling `:during()` at all.
|
|
contextual._valuesNow[thread] = { value = newValue }
|
|
-- local ok, value = xpcall(callback, parseError, ...)
|
|
local ok, value = pcall(callback, ...)
|
|
|
|
contextual._valuesNow[thread] = prevValue
|
|
if ok then
|
|
return value
|
|
else
|
|
logError("contextualCallbackError", parseError(value))
|
|
end
|
|
end
|
|
|
|
return methods
|
|
end
|
|
|
|
local function Contextual<T>(defaultValue: T): Types.Contextual<T>
|
|
local self = setmetatable({
|
|
type = "Contextual",
|
|
-- if we held strong references to threads here, then if a thread was
|
|
-- killed before this contextual had a chance to finish executing its
|
|
-- callback, it would be held strongly in this table forever
|
|
_valuesNow = setmetatable({}, WEAK_KEYS_METATABLE),
|
|
_defaultValue = defaultValue,
|
|
}, CLASS_METATABLE)
|
|
|
|
return self
|
|
end
|
|
|
|
return Contextual
|