Datastore saving table resulting in nil

I am still pretty new to roblox scripting and am having trouble saving a table for my game. I am currently working on the tower save system for my tower defense game. Here is the code:

local DataStore = game:GetService("DataStoreService"):GetDataStore("Tower2")

local function onPlayerJoin(player)
	
	local leaderstats = Instance.new("Folder")  --Sets up leaderstats folder
	leaderstats.Name = "Tower"
	leaderstats.Parent = player

	local Towers = {}
	
	local PlayerData = DataStore:GetAsync(player.UserId)
	print(PlayerData)


	if PlayerData ~= nil then
		print("Found Data")
		for _, i in pairs(PlayerData) do
			local TVal = Instance.new("StringValue")
			TVal.Name = PlayerData[i]
			TVal.Parent = leaderstats
			TVal.Value = PlayerData[i]
		end
	else
		warn("Could not find local save")
		local TVal = Instance.new("StringValue")
		TVal.Name = "Soldier"
		TVal.Parent = leaderstats
		TVal.Value = "Soldier"
	end
	
end

local function create_table(player)
	local player_stats = {}
	for _, stat in pairs(player.Tower:GetChildren()) do
		player_stats[stat.Name] = stat.Value
	end
	return player_stats
end

local function onPlayerExit(player)  --Runs when players exit
	print("Exited")
	local player_stats = create_table(player)
	local success, err = pcall(function()
		local playerUserId = "Player_" .. player.UserId
		DataStore:SetAsync(playerUserId, player_stats) --Saves player data
		print("Saved")
		print(player_stats)
	end)
	if not success then
		warn('Could not save data!')
	end
end


game.Players.PlayerAdded:Connect(onPlayerJoin)

game.Players.PlayerRemoving:Connect(onPlayerExit)

Inside the player is a folder with a value for each tower that they currently own. The script creates these values upon joining the game and is supposed to save them when the player leaves. The problem is that the script is not actually saving the players data. There are no errors in the output and the datastore prints nil when joining. I have tried to fix this for the last several hours but nothing is working. Can anyone give me some advice on how I could make this work?

first of all, the player keys are different here. You save it to "Player_"..player.UserId, but try to get it from just player.UserId
Make sure to use the same key. The saving should work perfectly fine. It’s just the getting. Also,

The “Name” here should be _ and the Value should be i, but It would probably just be better to change the for variables. So, I’ll do that for you.

for i, v in pairs(PlayerData) do
local TVal = Instance.new("StringValue")
TVal.Name = i
TVal.Parent = leaderstats
TVal.Value = v
end

@Kaid3n22 is right, also you should wrap those data store functions in a pcall function, because data stores can fail time to time.

local PlayerData
local success, err = pcall(function()
    local PlayerData = DataStore:GetAsync("Player_"..player.UserId)
end)
if PlayerData then
   -- set your data here
end

Edit: I noticed that you used SetAsync, which is not good and I recommend using UpdateAsync.

DataStore:UpdateAsync("Player_"..player.UserId,function(oldData) return SaveThisData end)

Thanks for the help I think it worked, It is not loading the saved data which I’m assuming is just because the server is shutting down before the game can save

A few things:

1- Are you ever using the game:BindToClose method?

local playersSaving = {}
local bindToCloseThreads = {}

local function onPlayerExit(player)  --Runs when players exit
    if not playersSaving[player] then
        playersSaving[player] = true
    	print("Exited")
	    local player_stats = create_table(player)
	    local success, err = pcall(function()
		    local playerUserId = "Player_" .. player.UserId
	    	DataStore:SetAsync(playerUserId, player_stats) --Saves player data
	    	print("Saved")
	    	print(player_stats)
	    end)
	    if not success then
		    warn('Could not save data!')
	    end
        if bindToCloseThreads[player] ~= nil then
            bindToCloseThreads[player] = true
        end
        playersSaving[player] = nil
    end
end

local function allSaved()
    for i,v in pairs(playersSaving) do
        if not v then
            return v
        end
    end
end

game:BindToClose(function()
    for i,player in pairs(game:GetService('Players'):GetPlayers()) do
        coroutine.wrap(onPlayerExit)(player)
        bindToCloseThreads[player] = false
    end
    repeat game:GetService('RunService').Heartbeat:Wait() until allSaved()
end)

2- You should check if the :GetAsync request failed, if it does fail, it’s going to set the player’s data to nil.

local playerData
local success, errorMessage = pcall(function()
    playerData = DataStore:GetAsync(player.UserId)
end)
if success then
    if playerData ~= nil then
        for i,value in pairs(PlayerData) do
			local TVal = Instance.new("StringValue")
			TVal.Name = i
			TVal.Parent = leaderstats
			TVal.Value = value
		end
    else -- if it's blank but the request was still successful
        local TVal = Instance.new("StringValue")
		TVal.Name = "Soldier"
		TVal.Parent = leaderstats
		TVal.Value = "Soldier"
    end
else -- if the request failed
    warn('GetAsync failed for',player,': ',tostring(errorMessage)
end

3- You’re writing and getting from different keys as someone mentioned already. You should only write and get from the same key.

DataStore:GetAsync(player.UserId)
DataStore:SetAsync(player.UserId, player_stats)

I have revised the code but it is still not able to find the saved data

revised code:

local DataStore = game:GetService("DataStoreService"):GetDataStore("Tower2")
local playersSaving = {}
local bindToCloseThreads = {}

local function onPlayerJoin(player)
	
	local leaderstats = Instance.new("Folder")  --Sets up leaderstats folder
	leaderstats.Name = "Tower"
	leaderstats.Parent = player

	local Towers = {}
	
	local playerUserId = "Player_" .. player.UserId
	
	local playerData
	local success, errorMessage = pcall(function()
		playerData = DataStore:GetAsync(player.UserId)
	end)
	if success then
		if playerData then
			print("Found Data")
			for i, v in pairs(playerData) do
				local TVal = Instance.new("StringValue")
				TVal.Name = i
				TVal.Parent = leaderstats
				TVal.Value = v
			end
		else
			warn("Could not find local save")
			local TVal = Instance.new("StringValue")
			TVal.Name = "Soldier"
			TVal.Parent = leaderstats
			TVal.Value = "Soldier"
		end
	else
		warn('GetAsync failed for',player,': ',tostring(errorMessage))
	end
	
end

local function create_table(player)
	local player_stats = {}
	for _, stat in pairs(player.Tower:GetChildren()) do
		player_stats[stat.Name] = stat.Value
	end
	return player_stats
end


local function onPlayerExit(player)
	if not playersSaving[player] then
		playersSaving[player] = true
		print("Exited")
		local player_stats = create_table(player)
		local success, err = pcall(function()
			DataStore:SetAsync(player.UserId, player_stats)
			print("Saved")
			print(player_stats)
		end)
		if not success then
			warn('Could not save data!')
		end
		if bindToCloseThreads[player] ~= nil then
			bindToCloseThreads[player] = true
		end
		playersSaving[player] = nil
	end
end


game.Players.PlayerAdded:Connect(onPlayerJoin)

game.Players.PlayerRemoving:Connect(onPlayerExit)

As @7z99 said, use the game:BindToClose() function.

game:BindToClose(function()
	for i,v in pairs(game.Players:GetPlayers()) do
		onPlayerExit(v)
	end
end)

i added the bind to close function but I am still having the same issue

ok so now it is loading the data but isnt saving it

Code:

local DataStore = game:GetService("DataStoreService"):GetDataStore("Tower2")
local playersSaving = {}
local bindToCloseThreads = {}

local function onPlayerJoin(player)
	local leaderstats = Instance.new("Folder")  --Sets up leaderstats folder
	leaderstats.Name = "Tower"
	leaderstats.Parent = player

	local Towers = {}
	
	local playerUserId = "Player_" .. player.UserId
	
	local playerData
	local success, errorMessage = pcall(function()
		playerData = DataStore:GetAsync(player.UserId)
	end)
	if success then
		if playerData then
			print("Found Data")
			for i, v in pairs(playerData) do
				local TVal = Instance.new("StringValue")
				TVal.Name = i
				TVal.Parent = leaderstats
				TVal.Value = v
			end
		else
			warn("Could not find local save")
			local TVal = Instance.new("StringValue")
			TVal.Name = "Soldier"
			TVal.Parent = leaderstats
			TVal.Value = "Soldier"
		end
	else
		warn('GetAsync failed for',player,': ',tostring(errorMessage))
	end
	
end

local function create_table(player)
	local player_stats = {}
	for _, stat in pairs(player.Tower:GetChildren()) do
		table.insert(player_stats, stat.Value)
	end
	return player_stats
end


local function onPlayerExit(player)
	if not playersSaving[player] then
		playersSaving[player] = true
		print("Exited")
		local player_stats = create_table(player)
		local success, err = pcall(function()
			DataStore:SetAsync(player.UserId, player_stats)
			print("Saved")
			print(player_stats)
		end)
		if not success then
			warn('Could not save data!')
		end
		if bindToCloseThreads[player] ~= nil then
			bindToCloseThreads[player] = true
		end
		playersSaving[player] = nil
	end
end


game:BindToClose(function()
	for i,v in pairs(game.Players:GetPlayers()) do
		onPlayerExit(v)
	end
end)

game.Players.PlayerAdded:Connect(onPlayerJoin)

game.Players.PlayerRemoving:Connect(onPlayerExit)

ok i dont know what i did but now it loaded the number 1 with a vale of Soldier. It should be loading “Soldier” with a value of soldier so I don’t know how this happened

Code:

local DataStore = game:GetService("DataStoreService"):GetDataStore("Tower2")
local playersSaving = {}
local bindToCloseThreads = {}

local function onPlayerJoin(player)
	
	local leaderstats = Instance.new("Folder")  --Sets up leaderstats folder
	leaderstats.Name = "Tower"
	leaderstats.Parent = player

	local Towers = {}
	
	local playerUserId = "Player_" .. player.UserId
	
	local playerData
	local success, errorMessage = pcall(function()
		playerData = DataStore:GetAsync(player.UserId)
	end)
	if success then
		if playerData then
			print("Found Data")
			for i, v in pairs(playerData) do
				local TVal = Instance.new("StringValue")
				TVal.Name = i
				TVal.Parent = leaderstats
				TVal.Value = v
			end
		else
			warn("Could not find local save")
			local TVal = Instance.new("StringValue")
			TVal.Name = "Soldier"
			TVal.Parent = leaderstats
			TVal.Value = "Soldier"
		end
	else
		warn('GetAsync failed for',player,': ',tostring(errorMessage))
	end
	
end

local function create_table(player)
	local player_stats = {}
	for _, stat in pairs(player.Tower:GetChildren()) do
		table.insert(player_stats, stat.Value)
	end
	return player_stats
end


local function onPlayerExit(player)
	if not playersSaving[player] then
		playersSaving[player] = true
		print("Exited")
		local player_stats = create_table(player)
		local success, err = pcall(function()
			DataStore:SetAsync(player.UserId, player_stats)
			print("Saved")
			print(player_stats)
		end)
		if not success then
			warn('Could not save data!')
		end
		if bindToCloseThreads[player] ~= nil then
			bindToCloseThreads[player] = true
		end
		playersSaving[player] = nil
	end
end


game:BindToClose(function()
	for i,v in pairs(game.Players:GetPlayers()) do
		onPlayerExit(v)
	end
end)

game.Players.PlayerAdded:Connect(onPlayerJoin)

game.Players.PlayerRemoving:Connect(onPlayerExit)```

you changed the way you inserted the table so ofc it’s not working, lol.

change this line to

TVal.Name = v

I can be very stupid sometimes

Ok so it loads correctly now but it still doesnt save

Ok so I figured out this part of the script:

local function create_table(player)
	local player_stats = {}
	for _, stat in pairs(player.Tower:GetChildren()) do
		table.insert(player_stats, stat.Value)
		print(stat.Value)
	end
	return player_stats
end

Is only saving the first value in the folder. I don’t know why this is happening. As far as I can tell this should be saving all the values and not just one value. Can someone please tell me how I could fix this?

Here is the rest of the script:

local DataStore = game:GetService("DataStoreService"):GetDataStore("Tower2")
local playersSaving = {}
local bindToCloseThreads = {}

local function onPlayerJoin(player)

	local leaderstats = Instance.new("Folder")  --Sets up leaderstats folder
	leaderstats.Name = "Tower"
	leaderstats.Parent = player

	local Towers = {}

	local playerUserId = "Player_" .. player.UserId

	local playerData
	local success, errorMessage = pcall(function()
		playerData = DataStore:GetAsync(player.UserId)
	end)
	if success then
		if playerData then
			print("Found Data")
			for i, v in pairs(playerData) do
				local TVal = Instance.new("StringValue")
				TVal.Name = v
				TVal.Parent = leaderstats
				TVal.Value = v
			end
		else
			warn("Could not find local save")
			local TVal = Instance.new("StringValue")
			TVal.Name = "Soldier"
			TVal.Parent = leaderstats
			TVal.Value = "Soldier"
		end
	else
		warn('GetAsync failed for',player,': ',tostring(errorMessage))
	end

end

local function create_table(player)
	local player_stats = {}
	for _, stat in pairs(player.Tower:GetChildren()) do
		table.insert(player_stats, stat.Value)
		print(stat.Value)
	end
	return player_stats
end


local function onPlayerExit(player)
	if not playersSaving[player] then
		playersSaving[player] = true
		print("Exited")
		local player_stats = create_table(player)
		local success, err = pcall(function()
			DataStore:SetAsync(player.UserId, player_stats)
			print("Saved")
			print(player_stats)
		end)
		if not success then
			warn('Could not save data!')
		end
		if bindToCloseThreads[player] ~= nil then
			bindToCloseThreads[player] = true
		end
		playersSaving[player] = nil
	end
end


game:BindToClose(function()
	for i,v in pairs(game.Players:GetPlayers()) do
		onPlayerExit(v)
	end
end)

game.Players.PlayerAdded:Connect(onPlayerJoin)

game.Players.PlayerRemoving:Connect(onPlayerExit)

OHHHHHHH im an idiot. I was trying to give the player values through the client while the save script was server sided. It automatically gives the player the soldier tower so it could only save that since the rest was client sided.

I don’t know what i did but it has now saved the number 0 as a tower and also fills the datastore request queue whenever I leave

here is the code:

local DataStore = game:GetService("DataStoreService"):GetDataStore("Tower2")
local playersSaving = {}
local bindToCloseThreads = {}

local function onPlayerJoin(player)

	local leaderstats = Instance.new("Folder")  --Sets up leaderstats folder
	leaderstats.Name = "Tower"
	leaderstats.Parent = player

	local Towers = {}

	local playerUserId = "Player_" .. player.UserId

	local playerData
	local success, errorMessage = pcall(function()
		playerData = DataStore:GetAsync(player.UserId)
	end)
	if success then
		if playerData then
			print("Found Data")
			for i, v in pairs(playerData) do
				local TVal = Instance.new("StringValue")
				TVal.Name = v
				TVal.Parent = leaderstats
				TVal.Value = v
			end
		else
			warn("Could not find local save")
			local TVal = Instance.new("StringValue")
			TVal.Name = "Soldier"
			TVal.Parent = leaderstats
			TVal.Value = "Soldier"
		end
	else
		warn('GetAsync failed for',player,': ',tostring(errorMessage))
	end

end

local function create_table(player)
	local player_stats = {}
	for _, stat in pairs(player.Tower:GetChildren()) do
		player_stats[stat.name] = stat.Value
		print(stat.Value)
	end
	return player_stats
end


local function onPlayerExit(player)
	if not playersSaving[player] then
		playersSaving[player] = true
		print("Exited")
		local player_stats = create_table(player)
		local success, err = pcall(function()
			DataStore:SetAsync(player.UserId, player_stats)
			print("Saved")
			print(player_stats)
		end)
		if not success then
			warn('Could not save data!')
		end
		if bindToCloseThreads[player] ~= nil then
			bindToCloseThreads[player] = true
		end
		playersSaving[player] = nil
	end
end


game:BindToClose(function()
	for i,v in pairs(game.Players:GetPlayers()) do
		onPlayerExit(v)
	end
end)

game.Players.PlayerAdded:Connect(onPlayerJoin)

game.Players.PlayerRemoving:Connect(onPlayerExit)