How to use UpdateAsync on a table value?

There was a report of data loss in one of my games. Since no one plays my games (fortunately), I was the one that lost all my data while testing the game. It hurt a little (ok maybe it hurt alot), so that is why I want to improve my datastore so it won’t happen again.

The thing is, I followed all the Datastore tutorials on the developer site, and applied all its good practices over to my datastore script. However, I haven’t considered using UpdateAsync, which people on this forum seem to recommend over SetAsync, because of its benefits. (like reviewing old value first)

How can I use UpdateAsync on a table value though? I don’t quite know how to approach saving all its values. Would I need a for loop or something? Any help and feedback is greatly appreciated.

Saving data function
local function getPlayerStats(player)
	local leaderstats = player:WaitForChild("leaderstats")
	local cash = leaderstats:WaitForChild("Cash")
	local wins = leaderstats:WaitForChild("Wins")
	
	local playerstats = player:WaitForChild("playerstats")
	local roundsPlayed = playerstats:WaitForChild("RoundsPlayed")
	local highestPoints = playerstats:WaitForChild("HighestPoints")
	local highestPointsDuringRound = playerstats:WaitForChild("HighestPointsDuringRound")
	local highestStreak = playerstats:WaitForChild("HighestStreak")
	local totalButtonsClicked = playerstats:WaitForChild("TotalButtonsClicked")
	
	local gameStats = {cash,wins,roundsPlayed,highestPoints,highestPointsDuringRound,highestStreak,totalButtonsClicked}
	local playerStats = {}
	for _,stat in ipairs(gameStats) do
		playerStats[stat.Name] = stat.Value
	end
	
	return playerStats
end

local function saveData(player)
	local playerStats = getPlayerStats(player)
	local tries = 0	
	local success
	repeat
		tries += 1
		success = pcall(function()
			local playerUserId = "Player_" .. player.UserId
			playerData:SetAsync(playerUserId, playerStats)
		end)
		if not success then wait(1) end
	until tries == NUMBER_OF_RETRIES or success
	
	if not success then
		warn("There was an issue saving the player's data.")
	end
end
Whole data script
-- [[ Services ]] --
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")

-- [[ Datastores ]] --
local playerData = DataStoreService:GetDataStore("PlayerData")

-- [[ Configurable ]] --
local NUMBER_OF_RETRIES = 3
local AUTOSAVE_INTERVAL = 5 * 60 

local function loadData(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local points = Instance.new("IntValue")
	points.Name = "Points"
	points.Parent = leaderstats

	local streak = Instance.new("IntValue")
	streak.Name = "Streak"
	streak.Parent = leaderstats
	
	-- Datastore stats
	local cash = Instance.new("IntValue")
	cash.Name = "Cash"
	cash.Parent = leaderstats	

	local wins = Instance.new("IntValue")
	wins.Name = "Wins"
	wins.Parent = leaderstats
	
	-- Other datastore stats
	local playerstats = Instance.new("Folder")
	playerstats.Name = "playerstats"
	playerstats.Parent = player

	local roundsPlayed = Instance.new("IntValue")
	roundsPlayed.Name = "RoundsPlayed"
	roundsPlayed.Parent = playerstats
	
	local totalButtonsClicked = Instance.new("IntValue")
	totalButtonsClicked.Name = "TotalButtonsClicked"
	totalButtonsClicked.Parent = playerstats
	
	local highestPoints = Instance.new("IntValue")
	highestPoints.Name = "HighestPoints"
	highestPoints.Parent = playerstats
	
	local highestPointsDuringRound = Instance.new("IntValue")
	highestPointsDuringRound.Name = "HighestPointsDuringRound"
	highestPointsDuringRound.Parent = playerstats
	
	local highestStreak = Instance.new("IntValue")
	highestStreak.Name = "HighestStreak"
	highestStreak.Parent = playerstats
	
	-- Non-datastore stats during round
	local roundstats = Instance.new("Folder")
	roundstats.Name = "roundstats"
	roundstats.Parent = player
	
	local finalPoints = Instance.new("IntValue")
	finalPoints.Name = "FinalPoints"
	finalPoints.Parent = roundstats

	local finalStreak = Instance.new("IntValue")
	finalStreak.Name = "FinalStreak"
	finalStreak.Parent = roundstats
	
	local redButtonsClicked = Instance.new("IntValue")
	redButtonsClicked.Name = "RedButtonsClicked"
	redButtonsClicked.Parent = roundstats
	
	local yellowButtonsClicked = Instance.new("IntValue")
	yellowButtonsClicked.Name = "YellowButtonsClicked"
	yellowButtonsClicked.Parent = roundstats
	
	local greenButtonsClicked = Instance.new("IntValue")
	greenButtonsClicked.Name = "GreenButtonsClicked"
	greenButtonsClicked.Parent = roundstats
	
	local buttonsClicked = Instance.new("IntValue")
	buttonsClicked.Name = "ButtonsClicked"
	buttonsClicked.Parent = roundstats
	
	local playercosmetics = Instance.new("Folder")
	playercosmetics.Name = "playercosmetics"
	playercosmetics.Parent = player

	local trail = Instance.new("IntValue")
	trail.Name = "Trail"
	trail.Parent = playercosmetics

	local title = Instance.new("StringValue")
	title.Name = "Title"
	title.Parent = playercosmetics
	
	local playerUserId = "Player_" .. player.UserId
	
	local success, data = pcall(function()
		return  playerData:GetAsync(playerUserId)
	end)
	if success then
		if data then
			cash.Value = data["Cash"]
			wins.Value = data["Wins"]
			roundsPlayed.Value = data["RoundsPlayed"]
			highestPoints.Value = data["HighestPoints"]
			highestPointsDuringRound.Value = data["HighestPointsDuringRound"]
			highestStreak.Value = data["HighestStreak"]
			totalButtonsClicked.Value = data["TotalButtonsClicked"]
		else
			cash.Value = 0
			wins.Value = 0
			roundsPlayed.Value = 0
			highestPoints.Value = 0
			highestPointsDuringRound.Value = 0
			highestStreak.Value = 0
			totalButtonsClicked.Value = 0
		end
	else
		warn("There was an issue loading the player's data.")
	end
end

local function getPlayerStats(player)
	local leaderstats = player:WaitForChild("leaderstats")
	local cash = leaderstats:WaitForChild("Cash")
	local wins = leaderstats:WaitForChild("Wins")
	
	local playerstats = player:WaitForChild("playerstats")
	local roundsPlayed = playerstats:WaitForChild("RoundsPlayed")
	local highestPoints = playerstats:WaitForChild("HighestPoints")
	local highestPointsDuringRound = playerstats:WaitForChild("HighestPointsDuringRound")
	local highestStreak = playerstats:WaitForChild("HighestStreak")
	local totalButtonsClicked = playerstats:WaitForChild("TotalButtonsClicked")
	
	local gameStats = {cash,wins,roundsPlayed,highestPoints,highestPointsDuringRound,highestStreak,totalButtonsClicked}
	local playerStats = {}
	for _,stat in ipairs(gameStats) do
		playerStats[stat.Name] = stat.Value
	end
	
	return playerStats
end

local function saveData(player)
	local playerStats = getPlayerStats(player)
	local tries = 0	
	local success
	repeat
		tries += 1
		success = pcall(function()
			local playerUserId = "Player_" .. player.UserId
			playerData:SetAsync(playerUserId, playerStats)
		end)
		if not success then wait(1) end
	until tries == NUMBER_OF_RETRIES or success
	
	if not success then
		warn("There was an issue saving the player's data.")
	end
end

local autoSave = coroutine.wrap(function()
	while true do
		for _,player in ipairs(Players:GetPlayers()) do
			saveData(player)
		end	
		wait(AUTOSAVE_INTERVAL)
	end
end)

autoSave()
Players.PlayerAdded:Connect(loadData)
Players.PlayerRemoving:Connect(saveData)
game:BindToClose(function()
	if RunService:IsStudio() then return end
	if #Players:GetPlayers() > 1 then
		for _,player in ipairs(Players:GetPlayers()) do
			saveData(player)
		end	
	end
end)
1 Like

if its a table then do list your stuff inside the list then compare the data you cannot ignore this and set the values

Would something like this be good in place of the SetAsync?

for _,stat in ipairs(playerStats) do
	local success, err = playerData:UpdateAsync(playerUserId,function(oldValue)
	    local newValue = oldValue or 0
        return newValue
	end)
	if not success then
		warn("There was an issue saving the player's data")
		end
	end
end
1 Like

I had a similar question mere minutes ago haha, maybe this will help?

In your case, I don’t think you would need to use OldData as my script is conceptually similar to yours.

Essentially you have to return the whole table, you can’t set a specific value inside of that table.

2 Likes

I see! This is pretty helpful, thank you.

What perfect timing lol.

1 Like