My player data management system, how does this look?

I parented this script to DataStoreService with RunContext set to Sever because script.Parent is faster to execute than game:GetService("DataStoreService").

local DataStore, Players, Stat, Data, Meta = script.Parent, game:GetService("Players"), {"Level", "Money", "Crystals"}, {}, {__index = function()
	return 0
end}
Players.PlayerAdded:Connect(function(Player)
	local Folder, UserId = Instance.new("Folder"), Player.UserId
	local Data = setmetatable(Data[UserId] or {}, Meta)
	for I, Stat in ipairs(Stat) do
		local Int, Value = Instance.new("IntValue", Folder), Data[I]
		Int.Name = Stat
		if Value then
			Int.Value = Value
		end
	end
	Folder.Name, Folder.Parent = "leaderstats", Player
end)
local Success, Store = pcall(DataStore.GetGlobalDataStore, DataStore)
if not Success then
	warn(Store)
	return
end
function Stat.__index(Table, Id)
	local Success, User = pcall(Store.GetAsync, Store, tostring(Id))
	if not Success then
		warn(User)
		return
	end
	Data[Id] = User
	return User
end
setmetatable(Data, Stat)
local function Set(Id, User)
	local Success, Error = pcall(Store.SetAsync, Store, tostring(Id), User)
	if not Success then
		warn(Error)
		return
	end
	Data[Id] = nil
end
Players.PlayerRemoving:Connect(function(Player)
	local Id = Player.UserId
	local User = Data[Id]
	if User then
		Set(Id, User)
	end
end)
game:BindToClose(function()
	for Id, User in pairs(Data) do
		Set(Id, User)
	end
end)
2 Likes

it looks good though i have one suggestion
if it doesnt work, add a failsafe rather than just leaving it be
i dont necessarily think its the best way to do things because it isnt fully safe due to datastores
but that isnt your fault so

But what recourse do I have if data fails to save? If it fails to save, it fails to save.

I meant during the loading data stage:

  1. Check if they arent in the datastore (did not play before) and try it one more time if they are
  2. A better option would to use DataStore2 (a module) which adds all of that (its less work on your side)

As TinkleTime has said, the normal approach would be to retry the API call. See this awesome guide by Roblox engineers.

You mean, the possibility of resetting their progress if they join and their data fails to load, but then leave and their blank data saves?

Ok, sounds like a good idea to show a warning that their data could not be loaded and to protect their existing progress nothing they do will be saved.

yes, you have the possibility of that happening
it is better to add a retry API like Phoenix said to help prevent this

although i think you should invest time into learning DataStore2
it is a module which uses DataStores but adds all of the features to help prevent potentional safety risks for you
or just find another one (and risk check it, dont forget to do that)

Instead of DS2, I would personally recommend ProfileService as it’s just better in every aspect and is incredibly simple to set up and use. :smile: :+1:

1 Like

I figured out a bug in this code which is that it is not possible to call GetAsync in a metamethod because it yields.

I figured out another problem which is that DataStoreService is serialized when saving a place to a file, but not when saving a place to Roblox. So this appears to work in my local copy but not in the actual game.