LeaderstatsHandler | Automatically load and save leaderstats

I’ve made a pretty straightforward module for loading and saving leaderstats here. One major drawback of this module is that it uses the DataStoreService’s SetAsync() function which may lead to data loss if you’re not careful.

Functions

You’ll work directly with three functions

.Init(STAT, STAT TYPE, DEFAULT_VALUE)

Returns two functions; get() and update()
Example usage:

local STAT = "Minutes"
local STAT_TYPE = "IntValue"
local DEFAULT_VALUE = 0

local get, update = LeaderstatsHandler.Init(STAT, STAT_TYPE, DEFAULT_VALUE)

get(PLAYER)

Returns value of stat. Type depends on the type you’ve chosen (i.e., IntValue, StringValue)
Example usage:

local oldValue = get(player)

update(PLAYER, NEW_VALUE)

Returns nil.
Example usage:

update(player, oldValue + 1)

Loading and Saving

This module will load and save automatically using the PlayerAdded and PlayerRemoving events. You may modify this to use datastore modules such DataStore2 and ProfileService.

Avoiding Data Loss

Only use get() and update() when the player’s datastores have loaded in properly. To do this, I used a BindableEvent that is fired by the module only after the game has tried to load in the player’s data.

Example Code

Below is a script that keeps track of how long the player has been in the game. It subscribes to a BindableEvent named Ready which is fired by the LeaderstatsHandler. This is so that get() and update() are only called once the player’s data has loaded.

If get() is called before the player’s data has been loaded, it will likely return the default value. If you update the player’s data with this, it may lead to data loss.

local Players = game:GetService("Players")
local LeaderstatsHandler = require(script.Parent)

local isReady = script.Parent.Ready

local STAT = "Minutes"
local STAT_TYPE = "IntValue"
local DEFAULT_VALUE = 0

local get, update = LeaderstatsHandler.Init(STAT, STAT_TYPE, DEFAULT_VALUE)
local TIME_TO_INCREMENT = 1 -- Every 'x' seconds, add 1
isReady.Event:Connect(function(player)
	local oldValue = get(player) or DEFAULT_VALUE

	while true do
		task.wait(TIME_TO_INCREMENT)
		oldValue = get(player)
		update(player, oldValue + 1)
	end
end)

Roblox Model

GitHub Repo

In hindsight, I think I should let get() query the datastore directly to prevent data loss but it’s set up this way to avoid hitting the request limits.

Use ProfileService + ReplicaService. It’s ideal for things like this