How can I improve this datastore?

Hi, so I’ve made a datastore and not really too sure how I can improve apon this code. (things such as data prevention, etc) Let me know how I could improve this, thanks!

-- Variables
local DataService = game:GetService("DataStoreService")
local DataStore = DataService:GetDataStore("data_01")
local camera = workspace.CurrentCamera
local cpFolder = game.Workspace:WaitForChild("Checkpoints")


-- PlayerAdded Function
game.Players.PlayerAdded:Connect(function(player)
	local key = "player-" .. player.UserId

	local GetSave = DataStore:GetAsync(key) -- check for existing data 

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

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

	local checkpoint = Instance.new("IntValue", leaderstats)
	checkpoint.Name = "Stage"

	local spawnpoint = Instance.new("IntValue", hidden)
	spawnpoint.Name = "SpawnPoint"

	if GetSave then
		checkpoint.Value = GetSave
		print("Data Loaded For " .. player.Name)
	else
		checkpoint.Value = 1
		print("New Data Created For " .. player.Name)
	end

	player.hidden.SpawnPoint.Value = player.leaderstats.Stage.Value

	local character = player.Character or player.CharacterAdded:Wait()
	local respawn2 = cpFolder:WaitForChild(player.leaderstats.Stage.Value)
	character:WaitForChild("HumanoidRootPart").CFrame = respawn2.CFrame + Vector3.new(0,3,0)
	
	
	-- CharacterAdded Function
	player.CharacterAdded:Connect(function(character)

		repeat wait() until workspace:FindFirstChild(character.Name)
		local player = game.Players:GetPlayerFromCharacter(character)
		local respawn = cpFolder[player.hidden.SpawnPoint.Value]

		character.HumanoidRootPart.CFrame = respawn.CFrame + Vector3.new(0,3,0)
	end)

end)


-- PlayerRemoving Event
game.Players.PlayerRemoving:Connect(function(player)
	local key = "player-" .. player.UserId
	
	DataStore:SetAsync(key, player.leaderstats.Stage.Value)
	
	print("Data Successfully Saved For " .. player.Name)
end)
2 Likes

One way you can improve your script is by using pcalls. Currently, if your datastore calls were to fail, the player’s data would be lost and overwritten.

Here is a topic about pcalls:

Instead of having a repeat - until loop, you can use Instance:WaitForChild().

repeat wait() until workspace:FindFirstChild(character.Name)
2 Likes

I have a slight question on datastore failure, currently when I test my game in studio and I progress a level (testing purposes) usually It won’t actually save my data at that specific level I’ve completed and it’ll set me back a stage when I test again. In-game it works perfectly fine though. Is this an issue on Roblox’s end or mine?

The test server may have closed before the server has finished saving.

You can also use game:BindToClose() to stop the server from shutting down until the server finishes with saving.

1 Like

Awesome, I read the API and adjusted some things. Does this look better for it saving in studio? (added BindToClose() event from the API)

-- Variables
local DataService = game:GetService("DataStoreService")
local DataStore = DataService:GetDataStore("data_01")
local camera = workspace.CurrentCamera
local cpFolder = game.Workspace:WaitForChild("Checkpoints")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local playerData = {
	-- [UserId] = data
}

game:BindToClose(function()
	-- if the current session is studio, do nothing
	if RunService:IsStudio() then
		return
	end

	print("saving player data")

	-- go through all players, saving their data
	local players = Players:GetPlayers()
	for _, player in pairs(players) do
		local userId = player.UserId
		local data = playerData[userId]
		if data then
			-- wrap in pcall to handle any errors
			local success, result = pcall(function()
				-- SetAsync yields so will stall shutdown
				DataStore:SetAsync(userId, data)
			end)
			if not success then
				warn(result)
			end    
		end
	end

	print("completed saving player data")

end)
	
-- PlayerAdded Function
game.Players.PlayerAdded:Connect(function(player)
	local key = "player-" .. player.UserId

	local GetSave = DataStore:GetAsync(key) -- check for existing data 

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

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

	local checkpoint = Instance.new("IntValue", leaderstats)
	checkpoint.Name = "Stage"

	local spawnpoint = Instance.new("IntValue", hidden)
	spawnpoint.Name = "SpawnPoint"

	if GetSave then
		checkpoint.Value = GetSave
		print("Data Loaded For " .. player.Name)
	else
		checkpoint.Value = 1
		print("New Data Created For " .. player.Name)
	end

	player.hidden.SpawnPoint.Value = player.leaderstats.Stage.Value

	local character = player.Character or player.CharacterAdded:Wait()
	local respawn2 = cpFolder:WaitForChild(player.leaderstats.Stage.Value)
	character:WaitForChild("HumanoidRootPart").CFrame = respawn2.CFrame + Vector3.new(0,3,0)
	
	
	-- CharacterAdded Function
	player.CharacterAdded:Connect(function(character)

		repeat wait() until workspace:FindFirstChild(character.Name)
		local player = game.Players:GetPlayerFromCharacter(character)
		local respawn = cpFolder[player.hidden.SpawnPoint.Value]

		character.HumanoidRootPart.CFrame = respawn.CFrame + Vector3.new(0,3,0)
	end)

end)


-- PlayerRemoving Event
game.Players.PlayerRemoving:Connect(function(player)
	local key = "player-" .. player.UserId
	
	DataStore:SetAsync(key, player.leaderstats.Stage.Value)
	
	print("Data Successfully Saved For " .. player.Name)
end)
1 Like

It looks like you are using a different key in the BindToClose function.

I recommend using coroutines in the BindToClose function because it only saves one player at a time.

You should also have a retry system in case data stores fail.

1 Like

Wait so the BindToClose() function works or do I have to switch the supposed key and it’ll work? I’ll look into coroutines and I’ve read about retry systems so I’ll add that but here’s my updated script:

Added a p-call yesterday night near the bottom.

-- Variables
local DataService = game:GetService("DataStoreService")
local DataStore = DataService:GetDataStore("data_01")
local camera = workspace.CurrentCamera
local cpFolder = game.Workspace:WaitForChild("Checkpoints")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local playerData = {
	-- [UserId] = data
}

game:BindToClose(function()
	-- if the current session is studio, do nothing
	if RunService:IsStudio() then
		print("WORKING")
		return
	end

	print("saving player data")

	-- go through all players, saving their data
	local players = Players:GetPlayers()
	for _, player in pairs(players) do
		local userId = player.UserId
		local data = playerData[userId]
		if data then
			-- wrap in pcall to handle any errors
			local success, result = pcall(function()
				-- SetAsync yields so will stall shutdown
				DataStore:SetAsync(userId, data)
			end)
			if not success then
				warn(result)
			end    
		end
	end

	print("completed saving player data")

end)
	
-- PlayerAdded Function
game.Players.PlayerAdded:Connect(function(player)
	local key = "player-" .. player.UserId

	local GetSave = DataStore:GetAsync(key) -- check for existing data 

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

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

	local checkpoint = Instance.new("IntValue", leaderstats)
	checkpoint.Name = "Stage"

	local spawnpoint = Instance.new("IntValue", hidden)
	spawnpoint.Name = "SpawnPoint"

	if GetSave then
		checkpoint.Value = GetSave
		print("Data Loaded For " .. player.Name)
	else
		checkpoint.Value = 1
		print("New Data Created For " .. player.Name)
	end

	player.hidden.SpawnPoint.Value = player.leaderstats.Stage.Value

	local character = player.Character or player.CharacterAdded:Wait()
	local respawn2 = cpFolder:WaitForChild(player.leaderstats.Stage.Value)
	character:WaitForChild("HumanoidRootPart").CFrame = respawn2.CFrame + Vector3.new(0,3,0)
	
	
	-- CharacterAdded Function
	player.CharacterAdded:Connect(function(character)

		repeat wait() until workspace:FindFirstChild(character.Name)
		local player = game.Players:GetPlayerFromCharacter(character)
		local respawn = cpFolder[player.hidden.SpawnPoint.Value]

		character.HumanoidRootPart.CFrame = respawn.CFrame + Vector3.new(0,3,0)
	end)

end)


