mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2025-12-27 11:35:22 +09:00
365 lines
13 KiB
Lua
365 lines
13 KiB
Lua
|
|
local Event = require 'utils.event' --- @dep utils.event
|
|
|
|
local DatastoreManager = {}
|
|
local Datastores = {}
|
|
local Datastore = {}
|
|
local Data = {}
|
|
local copy = table.deep_copy
|
|
|
|
--- Save datastores in the global table
|
|
global.datastores = Data
|
|
Event.on_load(function()
|
|
Data = global.datastores
|
|
for tableName, datastore in pairs(Datastores) do
|
|
datastore.data = Data[tableName]
|
|
end
|
|
end)
|
|
|
|
----- Datastore Manager -----
|
|
-- @section datastoreManager
|
|
|
|
--- Metatable used on datastores
|
|
DatastoreManager.metatable = {
|
|
__index = function(self, key) return rawget(self.children, key) or rawget(Datastore, key) end,
|
|
__newidnex = function(_, _, _) error('Datastore can not be modified', 2) end,
|
|
__call = function(self, ...) return self:get(...) end
|
|
}
|
|
|
|
--- Make a new datastore connection, if a connection already exists then it is returned
|
|
function DatastoreManager.connect(tableName, saveToDisk, autoSave, propagateChanges)
|
|
if Datastores[tableName] then return Datastores[tableName] end
|
|
if _LIFECYCLE ~= _STAGE.control then
|
|
-- Only allow this function to be called during the control stage
|
|
error('New datastore connection can not be created during runtime', 2)
|
|
end
|
|
|
|
local new_datastore = {
|
|
name = tableName,
|
|
table_name = tableName,
|
|
auto_save = autoSave or false,
|
|
save_to_disk = saveToDisk or false,
|
|
propagate_changes = propagateChanges or false,
|
|
serializer = false,
|
|
parent = false,
|
|
children = {},
|
|
metadata = {},
|
|
events = {},
|
|
data = {}
|
|
}
|
|
|
|
Data[tableName] = new_datastore.data
|
|
Datastores[tableName] = new_datastore
|
|
return setmetatable(new_datastore, DatastoreManager.metatable)
|
|
end
|
|
|
|
--- Make a new datastore that stores its data inside of another one
|
|
function DatastoreManager.combine(datastore, subTableName)
|
|
local new_datastore = DatastoreManager.connect(datastore.name..'.'..subTableName)
|
|
datastore.children[subTableName] = new_datastore
|
|
new_datastore.serializer = datastore.serializer
|
|
new_datastore.auto_save = datastore.auto_save
|
|
new_datastore.table_name = subTableName
|
|
new_datastore.parent = datastore
|
|
Data[new_datastore.name] = nil
|
|
new_datastore.data = nil
|
|
return new_datastore
|
|
end
|
|
|
|
--- Ingest the result from a request, this is used through a rcon interface to sync data
|
|
local function ingest_error(err) print('Datastore ingest error, Unable to parse json:', err) end
|
|
function DatastoreManager.ingest(action, tableName, key, valueJson)
|
|
local datastore = assert(Datastores[tableName], 'Datastore ingest error, Datastore not found '..tostring(tableName))
|
|
assert(type(action) == 'string', 'Datastore ingest error, Action is not a string got: '..type(action))
|
|
assert(type(key) == 'string', 'Datastore ingest error, Key is not a string got: '..type(key))
|
|
|
|
if action == 'remove' then
|
|
datastore:raw_set(key)
|
|
|
|
elseif action == 'message' then
|
|
local success, value = xpcall(game.json_to_table, ingest_error, valueJson)
|
|
if not success or value == nil then return end
|
|
datastore:raise_event('on_message', key, value)
|
|
|
|
elseif action == 'propagate' then
|
|
local success, value = xpcall(game.json_to_table, ingest_error, valueJson)
|
|
if not success or value == nil then return end
|
|
value = datastore:raise_event('on_load', key, value)
|
|
datastore:set(key, value)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
--- Debug, Use to get all datastores, or return debug info on a datastore
|
|
function DatastoreManager.debug(tableName)
|
|
if not tableName then return Datastores end
|
|
local datastore = assert(Datastores[tableName], 'Datastore not found '..tostring(tableName))
|
|
return datastore:debug()
|
|
end
|
|
|
|
--- Commonly used serializer, returns the name of the object
|
|
function DatastoreManager.name_serializer(rawKey)
|
|
return rawKey.name
|
|
end
|
|
|
|
----- Datastore -----
|
|
-- @section datastore
|
|
|
|
--- Debug, Get the debug info for this datastore
|
|
function Datastore:debug()
|
|
local debug_info = {}
|
|
|
|
if self.parent then
|
|
debug_info.parent = self.parent.name
|
|
else
|
|
debug_info.settings = { auto_save = self.auto_save, save_to_disk = self.save_to_disk, propagate_changes = self.propagate_changes, serializer = not not self.serializer }
|
|
end
|
|
|
|
local children = {}
|
|
for name in pairs(self.children) do children[#children+1] = name end
|
|
if #children > 0 then debug_info.children = children end
|
|
|
|
local events = {}
|
|
for name, handlers in pairs(self.events) do events[name] = #handlers end
|
|
if next(events) then debug_info.events = events end
|
|
|
|
if next(self.metadata) then debug_info.metadata = self.metadata end
|
|
debug_info.data = self:get_all()
|
|
|
|
return debug_info
|
|
end
|
|
|
|
--- Internal, Get data following combine logic
|
|
function Datastore:raw_get(key, fromChild)
|
|
local data = self.data
|
|
if self.parent then
|
|
data = self.parent:raw_get(key, true)
|
|
key = self.table_name
|
|
end
|
|
local value = data[key]
|
|
if value ~= nil then return value end
|
|
if fromChild then value = {} end
|
|
data[key] = value
|
|
return value
|
|
end
|
|
|
|
--- Internal, Set data following combine logic
|
|
function Datastore:raw_set(key, value)
|
|
if self.parent then
|
|
local data = self.parent:raw_get(key, true)
|
|
data[self.table_name] = value
|
|
else
|
|
self.data[key] = value
|
|
end
|
|
end
|
|
|
|
--- Internal, Return the serialized key
|
|
local function serialize_error(err) error('An error ocurred in a datastore serializer: '..err) end
|
|
function Datastore:serialize(rawKey)
|
|
if type(rawKey) == 'string' then return rawKey end
|
|
assert(self.serializer, 'Datastore does not have a serializer and received non string key')
|
|
local success, key = xpcall(self.serializer, serialize_error, rawKey)
|
|
return success and key or nil
|
|
end
|
|
|
|
--- Internal, Writes an event to the output file to be saved and/or propagated
|
|
function Datastore:write_action(action, key, value)
|
|
local data = {action, self.name, '"'..key..'"'}
|
|
if value ~= nil then
|
|
data[4] = type(value) == 'table' and '"'..game.table_to_json(value)..'"' or '"'..tostring(value)..'"'
|
|
end
|
|
game.write_file('datastore.pipe', table.concat(data, ' ')..'\n', true, 0)
|
|
end
|
|
|
|
--- Set a callback that will be used to serialize keys which aren't strings
|
|
function Datastore:set_serializer(callback)
|
|
assert(type(callback) == 'function', 'Callback must be a function')
|
|
self.serializer = callback
|
|
end
|
|
|
|
--- Set metadata tags on this datastore which can be accessed by other scripts
|
|
function Datastore:set_metadata(tags)
|
|
local metadata = self.metadata
|
|
for key, value in pairs(tags) do
|
|
metadata[key] = value
|
|
end
|
|
end
|
|
|
|
--- Create a new datastore which is stores its data inside of this datastore
|
|
Datastore.combine = DatastoreManager.combine
|
|
|
|
--- Request a value from an external source, will trigger on_load when data is received
|
|
function Datastore:request(key)
|
|
if self.parent then return self.parent:request(key) end
|
|
key = self:serialize(key)
|
|
self:write_action('request', key)
|
|
end
|
|
|
|
--- Save a value to an external source, will trigger on_save before data is saved, save_to_disk must be set to true
|
|
function Datastore:save(key)
|
|
if self.parent then self.parent:save(key) end
|
|
if not self.save_to_disk then return end
|
|
key = self:serialize(key)
|
|
local value = self:raise_event('on_save', key, copy(self:raw_get(key)))
|
|
local action = self.propagate_changes and 'propagate' or 'save'
|
|
self:write_action(action, key, value)
|
|
end
|
|
|
|
--- Save a value to an external source and remove locally, will trigger on_unload then on_save, save_to_disk is not required for on_unload
|
|
function Datastore:unload(key)
|
|
if self.parent then return self.parent:unload(key) end
|
|
key = self:serialize(key)
|
|
self:raise_event('on_unload', key, copy(self:raw_get(key)))
|
|
self:save(key)
|
|
self:raw_set(key)
|
|
end
|
|
|
|
--- Use to send a message over the connection, works regardless of saveToDisk and propagateChanges
|
|
function Datastore:message(key, message)
|
|
key = self:serialize(key)
|
|
self:write_action('message', key, message)
|
|
end
|
|
|
|
--- Remove a value locally and on the external source, works regardless of propagateChanges
|
|
function Datastore:remove(key)
|
|
key = self:serialize(key)
|
|
self:raw_set(key)
|
|
self:write_action('remove', key)
|
|
if self.parent and self.parent.auto_save then return self.parent:save(key) end
|
|
end
|
|
|
|
--- Get a value from local storage, option to have a default value
|
|
function Datastore:get(key, default)
|
|
key = self:serialize(key)
|
|
local value = self:raw_get(key)
|
|
if value ~= nil then return value end
|
|
return copy(default)
|
|
end
|
|
|
|
--- Set a value in local storage, will trigger on_update then on_save, save_to_disk and auto_save is required for on_save
|
|
function Datastore:set(key, value)
|
|
key = self:serialize(key)
|
|
self:raw_set(key, value)
|
|
self:raise_event('on_update', key, value)
|
|
if self.auto_save then self:save(key) end
|
|
return value
|
|
end
|
|
|
|
--- Increment the value in local storage, only works for number values, will trigger on_update then on_save, save_to_disk and auto_save is required for on_save
|
|
function Datastore:increment(key, delta)
|
|
key = self:serialize(key)
|
|
local value = self:raw_get(key) or 0
|
|
return Datastore:set(key, value + (delta or 1))
|
|
end
|
|
|
|
--- Use a function to update the value locally, will trigger on_update then on_save, save_to_disk and auto_save is required for on_save
|
|
local function update_error(err) error('An error ocurred in datastore update: '..err, 2) end
|
|
function Datastore:update(key, callback)
|
|
key = self:serialize(key)
|
|
local value = self:raw_get(key)
|
|
local success, new_value = xpcall(callback, update_error, key, value)
|
|
if success and new_value ~= nil then
|
|
self:set(key, new_value)
|
|
else
|
|
self:raise_event('on_update', key, value)
|
|
if self.auto_save then self:save(key) end
|
|
end
|
|
end
|
|
|
|
--- Internal, Used to filter elements from a table
|
|
local function filter_error(err) print('An error ocurred in a datastore filter:', err) end
|
|
local function filter(tbl, callback)
|
|
if not callback then return tbl end
|
|
local rtn = {}
|
|
for key, value in pairs(tbl) do
|
|
local success, add = xpcall(callback, filter_error, key, value)
|
|
if success and add then rtn[key] = value end
|
|
end
|
|
return rtn
|
|
end
|
|
|
|
--- Get all keys in this datastore, optional filter callback
|
|
function Datastore:get_all(callback)
|
|
if not self.parent then
|
|
return filter(self.data, callback)
|
|
else
|
|
local data, table_name = {}, self.table_name
|
|
for key, value in pairs(self.parent:get_all()) do
|
|
data[key] = value[table_name]
|
|
end
|
|
return filter(data, callback)
|
|
end
|
|
end
|
|
|
|
--- Save all the keys in the datastore, optional filter callback
|
|
function Datastore:save_all(callback)
|
|
local data = self:get_all(callback)
|
|
for key in pairs(data) do self:save(key) end
|
|
end
|
|
|
|
--- Unload all the keys in the datastore, optional filter callback
|
|
function Datastore:unload_all(callback)
|
|
local data = self:get_all(callback)
|
|
for key in pairs(data) do self:unload(key) end
|
|
end
|
|
|
|
----- Events -----
|
|
-- @section events
|
|
|
|
--- Internal, Raise an event on this datastore
|
|
local function event_error(err) print('An error ocurred in a datastore event handler:', err) end
|
|
function Datastore:raise_event(event_name, key, value, source)
|
|
-- Raise the event for the children of this datastore
|
|
if source ~= 'child' then
|
|
for table_name, child in pairs(self.children) do
|
|
value[table_name] = child:raise_event(event_name, key, value[table_name], 'parent')
|
|
end
|
|
end
|
|
|
|
-- Raise the event for this datastore
|
|
local handlers = self.events[event_name]
|
|
if handlers then
|
|
for _, handler in ipairs(handlers) do
|
|
local success, new_value = xpcall(handler, event_error, key, value)
|
|
if success and new_value ~= nil then value = new_value end
|
|
end
|
|
end
|
|
|
|
-- Raise the event for the parent of this datastore
|
|
if source ~= 'parent' and self.parent then
|
|
self.parent:raise_event(event_name, key, self.parent:raw_get(key), 'child')
|
|
end
|
|
return value
|
|
end
|
|
|
|
--- Internal, Returns a function which will add a callback to an event
|
|
local function event_factory(event_name)
|
|
return function(self, callback)
|
|
assert(type(callback) == 'function', 'Handler must be a function')
|
|
local handlers = self.events[event_name]
|
|
if not handlers then
|
|
self.events[event_name] = { callback }
|
|
else
|
|
handlers[#handlers+1] = callback
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Register a callback that triggers when data is loaded from an external source, returned value is saved locally
|
|
Datastore.on_load = event_factory('on_load')
|
|
|
|
--- Register a callback that triggers before data is saved, returned value is saved externally
|
|
Datastore.on_save = event_factory('on_save')
|
|
|
|
--- Register a callback that triggers before data is unloaded, returned value is ignored
|
|
Datastore.on_unload = event_factory('on_unload')
|
|
|
|
--- Register a callback that triggers when a message is received, returned value is ignored
|
|
Datastore.on_message = event_factory('on_message')
|
|
|
|
--- Register a callback that triggers any time a value is changed, returned value is ignored
|
|
Datastore.on_update = event_factory('on_update')
|
|
|
|
----- Module Return -----
|
|
return DatastoreManager |