local Y=require((game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")):WaitForChild("\078\101\118\101\114\109\111\114\101"))local w=game:GetService("\080\108\097\121\101\114\115")local T=game:GetService("\082\117\110\083\101\114\118\105\099\101")local t=Y("\066\097\115\101\079\098\106\101\099\116")local M=Y("\068\097\116\097\083\116\111\114\101")local p=Y("\080\114\111\109\105\115\101\085\116\105\108\115")local f=Y("\080\101\110\100\105\110\103\080\114\111\109\105\115\101\084\114\097\099\107\101\114")local y=Y("\077\097\105\100")local u=setmetatable({},t)u.ClassName="\080\108\097\121\101\114\068\097\116\097\083\116\111\114\101\077\097\110\097\103\101\114"u.__index=u function u.new(Y,T)local M=setmetatable(t.new(),u)M._robloxDataStore=Y or error("\078\111 \114\111\098\108\111\120\068\097\116\097\083\116\111\114\101")M._keyGenerator=T or error("\078\111 \107\101\121\071\101\110\101\114\097\116\111\114")M._maid._savingConns=y.new()M._datastores={}M._removing={}M._pendingSaves=f.new()M._removingCallbacks={}M._maid:GiveTask(w.PlayerRemoving:Connect(function(Y)if M._disableSavingInStudio then return end M:_removePlayerDataStore(Y)end))game:BindToClose(function()if M._disableSavingInStudio then return end;(M:PromiseAllSaves()):Wait()end)return M end function u.DisableSaveOnCloseStudio(Y)assert(T:IsStudio())Y._disableSavingInStudio=true end function u.AddRemovingCallback(w,Y)table.insert(w._removingCallbacks,Y)end function u.RemovePlayerDataStore(w,Y)w:_removePlayerDataStore(Y)end function u.GetDataStore(w,Y)assert(typeof(Y)=="\073\110\115\116\097\110\099\101")assert(Y:IsA("\080\108\097\121\101\114"))if w._removing[Y]then warn("\091\080\108\097\121\101\114\068\097\116\097\083\116\111\114\101\077\097\110\097\103\101\114\046\071\101\116\068\097\116\097\083\116\111\114\101\093 - \067\097\108\108\101\100 \071\101\116\068\097\116\097\083\116\111\114\101 \119\104\105\108\101 \112\108\097\121\101\114 \105\115 \114\101\109\111\118\105\110\103\044 \099\097\110\110\111\116 \114\101\116\114\105\101\118\101")return nil end if w._datastores[Y]then return w._datastores[Y]end return w:_createDataStore(Y)end function u.PromiseAllSaves(Y)for w,T in pairs(Y._datastores)do Y:_removePlayerDataStore(w)end return Y._maid:GivePromise(p.all(Y._pendingSaves:GetAll()))end function u._createDataStore(w,Y)assert(not w._datastores[Y])local T=M.new(w._robloxDataStore,w:_getKey(Y))w._maid._savingConns[Y]=T.Saving:Connect(function(Y)w._pendingSaves:Add(Y)end)w._datastores[Y]=T return T end function u._removePlayerDataStore(w,Y)assert(typeof(Y)=="\073\110\115\116\097\110\099\101")assert(Y:IsA("\080\108\097\121\101\114"))local T=w._datastores[Y]if not T then return end w._removing[Y]=true for w,T in pairs(w._removingCallbacks)do T(Y)end;(T:Save()):Finally(function()T:Destroy()w._removing[Y]=nil end)w._datastores[Y]=nil w._maid._savingConns[Y]=nil end function u._getKey(w,Y)return w._keyGenerator(Y)end return u
--- DataStore manager for player that automatically saves on player leave and game close
-- @classmod PlayerDataStoreManager

local require = require(game:GetService("ReplicatedStorage"):WaitForChild("Nevermore"))

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local BaseObject = require("BaseObject")
local DataStore = require("DataStore")
local PromiseUtils = require("PromiseUtils")
local PendingPromiseTracker = require("PendingPromiseTracker")
local Maid = require("Maid")

local PlayerDataStoreManager = setmetatable({}, BaseObject)
PlayerDataStoreManager.ClassName = "PlayerDataStoreManager"
PlayerDataStoreManager.__index = PlayerDataStoreManager

-- @param [keyGenerator] Function that takes in a player, and outputs a key
function PlayerDataStoreManager.new(robloxDataStore, keyGenerator)
	local self = setmetatable(BaseObject.new(), PlayerDataStoreManager)

	self._robloxDataStore = robloxDataStore or error("No robloxDataStore")
	self._keyGenerator = keyGenerator or error("No keyGenerator")

	self._maid._savingConns = Maid.new()

	self._datastores = {} -- [player] = datastore
	self._removing = {} -- [player] = true
	self._pendingSaves = PendingPromiseTracker.new()
	self._removingCallbacks = {} -- [func, ...]

	self._maid:GiveTask(Players.PlayerRemoving:Connect(function(player)
		if self._disableSavingInStudio then
			return
		end

		self:_removePlayerDataStore(player)
	end))

	game:BindToClose(function()
		if self._disableSavingInStudio then
			return
		end

		self:PromiseAllSaves():Wait()
	end)

	return self
end

--- For if you want to disable saving in studio for faster close time!
function PlayerDataStoreManager:DisableSaveOnCloseStudio()
	assert(RunService:IsStudio())

	self._disableSavingInStudio = true
end

--- Adds a callback to be called before save on removal
function PlayerDataStoreManager:AddRemovingCallback(callback)
	table.insert(self._removingCallbacks, callback)
end

--- Callable to allow manual GC so things can properly clean up.
-- This can be used to pre-emptively cleanup players.
function PlayerDataStoreManager:RemovePlayerDataStore(player)
	self:_removePlayerDataStore(player)
end

function PlayerDataStoreManager:GetDataStore(player)
	assert(typeof(player) == "Instance")
	assert(player:IsA("Player"))

	if self._removing[player] then
		warn("[PlayerDataStoreManager.GetDataStore] - Called GetDataStore while player is removing, cannot retrieve")
		return nil
	end

	if self._datastores[player] then
		return self._datastores[player]
	end

	return self:_createDataStore(player)
end

function PlayerDataStoreManager:PromiseAllSaves()
	for player, _ in pairs(self._datastores) do
		self:_removePlayerDataStore(player)
	end
	return self._maid:GivePromise(PromiseUtils.all(self._pendingSaves:GetAll()))
end

function PlayerDataStoreManager:_createDataStore(player)
	assert(not self._datastores[player])

	local datastore = DataStore.new(self._robloxDataStore, self:_getKey(player))

	self._maid._savingConns[player] = datastore.Saving:Connect(function(promise)
		self._pendingSaves:Add(promise)
	end)

	self._datastores[player] = datastore

	return datastore
end

function PlayerDataStoreManager:_removePlayerDataStore(player)
	assert(typeof(player) == "Instance")
	assert(player:IsA("Player"))

	local datastore = self._datastores[player]
	if not datastore then
		return
	end

	self._removing[player] = true

	for _, func in pairs(self._removingCallbacks) do
		func(player)
	end

	datastore:Save():Finally(function()
		datastore:Destroy()
		self._removing[player] = nil
	end)

	-- Prevent double removal or additional issues
	self._datastores[player] = nil
	self._maid._savingConns[player] = nil
end

function PlayerDataStoreManager:_getKey(player)
	return self._keyGenerator(player)
end

return PlayerDataStoreManager