Advertisement
Xetrill

xs_save.script

Oct 18th, 2013
293
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 12.81 KB | None | 0 0
  1. --[[-----------------------------------------------------------------------\\--
  2.     :: DOCUMENTATION ::
  3. --\\-----------------------------------------------------------------------]]--
  4.  
  5. --[[
  6. The SaveManager's (SM) job is to abstract storing and loading data, it does so by providing a simple
  7. API for clients to use.
  8.  
  9. -------------------------------------------------------------------------------
  10. A simple and crude overview of the SM protocol:
  11.     Whenever a client needs a SM:
  12.         client calls [xs_save.]getSaveManager()
  13.             This function ensures that only ever a sinle SM instance exists.
  14.  
  15.     when a client wants to persist data:
  16.         client calls SaveManager:Attach(self)
  17.             If true is returned, from here on out it will receive OnSave and OnLoad calls by the SM.
  18.             If false is returned, nothing will happen.
  19.                 This should only be the case when the SM detects that the client doesn't follow the
  20.                 protocol (it doesn't have a OnSave or OnLoad method).
  21.  
  22.     when the SM calls a clients OnSave method:
  23.         client calls SaveManager:Update(section, data)
  24.             The SM will store 'data' associated with the name 'section'.
  25.  
  26.             Note: the name 'section' has to be unique among all clients or they will overwrite each
  27.             others data.
  28.  
  29.     when the SM calls a clients OnLoad method:
  30.         client call SaveManager:Request(section[, clear])
  31.             The SM will try to retrieve the data from 'section'
  32.             when it finds 'section' it will return its associated data table,
  33.             when it doesn't find 'section' it will return nil instead.
  34.  
  35.             Note: The optional boolean 'clear' argument if its true, the SM will call Clear(section)
  36.                 after returning the data. Also 'clear' defaults to true.
  37.  
  38.                 Therefor if you wish for data to be retrievable by multiple clients, pass false. In
  39.                 which case the SM will hold on to the data forever or until Clear(section) is
  40.                 called.
  41.  
  42.     when client wants to reset the persisted data:
  43.         client calls SaveManager:Clear(section)
  44.             The SM will remove the section from its internal data store.
  45.  
  46.     when client doesn't need to persist data anymore:
  47.         client calls SaveManager:Detach(self)
  48.             If true is returned, the client will no longer receive OnSave and OnLoad events
  49.             If false is returned, the client is unknown to the SM and nothing will happen.
  50.  
  51. -------------------------------------------------------------------------------
  52. The SaveManager API:
  53.     bool SaveManager:Attach(table/userdata client)
  54.         When successfully attached, the SM will forward OnSave and OnLoad events to the client.
  55.  
  56.         client: a table or userdata object which must provide a OnSave and a OnLoad method.
  57.  
  58.     bool SaveManager:Detach(table/userdata client)
  59.         When the client no longer wishes to receive OnLoad/Save events it can detach itself.
  60.  
  61.         client: a table or userdata object which must provide a OnSave and a OnLoad method.
  62.  
  63.     void SaveManager:Update(string section, table data)
  64.         Called by clients to update its persistent data; should only be called from clients within
  65.         their OnSave method to update data.
  66.         As there is no need to preemptively update data when no save was requested.
  67.  
  68.         section:    A unique string associated and identifying the owner with the data.
  69.         data:       A table of data to be saved.
  70.                     Note that not everything can be saved, see xs_storage for more on that.
  71.  
  72.     table/nil SaveManager:Request(string section, table clear)
  73.         Called by clients to request previously saved data; should only be called from clients
  74.         within their OnLoad method.
  75.  
  76.         Note: That their might not be any previously saved data in which case nil will be returned.
  77.             As there is no good general way to deal with that, the client is left to deal with this
  78.             case by itself.
  79.  
  80.     void SaveManager:SaveAs(string basename)
  81.         Can be called to create a save apart from automatically created ones (auto-& quicksave).
  82.  
  83.         basename:   A string which will be used to construct a filename, the format is as follows:
  84.             <user_name>_<basename>.<save-extension>
  85.  
  86.             Where <user_name> is retrieved using: user_name():lower().
  87.             Where <save-extension> is defined by SM_EXTENSION.
  88.  
  89. -------------------------------------------------------------------------------
  90. Client API:
  91.     void client:OnSave(sm)
  92.         Called by the SM whenver a save is requested.
  93.  
  94.         sm: The SaveManger instance, use it to call Update() on it.
  95.  
  96.     void client:OnLoad(sm)
  97.         Called by the SM whenver a load is requested.
  98.  
  99.         sm: The SaveManger instance, use it to call Request() on it.
  100.  
  101. -------------------------------------------------------------------------------
  102. Optional Client API:
  103.     void client:OnLoadError(sm)
  104.  
  105.         Called by the SM whenever a OnLoad cannot happen.
  106.             The save file cannot be accessed (missing, locked, permissions).
  107.  
  108.         sm: The SaveManger instance, for now there are no uses...
  109. ]]
  110.  
  111.  
  112. --[[-----------------------------------------------------------------------\\--
  113.     :: PRIVATE ::
  114. --\\-----------------------------------------------------------------------]]--
  115.  
  116. local insert, remove, concat, ipairs, type
  117.     = table.insert, table.remove, table.concat, ipairs, type
  118. local gamePath, rootPath
  119.     = xs_fs.gamePath, xs_fs.rootPath
  120. local check, find
  121.     = xs_utils.check, xs_utils.find
  122. local save, load
  123.     = xs_storage.save, xs_storage.load
  124.  
  125. local m_sm
  126.  
  127. -- The extensions must be 3 characters long with a leading dot.
  128. local SM_EXTENSION = ".lua"
  129.  
  130.  
  131. --[[-----------------------------------------------------------------------\\--
  132.     :: PUBLIC ::
  133. --\\-----------------------------------------------------------------------]]--
  134.  
  135. __FILE__ = "xs_save"
  136.  
  137. class "SaveManager" (XsObj)
  138.  
  139.     --[[-------------------------------------------------------------------\\--
  140.         :: Constructor
  141.     --\\-------------------------------------------------------------------]]--
  142.  
  143.     function SaveManager:__init() super("SaveManager")
  144.         self.path   = nil
  145.         self.store   = {}
  146.         self.clients = {}
  147.     end
  148.  
  149.     --[[-------------------------------------------------------------------\\--
  150.         :: Methods: Overloads
  151.     --\\-------------------------------------------------------------------]]--
  152.  
  153.     function SaveManager:__tostring()
  154.         local count, isRefType
  155.             = xs_utils.count, xs_utils.isRefType
  156.  
  157.         local l   = 0
  158.         local buffer = xs_text.StringBuilder()
  159.  
  160.         buffer:AppendFormat("%s [%08X]:\n", self.__cls, self.__uid):IncreaseIndent()
  161.  
  162.         -- path
  163.         buffer:AppendLineIf(self.path ~= nil, "path: ", self.path)
  164.  
  165.         -- clients
  166.         l = #self.clients
  167.         if l > 0 then
  168.             buffer:AppendLine("clients: "):IncreaseIndent()
  169.             for i, v in ipairs(self.clients) do
  170.                 buffer:AppendIndent():Append("[", i, "]: ")
  171.                 if type(v.XsObj) == "function" then
  172.                     buffer:Append(XsObj.__tostring(v))
  173.                 elseif not isRefType(v) or type(v.__tostring) == "function" then
  174.                     buffer:Append(v)
  175.                 else
  176.                     buffer:Append("*omitted*")
  177.                 end
  178.                 buffer:AppendIfOr(i < l, ",\n", "\n")
  179.             end
  180.             buffer:DecreaseIndent()
  181.         else
  182.             buffer:AppendLine("clients: *empty*")
  183.         end
  184.  
  185.         -- store
  186.         l = count(self.store)
  187.         if l > 0 then
  188.             local i = 1
  189.             buffer:AppendLine("store: "):IncreaseIndent()
  190.             for k, v in pairs(self.store) do
  191.                 buffer:AppendIndent():Append("[", k, "]: ")
  192.                 if isRefType(v) then
  193.                     buffer:Append("*omitted*")
  194.                 else
  195.                     buffer:Append(v)
  196.                 end
  197.                 buffer:AppendIfOr(i < l, ",\n", "\n")
  198.                 i = i + 1
  199.             end
  200.         else
  201.             buffer:AppendLine("store: *empty*")
  202.         end
  203.         buffer:DecreaseIndent()
  204.         return buffer:ToString()
  205.     end
  206.  
  207.     --[[-------------------------------------------------------------------\\--
  208.         :: Methods: Private
  209.     --\\-------------------------------------------------------------------]]--
  210.  
  211.     function SaveManager:verifySection(section, caller)
  212.         check(type(section) == "string" and section:len() > 0,
  213.               "[SaveManager:%s]: Invalid section name '%s'.",
  214.               caller, section)
  215.     end
  216.  
  217.     function SaveManager:verifyClient(client, caller)
  218.         check(type(client.OnSave) == "function",
  219.               "[SaveManager:%s]: Invalid client (%s); missing OnSave function.",
  220.               caller, tostring(client))
  221.         check(type(client.OnLoad) == "function",
  222.               "[SaveManager:%s]: Invalid client (%s); missing OnLoad function.",
  223.               caller, tostring(client))
  224.     end
  225.  
  226.     function SaveManager:verifyWriter(writer, caller)
  227.         check(type(writer) == "userdata" and type(writer.w_stringZ) == "function",
  228.               "[SaveManager:%s]: Invalid argument #1 'writer' is not a net_packet instance.",
  229.               caller)
  230.     end
  231.  
  232.     function SaveManager:verifyReader(reader, caller)
  233.         check(type(reader) == "userdata" and type(reader.r_stringZ) == "function",
  234.               "[SaveManager:%s]: Invalid argument #1 'reader' is not a reader instance.",
  235.               caller)
  236.     end
  237.  
  238.     function SaveManager:makeName(savename, caller, skipChecks)
  239.         if skipChecks ~= true then
  240.             check(type(savename) == "string" and savename:len() > 0,
  241.                   "[SaveManager:%s]: Invalid #1 argument; must be a non-empty string.",
  242.                   caller)
  243.  
  244.             local value = savename:lower()
  245.             local roots = {
  246.                 gamePath("$app_data_root$"),
  247.                 gamePath("$fs_root$")
  248.             }
  249.             for _, v in ipairs(roots) do
  250.                 if value:find(v:lower(), 1, true) ~= nil then
  251.                     abort("[SaveManager:%s]: Invalid #1 argument; must be a basename only.", caller)
  252.                 end
  253.             end
  254.         end
  255.  
  256.         local name = {}
  257.  
  258.         local user = user_name():lower()
  259.         if savename:find(user, 1, true) == nil then
  260.             insert(name, user)
  261.             insert(name, "_")
  262.         end
  263.  
  264.         insert(name, savename)
  265.  
  266.         if savename:sub(-SM_EXTENSION:len()) ~= SM_EXTENSION then
  267.             insert(name, SM_EXTENSION)
  268.         end
  269.         return concat(name, "")
  270.     end
  271.  
  272.     function SaveManager:getSaveName(caller)
  273.         if self.path then
  274.             local temp = self.path
  275.             self.path = nil
  276.             return temp
  277.         end
  278.  
  279.         return self:makeName(
  280.             travel_manager.level_changing and "autosave" or "quicksave",
  281.             caller,
  282.             true
  283.         )
  284.     end
  285.  
  286.     function SaveManager:markAutoSave(caller)
  287.         self.path = self:makeName("autosave", caller, true)
  288.     end
  289.  
  290.     function SaveManager:findClient(client)
  291.         --[[local cu = type(client) == "userdata"
  292.         for i, v in ipairs(self.clients) do
  293.             local vu = type(v) == "userdata"
  294.             if vu and cu then
  295.                 if xs_utils.equals(v, client) then
  296.                     return i
  297.                 end
  298.             elseif vu or cu then
  299.                 -- skip; the types don't match so neither can the actual data
  300.             else
  301.                 if v == client then
  302.                     return i
  303.                 end
  304.             end
  305.         end
  306.  
  307.         return nil]]
  308.  
  309.         -- 2012-07-13 17:09: ~Xetrill: VERY FRAGILE because of the way luabind works.
  310.         --  it doesn't supply a default __eq overload and as a result of that
  311.         --  any C++ or luabind Lua class (=userdata) will case a fatal error
  312.         --  saying "No such operator defined".
  313.         -- And without Lua's debug.* table loaded there's no way to get a stacktrace.
  314.         -- Meaning this is a potential debugging hell.
  315.  
  316.         -- TODO: See above comment and do something about it.
  317.         return find(self.clients, client, nil, ipairs)
  318.     end
  319.  
  320.     --[[-------------------------------------------------------------------\\--
  321.         :: Methods: Public
  322.     --\\-------------------------------------------------------------------]]--
  323.  
  324.     function SaveManager:Attach(client)
  325.         self:verifyClient(client, "Attach")
  326.  
  327.         if self:findClient(client) ~= nil then
  328.             return false
  329.         end
  330.  
  331.         insert(self.clients, client)
  332.         return true
  333.     end
  334.  
  335.     function SaveManager:Detach(client)
  336.         self:verifyClient(client, "Detach")
  337.  
  338.         local index = self:findClient(client)
  339.         if index == nil then
  340.             return false
  341.         end
  342.  
  343.         return remove(self.clients, index) ~= nil
  344.     end
  345.  
  346.     function SaveManager:Update(section, data)
  347.         self:verifySection(section, "Update")
  348.  
  349.         self.store[section] = data
  350.     end
  351.  
  352.     function SaveManager:Request(section, clear)
  353.         self:verifySection(section, "Request")
  354.  
  355.         local data = self.store[section]
  356.         if clear == nil or clear == true then
  357.             self.store[section] = nil
  358.         end
  359.  
  360.         return data
  361.     end
  362.  
  363.     function SaveManager:Clear(section)
  364.         self:verifySection(section, "Clear")
  365.  
  366.         self.store[section] = nil
  367.     end
  368.  
  369.     function SaveManager:SaveAs(writer, savename)
  370.         self:verifyWriter(writer, "OnSave");
  371.  
  372.         self.path = self:makeName(savename, "SaveAs", false)
  373.         self:OnSave(writer)
  374.     end
  375.  
  376.     -- Use in bind_stalker.save()
  377.     function SaveManager:OnSave(writer)
  378.         self:verifyWriter(writer, "OnSave");
  379.  
  380.         local path = self:getSaveName("OnSave")
  381.         writer:w_stringZ(path)
  382.  
  383.         for _, client in ipairs(self.clients) do
  384.             client:OnSave(self)
  385.         end
  386.  
  387.         save(rootPath(path, "$game_saves$"), self.store)
  388.     end
  389.  
  390.     -- Use in bind_stalker.load()
  391.     function SaveManager:OnLoad(reader)
  392.         self:verifyReader(reader, "OnLoad");
  393.  
  394.         local path = reader:r_stringZ()
  395.         if not path or path:sub(-4) ~= SM_EXTENSION then
  396.             return
  397.         end
  398.  
  399.         local data = load(rootPath(path, "$game_saves$"))
  400.         if not data then
  401.             for _, client in ipairs(self.clients) do
  402.                 if type(client.OnLoadError) == "function" then
  403.                     client:OnLoadError(self)
  404.                 end
  405.             end
  406.             return
  407.         end
  408.  
  409.         self.store = data
  410.         for _, client in ipairs(self.clients) do
  411.             client:OnLoad(self)
  412.         end
  413.     end
  414.  
  415.  
  416. function getSaveManager()
  417.     if m_sm == nil then
  418.         m_sm = SaveManager()
  419.     end
  420.     return m_sm
  421. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement