Advertisement
Illunal

DatastoreModule.lua

Aug 21st, 2018
108
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 14.16 KB | None | 0 0
  1. local DATASTORE_NAME = "`DrZ5n@;2&/`4R{|,}GsOSJ46[Y!>h" -- API Encryption Key, changing this will erase all saved data.
  2. local SAVE_ON_LEAVE = true -- Boolean that dictates whether or not data will be saved before a player leaves the game.
  3. local DEBUG = false -- Boolean that dictates whether or not the API will log its activity, generally used for testing.
  4.  
  5. local PASSIVE_SAVE_FREQUENCY = 60*1 -- How long is the delay between passive saves?
  6. local CACHE_EXPIRY_TIME = 60*10 -- How long can data exist before it expires?
  7. local PASSIVE_GRANULARITY = 5
  8.  
  9.  
  10.  
  11. local SERIALIZE = {}
  12. local DESERIALIZE = {}
  13.  
  14.  
  15. if game.Players.LocalPlayer then
  16.     error("PlayerDataStore requested on the client, is a server-only module.", 2)
  17. end
  18.  
  19. --===============================================--
  20. --==             Utility Functions             ==--
  21. --===============================================--
  22. local function DeepCopy(tb)
  23.     if type(tb) == 'table' then
  24.         local new = {}
  25.         for k, v in pairs(tb) do
  26.             new[k] = DeepCopy(v)
  27.         end
  28.         return new
  29.     else
  30.         return tb
  31.     end
  32. end
  33.  
  34. local function SpawnNow(func)
  35.     local ev = Instance.new('BindableEvent')
  36.     ev.Event:connect(func)
  37.     ev:Fire()
  38. end
  39. --===============================================--
  40. --==              SaveData Class               ==--
  41. --===============================================--
  42. local SaveData = {}
  43. function SaveData.new(playerDataStore, userId)
  44.     local this = {}
  45.     --===============================================--
  46.     --==               Private Data                ==--
  47.     --===============================================--
  48.     this.userId = userId
  49.     this.lastSaved = 0
  50.    
  51.     this.locked = false
  52.     this.unlocked = Instance.new('BindableEvent')
  53.    
  54.     this.dataSet = nil
  55.    
  56.     this.dirtyKeySet = {}
  57.     this.ownedKeySet = {}
  58.     --===============================================--
  59.     --==          Private Implementation           ==--
  60.     --===============================================--
  61.     local function ownKey(key)
  62.         this.ownedKeySet[key] = true
  63.     end
  64.     local function dirtyKey(key)
  65.         this.dirtyKeySet[key] = true
  66.     end
  67.     local function markAsTouched(key)
  68.         ownKey(key)
  69.         playerDataStore:markAsTouched(this)
  70.     end
  71.     local function markAsDirty(key)
  72.         ownKey(key)
  73.         dirtyKey(key)
  74.         playerDataStore:markAsDirty(this)
  75.     end
  76.     function this:makeReady(data)
  77.         this.dataSet = data
  78.         this.lastSaved = tick()
  79.         playerDataStore:markAsTouched(this)
  80.     end
  81.     function this:waitForUnlocked()
  82.         while this.locked do
  83.             this.unlocked.Event:wait()
  84.         end
  85.     end
  86.     function this:lock()
  87.         this.locked = true
  88.     end
  89.     function this:unlock()
  90.         this.locked = false
  91.         this.unlocked:Fire()
  92.     end
  93.     --===============================================--
  94.     --==                Public API                 ==--
  95.     --===============================================--
  96.     function this:Get(key)
  97.         if type(key) ~= 'string' then
  98.             error("Bad argument #1 to SaveData::Get() (string expected)", 2)
  99.         end
  100.         if DEBUG then
  101.             print("SaveData<"..this.userId..">::Get("..key..")")
  102.         end
  103.         markAsTouched(key)
  104.         local value = this.dataSet[key]
  105.         if value == nil and DESERIALIZE[key] then
  106.             local v = DESERIALIZE[key](nil)
  107.             this.dataSet[key] = v
  108.             return v
  109.         else
  110.             return value
  111.         end
  112.     end
  113.     function this:Set(key, value, allowErase)
  114.         if type(key) ~= 'string' then
  115.             error("Bad argument #1 to SaveData::Set() (string expected)", 2)
  116.         end
  117.         if value == nil and not allowErase then
  118.             error("Attempt to SaveData::Set('"..key.."', nil) without allowErase = true", 2)
  119.         end
  120.         if DEBUG then
  121.             print("SaveData<"..this.userId..">::Set("..key..", "..tostring(value)..")")
  122.         end
  123.         markAsDirty(key)
  124.         this.dataSet[key] = value
  125.     end
  126.     function this:Update(keyList, func)
  127.         if type(keyList) ~= 'table' then
  128.             error("Bad argument #1 to SaveData::Update() (table of keys expected)", 2)
  129.         end
  130.         if type(func) ~= 'function' then
  131.             error("Bad argument #2 to SaveData::Update() (function expected)", 2)
  132.         end
  133.         if DEBUG then
  134.             print("SaveData<"..this.userId..">::Update("..table.concat(keyList, ", ")..", "..tostring(func)..")")
  135.         end
  136.         playerDataStore:doUpdate(this, keyList, func)
  137.     end
  138.     function this:Flush()
  139.         if DEBUG then
  140.             print("SaveData<"..this.userId..">::Flush()")
  141.         end
  142.         playerDataStore:doSave(this)
  143.     end
  144.     return this
  145. end
  146. --===============================================--
  147. --==           PlayerDataStore Class           ==--
  148. --===============================================--
  149. local PlayerDataStore = {}
  150. function PlayerDataStore.new()
  151.     local this = {}
  152.     --===============================================--
  153.     --==               Private Data                ==--
  154.     --===============================================--
  155.     local DataStoreService = Game:GetService('DataStoreService')
  156.     local mDataStore = DataStoreService:GetDataStore(DATASTORE_NAME)
  157.     local mUserIdSaveDataCache = setmetatable({}, {__mode = 'v'}) -- {UserId -> SaveData}
  158.     local mTouchedSaveDataCacheSet = {} -- {SaveData}
  159.     local mOnlinePlayerSaveDataMap = {} -- {Player -> SaveData}
  160.     local mDirtySaveDataSet = {} -- {SaveData}
  161.     local mOnRequestUserIdSet = {} -- {UserId}
  162.     local mRequestCompleted = Instance.new('BindableEvent')
  163.    
  164.     local mSavingCount = 0
  165.     --===============================================--
  166.     --==          Private Implementation           ==--
  167.     --===============================================--
  168.     local function userIdToKey(userId)
  169.         return 'PlayerList$'..userId
  170.     end    
  171.     function this:markAsTouched(saveData)
  172.         if DEBUG then
  173.             print("PlayerDataStore::markAsTouched("..saveData.userId..")")
  174.         end
  175.         mTouchedSaveDataCacheSet[saveData] = true
  176.         saveData.lastTouched = tick()
  177.         mUserIdSaveDataCache[saveData.userId] = saveData
  178.     end
  179.     function this:markAsDirty(saveData)
  180.         if DEBUG then
  181.             print("PlayerDataStore::markAsDirty("..saveData.userId..")")
  182.         end
  183.         this:markAsTouched(saveData)
  184.         mDirtySaveDataSet[saveData] = true
  185.         mUserIdSaveDataCache[saveData.userId] = saveData
  186.     end
  187.     local function initialData(userId)
  188.         return {}
  189.     end
  190.     local function collectDataToSave(saveData)
  191.         local toSave = {}
  192.         local toErase = {}
  193.         for key, _ in pairs(saveData.dirtyKeySet) do
  194.             local value = saveData.dataSet[key]
  195.             if value ~= nil then
  196.                 if SERIALIZE[key] then
  197.                     toSave[key] = SERIALIZE[key](value)
  198.                 else
  199.                     toSave[key] = DeepCopy(value)              
  200.                 end
  201.             else
  202.                 table.insert(toErase, key)
  203.             end
  204.             saveData.dirtyKeySet[key] = nil
  205.         end
  206.         return toSave, toErase
  207.     end
  208.     function this:doSave(saveData)
  209.         if DEBUG then
  210.             print("PlayerDataStore::doSave("..saveData.userId..") {")
  211.         end
  212.         saveData.lastSaved = tick()
  213.         mDirtySaveDataSet[saveData] = nil  
  214.         if next(saveData.dirtyKeySet) then
  215.             local toSave, toErase = collectDataToSave(saveData)
  216.            
  217.             saveData:waitForUnlocked()
  218.             saveData:lock()
  219.             mSavingCount = mSavingCount + 1
  220.             mDataStore:UpdateAsync(userIdToKey(saveData.userId), function(oldData)
  221.                 if not oldData then
  222.                     oldData = initialData(saveData.userId)
  223.                 end
  224.                 if DEBUG then
  225.                     print("\tattempting save:")
  226.                 end
  227.                 for key, data in pairs(toSave) do
  228.                     if DEBUG then
  229.                         print("\t\tsaving `"..key.."` = "..tostring(data))
  230.                     end
  231.                     oldData[key] = data
  232.                 end
  233.                 for _, key in pairs(toErase) do
  234.                     if DEBUG then
  235.                         print("\t\tsaving `"..key.."` = nil [ERASING])")
  236.                     end
  237.                     oldData[key] = nil
  238.                 end
  239.                 return oldData
  240.             end)
  241.             if DEBUG then
  242.                 print("\t saved.")
  243.             end
  244.             mSavingCount = mSavingCount - 1
  245.             saveData:unlock()
  246.         elseif DEBUG then
  247.             print("\tnothing to save")
  248.         end
  249.         if DEBUG then
  250.             print("}")
  251.         end
  252.     end
  253.     function this:doUpdate(saveData, keyList, updaterFunc)
  254.         if DEBUG then
  255.             print("PlayerDataStore::doUpdate("..saveData.userId..", {"..table.concat(keyList, ", ").."}, "..tostring(updaterFunc)..") {")
  256.         end
  257.         saveData:waitForUnlocked()
  258.         saveData:lock()
  259.         mSavingCount = mSavingCount + 1
  260.         saveData.lastSaved = tick()
  261.         mDirtySaveDataSet[saveData] = nil
  262.         local updateKeySet = {}
  263.         for _, key in pairs(keyList) do
  264.             saveData.ownedKeySet[key] = true
  265.             updateKeySet[key] = true
  266.         end
  267.         local toSave, toErase = collectDataToSave(saveData)
  268.         mDataStore:UpdateAsync(userIdToKey(saveData.userId), function(oldData)
  269.             if DEBUG then
  270.                 print("\ttrying update:")
  271.             end
  272.             if not oldData then
  273.                 oldData = initialData(saveData.userId)
  274.             end
  275.             local valueList = {}
  276.             for i, key in pairs(keyList) do
  277.                 local value = saveData.dataSet[key]
  278.                 if value == nil and DESERIALIZE[key] then
  279.                     valueList[i] = DESERIALIZE[key](nil)
  280.                 else
  281.                     valueList[i] = value
  282.                 end
  283.             end
  284.             local results = {updaterFunc(unpack(valueList, 1, #keyList))}
  285.             for i, result in pairs(results) do         
  286.                 local key = keyList[i]
  287.                 if SERIALIZE[key] then
  288.                     local serialized = SERIALIZE[key](result)
  289.                     if DEBUG then
  290.                         print("\t\tsaving result: `"..key.."` = "..tostring(serialized).." [SERIALIZED]")
  291.                     end
  292.                     oldData[key] = serialized
  293.                 else
  294.                     if DEBUG then
  295.                         print("\t\tsaving result: `"..key.."` = "..tostring(result))
  296.                     end
  297.                     oldData[key] = result
  298.                 end
  299.                 saveData.dataSet[key] = result
  300.             end
  301.             for key, value in pairs(toSave) do
  302.                 if not updateKeySet[key] then
  303.                     if DEBUG then
  304.                         print("\t\tsaving unsaved value: `"..key.."` = "..tostring(value))
  305.                     end
  306.                     oldData[key] = value
  307.                 end
  308.             end
  309.             for _, key in pairs(toErase) do
  310.                 if not updateKeySet[key] then
  311.                     if DEBUG then
  312.                         print("\t\tsaving unsaved value: `"..key.."` = nil [ERASING]")
  313.                     end
  314.                     oldData[key] = nil
  315.                 end
  316.             end
  317.             return oldData
  318.         end)
  319.         mSavingCount = mSavingCount - 1
  320.         saveData:unlock()
  321.         if DEBUG then
  322.             print("}")
  323.         end
  324.     end
  325.     local function doLoad(userId)
  326.         if DEBUG then
  327.             print("PlayerDataStore::doLoad("..userId..") {")
  328.         end
  329.         local saveData;
  330.         saveData = mUserIdSaveDataCache[userId]
  331.         if saveData then
  332.             if DEBUG then
  333.                 print("\tRecord was already in cache")
  334.             end
  335.             this:markAsTouched(saveData)
  336.             if DEBUG then
  337.                 print("}")
  338.             end
  339.             return saveData
  340.         end
  341.         if mOnRequestUserIdSet[userId] then
  342.             if DEBUG then
  343.                 print("\tRecord already requested, wait for it...")
  344.             end
  345.             while true do
  346.                 saveData = mRequestCompleted.Event:wait()()
  347.                 if saveData.userId == userId then
  348.                     this:markAsTouched(saveData)
  349.                     if DEBUG then
  350.                         print("\tRecord successfully retrieved by another thread")
  351.                         print("}")
  352.                     end
  353.                     return saveData
  354.                 end
  355.             end
  356.         else if DEBUG then
  357.             print("\tRequest record...")
  358.         end
  359.         mOnRequestUserIdSet[userId] = true
  360.         local data = mDataStore:GetAsync(userIdToKey(userId)) or {}
  361.         for key, value in pairs(data) do
  362.             if DESERIALIZE[key] then
  363.                 data[key] = DESERIALIZE[key](value)
  364.             end
  365.         end
  366.         saveData = SaveData.new(this, userId)
  367.         saveData:makeReady(data)
  368.         this:markAsTouched(saveData)
  369.         mOnRequestUserIdSet[userId] = nil
  370.         mRequestCompleted:Fire(function()
  371.             return saveData
  372.         end)
  373.         if DEBUG then
  374.             print("\tRecord successfully retrieved from data store")
  375.             print("}")
  376.         end
  377.         return saveData
  378.     end
  379. end
  380. local function HandlePlayer(player)
  381.     if DEBUG then
  382.         print("PlayerDataStore> Player "..player.userId.." Entered > Load Data")
  383.     end
  384.     local saveData = doLoad(player.userId)
  385.     if player.Parent then
  386.         mOnlinePlayerSaveDataMap[player] = saveData
  387.     end
  388. end
  389.  
  390. Game.Players.PlayerAdded:connect(HandlePlayer)
  391.     for _, player in pairs(Game.Players:GetChildren()) do
  392.         if player:IsA('Player') then
  393.             HandlePlayer(player)
  394.         end
  395.     end
  396.     Game.Players.PlayerRemoving:connect(function(player)
  397.         local oldSaveData = mOnlinePlayerSaveDataMap[player]
  398.         mOnlinePlayerSaveDataMap[player] = nil
  399.         if SAVE_ON_LEAVE and oldSaveData then
  400.             if DEBUG then
  401.                 print("PlayerDataStore> Player "..player.userId.." Left with data to save > Save Data")
  402.             end
  403.             this:doSave(oldSaveData)
  404.         end
  405.     end)
  406.     Game.OnClose = function()
  407.         if DEBUG then
  408.             print("PlayerDataStore> OnClose Shutdown\n\tFlushing...")
  409.         end
  410.         this:FlushAll()
  411.         if DEBUG then
  412.             print("\tFlushed, additional wait...")
  413.         end
  414.         while mSavingCount > 0 do
  415.             wait()
  416.         end
  417.         if DEBUG then
  418.             print("\tShutdown completed normally.")
  419.         end
  420.     end
  421.     local function removeTimedOutCacheEntries()
  422.         local now = tick()
  423.         for saveData, _ in pairs(mTouchedSaveDataCacheSet) do
  424.             if (now - saveData.lastTouched) > CACHE_EXPIRY_TIME then
  425.                 if mDirtySaveDataSet[saveData] then
  426.                     if DEBUG then
  427.                         print(">> Cache expired for: "..saveData.userId..", has unsaved changes, wait.")
  428.                     end
  429.                     SpawnNow(function()
  430.                         this:doSave(saveData)
  431.                     end)
  432.                 else
  433.                     if DEBUG then
  434.                         print(">> Cache expired for: "..saveData.userId..", removing.")
  435.                     end
  436.                     mTouchedSaveDataCacheSet[saveData] = nil
  437.                 end
  438.             end
  439.         end
  440.     end
  441.     local function passiveSaveUnsavedChanges()
  442.         local now = tick()
  443.         for saveData, _ in pairs(mDirtySaveDataSet) do
  444.             if (now - saveData.lastSaved) > PASSIVE_SAVE_FREQUENCY then
  445.                 if DEBUG then
  446.                     print("PlayerDataStore>> Passive save for: "..saveData.userId)
  447.                 end
  448.                 SpawnNow(function()
  449.                     this:doSave(saveData)
  450.                 end)
  451.             end
  452.         end
  453.     end
  454.     Spawn(function()
  455.         while true do
  456.             removeTimedOutCacheEntries()
  457.             passiveSaveUnsavedChanges()
  458.             wait(PASSIVE_GRANULARITY)
  459.         end
  460.     end)
  461.     --===============================================--
  462.     --==                Public API                 ==--
  463.     --===============================================--
  464.     function this:GetSaveData(player)
  465.         if not player or not player:IsA('Player') then
  466.             error("Bad argument #1 to PlayerDataStore::GetSaveData(), Player expected", 2)
  467.         end
  468.         return doLoad(player.userId)
  469.     end
  470.     function this:GetSaveDataById(userId)
  471.         if type(userId) ~= 'number' then
  472.             error("Bad argument #1 to PlayerDataStore::GetSaveDataById(), userId expected", 2)
  473.         end
  474.         return doLoad(userId)
  475.     end
  476.     function this:FlushAll()
  477.         local savesRunning = 0
  478.         local complete = Instance.new('BindableEvent')
  479.         for saveData, _ in pairs(mDirtySaveDataSet) do
  480.             SpawnNow(function()
  481.                 savesRunning = savesRunning + 1
  482.                 this:doSave(saveData)
  483.                 savesRunning = savesRunning - 1
  484.                 if savesRunning <= 0 then
  485.                     complete:Fire()
  486.                 end
  487.             end)
  488.         end
  489.         if savesRunning > 0 then
  490.             complete.Event:wait()
  491.         end
  492.     end
  493.     return this
  494. end
  495. return PlayerDataStore.new()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement