Is there any improvements I can do to my simple datastore

Hi, I made a simple datastore to save how long the player has been ingame for.
Is there any improvements I could do? I am learning how to do datastores interested to know if there are any improvements I can make.

local players = game:GetService("Players")
local dataStore = game:GetService("DataStoreService")
local Data = dataStore:GetDataStore("Data")
game.Players.PlayerAdded:Connect(function(plr)
	local folder = Instance.new("Folder")
	folder.Name = "Datafolder"
	folder.Parent = plr
	
	local valuecreated = Instance.new("NumberValue")
	valuecreated.Name = "value"
	valuecreated.Parent = plr.Datafolder
	valuecreated.Value = Data:GetAsync(plr.UserId) or false
	Data:SetAsync(plr.UserId, valuecreated.Value)

	game.Players.PlayerRemoving:Connect(function()
		Data:SetAsync(plr.UserId, valuecreated.Value)
	end)
	end)
5 Likes

Yes, please don’t use Data:SetAsync(). UpdateAsync() is much more safer to use other than :SetAsync().

Refer to: Stop using SetAsync() to save player data

3 Likes
  1. Wrap GetAsync and SetAsync in Pcalls.
  2. When the player joins, if they already have data, it’s unnecessary to save their data on entering. So on this line:

Data:SetAsync(plr.UserId, valuecreated.Value)

After you GetAsync, check to see if you retrieved anything from the datastore and only Set/Update Async if you don’t retrieve anything.

7 Likes
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local DataStore = DataStoreService:GetDataStore("Data")

Players.PlayerAdded:Connect(function(Player)
	local leaderstats = Instance.new("Folder", Player)
	leaderstats .Name = "leaderstats"
	leaderstats .Parent = Player
	local timePlayed = Instance.new("NumberValue", leaderstats)
	timePlayed.Name = "TimePlayed"
	timePlayed.Value = 0
	local data
	local success, err = pcall(function()
		data = DataStore:GetAsync(Player.UserId.."_timePlayed")
	end)

	if data ~= nil then
		timePlayed.Value = data[1]
	end
	
end)

Players.PlayerRemoving:Connect(function(Player)
	local leaderstats = Player.leaderstats
	local Data = {}
	Data[1] = leaderstats.TimePlayed.Value
	local success, err = pcall(function()
		DataStore:SetAsync(Player.UserId.."_timePlayed", Data)
	end)
	
	if not success then warn(err) end
	
end)
--Don't forget to turn on Enable Studio Access to API Services in Game Setting > Security

3 Likes

DataStore:SetAsync is fine like it is.

3 Likes

I tried to use UpdateAsync() but it doesn’t save them. I changed all the SetAsync() to UpdateAsync(). Also do I need to change the GetAsync() in my script to UpdateAsync() too??

3 Likes

In most cases it’s fine to use SetAsync. UpdateAsync is really only necessary when projects scale large, but if you want to use UpdateAsync anyway, it’s setup different from SetAsync. UpdateAsync takes the Key as the first param and a callback for the second param. Official documentation here:

4 Likes

This won’t work for me because I disabled the leaderstats in my game. I did try to change the code

--local leaderstats = Player.leaderstats removed this
	local Data = {}
	Data[1] = Player.Folder.TimePlayed.Value--I changed the name of the folder to folder
2 Likes

I tried to improve my script using your suggestions sadly it doesn’t work I don’t know if I did it right though.

local players = game:GetService("Players")
local dataStore = game:GetService("DataStoreService")
local Data = dataStore:GetDataStore("Data")
game.Players.PlayerAdded:Connect(function(plr)
	local folder = Instance.new("Folder")
	folder.Name = "Datafolder"
	folder.Parent = plr

	local valuecreated = Instance.new("NumberValue")
	valuecreated.Name = "value"
	valuecreated.Parent = plr.Datafolder
	
	local data
	local success, err = pcall(function()--this what you mean by wrap in pcall??
		data = Data:GetAsync(plr.UserId, valuecreated.Value)
		if data ~= nil then
			Data:SetAsync(plr.UserId, valuecreated.Value)--Should I change SetAsync to UpdateAsync here
	Data:SetAsync(plr.UserId, valuecreated.Value)

	game.Players.PlayerRemoving:Connect(function()
				Data:SetAsync(plr.UserId, valuecreated.Value)
			end)
			end
	end)
end)
2 Likes

If I’m being honest you probably should just use tables a modulescript that returns data and saves data
Also I’m not too sure how safe storing data in a player’s player object is

2 Likes

Can you expand on this please.
Also what do you mean by storing it in a player player’s object?

Your datastore isn’t the best to store data securely here is an example of mine.
I think mine is a good datastore.

local DSS = game:GetService("DataStoreService")
local CashData = DSS:GetDataStore("CashData")
local GemsData = DSS:GetDataStore("GemsData")

