Changed store to use a single function

This commit is contained in:
Cooldude2606
2019-06-01 16:11:14 +01:00
parent 32723af8ac
commit 67933e67ba
8 changed files with 175 additions and 246 deletions

View File

@@ -1,6 +1,6 @@
--- Config for the different action buttons that show on the player list
-- each button has the button define(s) given along side an auth function, and optional reason callback
-- if a reason callback is used then Store.set_child(action_name_store,player.name,'BUTTON_NAME') should be called during on_click
-- if a reason callback is used then Store.set(action_name_store,player.name,'BUTTON_NAME') should be called during on_click
-- buttons can be removed from the gui by commenting them out of the config at the bottom of this file
-- the key used for the name of the button is the permision name used by the role system
local Gui = require 'expcore.gui'
@@ -34,7 +34,7 @@ end
-- gets the action player and a coloured name for the action to be used on
local function get_action_player_name(player)
local action_player_name = Store.get_child(action_player_store,player.name)
local action_player_name = Store.get(action_player_store,player.name)
local action_player = Game.get_player_from_any(action_player_name)
local action_player_name_color = format_chat_player_name(action_player)
return action_player,action_player_name_color
@@ -109,7 +109,7 @@ Gui.new_button()
if Reports.player_is_reported_by(action_player_name,player.name) then
player.print({'expcom-report.already-reported'},Colors.orange_red)
else
Store.set_child(action_name_store,player.name,'command/report')
Store.set(action_name_store,player.name,'command/report')
end
end)
@@ -128,7 +128,7 @@ Gui.new_button()
:set_tooltip{'player-list.warn-player'}
:set_style('tool_button',tool_button_style)
:on_click(function(player,element)
Store.set_child(action_name_store,player.name,'command/give-warning')
Store.set(action_name_store,player.name,'command/give-warning')
end)
local function warn_player_callback(player,reason)
@@ -149,7 +149,7 @@ Gui.new_button()
if Roles.player_has_role(action_player_name,'Jail') then
player.print({'expcom-jail.already-jailed',action_player_name_color},Colors.orange_red)
else
Store.set_child(action_name_store,player.name,'command/jail')
Store.set(action_name_store,player.name,'command/jail')
end
end)
@@ -171,7 +171,7 @@ Gui.new_button()
if Roles.player_has_role(action_player_name,'Jail') then
player.print({'expcom-jail.already-banned',action_player_name_color},Colors.orange_red)
else
Store.set_child(action_name_store,player.name,'command/temp-ban')
Store.set(action_name_store,player.name,'command/temp-ban')
end
end)
@@ -189,7 +189,7 @@ Gui.new_button()
:set_tooltip{'player-list.kick-player'}
:set_style('tool_button',tool_button_style)
:on_click(function(player,element)
Store.set_child(action_name_store,player.name,'command/kick')
Store.set(action_name_store,player.name,'command/kick')
end)
local function kick_player_callback(player,reason)
@@ -204,7 +204,7 @@ Gui.new_button()
:set_tooltip{'player-list.ban-player'}
:set_style('tool_button',tool_button_style)
:on_click(function(player,element)
Store.set_child(action_name_store,player.name,'command/ban')
Store.set(action_name_store,player.name,'command/ban')
end)
local function ban_player_callback(player,reason)

View File

@@ -112,12 +112,12 @@ function Checkbox.new_checkbox(name)
if self.option_set then
local value = Checkbox.option_sets[self.option_set][element.name]
local category = self.categorize and self.categorize(element) or value
local category = self.categorize and self.categorize(element)
self:set_store(category,value)
elseif self.store then
local value = element.state
local category = self.categorize and self.categorize(element) or value
local category = self.categorize and self.categorize(element)
self:set_store(category,value)
else
@@ -166,12 +166,7 @@ end
function Checkbox._prototype_radiobutton:get_store(category,internal)
if not self.store then return end
local location = not internal and self.option_set or self.store
if self.categorize then
return Store.get_child(location,category)
else
return Store.get(location)
end
return Store.get(location,category)
end
--- Sets the stored value of the radiobutton or the option set if present
@@ -182,12 +177,7 @@ end
function Checkbox._prototype_radiobutton:set_store(category,value,internal)
if not self.store then return end
local location = not internal and self.option_set or self.store
if self.categorize then
return Store.set_child(location,category,value)
else
return Store.set(location,category)
end
return Store.set(location,category,value)
end
--- Registers a new option set that can be linked to radiobutotns (only one can be true at a time)

