From bd93c703d5611a92623fdb4568286dd6100b72ab Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Fri, 3 May 2019 21:47:27 +0100 Subject: [PATCH 1/9] Added Store --- expcore/common.lua | 73 ++++++++++++++++++++++++++++++++++++---------- expcore/store.lua | 72 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 16 deletions(-) create mode 100644 expcore/store.lua diff --git a/expcore/common.lua b/expcore/common.lua index 481fc4b1..38ef5276 100644 --- a/expcore/common.lua +++ b/expcore/common.lua @@ -7,12 +7,33 @@ Public.type_check_error(value,test_type,error_message,level) --- Raises an error if the value is of the incorrect type Public.param_check(value,test_type,param_name,param_number) --- Raises an error when the value is the incorrect type, uses a consistent error message format - Public.extract_keys(tbl,...) --- Extracts certain keys from a table - Public.player_return(value,colour,player) --- Will return a value of any type to the player/server console, allows colour for in-game players Public.opt_require(path) --- Calls a require that will not error if the file is not found Public.ext_require(path,...) --- Calls a require and returns only the keys given, file must return a table + + Public.format_time(ticks,options) --- Formats tick into a clean format, denominations from highest to lowest + + Public.move_items(items,surface,position,radius,chest_type) --- Moves items to the position and stores them in the closest entity of the type given + + Public.print_grid_value(value, surface, position, scale, offset, immutable) --- Prints a colored value on a location. + Public.print_colored_grid_value(value, surface, position, offset, immutable, + color_value, base_color, delta_color, under_bound, over_bound) --- Prints a colored value on a location. with extra settings. + Public.clear_flying_text(surface) --- Clears all flying text entites on a surface + + Public.string_contains(s, contains) --- Tests if a string contains a given substring. + + Public.extract_keys(tbl,...) --- Extracts certain keys from a table + Public.enum(tbl) --- Converts a table to an enum + Public.auto_complete(options,input,use_key,rtn_key) --- Returns the closest match to the input + Public.table_keys(tbl) --- Returns all the keys of a table + Public.table_values(tbl) --- Returns all the values of a table + Public.table_alphanumsort(tbl) --- Returns the list is a sorted way that would be expected by people (this is by key) + Public.table_keysort(tbl) --- Returns the list is a sorted way that would be expected by people (this is by key) (faster alterative than above) + + Public.format_chat_colour(message,color) --- Returns a message with valid chat tags to change its colour + Public.format_chat_colour_localized(message,color) --- Returns a message with valid chat tags to change its colour, using localization + Public.format_chat_player_name(player,raw_string) --- Returns the players name in the players color ]] local Colours = require 'resources.color_presets' @@ -61,19 +82,6 @@ function Public.param_check(value,test_type,param_name,param_number) return true end ---- Extracts certain keys from a table --- @usage local key_three, key_one = extract({key_one='foo',key_two='bar',key_three=true},'key_three','key_one') --- @tparam tbl table the table which contains the keys --- @tparam ... string the names of the keys you want extracted --- @return the keys in the order given -function Public.extract_keys(tbl,...) - local values = {} - for _,key in pairs({...}) do - table.insert(values,tbl[key]) - end - return unpack(values) -end - --- Will return a value of any type to the player/server console, allows colour for in-game players -- @usage player_return('Hello, World!') -- returns 'Hello, World!' to game.player or server console -- @usage player_return('Hello, World!','green') -- returns 'Hello, World!' to game.player with colour green or server console @@ -413,6 +421,8 @@ function Public.print_colored_grid_value(value, surface, position, offset, immut }.active = false end +--- Clears all flying text entites on a surface +-- @tparam surface LuaSurface the surface to clear function Public.clear_flying_text(surface) local entities = surface.find_entities_filtered{name ='flying-text'} for _,entity in pairs(entities) do @@ -430,7 +440,38 @@ function Public.string_contains(s, contains) return s and string.find(s, contains) ~= nil end ---- Returns the closest match to a key +--- Extracts certain keys from a table +-- @usage local key_three, key_one = extract({key_one='foo',key_two='bar',key_three=true},'key_three','key_one') +-- @tparam tbl table the table which contains the keys +-- @tparam ... string the names of the keys you want extracted +-- @return the keys in the order given +function Public.extract_keys(tbl,...) + local values = {} + for _,key in pairs({...}) do + table.insert(values,tbl[key]) + end + return unpack(values) +end + +--- Converts a table to an enum +-- @tparam tbl table the table that will be converted +-- @treturn table the new table that acts like an enum +function Public.enum(tbl) + local rtn = {} + for k,v in pairs(tbl) do + if type(k) ~= 'number' then + rtn[v]=k + end + end + for k,v in pairs(tbl) do + if type(k) == 'number' then + table.insert(rtn,v) + end + end + return rtn +end + +--- Returns the closest match to the input -- @tparam options table a table of options for the auto complete -- @tparam input string the input string that will be completed -- @tparam[opt=false] use_key boolean when true the keys of options will be used as the options diff --git a/expcore/store.lua b/expcore/store.lua new file mode 100644 index 00000000..f4d7d32d --- /dev/null +++ b/expcore/store.lua @@ -0,0 +1,72 @@ +--- A system which stores peristent data and makes it easy to sync updates between changes +local Global = require 'utils.global' +local Enum = ext_require('expcore.common','enum') + +local Store = { + 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 + } +} +Global.register(Store.data,function(tbl) + Store.data = tbl +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) + 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.locations[location] = { + location=location, + type=store_type, + update_callback=update_callback + } + if store_type ~= Store.types['local'] and store_type ~= Store.types.global then + Store.data[location] = {} + end +end + +--- Sets a new value for a location, will trigger 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 + 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 + end + location.update_callback(sub_location,value,...) +end + +--- Gets the value for a location +function Store.get_location(location,sub_location) + if not Store.locations[location] then + return error('Invalid store location: '..location,2) + end + location = Store.locations[location] + if location.type == Store.types.game then + return Store.data[location.location] + elseif location.type ~= Store.types['local'] and location.type ~= Store.types.global then + return Store.data[location.location][sub_location] + end +end + +return Store \ No newline at end of file From 1f7f9e55152179937462dfa22329b059e2768191 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Sat, 4 May 2019 13:31:52 +0100 Subject: [PATCH 2/9] Added comments to store --- expcore/store.lua | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/expcore/store.lua b/expcore/store.lua index f4d7d32d..c176e757 100644 --- a/expcore/store.lua +++ b/expcore/store.lua @@ -43,6 +43,10 @@ function Store.register_location(location,store_type,update_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 +-- @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) @@ -57,9 +61,13 @@ function Store.set_location(location,sub_location,value,...) end --- Gets the value for a location -function Store.get_location(location,sub_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 error('Invalid store location: '..location,2) + return not allow_invalid_location and error('Invalid store location: '..location,2) or nil end location = Store.locations[location] if location.type == Store.types.game then From c78b114745b699f787428945937f186b9835327e Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Sat, 4 May 2019 14:28:13 +0100 Subject: [PATCH 3/9] Added Top Comment --- expcore/store.lua | 118 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 111 insertions(+), 7 deletions(-) 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 From 9a9284df028ea6d16d0b6f73833b2c033dfdf26c Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Sat, 4 May 2019 16:17:06 +0100 Subject: [PATCH 4/9] 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 From 0189253b2cdb70d66440d7049240dd447cd3b8e9 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Sat, 4 May 2019 16:27:54 +0100 Subject: [PATCH 5/9] Added Comments --- expcore/store.lua | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/expcore/store.lua b/expcore/store.lua index 83ed9299..2c17d1da 100644 --- a/expcore/store.lua +++ b/expcore/store.lua @@ -19,6 +19,7 @@ Global.register(Store.data,function(tbl) Store.data = table end) +--- Returns a factorio object for the sub_location 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) @@ -35,10 +36,17 @@ local function get_sub_location_object(store_type,sub_location) end end +--- Emits an event to the external store that a value was updated 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 +--- Register a new location to store a value, the valu returned from getter will be watched for updates +-- @tparam location string the location path for the data must be unqiue +-- @tparam store_type string the type of store this is, see Store.types +-- @tparam getter function will be called to get the value for the store, the value is watched for updates +-- @tparam setter function when the store value changes the setter will be called +-- @tparam[opt=false] no_error boolean when true will skip check for location already registered 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) @@ -52,6 +60,10 @@ function Store.register(location,store_type,getter,setter,no_error) } end +--- Sets the stored values at the location, will call the setter function +-- @tparam location string the location to be updated, must be registed +-- @tparam sub_location string sub_location to set, either string,player,force or surface depending on store type +-- @tparam value any the value to set at the location function Store.set(location,sub_location,value) if not Store.locations[location] then return error('The location is not registed: '..location) @@ -68,6 +80,10 @@ function Store.set(location,sub_location,value) location.setter(sub_location_object or sub_location,value) end +--- Gets the value at the location, if the value is nil then the getter function is called +-- @tparam location string the location to be returned, must be registed +-- @tparam sub_location string sub_location to get, either string,player,force or surface depending on store type +-- @treturn any the value that was at this location function Store.get(location,sub_location) if not Store.locations[location] then return end location = Store.locations[location] @@ -77,6 +93,10 @@ function Store.get(location,sub_location) return rtn end +--- Checks if the store value needs updating, and if true will update it calling the setter function +-- @tparam location string the location to be check, must be registed +-- @tparam sub_location string sub_location to check, either string,player,force or surface depending on store type +-- @treturn boolean if the value was updated and setter function called function Store.check(location,sub_location) if not Store.locations[location] then return false end location = Store.locations[location] @@ -91,6 +111,7 @@ function Store.check(location,sub_location) return false end +--- Checks once per second for changes to the store values Event.on_nth_tick(60,function() for _,location in pairs(Store.locations) do if location.store_type ~= Store.types['local'] then From 86daedf4f590ccde0ba77863743f9087afc75598 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Sat, 4 May 2019 17:05:07 +0100 Subject: [PATCH 6/9] Added Top Comment --- expcore/store.lua | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/expcore/store.lua b/expcore/store.lua index 2c17d1da..4ab0adf3 100644 --- a/expcore/store.lua +++ b/expcore/store.lua @@ -1,3 +1,63 @@ +--- This module is for storing and watching values for updates, useful for config settings or limiting what can be changed +--[[ +>>>> When to use this system + This system is to be used when you want to store a value and watch when it is changed or watch any value for changes. + Examples would include runtime config settings where something needs to change when the value is updated or when you have + values entered in a gui and you want them to be persistent between players like a force modifer gui + +>>>> What store type to use + There are different types of store that can be used each is designed to be used in a certain situation: + local - this store type doesnt actually store any data and it has its use in only triggering the setter function when you use + the set function rather than watching for updates, this might be used as an interface between modules where when you change the + local varible you dont want it to trigger but when an outside source uses set it will trigger the setter. + player - this will use the sub_location as a player so each player will have they own entry in the store location, this can be used + with player modifiers where even if set is not used the update will still be detected. + force - this will use the sub_location as a force so each force will have its own entry in the store location, this can be used to store + custom settings for a force where if a player uses a gui to edit the setting it will detect the update and call the setter where you + can update the value on the gui for other players. + surface - this will use the sub_location as a surface so each surface will have its own entry in the store location, this will have the + same use case as force but for a surface rather than a force. + game - this will store all a single value so any sub_location string can be used, this is the general case so you really can store what + ever values you want to in this and watch for external updates, this would be used when its not a local varible for example if you are + watching the number of online players. + global - WIP this will store all of its data in an external source indepentent of the lua code, this means that you can store data between + maps and even instances, when the value is updated it will trigger an emit where some external code should send a message to the other + connected instances to update they value. lcoal set -> emit update -> local setter -> remote set -> remote setter + +>>>> Force mining speed example: + For this will print a message when the force mining speed has been updated, we will use the force type since each force will have its own + mining speed and our getter will just return the current minning speed of the force. + + Store.register('force.mining_speed','force',function(force) + return force.manual_mining_speed_modifier + end,function(force,value) + force.manual_mining_speed_modifier = value + game.print(force.name..' how has '..value..' mining speed') + end) + + Note that because we used type force the getter and setter are passed the force which the current check/update effects; if we used player or surface + the same would be true. However for local, game and global they are passed the sub_location string which allows you to store multiple things in the same + location; however one limitation is that a sub_location is required even if you only plan to store one value. + + Store.set('force.mining_speed','player',2) + game.forces.player.manual_mining_speed_modifier = 2 + + The two cases above will have the effect of both setting the minning speed and outputing the update message. This can be quite useful when you start to + indroduce custom settings or do more than just output that the value was updated. + + Store.get('force.mining_speed','player') + + In a similar way get can be used to get the current value that is stored, if no value is stored then the getter function is called to get the value, this + function is more useful when you have custom settings since they would be no other way to access them. + +>>>> Functions: + Store.register(location,store_type,getter,setter,no_error) --- Register a new location to store a value, the valu returned from getter will be watched for updates + Store.set(location,sub_location,value) --- Sets the stored values at the location, will call the setter function + Store.get(location,sub_location) --- Gets the value at the location, if the value is nil then the getter function is called + Store.check(location,sub_location) --- Checks if the store value needs updating, and if true will update it calling the setter function +]] + + local Global = require 'utils.global' local Event = require 'utils.event' local Game = require 'utils.game' From 6b14fe9649cdd5b309b5fe2126ee383631917e25 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Sun, 5 May 2019 13:35:48 +0100 Subject: [PATCH 7/9] Added global store type --- expcore/commands.lua | 6 +++--- expcore/common.lua | 7 +++++++ expcore/roles.lua | 5 +++-- expcore/store.lua | 10 +++++++--- modules/commands/interface.lua | 3 ++- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/expcore/commands.lua b/expcore/commands.lua index c96dcce4..c7698802 100644 --- a/expcore/commands.lua +++ b/expcore/commands.lua @@ -218,7 +218,7 @@ ]] local Game = require 'utils.game' -local player_return = ext_require('expcore.common','player_return') +local player_return,write_json = ext_require('expcore.common','player_return','write_json') local Commands = { defines={ -- common values are stored error like signals @@ -580,14 +580,14 @@ end -- logs command usage to file local function command_log(player,command,comment,params,raw,details) local player_name = player and player.name or '' - game.write_file('log/commands.log',game.table_to_json{ + write_json('log/commands.log',{ player_name=player_name, command_name=command.name, comment=comment, details=details, params=params, raw=raw - }..'\n',true,0) + }) end --- Main event function that is ran for all commands, used internally please avoid direct use diff --git a/expcore/common.lua b/expcore/common.lua index 38ef5276..aeab73e8 100644 --- a/expcore/common.lua +++ b/expcore/common.lua @@ -123,6 +123,13 @@ function Public.player_return(value,colour,player) else rcon.print(returnAsString) end end +--- Writes a table object to a file in json format +-- @tparam path string the path of the file to write include / to use dir +-- @tpatam tbl table the table that will be converted to a json string and wrote to file +function Public.write_json(path,tbl) + game.write_file(path,game.table_to_json(tbl)..'\n',true,0) +end + --- Calls a require that will not error if the file is not found -- @usage local file = opt_require('file.not.present') -- will not cause any error -- @tparam path string the path that you want to require diff --git a/expcore/roles.lua b/expcore/roles.lua index 11df21dc..a2dca09e 100644 --- a/expcore/roles.lua +++ b/expcore/roles.lua @@ -160,6 +160,7 @@ local Global = require 'utils.global' local Event = require 'utils.event' local Groups = require 'expcore.permission_groups' local Colours = require 'resources.color_presets' +local write_json = ext_require('expcore.common','write_json') local Roles = { config={ @@ -223,12 +224,12 @@ local function emit_player_roles_updated(player,type,roles,by_player_name,skip_g by_player_index=by_player_index, roles=roles }) - game.write_file('log/roles.log',game.table_to_json{ + write_json('log/roles.log',{ player_name=player.name, by_player_name=by_player_name, type=type, roles_changed=role_names - }..'\n',true,0) + }) end --- Returns a string which contains all roles in index order displaying all data for them diff --git a/expcore/store.lua b/expcore/store.lua index 4ab0adf3..d8834ef2 100644 --- a/expcore/store.lua +++ b/expcore/store.lua @@ -20,7 +20,7 @@ game - this will store all a single value so any sub_location string can be used, this is the general case so you really can store what ever values you want to in this and watch for external updates, this would be used when its not a local varible for example if you are watching the number of online players. - global - WIP this will store all of its data in an external source indepentent of the lua code, this means that you can store data between + global - this will store all of its data in an external source indepentent of the lua code, this means that you can store data between maps and even instances, when the value is updated it will trigger an emit where some external code should send a message to the other connected instances to update they value. lcoal set -> emit update -> local setter -> remote set -> remote setter @@ -61,7 +61,7 @@ local Global = require 'utils.global' local Event = require 'utils.event' local Game = require 'utils.game' -local Enum = ext_require('expcore.common','enum') +local Enum,write_json = ext_require('expcore.common','enum','write_json') local Store = { data={}, @@ -98,7 +98,11 @@ end --- Emits an event to the external store that a value was updated 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 + write_json('log/store.log',{ + location=location, + sub_location=sub_location, + value=value + }) end --- Register a new location to store a value, the valu returned from getter will be watched for updates diff --git a/modules/commands/interface.lua b/modules/commands/interface.lua index 6e5b89b0..98452174 100644 --- a/modules/commands/interface.lua +++ b/modules/commands/interface.lua @@ -9,7 +9,8 @@ local interface_modules = { ['Commands']=Commands, ['output']=Common.player_return, ['Group']='expcore.permission_groups', - ['Roles']='expcore.roles' + ['Roles']='expcore.roles', + ['Store']='expcore.store' } -- loads all the modules given in the above table From 34fcb94200ac494bcb7c4f86a6ad691951128828 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Sun, 5 May 2019 20:50:21 +0100 Subject: [PATCH 8/9] Added watch to allow optimistion --- config/file_loader.lua | 1 + control.lua | 2 +- expcore/common.lua | 8 +++- expcore/store.lua | 89 ++++++++++++++++++++++++++++++++++++------ expcore/store_test.lua | 10 +++++ 5 files changed, 96 insertions(+), 14 deletions(-) create mode 100644 expcore/store_test.lua diff --git a/config/file_loader.lua b/config/file_loader.lua index 790aa6c6..72016f49 100644 --- a/config/file_loader.lua +++ b/config/file_loader.lua @@ -38,4 +38,5 @@ return { 'config.command_auth_runtime_disable', -- allows commands to be enabled and disabled during runtime 'config.permission_groups', -- loads some predefined permission groups 'config.roles', -- loads some predefined roles + 'expcore.store_test' } \ No newline at end of file diff --git a/control.lua b/control.lua index 02ff8cec..ec8f0068 100644 --- a/control.lua +++ b/control.lua @@ -30,7 +30,7 @@ local errors = {} for index,path in pairs(files) do -- Loads the next file in the list - log(string.format('[INFO] Loading files %3d/%s',index,total_file_count)) + log(string.format('[INFO] Loading files %3d/%s (%s)',index,total_file_count,path)) local success,file = pcall(require,path) -- Error Checking diff --git a/expcore/common.lua b/expcore/common.lua index aeab73e8..8e8c6e72 100644 --- a/expcore/common.lua +++ b/expcore/common.lua @@ -8,6 +8,7 @@ Public.param_check(value,test_type,param_name,param_number) --- Raises an error when the value is the incorrect type, uses a consistent error message format Public.player_return(value,colour,player) --- Will return a value of any type to the player/server console, allows colour for in-game players + Public.write_json(path,tbl) --- Writes a table object to a file in json format Public.opt_require(path) --- Calls a require that will not error if the file is not found Public.ext_require(path,...) --- Calls a require and returns only the keys given, file must return a table @@ -94,8 +95,8 @@ function Public.player_return(value,colour,player) player = player or game.player -- converts the value to a string local returnAsString - if Public.type_check(value,'table') then - if Public.type_check(value.__self,'userdata') then + if Public.type_check(value,'table') or type(value) == 'userdata' then + if Public.type_check(value.__self,'userdata') or type(value) == 'userdata' then -- value is userdata returnAsString = 'Cant Display Userdata' elseif Public.type_check(value[1],'string') and string.find(value[1],'.+[.].+') and not string.find(value[1],'%s') then @@ -475,6 +476,9 @@ function Public.enum(tbl) table.insert(rtn,v) end end + for k,v in pairs(rtn) do + rtn[v]=k + end return rtn end diff --git a/expcore/store.lua b/expcore/store.lua index d8834ef2..e03036f9 100644 --- a/expcore/store.lua +++ b/expcore/store.lua @@ -50,10 +50,21 @@ In a similar way get can be used to get the current value that is stored, if no value is stored then the getter function is called to get the value, this function is more useful when you have custom settings since they would be no other way to access them. +>>>> Optimise the watching + When you use player,force or surface you will be checking alot of values for updates for this reason you might want to limit which sub_locations are checked + for updates because by default every player/force/surface is checked. You might also want to do this if you want a sub_location that is nil but still want to + check for it being updated (because by deafult it only checks non nil sub_locations). To do both these things you will use Store.watch + + Store.watch('force.mining_speed','player') + For our force example we dont care about the enemy or neutral force only the player force, so we tell it to watch player and these means that the values for + the other forces are not be watched for updates (although Store.get and Store.set will still work). Store.watch will also accept a table of sub_locations in + case you want more than one thing to be watch. + >>>> Functions: Store.register(location,store_type,getter,setter,no_error) --- Register a new location to store a value, the valu returned from getter will be watched for updates Store.set(location,sub_location,value) --- Sets the stored values at the location, will call the setter function Store.get(location,sub_location) --- Gets the value at the location, if the value is nil then the getter function is called + Store.watch(location,sub_location,state) --- If used then only sub_locations marked to be watched will be watched for updates, this will also midigate the nil value problem Store.check(location,sub_location) --- Checks if the store value needs updating, and if true will update it calling the setter function ]] @@ -61,10 +72,11 @@ local Global = require 'utils.global' local Event = require 'utils.event' local Game = require 'utils.game' -local Enum,write_json = ext_require('expcore.common','enum','write_json') +local Enum,write_json,table_keys = ext_require('expcore.common','enum','write_json','table_keys') local Store = { data={}, + watching={}, locations={}, types = Enum{ 'local', -- data is not stored with any sub_location, updates caused only by set @@ -75,8 +87,9 @@ local Store = { '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 = table +Global.register({Store.data,Store.watching},function(tbl) + Store.data = tbl[1] + Store.watching = tbl[2] end) --- Returns a factorio object for the sub_location @@ -96,6 +109,14 @@ local function get_sub_location_object(store_type,sub_location) end end +--- Returns three common parts that are used +local function get_location_parts(location,sub_location) + location = Store.locations[location] + local sub_location_object = get_sub_location_object(location.store_type,sub_location) + sub_location = sub_location_object and sub_location_object.name or sub_location + return location, sub_location, sub_location_object +end + --- Emits an event to the external store that a value was updated local function set_global_location_value(location,sub_location,value) write_json('log/store.log',{ @@ -132,8 +153,7 @@ function Store.set(location,sub_location,value) if not Store.locations[location] then return error('The location is not registed: '..location) end - location = Store.locations[location] - local sub_location_object = get_sub_location_object(location.store_type,sub_location) + local location, sub_location, sub_location_object = get_location_parts(location,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 @@ -142,6 +162,7 @@ function Store.set(location,sub_location,value) set_global_location_value(location.location,value) end location.setter(sub_location_object or sub_location,value) + return true end --- Gets the value at the location, if the value is nil then the getter function is called @@ -150,13 +171,33 @@ end -- @treturn any the value that was at this location function Store.get(location,sub_location) if not Store.locations[location] then return end - location = Store.locations[location] - local sub_location_object = get_sub_location_object(location.store_type,sub_location) + local location, sub_location, sub_location_object = get_location_parts(location,sub_location) local rtn = Store.data[location.location][sub_location] - if rtn == nil then rtn = location.getter(sub_location_object or sub_location) end + if rtn == nil or Store.watching[location.location] and not Store.watching[location.location][sub_location] then + rtn = location.getter(sub_location_object or sub_location) + end return rtn end +--- If used then only sub_locations marked to be watched will be watched for updates, this will also midigate the nil value problem +-- @tparam location string the location to be returned, must be registed +-- @tparam sub_location string sub_location to watch, either string,player,force or surface depending on store type, can be a table of sub_locations +-- @tparam[opt=true] state boolean when true it will be marked to be watched, when false it will be removed +function Store.watch(location,sub_location,state) + if not Store.locations[location] then + return error('The location is not registed: '..location) + end + if type(sub_location) ~= 'table' or type(sub_location.__self) == 'userdata' then + sub_location = {sub_location} + end + for _,v in pairs(sub_location) do + if not Store.watching[location] then Store.watching[location] = {} end + if state == false then Store.watching[location][v] = nil + else Store.watching[location][v] = true end + end + if #table_keys(Store.watching[location]) == 0 then Store.watching[location] = nil end +end + --- Checks if the store value needs updating, and if true will update it calling the setter function -- @tparam location string the location to be check, must be registed -- @tparam sub_location string sub_location to check, either string,player,force or surface depending on store type @@ -165,6 +206,7 @@ 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) + sub_location = sub_location_object and sub_location_object.name or 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 @@ -177,11 +219,36 @@ end --- Checks once per second for changes to the store values Event.on_nth_tick(60,function() + local types = {} 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) + if not types[location.store_type] then types[location.store_type] = {} end + table.insert(types[location.store_type],location) + end + end + for store_type,locations in pairs(types) do + local keys + if store_type == Store.types.player then keys = game.players + elseif store_type == Store.types.force then keys = game.forces + elseif store_type == Store.types.surface then keys = game.surfaces + end + if keys then + for _,sub_location in pairs(keys) do + for _,location in pairs(locations) do + if not Store.watching[location.location] or Store.watching[location.location][sub_location.name] then + if not Store.data[location.location] then Store.data[location.location] = {} end + Store.check(location.location,sub_location) + end + end + end + else + for _,location in pairs(locations) do + if not Store.data[location.location] then Store.data[location.location] = {} end + if Store.watching[location.location] then keys = Store.watching[location.location] + else keys = table_keys(Store.data[location.location]) end + for _,sub_location in pairs(keys) do + Store.check(location.location,sub_location) + end end end end diff --git a/expcore/store_test.lua b/expcore/store_test.lua new file mode 100644 index 00000000..f1ce44be --- /dev/null +++ b/expcore/store_test.lua @@ -0,0 +1,10 @@ +local Store = require 'expcore.store' + +Store.register('force.mining_speed','force',function(force) + return force.manual_mining_speed_modifier +end,function(force,value) + force.manual_mining_speed_modifier = value + game.print(force.name..' how has '..value..' mining speed') +end) + +Store.watch('force.mining_speed','player') \ No newline at end of file From fa1e650ca1c17aaa4232f288b4d3f4bf3be398bf Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Sun, 5 May 2019 20:50:42 +0100 Subject: [PATCH 9/9] Removed Test File --- expcore/store_test.lua | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 expcore/store_test.lua diff --git a/expcore/store_test.lua b/expcore/store_test.lua deleted file mode 100644 index f1ce44be..00000000 --- a/expcore/store_test.lua +++ /dev/null @@ -1,10 +0,0 @@ -local Store = require 'expcore.store' - -Store.register('force.mining_speed','force',function(force) - return force.manual_mining_speed_modifier -end,function(force,value) - force.manual_mining_speed_modifier = value - game.print(force.name..' how has '..value..' mining speed') -end) - -Store.watch('force.mining_speed','player') \ No newline at end of file