game.Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder", player)
	leaderstats.Name = "leaderstats"
	
	local gemstats = Instance.new("Folder", player)
	gemstats.Name = "gems"

	local cash = Instance.new("IntValue", leaderstats)
	cash.Name = "Cash"

	local gems = Instance.new("IntValue", gemstats)
	gems.Name = "Gems"

	local playerId = "Player_"..player.UserId

	local cashdata
	local gemsdata
	local success, errormessage = pcall(function()
		cashdata = CashData:GetAsync(playerId)
		gemsdata = GemsData:GetAsync(playerId)
	end)

	if success then
		cash.Value = cashdata
		gems.Value = gemsdata
	end
end)

game.Players.PlayerRemoving:Connect(function(player)
	local playerId = "Player_"..player.UserId

	local cashdata = player.leaderstats.Cash.Value
	local gemsdata = player.gems.Gems.Value

	local success, errormessage = pcall(function()
		CashData:SetAsync(playerId, cashdata)
		GemsData:SetAsync(playerId, gemsdata)
	end)

	if success then
		print("Saved")
	else
		print("There was an error")
		warn(errormessage)
	end
end)

1 Like

I am going to start trying this right now. Quick question though if I have a lot of datastores and I am putting them on this new script. Would I receive and error about too many datastores requests. Because how I was doing it before I was using separate scripts

Without game:BindToClose() this can cause data loss as sometimes the server shuts down and doesn’t fire playerRemoving due to errors. The function holds the server open and you can save data for each player left. You can read the documentation for more info.

1 Like

If many people are joining and leaving in a 1 minute frame and you have many DataStores, then it could error. You can just pack them all into a table then save to 1 DataStore (they can save tables)

Is this how I add

game.Players.PlayerRemoving:Connect(function(player) 
	local playerId = "Player_"..player.UserId

	local cashdata = player.leaderstats.Cash.Value
	local gemsdata = player.gems.Gems.Value

	local success, errormessage = pcall(function()
		CashData:SetAsync(playerId, cashdata)
		GemsData:SetAsync(playerId, gemsdata)
	end)

	if success then
		print("Saved")
	else
		print("There was an error")
		warn(errormessage)
		
	end
	game:BindToClose(function()
		local playerId = "Player_"..player.UserId

		local cashdata = player.leaderstats.Cash.Value
		local gemsdata = player.gems.Gems.Value

		local success, errormessage = pcall(function()
			CashData:SetAsync(playerId, cashdata)
			GemsData:SetAsync(playerId, gemsdata)
		end)

		if success then
			print("Saved")
		else
			print("There was an error")
			warn(errormessage)
		
		
			end
	end)
end)

How would I create a data table??

As I said above, playerRemoving can sometimes not fire when server shuts down, so it is not the best idea. You should put it outside as a separate function then loop through each player left

game:BindToClose(function()
for i, player in pairs(game.Players:GetPlayers()) do
-- save data for player
end
end)
1 Like

Ok, I made it a complete separate function, so like this?

game:BindToClose(function()
	for i, player in pairs(game.Players:GetPlayers()) do
		local playerId = "Player_"..player.UserId

		local cashdata = player.leaderstats.Cash.Value
		local gemsdata = player.gems.Gems.Value

		local success, errormessage = pcall(function()
			CashData:SetAsync(playerId, cashdata)
			GemsData:SetAsync(playerId, gemsdata)
		end)

		if success then
			print("Saved")
		else
			print("There was an error")
			warn(errormessage)
			end
	end
end)
1 Like

You can add a table containing the data you want to save, then load them into a playerTable.

ocal playertabledata = {}
local template = {
	["value"] = false
}

--variables
local DataStoreService = game:GetService("DataStoreService")
local Data = game:GetDataStore("insertnamehere")

local function CheckData(async, dataname) -- replace with default value if not found data
	if async == nil then
		return dataname
	elseif async ~= nil then
		return async
	end
end

local function AddNewTemplateData(player) -- updates player data with latest values
	for i1,v1 in pairs(template) do
		local available = false
		for i2, v2 in pairs(playertabledata[player.Name]) do
			if i2 == i1 then
				available = true
			end
		end
		if available == false then
			playertabledata[player.Name][i1] = template[i1]
		end
	end
end

After that, we create functions to update values to table.

local function AddDataToTable(player) -- (save data)
	if not playertabledata[player.Name] then playertabledata[player.Name] = template end -- adds playerdata to table if not found
	for i,v in pairs(playertabledata[player.Name]) do -- loops through each data
		playertabledata[player.Name][i] = player.leaderstats[i].Value
	end
end

local function RetrieveDataFromTable(player) -- load data
	if not playertabledata[player.Name] then playertabledata[player.Name] = template end -- adds playerdata to table if not found
	for i,v in pairs(playertabledata[player.Name]) do -- loops through each data
		player.leaderstats[i].Value = playertabledata[player.Name][i] 
	end
end

Then just do the same thing as normal, but with tables

game.Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local value = Instance.new("BoolValue")
	value.Name = "value"
	value.Parent = leaderstats

	local playerId = "Player_"..player.UserId
	local data1
	local success, errorMessage = pcall(function()
		data1 = Data:GetAsync(playerId)
	end)

	if success then
		local success1, errorMessage1 = pcall(function()
			playertabledata[player.Name] = template
			playertabledata[player.Name] = CheckData(data1, template)
			if data1 then
				RetrieveDataFromTable(player)
			end
		end)
		if success1 then
			print("Loaded")
		else
			print("There was an error")
			warn(errorMessage1)
		end
	else
		print("There was an error")
		warn(errorMessage)
	end
end)

game.Players.PlayerRemoving:Connect(function(player)
	local playerId = "Player_"..player.UserId

	local data1
	local success, errorMessage = pcall(function()
		AddDataToTable(player)
		data1 = Data:SetAsync(player.UserId, playertabledata[player.Name])

	end)

	if success then
		print("Saved")
	else
		print("There was an error")
		warn(errorMessage)
	end
end)

game:BindToClose(function()
	for i,player in pairs(game.Players:GetPlayers()) do
		local playerId = "Player_"..player.UserId

		local data1
		local success, errorMessage = pcall(function()
			AddDataToTable(player)
			data1 = Data:SetAsync(player.UserId, playertabledata[player.Name])

		end)

		if success then
			print("Saved")
		else
			print("There was an error")
			warn(errorMessage)
		end
	end
end)

It is kinda long though, tell me if you need any explanations. (this is pretty similar to my game’s DataStore)

1 Like

I think I did something wrong I got this error GetDataStore is not a valid member of DataModel “Game” - Server - dats:22

Also do I need to put this in there?
local CashData = DSS:GetDataStore(“CashData”)
local GemsData = DSS:GetDataStore(“GemsData”)
for each piece of data I am saving?

game.Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder", player)
	leaderstats.Name = "leaderstats"

	local gemstats = Instance.new("Folder", player)
	gemstats.Name = "gems"

	local cash = Instance.new("IntValue", leaderstats)
	cash.Name = "Cash"

	local gems = Instance.new("IntValue", gemstats)
	gems.Name = "Gems"

	local playertabledata = {}
	local template = {
		["Gems"] = "",
		["Cash"] = ""
	}

	--variables
	local DataStoreService = game:GetService("DataStoreService")
	local Data = game:GetDataStore("gamedata")

	local function CheckData(async, dataname) -- replace with default value if not found data
		if async == nil then
			return dataname
		elseif async ~= nil then
			return async
		end
	end

	local function AddNewTemplateData(player) -- updates player data with latest values
		for i1,v1 in pairs(template) do
			local available = false
			for i2, v2 in pairs(playertabledata[player.Name]) do
				if i2 == i1 then
					available = true
				end
			end
			if available == false then
				playertabledata[player.Name][i1] = template[i1]
			end
		end
	end
	
	local function AddDataToTable(player) -- (save data)
		if not playertabledata[player.Name] then playertabledata[player.Name] = template end -- adds playerdata to table if not found
		for i,v in pairs(playertabledata[player.Name]) do -- loops through each data
			playertabledata[player.Name][i] = player.leaderstats[i].Value
		end
	end

	local function RetrieveDataFromTable(player) -- load data
		if not playertabledata[player.Name] then playertabledata[player.Name] = template end -- adds playerdata to table if not found
		for i,v in pairs(playertabledata[player.Name]) do -- loops through each data
			player.leaderstats[i].Value = playertabledata[player.Name][i] 
		end
	end
	

	local playerId = "Player_"..player.UserId

	local cashdata
	local gemsdata
	local success, errormessage = pcall(function()
		cashdata = Data:GetAsync(playerId)
		gemsdata = Data:GetAsync(playerId)
	end)

	if success then
		cash.Value = cashdata
		gems.Value = gemsdata
	end
end)

game.Players.PlayerRemoving:Connect(function(player) 
	local playerId = "Player_"..player.UserId

	local cashdata = player.leaderstats.Cash.Value
	local gemsdata = player.gems.Gems.Value

	local success, errormessage = pcall(function()
		cashdata:SetAsync(playerId, cashdata)
		gemsdata:SetAsync(playerId, gemsdata)
	end)

	if success then
		print("Saved")
	else
		print("There was an error")
		warn(errormessage)
		
	end
	
		
			
	
end)
game:BindToClose(function()
	for i, player in pairs(game.Players:GetPlayers()) do
		local playerId = "Player_"..player.UserId

		local cashdata = player.leaderstats.Cash.Value
		local gemsdata = player.gems.Gems.Value

		local success, errormessage = pcall(function()
			cashdata:SetAsync(playerId, cashdata)
			gemsdata:SetAsync(playerId, gemsdata)
		end)

		if success then
			print("Saved")
		else
			print("There was an error")
			warn(errormessage)
			end
	end
end)