nulllib-lua/sharedstate.lua

167 lines
4.9 KiB
Lua

local sharedstate={}
local logging=require((...):gsub("(.)$", "%1.") .. 'logging')
-- function names
local is_initialized, callback_value, set_value,
get_value, resolve_key, resolve_index
-- we're protecting internal variables here, i'm *that* scared of writing bad
-- code again.
-- this section is purely for making sure the tables are in a consistent state
-- maybe this should be a separate library for general state tracking, then i
-- could use it in other parts of my code for local state tracking
-- (aka how to unnecessarily increase complexity, if i want to do something i
-- can just make a function for it)
do
-- schema: {key: {value: val, old_value: val, callback: function,
-- index: integer}}
-- wrapping the value in a table should allow the user to store nil
local state_table={}
-- schema: {1: key_name_for_val_1, ...}
-- this is to save bandwidth in pings,
local state_map={}
--- Check if initialized
---Check if a key is initialized
---@param key string key
---@return true if initialized, else false
function is_initialized(key)
logging.trace('is_initialized', key)
return state_table[key] ~= nil
end
--- Internal; ensure key is initialized
---Properly initialize a key in all tables.
---already initialized
---@param key string key name
---@return boolean if key has been initialized
local function init_key(key)
logging.trace('init_key', key)
if key == nil then
local errormsg="sharedstate: A key name is required"
error(errormsg)
end
if is_initialized(key) then
return false
end
-- lua is fucking asinine and starts tables at 1
local index=#state_map+1
state_map[index]=key
local tbl={}
tbl["index"]=index
state_table[key]=tbl
return true
end
--- Run callback function
---Run the callback function of the given key
---@param key string Key
function callback_value(key)
logging.trace("callback_value", key)
local new_value=state_table[key]["value"]
local old_value=state_table[key]["old_value"]
if type(state_table[key]["callback"]) == "function"
and old_value ~= new_value then
state_table[key]["callback"](new_value, old_value)
end
end
--- Set value and run callback
-- Sets a value in the state table, initializing it if neede, and runs
-- the callback if it has been previously set
---@param key string key
---@param value any value
---@param callback? function callback function
function set_value(key, value, callback)
logging.trace("set_value", key, value, callback)
local initialized=init_key(key)
local entry=state_table[key]
if initialized then
entry["value"]=value
entry["old_value"]=value
entry["callback"]=callback
else
entry["value"]=value or entry["value"]
callback_value(key)
entry["callback"]=callback or entry["callback"]
entry["old_value"]=value
end
end
--- Get value
---Get a value from the state table
---@param key string key
---@return any value from state table if set, else nil
function get_value(key)
logging.trace("get_value", key)
if not is_initialized(key) then return nil end
return state_table[key]["value"]
end
--- Resolve key
---Resolve key of given index
---@param index integer index
---@return any key
function resolve_key(index)
logging.trace("resolve_key", index)
return state_map[index]
end
--- Resolve index
---Resolve index of given key
---@param key string key
---@return integer index
function resolve_index(key)
logging.trace("resolve_index", key)
return state_table[key]["index"]
end
end
---Add an item to the shared state store.
---Adds an item to the shared state store, as well as initializes it with a
---value. This does not send a ping and should only be used during avatar
---initialization.
---@param key string key name
---@param value any initial value
---@param callback? function Callback function to run on value change
function sharedstate.add(key, value, callback)
logging.trace("sharedstate.add", key, value, callback)
set_value(key, value, callback)
end
-- alias so i can decide what feels better
sharedstate.init=sharedstate.add
--- Internal; transfer value over network
---Ping used to transfer a value over the network, should not be called
---directly
---@param index integer Index of key
---@param value any New value
function pings.sharedstate_transfer(index, value)
logging.trace("pings.sharedstate_transfer", index, value)
set_value(resolve_key(index), value)
end
--- Set shared value
---Sets a shared value. This sends a ping to transfer it over the network.
---@param key string key name
---@param value any value
function sharedstate.set(key, value)
logging.trace("sharedstate.set", key, value)
if not is_initialized(key) then
-- this makes the error traceback less confusing
local errormsg="sharedstate: Key " .. key .. " has not been initialized."
error(errormsg)
end
pings.sharedstate_transfer(resolve_index(key), value)
end
-- this can be copied directly from the internal functions as it is read only
sharedstate.get=get_value
return sharedstate