DataStore script help

Hello. My data saving script isn’t working, and I can’t figure out what’s wrong with it.

local DSS = game:GetService("DataStoreService")
local DS = DSS:GetDataStore("ArksieData", "Players")

local function GenerateDataKey(Player)
	local Key = "UID_" .. Player.UserId
	return Key
end    

game.Players.PlayerAdded:connect(function(Player)
	local ls = Instance.new("Folder")
	ls.Name = "leaderstats"
	ls.Parent = Player
	
	local bits = Instance.new("IntValue")
	bits.Name = "Bits"
	bits.Value = 10 --starter amt
	bits.Parent = ls

	local key = GenerateDataKey(Player)
	local data = DS:GetAsync(key)

	if data then
		bits.Value = data.Bits
	end
end)

game.Players.PlayerRemoving:Connect(function(Player)
	local key = GenerateDataKey(Player)
	local data = {
		Bits = Player.leaderstats.Bits.Value,
	}
	local success,err = pcall(function()
		DS:SetAsync(key, data)
	end)
	if not success then
		warn('ArksieData Error: '..err)
	end
end)

Have you tried playing outside the roblox studio? And is access to API services enabled?

Yes, API services are enabled, and it should work in studio.

Try printing the success so you can see if it saved. I have experienced sometimes that studio doesn’t save to the datastore.

Did you try adding prints statements? I.E. printing out the player.leaderstats.Bits.Value right before set aysnc to see if its an issue with your leaderstats not being updated correctly vs the data itself? Is there any errors besides its not working?

It just prints “no data”. I guess it can’t get the data.

Datastores almost never work in studio, even if you are sure that it should work in studio, try testing it in roblox, and not roblox studio if you haven’t already.

It does load data but not save it. You need to start a local server or test it on roblox

Thanks @Babybunnyiscute19. But 1 more question. How do I use GlobalDataStore. Here’s the code I have right now. (The game is a place inside the main game)

local DSS = game:GetService("DataStoreService")
local DS = DSS:GetGlobalDataStore("ArksieData")

local function GenerateDataKey(Player)
	local Key = "UID_" .. Player.UserId
	return Key
end 

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

	local bits = Instance.new("IntValue")
	bits.Name = "Bits"
	bits.Value = 10 --starter amt
	bits.Parent = ls
	
	local key = GenerateDataKey(player)
	local data = nil
	local success,err = pcall(function()
		data = DS:GetAsync(key)
	end)
	if not success then
		warn(err)
	end
	
	if data then
		print(data.Bits)
        bits.Value = data.Bits
	else
		print('no data')
	end
end)

game.Players.PlayerRemoving:Connect(function(player)
	local key = GenerateDataKey(player)
	local data = {
		Bits = player.leaderstats.Bits.Value,
	}
	local success, err = pcall(function()
		DS:SetAsync(key, data)
	end)
	if not success then
		warn(err)
	end
end)

That’s not an issue for me.

This seems to have something to do with the fact there’s no :BindToClose to allow the server to save everything properly.

How would I use BindToClose to save everyone’s data?

I hate having to overcomplicate sentences to get past 30.

Well every function you add to game:BindToClose once the server starts to shutdown. 30 seconds after that the server just kills any code that was running anyway if it took longer than that.

Basically :BindToClose is used for doing stuff when a server is about to shutdown and make it so that you can finish everything and make it so that the server takes a bit longer to shutdown.

What you wanna do on a :BindToClose callback is save everyone’s data and make the function yield until that finishes.

Here’s a pseudo code example for that:

game:BindToClose(function()
    local players = Players:GetPlayers()
    local leftToSave = #players
    
    for _, player in ipairs(players) do
        coroutine.wrap(function()
            local success, errorMessage = pcall(function()
                DataStore:SetAsync("Player_".. player.UserId, "data")
            end)
            leftToSave -= 1
        end)()
    end

    while leftToSave >= 1 do
        RunService.Heartbeat:Wait()
    end
end)

There are more factors and improvements you can do, but this is the basis

2 Likes

Thanks! I edited the code, does this look right?

game:BindToClose(function()
	local players = game.Players:GetPlayers()
	local leftToSave = #players

	for _, player in ipairs(players) do
		coroutine.wrap(function()
			local data = {
				Bits = player.leaderstats.Bits.Value,
			}
			
			local success, errorMessage = pcall(function()
				DS:SetAsync(GenerateDataKey(player), data)
			end)
			leftToSave -= 1
		end)()
	end

	while leftToSave >= 1 do
		game:GetService("RunService").Heartbeat:Wait()
	end
end)
1 Like

