PlayerDataStoreManager
DataStore manager for player that automatically saves on player leave and game close.
TIP
Consider using PlayerDataStoreService instead, which wraps one PlayerDataStoreManager.
This will ensure that the datastores are reused between different services and other things integrating with Nevermore.
local serviceBag = ServiceBag.new()
local playerDataStoreService = serviceBag:GetService(require("PlayerDataStoreService"))
serviceBag:Init()
serviceBag:Start()
local topMaid = Maid.new()
local function handlePlayer(player: Player)
local maid = Maid.new()
local playerMoneyValue = Instance.new("IntValue")
playerMoneyValue.Name = "Money"
playerMoneyValue.Value = 0
playerMoneyValue.Parent = player
maid:GivePromise(playerDataStoreService:PromiseDataStore(Players)):Then(function(dataStore)
maid:GivePromise(dataStore:Load("money", 0))
:Then(function(money)
playerMoneyValue.Value = money
maid:GiveTask(dataStore:StoreOnValueChange("money", playerMoneyValue))
end)
end)
topMaid[player] = maid
end
Players.PlayerAdded:Connect(handlePlayer)
Players.PlayerRemoving:Connect(function(player)
topMaid[player] = nil
end)
for _, player in Players:GetPlayers() do
task.spawn(handlePlayer, player)
end
Functions
new
PlayerDataStoreManager.new(serviceBag: ServiceBag.ServiceBag,keyGenerator: (player) → string,--
Function that takes in a player, and outputs a key
skipBindingToClose: boolean?) → PlayerDataStoreManagerConstructs a new PlayerDataStoreManager.
Unless skipBindingToClose is true, this resolves BindToCloseService from the serviceBag to
save on game close, so that service must be registered before the serviceBag starts.
DisableSaveOnCloseStudio
For if you want to disable saving in studio for faster close time!
SetLoadRetryOptions
Overrides the load retry backoff on every datastore this manager creates. See DataStore.SetLoadRetryOptions.
This is the knob that decides how long a player waits on a lock held by a dead server: the ladder runs, and only once it is exhausted is the lock stolen unconditionally. Defaults to ~49s.
INFO
Must be set before the first datastore is created.
SetAutoSaveTimeSeconds
PlayerDataStoreManager.SetAutoSaveTimeSeconds(autoSaveTimeSeconds: number?) → ()Sets the autosave interval on every datastore this manager creates. See DataStore.SetAutoSaveTimeSeconds. Passing nil disables syncing entirely.
INFO
Must be set before the first datastore is created.
SetSessionMessagingCloseDelaySeconds
PlayerDataStoreManager.SetSessionMessagingCloseDelaySeconds(seconds: number) → ()Sets the post-graceful-close replication delay on every datastore this manager creates. See DataStore.SetSessionMessagingCloseDelaySeconds.
INFO
Must be set before the first datastore is created.
AddRemovingCallback
PlayerDataStoreManager.AddRemovingCallback(callback: function--
May return a promise
) → ()Adds a callback to be called before save on removal
RemovePlayerDataStore
PlayerDataStoreManager.RemovePlayerDataStore() → ()Callable to allow manual GC so things can properly clean up. This can be used to pre-emptively cleanup players.
PromiseDataStoreHandle
Gets the datastore for a player as a counted handle, opening a session if none is live.
Prefer this over PlayerDataStoreManager.PromiseDataStore for anything acting on a player who may not be in this server. Opening their store takes the session lock, which kicks them from wherever they were and keeps them from rejoining until it is dropped -- and destroying the handle is what drops it.
Handles are counted, so several systems can hold the same player's store at once and the session survives until the last handle is destroyed.
NOTE
The join/leave path deliberately does not run through handles. Making a player's presence just another reference would be tidier, but removal is reached from several directions already -- a stolen session, a close request, a failed lock, PlayerRemoving, server shutdown -- and a handle leaked on any of them would hold a player's save open instead of closing it, which is worse than the asymmetry. So a handle never removes a store belonging to a player who is in this server; their own path owns that.
PromiseSessionClosed
Resolves once any removal in flight for this player has saved and closed their session, and immediately when there is nothing being removed.
Destroying the last handle for an absent player starts the save-and-close; it does not wait for it. Tooling that reports back to an operator waits here first, so it says the lock is released only once the write that releases it has actually landed.
GetDataStore
Gets the datastore for a player. If it does not exist, it will create one.
TIP
Returns nil if the player is in the process of being removed.
PromiseDataStore
Gets the datastore for a player, waiting for any in-progress removal/save first. Use this in async flows to safely support fast leave/rejoin behavior.
PromiseReadSessionLock
Reads the session lock on a player's key without opening a session on it.
This is the read side of the tooling path: it answers "who holds this key, and how stale is that claim", whether or not the player is in this server. Resolves nil when the key is unlocked or absent. Reads the stored key, so for a player in this server it reflects their last save rather than unsaved in-memory state.
PromiseUnlockSession
PlayerDataStoreManager.PromiseUnlockSession() → Promise<LockData?>--
the lock that was cleared, or nil if it was already unlocked
Clears the session lock on a player's key with a raw write, releasing a claim left behind by a server that died without closing its session.
WARNING
This is a soft lock. A loading session steals it anyway once its retry ladder is exhausted (see PlayerDataStoreManager.SetLoadRetryOptions) -- clearing it early only saves the player that wait.
DANGER
Permitted against a session this server holds, which desynchronizes that session from the key -- its next save either re-writes the lock or reads this as a theft and kicks the player. That is a debug/stress-test capability, not a normal one.
PromiseLockSession
PlayerDataStoreManager.PromiseLockSession() → Promise<LockData?>--
the lock that was replaced, or nil if it was unlocked
Claims a player's key with a raw write, under a session this server will never answer for. Parks the key so an inspection is not racing a live server.
WARNING
This is a soft lock, and holds only for as long as a loading session's retry ladder. It is not a way to keep a player out of their data.
DANGER
Permitted against a session this server holds, with the same desynchronizing effect described on PlayerDataStoreManager.PromiseUnlockSession.
PromiseAllSaves
Removes all player data stores, and returns a promise that resolves when all pending saves are saved.
On a closing server Roblox fires PlayerRemoving for every player, so a removal is usually already in flight by the time this runs. Those removals do the real save-and-close themselves; this waits for them rather than starting anything of its own.