-- PlayerRemoving Event
game.Players.PlayerRemoving:Connect(function(player)
	local key = "player-" .. player.UserId
	
	DataStore:SetAsync(key, player.leaderstats.Stage.Value)
	
	print("Data Successfully Saved For " .. player.Name)
end)

local success, response = pcall(
	DataStore.GetAsync, --1
	DataStore, --2
	"key" --3
)

It works, but you are using separate keys to save data.

-- BindToClose function
local userId = player.UserId
DataStore:SetAsync(userId, data)

-- PlayerAdded function
local key = "player-" .. player.UserId
local GetSave = DataStore:GetAsync(key)

-- PlayerRemoving function
local key = "player-" .. player.UserId
DataStore:SetAsync(key, player.leaderstats.Stage.Value)

Also, the call at the bottom of your script will not do anything. You have to enclose it in the actual PlayerAdded/PlayerRemoving functions.

1 Like

So should I just do this after each key:
DataStore:SetAsync(key, player.leaderstats.Stage.Value)
I see what you mean though.

I put the p-calls in the function and adjusted the data save keys:

-- Variables
local DataService = game:GetService("DataStoreService")
local DataStore = DataService:GetDataStore("data_01")
local camera = workspace.CurrentCamera
local cpFolder = game.Workspace:WaitForChild("Checkpoints")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local playerData = {
	-- [UserId] = data
}

game:BindToClose(function()
	-- if the current session is studio, do nothing
	if RunService:IsStudio() then
		print("WORKING")
		return
	end

	print("saving player data")

	-- go through all players, saving their data
	local players = Players:GetPlayers()
	for _, player in pairs(players) do
		local userId = player.UserId
		local key = playerData[userId]
		if key then
			-- wrap in pcall to handle any errors
			local success, result = pcall(function()
				-- SetAsync yields so will stall shutdown
				DataStore:SetAsync(key, player.leaderstats.Stage.Value)
			end)
			if not success then
				warn(result)
			end    
		end
	end

	print("completed saving player data")

end)
	
-- PlayerAdded Function
game.Players.PlayerAdded:Connect(function(player)
	local key = "player-" .. player.UserId

	local GetSave = DataStore:GetAsync(key) -- check for existing data 

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

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

	local checkpoint = Instance.new("IntValue", leaderstats)
	checkpoint.Name = "Stage"

	local spawnpoint = Instance.new("IntValue", hidden)
	spawnpoint.Name = "SpawnPoint"

	if GetSave then
		checkpoint.Value = GetSave
		print("Data Loaded For " .. player.Name)
	else
		checkpoint.Value = 1
		print("New Data Created For " .. player.Name)
	end

	player.hidden.SpawnPoint.Value = player.leaderstats.Stage.Value

	local character = player.Character or player.CharacterAdded:Wait()
	local respawn2 = cpFolder:WaitForChild(player.leaderstats.Stage.Value)
	character:WaitForChild("HumanoidRootPart").CFrame = respawn2.CFrame + Vector3.new(0,3,0)
	
	local success, response = pcall(
		DataStore.GetAsync, --1
		DataStore, --2
		"key" --3
	)
	
	-- CharacterAdded Function
	player.CharacterAdded:Connect(function(character)

		repeat wait() until workspace:FindFirstChild(character.Name)
		local player = game.Players:GetPlayerFromCharacter(character)
		local respawn = cpFolder[player.hidden.SpawnPoint.Value]

		character.HumanoidRootPart.CFrame = respawn.CFrame + Vector3.new(0,3,0)
	end)

end)


-- PlayerRemoving Event
game.Players.PlayerRemoving:Connect(function(player)
	local key = "player-" .. player.UserId
	
	DataStore:SetAsync(key, player.leaderstats.Stage.Value)
	
	print("Data Successfully Saved For " .. player.Name)
	
	local success, response = pcall(
		DataStore.GetAsync, --1
		DataStore, --2
		"key" --3
	)
end)


Changed a lot of things, everything seem to work:

-- Variables
local DataService = game:GetService("DataStoreService")
local DataStore = DataService:GetDataStore("data_01")
local camera = workspace.CurrentCamera
local cpFolder = game.Workspace:WaitForChild("Checkpoints")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local function saveData(player) -- The functions that saves data

	local tableToSave = {
		player.leaderstats.Stage.Value -- First value from the table
	}

	local success, err = pcall(function()
		DataStore:SetAsync(player.UserId, tableToSave) -- Save the data with the player UserId, and the table we wanna save
	end)

	if success then -- If the data has been saved
		print("Data has been saved!")
	else -- Else if the save failed
		print("Data hasn't been saved!")
		warn(err)		
	end
end
	
-- PlayerAdded Function
game.Players.PlayerAdded:Connect(function(player)
	local key = "player-" .. player.UserId

	local GetSave = DataStore:GetAsync(key) -- check for existing data 

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

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

	local checkpoint = Instance.new("IntValue", leaderstats)
	checkpoint.Name = "Stage"

	local spawnpoint = Instance.new("IntValue", hidden)
	spawnpoint.Name = "SpawnPoint"

	if GetSave then
		checkpoint.Value = GetSave
		print("Data Loaded For " .. player.Name)
	else
		checkpoint.Value = 1
		print("New Data Created For " .. player.Name)
	end
	
	local data -- We will define the data here so we can use it later, this data is the table we saved
	local success, err = pcall(function()

		data = DataStore:GetAsync(player.UserId) 

	player.hidden.SpawnPoint.Value = player.leaderstats.Stage.Value

	local character = player.Character or player.CharacterAdded:Wait()
	local respawn2 = cpFolder:WaitForChild(player.leaderstats.Stage.Value)
	character:WaitForChild("HumanoidRootPart").CFrame = respawn2.CFrame + Vector3.new(0,3,0)
	
	-- CharacterAdded Function
	player.CharacterAdded:Connect(function(character)

		repeat wait() until workspace:FindFirstChild(character.Name)
		local player = game.Players:GetPlayerFromCharacter(character)
		local respawn = cpFolder[player.hidden.SpawnPoint.Value]

		character.HumanoidRootPart.CFrame = respawn.CFrame + Vector3.new(0,3,0)
	end)
	end)
	if success then -- If there were no errors and player loaded the data

		checkpoint.Value = data[1] -- Set the money to the first value of the table (data)
		print("Sucess!")
	else -- The player didn't load in the data, and probably is a new player
		print("The player has no data!") -- The default will be set to 0
	end
end)


-- PlayerRemoving Event
game.Players.PlayerRemoving:Connect(function(player)
	local success, err  = pcall(function()
		local key = "player-" .. player.UserId

		DataStore:SetAsync(key, player.leaderstats.Stage.Value)

		print("Data Successfully Saved For " .. player.Name)
	end)
	
	if success then
		print("Data has been saved")
	else
		print("Data has not been saved!")
	end
end)

-- BindToClose Event
game:BindToClose(function() -- When the server shuts down
	for _, player in pairs(game.Players:GetPlayers()) do -- Loop through all the players
		local success, err  = pcall(function()
			local key = "player-" .. player.UserId

			DataStore:SetAsync(key, player.leaderstats.Stage.Value)

			print("Data Successfully Saved For " .. player.Name)
		end)

		if success then
			print("Data has been saved")
		else
			print("Data has not been saved!")
		end
	end
end)

Expect for the PlayerAdded function because I get this error:
ServerScriptService.Datastores.MainDatastore:76: attempt to index nil with number - Server - MainDatastore:76

data is an integer, not an array.

1 Like

Deleted that, does this work though? I haven’t seen it print in the output so I’m assuming not and how would I fix that?

local function saveData(player) -- The functions that saves data

	local tableToSave = {
		player.leaderstats.Stage.Value -- First value from the table
	}

	local success, err = pcall(function()
		DataStore:SetAsync(player.UserId, tableToSave) -- Save the data with the player UserId, and the table we wanna save
	end)

	if success then -- If the data has been saved
		print("Data has been saved!")
	else -- Else if the save failed
		print("Data hasn't been saved!")
		warn(err)		
	end
end

You should use UpdateAsync instead of SetAsync to avoid possibly overriding someones data.

1 Like

to broad explain the diffrences between the 2

Read up on it here.

You aren’t using pcalls for loading and saving data. Both of them are Async functions which send a HTTP call to an API and the response from that API can be an error.