Sorry for replying again, but it seems this script gives me this:

image

thanks for doing so i’m bored as heck today

Is this everytime you close the game?
Well, what you can do is store what players have their data loaded, which is better anyways.
You should be only saving their data if their data was even loaded anyways :wink:
Otherwise you would be replacing their data with empty data accidentally!

If you want more improvements to your saving like using :UpdateAsync and other things (handling OnPlayerAdded function properly on startup), you can ask on DMs :P
Example:

--\\ pseudo code hehee

local LOADED_PLAYERS = {}

Players.PlayerAdded:Connect(function(player)
    --\\ Loads data and stuff

    LOADED_PLAYERS[player] = true
end)

Players.PlayerRemoving:Connect(function(player)
    if not LOADED_PLAYERS[player] then return end;
    LOADED_PLAYERS[player] = nil

    --\\ try saving data
end)

game:BindToClose(function()
    local leftToSave = 0
    for player in pairs(LOADED_PLAYERS) do
        LOADED_PLAYERS[player] = nil
        leftToSave += 1

        coroutine.wrap(function()
            --\\ Try saving data

            leftToSave -= 1
        end)()
    end

    while leftToSave > 0 do
        RunService.Heartbeat:Wait()
    end
end)

Now it doesn’t seem to be saving. Here’s the full code.

local DSS = game:GetService("DataStoreService")
local DS = DSS:GetGlobalDataStore("ArksieData")

local LOADED_PLAYERS = {}

    local function GenerateDataKey(Player)
    	local Key = "UID_" .. Player.UserId
    	return Key
    end 

    game.Players.PlayerAdded:Connect(function(player)
    	LOADED_PLAYERS[player] = true
    	
    	local ls = Instance.new("Folder")
    	ls.Name = "leaderstats"
    	ls.Parent = player

    	local bits = Instance.new("IntValue")
    	bits.Name = "Bits"
    	bits.Value = 10 --starter amt
    	bits.Parent = ls
    	
    	local key = GenerateDataKey(player)
    	local data = nil
    	local success,err = pcall(function()
    		data = DS:GetAsync(key)
    	end)
    	if not success then
    		warn(err)
    	end
    	
    	if data then
    		print(data.Bits)
    		bits.Value = data.Bits
    	else
    		print('no data')
    	end
    end)

    game.Players.PlayerRemoving:Connect(function(player)
    	if not LOADED_PLAYERS[player] then return end;
    	LOADED_PLAYERS[player] = nil
    	
    	local key = GenerateDataKey(player)
    	local data = {
    		Bits = player.leaderstats.Bits.Value,
    	}
    	local success, err = pcall(function()
    		DS:SetAsync(key, data)
    	end)
    	if not success then
    		warn(err)
    	end
    end)

    game:BindToClose(function()
    	local leftToSave = 0
    	for player in pairs(LOADED_PLAYERS) do
    		LOADED_PLAYERS[player] = nil
    		leftToSave += 1

    		coroutine.wrap(function()
    			local data = {
    				Bits = player.leaderstats.Bits.Value,
    			}

    			local success, errorMessage = pcall(function()
    				DS:SetAsync(GenerateDataKey(player), data)
    			end)
    			leftToSave -= 1
    		end)()
    	end
    	local RunService = game:GetService("RunService")
    	while leftToSave > 0 do
    		RunService.Heartbeat:Wait()
    	end
    end)

Any ideas?

you forgot to do LOADED_PLAYERS[player] = true once the player had loaded their data.

On the pcall I recommend you kick the player if it errors, and return so that no more code runs.

Oh sorry! I don’t know if you edited it or something or I just didn’t notice.

Can you do some printing? Add some prints to when PlayerAdded fires, PlayerRemoving fires, BindToClose and maybe print LOADED_PLAYERS

also you’re supposed to do LOADED_PLAYERS[player] = true AFTER the data is loaded

Also if you can, make it so that the PlayerAdded function is a local variable instead (called OnPlayerAdded in this case) and make it handle already loaded players like this.

local function OnPlayerAdded(player)
    --// lol code
end

for _, player in ipairs(Players:GetPlayers()) do
    coroutine.wrap(OnPlayerAdded)(player)
end

Players.PlayerAdded:Connect(OnPlayerAdded)
1 Like

Thanks! My datastore is working across the entire game universe now.

1 Like