diff --git a/expcore/store.lua b/expcore/store.lua index c176e757..f86d85a7 100644 --- a/expcore/store.lua +++ b/expcore/store.lua @@ -1,5 +1,68 @@ --- 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 Game = require 'utils.game' local Enum = ext_require('expcore.common','enum') local Store = { @@ -18,6 +81,28 @@ Global.register(Store.data,function(tbl) Store.data = tbl 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 + 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 + 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 + 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 + 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 @@ -42,6 +127,17 @@ function Store.register_location(location,store_type,update_callback) 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) + if not Store.locations[location] then + return error('Invalid store location: '..location,2) + end + location = Store.locations[location] + location.get_callback = get_callback +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 @@ -52,12 +148,14 @@ function Store.set_location(location,sub_location,value,...) return error('Invalid store location: '..location,2) end location = Store.locations[location] - if location.type == Store.types.game then - Store.data[location.location] = sub_location - elseif location.type ~= Store.types['local'] and location.type ~= Store.types.global then - Store.data[location.location][sub_location] = value + 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 - location.update_callback(sub_location,value,...) end --- Gets the value for a location @@ -70,11 +168,17 @@ function Store.get_location(location,sub_location,allow_invalid_location) 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 - return Store.data[location.location] + rtn = Store.data[location.location] elseif location.type ~= Store.types['local'] and location.type ~= Store.types.global then - return Store.data[location.location][sub_location] + 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 + return rtn end return Store \ No newline at end of file