View File

@@ -255,7 +255,7 @@ function Gui._sync_store_factory(callback)
Instances.register(self.name,self.categorize)
Store.register_synced(self.store,function(value,category)
Store.register(self.store,true,function(value,category)
if self.events.on_store_update then
self.events.on_store_update(value,category)
end
@@ -419,24 +419,24 @@ end
-- @treturn any the value that is stored for this define
function Gui._prototype:get_store(category)
if not self.store then return end
if self.categorize then
return Store.get_child(self.store,category)
else
return Store.get(self.store)
end
return Store.get(self.store,category)
end
--- Sets the value in this elements store, category needed if categorize function used
-- @tparam string category[opt] the category to get such as player name or force name
-- @tparam boolean any value the value to set for this define, must be valid for its type ie for checkbox etc
-- @tparam any value the value to set for this define, must be valid for its type ie for checkbox etc
-- @treturn boolean true if the value was set
function Gui._prototype:set_store(category,value)
if not self.store then return end
if self.categorize then
return Store.set_child(self.store,category,value)
else
return Store.set(self.store,category)
end
return Store.set(self.store,category,value)
end
--- Sets the value in this elements store to nil, category needed if categorize function used
-- @tparam[opt] string category the category to get such as player name or force name
-- @treturn boolean true if the value was set
function Gui._prototype:clear_store(category)
if not self.store then return end
return Store.clear(self.store,category)
end
--- Gets an element define give the uid, debug name or a copy of the element define

View File

@@ -250,8 +250,7 @@ local function change_value_prototype(self,amount,category,filter)
local function reset_store()
local value = self.count_down and 1 or 0
local _category = category or value
self:set_store(_category,value)
self:set_store(category,value)
end
if self.store then
@@ -261,15 +260,13 @@ local function change_value_prototype(self,amount,category,filter)
if self.count_down and new_value <= 0
or not self.count_down and new_value >= 1 then
self:set_store(category)
self:clear_store(category)
if self.events.on_store_complete then
category = category or reset_store
self.events.on_store_complete(category,reset_store)
end
end
category = category or new_value
self:set_store(category,new_value)
return

View File

@@ -652,7 +652,7 @@ Event.add(defines.events.on_tick,function()
progressbar_one:increment()
progressbar_three:decrement()
local categories = Store.get_children(progressbar_two.store)
for category,_ in pairs(categories) do
for _,category in pairs(categories) do
progressbar_two:increment(1,category)
end
end)

View File

@@ -7,43 +7,42 @@
This may be useful when storing config values and when they get set you want to make sure it is taken care of, or maybe you want
to have a value that you can trigger an update of from different places.
-- this will register a new location called 'scenario.dificutly' and the start value is 'normal'
-- this will register a new location called 'scenario.dificutly'
-- note that setting a start value is optional and we could take nil to mean normal
Store.register('scenario.dificutly',function(value)
game.print('The scenario dificulty has be set to: '..value)
end,'normal')
-- this will return 'normal' as we have not set the value anywhere else
Store.get('scenario.dificutly')
end)
-- this will set the value in the store to 'hard' and will trigger the update callback which will print a message to the game
Store.set('scenario.dificutly','hard')
-- this will return 'hard'
Store.get('scenario.dificutly')
>>>> Using Children
One limitation of store is that all lcoations must be registered to avoid desyncs, to get round this issue "children" can be used.
When you set the value of a child it does not have its own update callback so rather the "partent" location which has been registerd
When you set the value of a child it does not have its own update callback so rather the "parent" location which has been registerd
will have its update value called with a second param of the name of that child.
This may be useful when you want a value of each player or force and since you cant regisier every player at the start you must use
the players name as the child name.
-- this will register the lcoation 'scenario.score' where we plan to use force names as the child
-- here we have not set a start value since it will be an empty location
Store.register('scenario.score',function(value,child)
game.print(child..' now has a score of '..value)
end)
-- this will return nil, but will not error as children dont need to be registerd
Store.get_child('scenario.score','player')
Store.get('scenario.score','player')
-- this will set 'player' to have a value of 10 for 'scenario.score' and trigger the game message print
Store.set_child('scenario.score','player',10)
Store.set('scenario.score','player',10)
-- this would be the same as Store.get however this will return an empty table rather than nil
-- this would be the silliar to Store.get however this will return the names of all the children
Store.get_children('scenario.score')
>>>> Using Sync
There is the option to use Store.register_synced which is the same as Store.register however you can combine this with an external script
There is the option to use synced values which is the same as a normal value however you can combine this with an external script
which can read the output from 'script-output/log/store.log' and have it send rcon commands back to the game allowing for cross instance
syncing of values.
@@ -52,66 +51,51 @@
-- this example will register the location 'stastics.total-play-time' where we plan to use plan names as the child
-- note that the location must be the same across instances
Store.register_synced('stastics.total-play-time',function(value,child)
Store.register('stastics.total-play-time',true,function(value,child)
game.print(child..' now has now played for '..value)
end)
-- use of set,get,set_child and get_chlid are all the same as non synced
>>>> Using a watch function
Some times the value that you want is not some made up value that you have but rather a factorio value or something similar, in order to recive
updates on these values (if the factorio api does not provide an event for it) you will need to add a watch function to update the store when the
values changes. You will want to keep these watch functions small since they run every tick.
-- this will register a location 'game.speed', note that the lcoation can be anything but we chose 'game.speed' to match what we are watching
-- also note that we do not need a start value here since it will be set on the first tick, but you may want a start value to avoid a trigger of the callback
Store.register('game.speed',function(value)
game.print('The game speed has been set to: '..value)
end)
-- this will add the watch function to the lcoation, every tick the function is ran and the value returned in compeared to the stored value
-- if the two values are different then the store is overriden and the update function is called
Store.add_watch('game.speed',function()
return game.speed
end)
-- use of set and are all the same as non synced but you should include from_sync as true
>>>> Alternative method
Some people may prefer to use a varible rather than a string for formating reasons here is an example. Also for any times when
there will be little external input Store.uid_location() can be used to generate non conflicting locations, use of register_synced will
still require a name other wise there may be mirgration issuses.
there will be little external input Store.uid_location() can be used to generate non conflicting locations, uid_location will also
be used if you give a nil location.
local store_game_speed = Store.uid_location()
Store.register(store_game_speed,function(value)
local store_game_speed =
Store.register(function(value)
game.print('The game speed has been set to: '..value)
end)
Store.add_watch(store_game_speed,function()
return game.speed
end)
]]
local Global = require 'utils.global'
local Event = require 'utils.event'
local write_json = ext_require('expcore.common','write_json','table_keys')
local table_keys,write_json = ext_require('expcore.common','table_keys','write_json')
local Token = require 'utils.token'
local Store = {
data={},
callbacks={},
registered={},
synced={},
watchers={}
callbacks={},
on_value_update=script.generate_event_name()
}
Global.register(Store.data,function(tbl)
Store.data = tbl
end)
local function error_not_table(value)
if type(value) ~= 'table' then
error('Location is not a table can not use child locations',3)
end
end
--- Check for if a lcoation is registered
-- @tparam string location the location to test for
-- @treturn boolean true if registered
function Store.is_registered(location)
return not not Store.callbacks[location]
return Store.registered[location]
end
--- Returns a unqiue name that can be used for a store
@@ -121,185 +105,143 @@ function Store.uid_location()
end
--- Registers a new location with an update callback which is triggered when the value updates
-- @tparam string location string a unique that points to the data, string used rather than token to allow migration
-- @tparam function callback this callback will be called when the stored value is set to a new value
-- @tparam[opt] any start_value this value will be the inital value that is stored at this location
function Store.register(location,callback,start_value)
-- @tparam[opt] string location string a unique that points to the data, string used rather than token to allow migration
-- @tparam[opt] boolean synced when true will output changes to a file so it can be synced
-- @tparam[opt] function callback when given the callback will be automatically registered to the update of the value
-- @treturn string the lcoation that is being used
function Store.register(location,synced,callback)
if _LIFECYCLE ~= _STAGE.control then
return error('Can only be called during the control stage', 2)
end
if Store.callbacks[location] then
if type(location) ~= 'string' then
callback = synced
synced = location
end
if type(synced) ~= 'boolean' then
callback = synced
end
location = type(location) == 'string' and location or Store.uid_location()
if Store.registered[location] then
return error('Location is already registered', 2)
end
if type(callback) ~= 'function' then
return error('Callback must be a function', 2)
end
Store.data[location] = start_value
Store.callbacks[location] = callback
Store.registered[location] = true
Store.synced[location] = synced and true or nil
Store.callbacks[location] = callback or nil
return location
end
--- Registers a new cross server synced location with an update callback, and external script is required for cross server
-- @tparam string location string a unique that points to the data, string used rather than token to allow migration
-- @tparam function callback this callback will be called when the stored value is set to a new value
-- @tparam[opt] any start_value this value will be the inital value that is stored at this location
function Store.register_synced(location,callback,start_value)
if _LIFECYCLE ~= _STAGE.control then
return error('Can only be called during the control stage', 2)
end
if Store.callbacks[location] then
return error('Location is already registered', 2)
end
if type(callback) ~= 'function' then
return error('Callback must be a function', 2)
end
Store.data[location] = start_value
Store.callbacks[location] = callback
Store.synced[location] = true
end
--- Adds a function that will be checked every tick for a change in the returned value, when the value changes it will be saved in the store
-- @tparam string location the location where the data will be saved and compeared to, must already be a registered location
-- @tparam function callback this function will be called every tick to check for a change in value
function Store.add_watch(location,callback)
if _LIFECYCLE ~= _STAGE.control then
return error('Can only be called during the control stage', 2)
end
if Store.callbacks[location] then
return error('Location is already being watched', 2)
end
if type(callback) ~= 'function' then
return error('Callback must be a function', 2)
end
Store.watchers[location] = callback
end
--- Gets the value stored at a location, this location must be registered
-- @tparam string location the location to get the data from
-- @tparam[opt=false] boolean no_error when true no error is returned if the location is not registered
-- @tparam[opt] string child the child location if required
-- @tparam[opt=false] boolean allow_unregistered when true no error is returned if the location is not registered
-- @treturn any the data which was stored at the location
function Store.get(location,no_error)
if not Store.callbacks[location] and not no_error then
function Store.get(location,child,allow_unregistered)
if not Store.callbacks[location] and not allow_unregistered then
return error('Location is not registered', 2)
end
return Store.data[location]
local data = Store.data[location]
if child and data then
error_not_table(data)
return data[child]
end
return data
end
--- Sets the value at a location, this location must be registered, if server synced it will emit the change to file
--- Sets the value at a location, this location must be registered
-- @tparam string location the location to set the data to
-- @tparam any value the new value to set at the location, value may be reverted if there is a watch callback
-- @tparam[opt] string child the child location if required
-- @tparam any value the new value to set at the location, value may be reverted if there is a watch callback, cant be nil
-- @tparam[opt] boolean from_sync set this true to avoid an output to the sync file
-- @treturn boolean true if it was successful
function Store.set(location,value)
function Store.set(location,child,value,from_sync)
if not Store.callbacks[location] then
return error('Location is not registered', 2)
end
Store.data[location] = value
Store.callbacks[location](value)
if Store.synced[location] then
write_json('log/store.log',{
location=location,
value=value
})
if child == nil or value == nil then
value = child or value
child = nil
end
local data = Store.data
if child then
data = data[location]
if not data then
data = {}
Store.data[location] = data
end
error_not_table(data)
data[child] = value
else
data[location] = value
end
script.raise_event(Store.on_value_update,{
tick=game.tick,
location=location,
child=child,
value=value,
from_sync=from_sync
})
return true
end
--- Sets the value at a location to nil, this location must be registered
-- @tparam string location the location to set the data to
-- @tparam[opt] string child the child location if required
-- @tparam[opt] boolean from_sync set this true to avoid an output to the sync file
-- @treturn boolean true if it was successful
function Store.clear(location,child,from_sync)
if not Store.callbacks[location] then
return error('Location is not registered', 2)
end
local data = Store.data
if child then
data = data[location]
if not data then return end
error_not_table(data)
data[child] = nil
else
data[location] = nil
end
script.raise_event(Store.on_value_update,{
tick=game.tick,
location=location,
child=child,
from_sync=from_sync
})
return true
end
--- Gets all non nil children at a location, children can be added and removed during runtime
-- this is similar to Store.get but will always return a table even if it is empty
-- @tparam string location the location to get the children of
-- @treturn table a table containg all the children and they values
-- @treturn table a table containg all the children names
function Store.get_children(location)
local store = Store.get(location)
if type(store) ~= 'table' and store ~= nil then
return error('Location has a non table value', 2)
end
return store or {}
local data = Store.get(location)
return type(data) == 'table' and table_keys(data) or {}
end
--- Gets the value of the child to a location, children can be added and removed during runtime
-- @tparam string location the location of which the child is located
-- @tparam string child the child element to get the value of
-- @treturn any the value which was stored at that location
function Store.get_child(location,child)
local store = Store.get(location)
if type(store) ~= 'table' and store ~= nil then
return error('Location has a non table value', 2)
-- Handels syncing
Event.add(Store.on_value_update,function(event)
if Store.callbacks[event.location] then
Store.callbacks[event.location](event.value,event.child)
end
return store and store[child]
end
--- Sets the value of the chlid to a location, children can be added and removed during runtime
-- when a child is set it will call the update handler of the parent allowing children be to added at runtime
-- this may be used when a player joins the game and the child is the players name
-- @tparam string location the location of which the child is located
-- @tparam string child the child element to set the value of
-- @tparam any value the value to set at this location
-- @treturn boolean true if it was successful
function Store.set_child(location,child,value)
local store = Store.get(location)
if type(store) ~= 'table' and store ~= nil then
return error('Location has a non table value', 2)
end
if not store then
Store.data[location] = {}
end
Store.data[location][child] = value
Store.callbacks[location](value,child)
if Store.synced[location] then
write_json('log/store.log',{
location=location,
child=child,
value=value
})
end
return true
end
-- Event handler for the watcher callbacks
Event.add(defines.events.on_tick,function()
local errors = {}
for location,callback in pairs(Store.watchers) do
local store_old = Store.data[location]
local success,store_new = pcall(callback)
if not success then
table.insert(errors,store_new)
else
if type(store_old) ~= type(store_new)
or type(store_old) == 'table' and not table.compare(store_new,store_new)
or store_old ~= store_new then
Store.data[location] = store_new
Store.callbacks[location](store_new)
end
end
end
if #errors > 0 then
error(table.concat(errors,'; '))
if not event.from_sync then
write_json('log/store.log',event)
end
end)

View File

@@ -23,11 +23,11 @@ Gui.on_click(zoom_to_map_name,function(event)
else
-- rmb will open settings
local player_name = event.player.name
local old_action_player_name = Store.get_child(action_player_store,player_name)
local old_action_player_name = Store.get(action_player_store,player_name)
if action_player_name == old_action_player_name then
Store.set_child(action_player_store,player_name) -- will close if already open
Store.clear(action_player_store,player_name) -- will close if already open
else
Store.set_child(action_player_store,player_name,action_player_name)
Store.set(action_player_store,player_name,action_player_name)
end
end
end)
@@ -47,11 +47,11 @@ end)
end)
:on_click(function(player,element)
local new_action_player_name = element.parent.name
local action_player_name = Store.get_child(action_player_store,player.name)
local action_player_name = Store.get(action_player_store,player.name)
if action_player_name == new_action_player_name then
Store.set_child(action_player_store,player.name) -- will close if already open
Store.clear(action_player_store,player.name) -- will close if already open
else
Store.set_child(action_player_store,player.name,element.parent.name)
Store.set(action_player_store,player.name,new_action_player_name)
end
end)
@@ -66,8 +66,8 @@ Gui.new_button()
style.width = 28
end)
:on_click(function(player,element)
Store.set_child(action_player_store,player.name,nil)
Store.set_child(action_name_store,player.name,nil)
Store.clear(action_player_store,player.name)
Store.clear(action_name_store,player.name)
end)
--- Button used to confirm a reason
@@ -85,8 +85,8 @@ end)
local action_name = Store.get_child(action_name_store,player.name)
local reason_callback = config[action_name].reason_callback
reason_callback(player,reason)
Store.set_child(action_player_store,player.name,nil)
Store.set_child(action_name_store,player.name,nil)
Store.clear(action_player_store,player.name)
Store.clear(action_name_store,player.name)
element.parent.entry.text = ''
end)
@@ -157,7 +157,7 @@ local function generate_container(player,element)
Gui.set_padding(reason_bar,-1,-1,3,3)
reason_bar.style.horizontally_stretchable = true
reason_bar.style.height = 35
local action_name = Store.get_child(action_name_store,player.name)
local action_name = Store.get(action_name_store,player.name)
reason_bar.visible = action_name ~= nil
-- text entry for the reason bar
@@ -180,7 +180,7 @@ end
--- Adds buttons and permission flows to the action bar
local function generate_action_bar(player,element)
close_action_bar(element)
local action_player = Store.get_child(action_player_store,player.name)
local action_player = Store.get(action_player_store,player.name)
for action_name,buttons in pairs(config) do
local permission_flow =
@@ -212,7 +212,7 @@ local player_list_name
local function update_action_bar(player)
local frame = Gui.classes.left_frames.get_frame(player_list_name,player)
local element = frame.container.action_bar
local action_player_name = Store.get_child(action_player_store,player.name)
local action_player_name = Store.get(action_player_store,player.name)
if not action_player_name then
element.visible = false
@@ -220,8 +220,8 @@ local function update_action_bar(player)
local action_player = Game.get_player_from_any(action_player_name)
if not action_player.connected then
element.visible = false
Store.set(action_player_store,player.name) -- clears store if player is offline
Store.set_child(action_name_store,player.name)
Store.clear(action_player_store,player.name) -- clears store if player is offline
Store.clear(action_name_store,player.name)
else
element.visible = true
for action_name,buttons in pairs(config) do
@@ -347,13 +347,13 @@ Store.register(action_name_store,function(value,category)
local frame = Gui.classes.left_frames.get_frame(player_list_name,player)
local element = frame.container.reason_bar
if value then
local action_player_name = Store.get_child(action_player_store,category)
local action_player_name = Store.get(action_player_store,category)
local action_player = Game.get_player_from_any(action_player_name)
if action_player.connected then
element.visible = true
else
Store.set_child(action_player_store,category) -- clears store if player is offline
Store.set_child(action_name_store,category)
Store.clear(action_player_store,category) -- clears store if player is offline
Store.clear(action_name_store,category)
end
else
element.visible = false

View File

@@ -41,13 +41,13 @@ local function add_task(player,task_number)
editing={[player.name]=true}
}
Store.set_child(task_store,task_id,'New task')
Store.set(task_store,task_id,'New task')
end
--- Removes all refrences to a task
local function remove_task(task_id)
local force_name = task_details[task_id].force
Store.set_child(task_store,task_id)
Store.clear(task_store,task_id)
task_details[task_id] = nil
table.remove_element(force_tasks[force_name],task_id)
end
@@ -108,7 +108,7 @@ end)
details.editing[player.name] = nil
details.last_edit_player = player.name
details.last_edit_time = game.tick
Store.set_child(task_store,task_id,task)
Store.set(task_store,task_id,task)
end)
--- Used to cancel any changes you made to a task
@@ -175,7 +175,7 @@ end)
>> discard_task
]]
function generate_task(player,element,task_id)
local task = Store.get_child(task_store,task_id)
local task = Store.get(task_store,task_id)
local details = task_details[task_id]
local editing = details.editing[player.name]
local last_edit_player = details.last_edit_player