Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[-----------------------------------------------------------------------\\--
- :: DOCUMENTATION ::
- --\\-----------------------------------------------------------------------]]--
- --[[
- The SaveManager's (SM) job is to abstract storing and loading data, it does so by providing a simple
- API for clients to use.
- -------------------------------------------------------------------------------
- A simple and crude overview of the SM protocol:
- Whenever a client needs a SM:
- client calls [xs_save.]getSaveManager()
- This function ensures that only ever a sinle SM instance exists.
- when a client wants to persist data:
- client calls SaveManager:Attach(self)
- If true is returned, from here on out it will receive OnSave and OnLoad calls by the SM.
- If false is returned, nothing will happen.
- This should only be the case when the SM detects that the client doesn't follow the
- protocol (it doesn't have a OnSave or OnLoad method).
- when the SM calls a clients OnSave method:
- client calls SaveManager:Update(section, data)
- The SM will store 'data' associated with the name 'section'.
- Note: the name 'section' has to be unique among all clients or they will overwrite each
- others data.
- when the SM calls a clients OnLoad method:
- client call SaveManager:Request(section[, clear])
- The SM will try to retrieve the data from 'section'
- when it finds 'section' it will return its associated data table,
- when it doesn't find 'section' it will return nil instead.
- Note: The optional boolean 'clear' argument if its true, the SM will call Clear(section)
- after returning the data. Also 'clear' defaults to true.
- Therefor if you wish for data to be retrievable by multiple clients, pass false. In
- which case the SM will hold on to the data forever or until Clear(section) is
- called.
- when client wants to reset the persisted data:
- client calls SaveManager:Clear(section)
- The SM will remove the section from its internal data store.
- when client doesn't need to persist data anymore:
- client calls SaveManager:Detach(self)
- If true is returned, the client will no longer receive OnSave and OnLoad events
- If false is returned, the client is unknown to the SM and nothing will happen.
- -------------------------------------------------------------------------------
- The SaveManager API:
- bool SaveManager:Attach(table/userdata client)
- When successfully attached, the SM will forward OnSave and OnLoad events to the client.
- client: a table or userdata object which must provide a OnSave and a OnLoad method.
- bool SaveManager:Detach(table/userdata client)
- When the client no longer wishes to receive OnLoad/Save events it can detach itself.
- client: a table or userdata object which must provide a OnSave and a OnLoad method.
- void SaveManager:Update(string section, table data)
- Called by clients to update its persistent data; should only be called from clients within
- their OnSave method to update data.
- As there is no need to preemptively update data when no save was requested.
- section: A unique string associated and identifying the owner with the data.
- data: A table of data to be saved.
- Note that not everything can be saved, see xs_storage for more on that.
- table/nil SaveManager:Request(string section, table clear)
- Called by clients to request previously saved data; should only be called from clients
- within their OnLoad method.
- Note: That their might not be any previously saved data in which case nil will be returned.
- As there is no good general way to deal with that, the client is left to deal with this
- case by itself.
- void SaveManager:SaveAs(string basename)
- Can be called to create a save apart from automatically created ones (auto-& quicksave).
- basename: A string which will be used to construct a filename, the format is as follows:
- <user_name>_<basename>.<save-extension>
- Where <user_name> is retrieved using: user_name():lower().
- Where <save-extension> is defined by SM_EXTENSION.
- -------------------------------------------------------------------------------
- Client API:
- void client:OnSave(sm)
- Called by the SM whenver a save is requested.
- sm: The SaveManger instance, use it to call Update() on it.
- void client:OnLoad(sm)
- Called by the SM whenver a load is requested.
- sm: The SaveManger instance, use it to call Request() on it.
- -------------------------------------------------------------------------------
- Optional Client API:
- void client:OnLoadError(sm)
- Called by the SM whenever a OnLoad cannot happen.
- The save file cannot be accessed (missing, locked, permissions).
- sm: The SaveManger instance, for now there are no uses...
- ]]
- --[[-----------------------------------------------------------------------\\--
- :: PRIVATE ::
- --\\-----------------------------------------------------------------------]]--
- local insert, remove, concat, ipairs, type
- = table.insert, table.remove, table.concat, ipairs, type
- local gamePath, rootPath
- = xs_fs.gamePath, xs_fs.rootPath
- local check, find
- = xs_utils.check, xs_utils.find
- local save, load
- = xs_storage.save, xs_storage.load
- local m_sm
- -- The extensions must be 3 characters long with a leading dot.
- local SM_EXTENSION = ".lua"
- --[[-----------------------------------------------------------------------\\--
- :: PUBLIC ::
- --\\-----------------------------------------------------------------------]]--
- __FILE__ = "xs_save"
- class "SaveManager" (XsObj)
- --[[-------------------------------------------------------------------\\--
- :: Constructor
- --\\-------------------------------------------------------------------]]--
- function SaveManager:__init() super("SaveManager")
- self.path = nil
- self.store = {}
- self.clients = {}
- end
- --[[-------------------------------------------------------------------\\--
- :: Methods: Overloads
- --\\-------------------------------------------------------------------]]--
- function SaveManager:__tostring()
- local count, isRefType
- = xs_utils.count, xs_utils.isRefType
- local l = 0
- local buffer = xs_text.StringBuilder()
- buffer:AppendFormat("%s [%08X]:\n", self.__cls, self.__uid):IncreaseIndent()
- -- path
- buffer:AppendLineIf(self.path ~= nil, "path: ", self.path)
- -- clients
- l = #self.clients
- if l > 0 then
- buffer:AppendLine("clients: "):IncreaseIndent()
- for i, v in ipairs(self.clients) do
- buffer:AppendIndent():Append("[", i, "]: ")
- if type(v.XsObj) == "function" then
- buffer:Append(XsObj.__tostring(v))
- elseif not isRefType(v) or type(v.__tostring) == "function" then
- buffer:Append(v)
- else
- buffer:Append("*omitted*")
- end
- buffer:AppendIfOr(i < l, ",\n", "\n")
- end
- buffer:DecreaseIndent()
- else
- buffer:AppendLine("clients: *empty*")
- end
- -- store
- l = count(self.store)
- if l > 0 then
- local i = 1
- buffer:AppendLine("store: "):IncreaseIndent()
- for k, v in pairs(self.store) do
- buffer:AppendIndent():Append("[", k, "]: ")
- if isRefType(v) then
- buffer:Append("*omitted*")
- else
- buffer:Append(v)
- end
- buffer:AppendIfOr(i < l, ",\n", "\n")
- i = i + 1
- end
- else
- buffer:AppendLine("store: *empty*")
- end
- buffer:DecreaseIndent()
- return buffer:ToString()
- end
- --[[-------------------------------------------------------------------\\--
- :: Methods: Private
- --\\-------------------------------------------------------------------]]--
- function SaveManager:verifySection(section, caller)
- check(type(section) == "string" and section:len() > 0,
- "[SaveManager:%s]: Invalid section name '%s'.",
- caller, section)
- end
- function SaveManager:verifyClient(client, caller)
- check(type(client.OnSave) == "function",
- "[SaveManager:%s]: Invalid client (%s); missing OnSave function.",
- caller, tostring(client))
- check(type(client.OnLoad) == "function",
- "[SaveManager:%s]: Invalid client (%s); missing OnLoad function.",
- caller, tostring(client))
- end
- function SaveManager:verifyWriter(writer, caller)
- check(type(writer) == "userdata" and type(writer.w_stringZ) == "function",
- "[SaveManager:%s]: Invalid argument #1 'writer' is not a net_packet instance.",
- caller)
- end
- function SaveManager:verifyReader(reader, caller)
- check(type(reader) == "userdata" and type(reader.r_stringZ) == "function",
- "[SaveManager:%s]: Invalid argument #1 'reader' is not a reader instance.",
- caller)
- end
- function SaveManager:makeName(savename, caller, skipChecks)
- if skipChecks ~= true then
- check(type(savename) == "string" and savename:len() > 0,
- "[SaveManager:%s]: Invalid #1 argument; must be a non-empty string.",
- caller)
- local value = savename:lower()
- local roots = {
- gamePath("$app_data_root$"),
- gamePath("$fs_root$")
- }
- for _, v in ipairs(roots) do
- if value:find(v:lower(), 1, true) ~= nil then
- abort("[SaveManager:%s]: Invalid #1 argument; must be a basename only.", caller)
- end
- end
- end
- local name = {}
- local user = user_name():lower()
- if savename:find(user, 1, true) == nil then
- insert(name, user)
- insert(name, "_")
- end
- insert(name, savename)
- if savename:sub(-SM_EXTENSION:len()) ~= SM_EXTENSION then
- insert(name, SM_EXTENSION)
- end
- return concat(name, "")
- end
- function SaveManager:getSaveName(caller)
- if self.path then
- local temp = self.path
- self.path = nil
- return temp
- end
- return self:makeName(
- travel_manager.level_changing and "autosave" or "quicksave",
- caller,
- true
- )
- end
- function SaveManager:markAutoSave(caller)
- self.path = self:makeName("autosave", caller, true)
- end
- function SaveManager:findClient(client)
- --[[local cu = type(client) == "userdata"
- for i, v in ipairs(self.clients) do
- local vu = type(v) == "userdata"
- if vu and cu then
- if xs_utils.equals(v, client) then
- return i
- end
- elseif vu or cu then
- -- skip; the types don't match so neither can the actual data
- else
- if v == client then
- return i
- end
- end
- end
- return nil]]
- -- 2012-07-13 17:09: ~Xetrill: VERY FRAGILE because of the way luabind works.
- -- it doesn't supply a default __eq overload and as a result of that
- -- any C++ or luabind Lua class (=userdata) will case a fatal error
- -- saying "No such operator defined".
- -- And without Lua's debug.* table loaded there's no way to get a stacktrace.
- -- Meaning this is a potential debugging hell.
- -- TODO: See above comment and do something about it.
- return find(self.clients, client, nil, ipairs)
- end
- --[[-------------------------------------------------------------------\\--
- :: Methods: Public
- --\\-------------------------------------------------------------------]]--
- function SaveManager:Attach(client)
- self:verifyClient(client, "Attach")
- if self:findClient(client) ~= nil then
- return false
- end
- insert(self.clients, client)
- return true
- end
- function SaveManager:Detach(client)
- self:verifyClient(client, "Detach")
- local index = self:findClient(client)
- if index == nil then
- return false
- end
- return remove(self.clients, index) ~= nil
- end
- function SaveManager:Update(section, data)
- self:verifySection(section, "Update")
- self.store[section] = data
- end
- function SaveManager:Request(section, clear)
- self:verifySection(section, "Request")
- local data = self.store[section]
- if clear == nil or clear == true then
- self.store[section] = nil
- end
- return data
- end
- function SaveManager:Clear(section)
- self:verifySection(section, "Clear")
- self.store[section] = nil
- end
- function SaveManager:SaveAs(writer, savename)
- self:verifyWriter(writer, "OnSave");
- self.path = self:makeName(savename, "SaveAs", false)
- self:OnSave(writer)
- end
- -- Use in bind_stalker.save()
- function SaveManager:OnSave(writer)
- self:verifyWriter(writer, "OnSave");
- local path = self:getSaveName("OnSave")
- writer:w_stringZ(path)
- for _, client in ipairs(self.clients) do
- client:OnSave(self)
- end
- save(rootPath(path, "$game_saves$"), self.store)
- end
- -- Use in bind_stalker.load()
- function SaveManager:OnLoad(reader)
- self:verifyReader(reader, "OnLoad");
- local path = reader:r_stringZ()
- if not path or path:sub(-4) ~= SM_EXTENSION then
- return
- end
- local data = load(rootPath(path, "$game_saves$"))
- if not data then
- for _, client in ipairs(self.clients) do
- if type(client.OnLoadError) == "function" then
- client:OnLoadError(self)
- end
- end
- return
- end
- self.store = data
- for _, client in ipairs(self.clients) do
- client:OnLoad(self)
- end
- end
- function getSaveManager()
- if m_sm == nil then
- m_sm = SaveManager()
- end
- return m_sm
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement