From 9a9284df028ea6d16d0b6f73833b2c033dfdf26c Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Sat, 4 May 2019 16:17:06 +0100 Subject: [PATCH] Store Refactor --- expcore/store.lua | 217 +++++++++++++++------------------------------- 1 file changed, 69 insertions(+), 148 deletions(-) diff --git a/expcore/store.lua b/expcore/store.lua index f86d85a7..83ed9299 100644 --- a/expcore/store.lua +++ b/expcore/store.lua @@ -1,184 +1,105 @@ ---- A system which stores peristent data and makes it easy to sync updates between changes - ---[[ ->>>> What the system is for - This module is made so that data can be saved easily in global and have data be synced for the player, force or game. - This means that when the value is updated the callback will be rasied with object which it effects eg when set to force - type each force will have a stored value and when it changes the force and the new value will be passed to the update callback - the same can be done for a surface or players. In a sence this is just an easy way to keep data that can be updated at any time - and not have confilcts with other data; for example force settings changed in a gui. - ->>>> How to register a location - Every location that is used must be registed with an update_callback which will be called when the value is updated, to do this you - must use Store.register_location and pass a string that referses to the location (must be unqiue) for example 'settings.force' might be - the location where you store custom settings for a force and 'settings.force.manual_mining_speed_modifier' will be a value for the mining speed. - Note: these are not connected directly to the objects you need your own way to update the stored value. - - Continuing with this example you would want to have the store_type as 'force' which will mean that sub_location will refer to different - force names, this way each force will have its own store of data all at the same location. The store_type has two speaial cases: local and - game (global not yet impimented) where 'local' will not store any data and acts as a reditrect to update_callback and can be used when you - dont want any persitent data but still have mutiple places where the value will be updated from. 'game' will have only a single stored value - and is because of this will not have a sub_location. - - Finaly the update_callback is the method which you should use to watch for updates to the value from any source, for all types (apart from the - 2 speaial types listed above) the first param will be the object that it conceners ie the player, force or surface and the second param will be - the new value that is set. For game and local there is no sub_location and so there is no object that is passed so the first param is the value - that was set. Note that they may be more args after these which were passed from the set_location call these are for your own use. - - Example: - - Store.register_location('settings.force.manual_mining_speed_modifier','force',function(force,value) - force.manual_mining_speed_modifier = value - end) - ->>>> When should I use a getter - Some types of data (such as the mining speed for a force) lend them selfs nicely to using a getter function, where unless a value was set then - the getter function will be used to get the value for get_location. This is basicly a way to have a default value for the store when no calls - have been made to set a value. - - Note that using a getter function does not mean the store will listen for updates for the returned value of this function. - - Example: - - Store.register_getter('settings.force.manual_mining_speed_modifier',function(force) - return force.manual_mining_speed_modifier - end) - ->>>> Getting and setting values - Once a location is registered you can use the get and set location functions this will allow new values to be set at a location and to retive the current - or default (via getter if present). - - Example: - - Store.set_location('settings.force.manual_mining_speed_modifier',game.player.force,5) - - Store.get_location('settings.force.manual_mining_speed_modifier',game.player.force) - ->>>> Functions: - Store.register_location(location,store_type,update_callback) --- Registers a new store location - Store.register_getter(location,get_callback) --- Registers an optional getter funtion that will return a value when the stored value is nil - Store.set_location(location,sub_location,value,...) --- Sets a new value for a location, will trigger the update callback - Store.get_location(location,sub_location,allow_invalid_location) --- Gets the value for a location -]] - local Global = require 'utils.global' +local Event = require 'utils.event' local Game = require 'utils.game' local Enum = ext_require('expcore.common','enum') local Store = { - data = {}, - locations = {}, + data={}, + locations={}, types = Enum{ - 'local', -- no persistent data, only triggers update_callback - 'player', -- each player has they own sub_location - 'force', -- each force has its own sub_location - 'surface', -- each surface has its own sub_location - 'game', -- the entrie game has a single store of data - 'global' -- not yet impimented, data will sync between all servers + 'local', -- data is not stored with any sub_location, updates caused only by set + 'player', -- data is stroed per player, updates caused by watch and set + 'force', -- data is stroed per force, updates caused by watch and set + 'surface', -- data is stroed per surface, updates caused by watch and set + 'game', -- data is stored with any sub_location, updates caused by watch and set + 'global' -- data is stored externaly with any sub_location, updates casued by watch, set and the external source } } Global.register(Store.data,function(tbl) - Store.data = tbl + Store.data = table end) -local function get_sub_location(type,sub_location,value) - if location.type == Store.types['local'] then - return nil,sub_location - elseif location.type == Store.types.player then +local function get_sub_location_object(store_type,sub_location) + if store_type == Store.types.player then sub_location = Game.get_player_from_any(sub_location) if not sub_location then return error('Invalid player for sub_location',3) end - return sub_location,value - elseif location.type == Store.types.force then + return sub_location + elseif store_type == Store.types.force then sub_location = type(sub_location) == 'table' and type(sub_location.__self) == 'userdata' and sub_location or game.forces[sub_location] if not sub_location then return error('Invalid force for sub_location',3) end - return sub_location,value - elseif location.type == Store.types.surface then + return sub_location + elseif store_type == Store.types.surface then sub_location = type(sub_location) == 'table' and type(sub_location.__self) == 'userdata' and sub_location or game.surfaces[sub_location] if not sub_location then return error('Invalid surface for sub_location',3) end - return sub_location,value - elseif location.type == Store.types.game then - return nil,sub_location - elseif location.type == Store.types.glboal then - return nil,sub_location + return sub_location end end ---- Registers a new store location --- @tparam location string a unique location string that will hold the data --- @tparam type string see Store.types --- @tparam update_callback function the function which will be called with the new value that is set -function Store.register_location(location,store_type,update_callback) - if Store.locations[location] then - store_type = Store.locations[location] - store_type = type(store_type) == 'number' and Store.types(store_type) or store_type - return error('The location is already registed: '..location..' and is type: '..store_type,2) +local function set_global_location_value(location,sub_location,value) + -- not yet impimented, this will emit to a file in some way to set the value in an external database +end + +function Store.register(location,store_type,getter,setter,no_error) + if not no_error and Store.locations[location] then + return error('The location is already registed: '..location,2) end - if not Store.type[type] then - return error('Attempted to set invlid type: '..type..' for location: '..location,2) - end - store_type = type(store_type) == 'string' and Store.types(store_type) or store_type + store_type = type(store_type) == 'string' and Store.types[store_type] or store_type Store.locations[location] = { location=location, - type=store_type, - update_callback=update_callback + store_type=store_type, + getter=getter, + setter=setter } - if store_type ~= Store.types['local'] and store_type ~= Store.types.global then - Store.data[location] = {} - end end ---- Registers an optional getter funtion that will return a value when the stored value is nil --- @tparam location string the location to set the data at, must be registed --- @tparam get_callback function the function that will be called to return the value -function Store.register_getter(location,get_callback) +function Store.set(location,sub_location,value) if not Store.locations[location] then - return error('Invalid store location: '..location,2) + return error('The location is not registed: '..location) end location = Store.locations[location] - location.get_callback = get_callback + local sub_location_object = get_sub_location_object(location.store_type,sub_location) + if location.store_type ~= Store.types['local'] then + if not Store.data[location.location] then Store.data[location.location] = {} end + Store.data[location.location][sub_location] = value + end + if location.store_type == Store.types.global then + set_global_location_value(location.location,value) + end + location.setter(sub_location_object or sub_location,value) end ---- Sets a new value for a location, will trigger the update callback --- @tparam location string the location to set the data at, must be registed --- @tparam[opt] sub_location string a second location value that can be a player's name force name etc --- @tparam value any the value to be stored, passed via sublocation if sub_location is not required --- @tparam[opt] ... any any more values that you want to pass to the update callback -function Store.set_location(location,sub_location,value,...) - if not Store.locations[location] then - return error('Invalid store location: '..location,2) - end +function Store.get(location,sub_location) + if not Store.locations[location] then return end location = Store.locations[location] - local _sub_location,_value = get_sub_location(type,sub_location,value) - if _sub_location then - Store.data[location][_sub_location] = _value - location.update_callback(_sub_location,_value,...) - else - Store.data[location] = _value - location.update_callback(_value,...) - end -end - ---- Gets the value for a location --- @tparam location string the location to set the data at, must be registed --- @tparam[opt] sub_location string a second location value that can be a player's name force name etc --- @tparam[opt=false] allow_invalid_location boolean when true will not error when location is invalid --- @treturn any the value found at that location -function Store.get_location(location,sub_location,allow_invalid_location) - if not Store.locations[location] then - return not allow_invalid_location and error('Invalid store location: '..location,2) or nil - end - location = Store.locations[location] - local rtn - if location.type == Store.types.game then - rtn = Store.data[location.location] - elseif location.type ~= Store.types['local'] and location.type ~= Store.types.global then - rtn = Store.data[location.location][sub_location] - end - if rtn == nil and location.get_callback then - sub_location = get_sub_location(location.type,sub_location) - rtn = location.get_callback(sub_location) - end + local sub_location_object = get_sub_location_object(location.store_type,sub_location) + local rtn = Store.data[location.location][sub_location] + if rtn == nil then rtn = location.getter(sub_location_object or sub_location) end return rtn end +function Store.check(location,sub_location) + if not Store.locations[location] then return false end + location = Store.locations[location] + local sub_location_object = get_sub_location_object(location.store_type,sub_location) + local store,getter = Store.data[location.location][sub_location],location.getter(sub_location_object or sub_location) + if store ~= getter then + if not Store.data[location.location] then Store.data[location.location] = {} end + Store.data[location.location][sub_location] = getter + location.setter(sub_location_object or sub_location,getter) + return true + end + return false +end + +Event.on_nth_tick(60,function() + for _,location in pairs(Store.locations) do + if location.store_type ~= Store.types['local'] then + if not Store.data[location.location] then Store.data[location.location] = {} end + for sub_location,_ in pairs(Store.data[location.location]) do + Store.check(location,sub_location) + end + end + end +end) + return Store \ No newline at end of file