Advertisement
IHATEMICROWAVEOVEN

save data module

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