--[[-- Core Module - Store - Adds an easy way to store and watch for updates to a value @core Store @alias Store @usage -- The data store module is designed to be an alterative way to store data in the global table -- each piece of data is stored at a location and optional key of that location -- it is recomented that you use a local varible to store the location local scenario_difficuly = Store.uid_location() local team_scores = 'team-scores' -- Setting and getting data is then as simple as -- note that when storing a table you must use Store.update Store.set(scenario_difficuly,'Hard') Store.set(team_scores,game.player.force.name,20) Store.get(scenario_difficuly) -- returns 'Hard' Store.get(team_scores,game.player.force.name) -- returns 20 Store.update(team_scores,game.player.force.name,function(value,key) return value + 10 -- add 10 to the score end) -- The reason for using stores over global is the abilty to watch for updates -- for stores to work you must register them, often at the end of the file Store.register(scenario_difficuly,function(value) game.print('Scenario difficulty has been set to: '..value) end) Store.register(team_scores,function(value,key) game.print('Team '..key..' now has a score of '..value) end) -- This can be very powerful when working with data that can be changed for a number of locations -- with this module you can enable any location to output its changes to a file -- say we wanted team scores to be synced across servers or between saves -- although you will need to set up a method of storing the data outside the game Store.register(team_scores,true,function(value,key) game.print('Team '..key..' now has a score of '..value) end) -- If you want multiple handlers on one store location then you can register to the raw event Event.add(Store.events.on_value_changed,function(event) game.print('Store '..event.location..'/'..event.key..' was updated to: '..event.value) end) ]] local Global = require 'utils.global' --- @dep utils.global local Event = require 'utils.event' --- @dep utils.event local table_keys,write_json,get_file_path = ext_require('expcore.common','table_keys','write_json','get_file_path') --- @dep expcore.common local Token = require 'utils.token' --- @dep utils.token local Store = { registered={}, synced={}, callbacks={}, events = { on_value_changed=script.generate_event_name() } } local store_data = {} 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 key locations',3) end end --[[-- Registers a new location with an update callback which is triggered when the value updates @tparam[opt] string location string a unique that points to the data, string used rather than token to allow migration @tparam[opt=false] 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 location that is being used @usage-- Registering a new store location local store_id = Store.register() @usage-- Registering a new store location, with custom update callback local store_id = Store.uid_location() Store.register(store_id,function(value,key) game.print('Store '..store_id..'/'..key..' was updated to: '..value) end) ]] function Store.register(location,synced,callback) if _LIFECYCLE ~= _STAGE.control then return error('Can only be called during the control stage', 2) end 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 '..location..' is already registered by '..Store.registered[location], 2) end Store.registered[location] = get_file_path(1) Store.synced[location] = synced and true or nil Store.callbacks[location] = callback or nil return location 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] string key the key location if used @treturn any the data which was stored at the location @usage-- Getting the data at a store location local data = Store.get(store_id_no_keys) local data = Store.get(store_id_with_keys,'key_one') ]] function Store.get(location,key) if not Store.registered[location] then return error('Location is not registered', 2) end local data = store_data[location] if key and data then error_not_table(data) return data[key] end return data end --[[-- Sets the value at a location, this location must be registered @tparam string location the location to set the data to @tparam[opt] string key the key location if used @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=false] boolean from_sync set this true to avoid an output to the sync file @tparam[opt=false] boolean from_internal set this true to add one to the error stack offset @treturn boolean true if it was successful @usage-- Setting the data at a store location Store.set(store_id_no_keys,'Hello, World!') Store.set(store_id_with_keys,'key_one','Hello, World!') ]] function Store.set(location,key,value,from_sync,from_internal) if not Store.registered[location] then return error('Location is not registered', from_internal and 3 or 2) end if value == nil then value = key key = nil end if key then local data = store_data[location] if not data then data = {} store_data[location] = data end error_not_table(data) data[key] = value else store_data[location] = value end script.raise_event(Store.events.on_value_changed,{ tick=game.tick, location=location, key=key, value=value, from_sync=from_sync or false }) return true end --[[-- Allows for updating a value based on the current value; only valid way to change tables in a store @tparam string location the location to set the data to @tparam[opt] string key the key location if required @tparam[opt] function update_callback the function called to update the value stored, rtn value to set new value @usage-- Updating a value stored at a location Store.update(store_id_no_keys,function(value) return value + 1 end) Store.update(store_id_with_keys,'key_one',function(value) return value + 1 end) @usage-- Updating a table stored at a location Store.update(store_id_no_keys,function(value) value.ctn = value.ctn + 1 end) Store.update(store_id_with_keys,'key_one',function(value) value.ctn = value.ctn + 1 end) ]] function Store.update(location,key,update_callback,...) local value = Store.get(location,key) local args if type(key) == 'function' then args = {update_callback,...} update_callback = key key = nil end local rtn if update_callback and type(update_callback) == 'function' then if args then rtn = update_callback(value,key,unpack(args)) else rtn = update_callback(value,key,...) end end if rtn then Store.set(location,key,rtn,nil,true) else script.raise_event(Store.events.on_value_changed,{ tick=game.tick, location=location, key=key, value=value, from_sync=false }) end end --[[-- Allows for updating all values at a location based on the current value; only valid way to change tables in a store @tparam string location the location to set the data to @tparam[opt] function update_callback the function called to update the value stored @usage-- Updating all values at a location Store.update(store_id_with_keys,function(value) return value + 1 end) @usage-- Updating all tables at a location Store.update(store_id_with_keys,function(value) value.ctn = value.ctn + 1 end) ]] function Store.update_all(location,update_callback,...) local data = Store.get(location) error_not_table(data) for key,value in pairs(data) do local rtn if update_callback and type(update_callback) == 'function' then rtn = update_callback(value,key,...) end if rtn then Store.set(location,key,rtn,nil,true) else script.raise_event(Store.events.on_value_changed,{ tick=game.tick, location=location, key=key, value=value, from_sync=false }) end end 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 key the key location if used @tparam[opt=false] boolean from_sync set this true to avoid an output to the sync file @treturn boolean true if it was successful @usage-- Clear the data at a location Store.clear(store_id_no_keys) Store.clear(store_id_with_keys,'key_one') ]] function Store.clear(location,key,from_sync) if not Store.callbacks[location] then return error('Location is not registered', 2) end if key then local data = store_data[location] if not data then return end error_not_table(data) data[key] = nil else store_data[location] = nil end script.raise_event(Store.events.on_value_changed,{ tick=game.tick, location=location, key=key, from_sync=from_sync or false }) return true end --[[-- Gets all non nil keys at a location, keys 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 keys of @treturn table a table containing all the keys names @usage-- Get all keys at a store location local keys = Store.get_keys(store_id_with_keys) ]] function Store.get_keys(location) local data = Store.get(location) return type(data) == 'table' and table_keys(data) or {} end --[[-- Check for if a location is registered @tparam string location the location to test for @treturn boolean true if registered @usage-- Check that a store is registered local registerd = Store.is_registered(store_id) ]] function Store.is_registered(location) return Store.registered[location] end --[[-- Returns a unique name that can be used for a store @treturn string a unique name @usage-- Get a new unique store id local store_id = Store.uid_location() ]] function Store.uid_location() return tostring(Token.uid()) end -- Handles syncing Event.add(Store.events.on_value_changed,function(event) if Store.callbacks[event.location] then Store.callbacks[event.location](event.value,event.key) end if not event.from_sync and Store.synced[event.location] then write_json('log/store.log',{ tick=event.tick, location=event.location, key=event.key, value=event.value, }) end end) return Store