DataStore Saving Problem

The Goal

I want to achieve a simple saving system for DataStores. However, it does not save the data.

Possible Problem

The first issue is that, in the PlayerRemoving event, when calling dataStore:GetAsync(uniqueKey) in the pcall, it infinitely yields.
According to some documentation, GetAsync yields. From further testing, pcall doesn’t create a new thread, meaning that if I yield in a pcall, it would yield the current thread. This means that GetAsync would yield the thread forever. It seems like it is a problem from GetAsync.

I tried looking at the DevHub on DataStores (and saving PlayerData). I also tried integrating DataStore2, however, it was hard to put together with an already function Stats ModuleScript.

I do have Studio DataStore API enabled.

Code

ReplicatedStorage.Data.PlayerData

-- The PlayerData code is added to the problem for context.  
-- This isn't actually the root of the problem

-- Creator: NilScripter_Alt
-- Description: A ModuleScript that creates stat tables for each player.
                      --Includes events

local statCreated = Instance.new("BindableEvent")
local statDeleted = Instance.new("BindableEvent")

local function isValidPlayer(player)
	if not player then
		return false
	end
	
	if not player:IsA("Player") then
		return false
	end
	
	return true
end

local PlayerData = {}
PlayerData.StatCreated = statCreated.Event
PlayerData.StatDeleted = statDeleted.Event

local stats = {}

function PlayerData.getStats(player)
	if not isValidPlayer(player) then
		error("Cannot get stat of an invalid player", 2)
	end
	
	local stat = stats[player]
	return stat
end

function PlayerData.createStats(player, overrideStats)
	if not isValidPlayer(player) then
		error("Cannot create stat of an invalid player", 2)
	end
	
	if PlayerData.getStats(player) then
		error("Cannot create stat that has already been created", 2)
	end
	
	stats[player] = overrideStats or {}
	statCreated:Fire(player, stats[player])
end

function PlayerData.deleteStats(player)
	if not isValidPlayer(player) then
		error("Cannot delete stats of invalid player", 2)
	end
	
	local stat = PlayerData.getStats(player)
	if not stat then
		error("Cannot delete stats that are already deleted", 2)
	end
	
	statDeleted:Fire(player, stat)
	for key, value in pairs(stat) do
		key = nil
		value = nil
	end
end

return PlayerData

ServerScriptService.PlayerDataManager

-- Scroll down to the onPlayerRemoving function.  
-- There, you will find the problem

-- Creator: NilScripter_Alt
-- Description: A Script that puts the PlayerData module into 
                  -- action and manages the loading and saving

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local data = ReplicatedStorage.Data
local playerData = require(data.PlayerData)

local Players = game:GetService("Players")

local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("PLAYER_DATA")

local defaultData = {
	coinsCollected = {
		["workspace.Coins.DeleteRedCoin"] = true
	};
		
	money = 0;
	experience = 0;
	dataId = 0;
	level = 1;
	inBattle = false;
}

local function getUniqueKey(player)
	return "PLAYER_DATA:" .. player.UserId 
end

local function onPlayerAdded(player)
	local uniqueKey = getUniqueKey(player)
	local success, overrideData = pcall(function()
		return dataStore:GetAsync(uniqueKey)
	end)
	
	if not success then
		warn(player.Name, "'s data could not be found")
	elseif not overrideData then
		warn(player.Name, "'s does not have any DataStores yet")
		overrideData = defaultData
		pcall(function()
			dataStore:SetAsync(uniqueKey, overrideData)
		end)
	end
	
	print("COINS:", overrideData.money)
	playerData.createStats(player, overrideData)
	print("CREATED")
end

local function onPlayerRemoving(player)
	print("is this even working")
	local uniqueKey = getUniqueKey(player)
	local stats = playerData.getStats(player)
	print("did I get it?")
	local s, overrideData = pcall(function()
		print("pcall?")
		return dataStore:GetAsync(uniqueKey)
	end)

        -- (PROBLEM HERE!)
        -- after some debugging (using prints), it seems like dataStore:GetAsync()
        -- yields forever, not allowing it to continue
	print("Hello?") -- This doesn't print
	print(s, ":", overrideData) -- This doesn't print
	-- Everything after doesn't work
	if not overrideData then
		warn(player.Name, "does not have any data to save")
		return
	end
	
	local success, err = pcall(function()
		dataStore:UpdateAsync(uniqueKey, function(oldData)
			if not oldData then
				return nil
			end
				
			if playerData.dataId ~= oldData.dataId then
				return nil
			end
				
			playerData.dataId = playerData.dataId + 1
			return playerData
		end)
	end)
	print(err)
	
	playerData.deleteStats(player)
	print("DELETE")
end

for _, player in ipairs(Players:GetPlayers()) do
	spawn(function() onPlayerAdded(player) end)
end

Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(onPlayerRemoving)

game:BindToClose(function()
	for _, player in ipairs(Players:GetPlayers()) do
		spawn(function() onPlayerRemoving(player) end)
	end
end)
  • Thank You!
1 Like

Try replacing the onPlayerRemoving section with this.

local function OnPlayerRemoving(Player)
-- save
end

local function OnGameShutdown()
local Players = game.Players:GetPlayers()

for Index = 1, #Players do
    local Player = Players[Index]

    OnPlayerRemoving(Player)
end
end

game.Players.PlayerRemoving:Connect(OnPlayerRemoving)
game:BindToClose(OnGameShutdown)

If it doesn’t work, then I’m the one who misunderstood.

That doesn’t do anything. You simply created a function for game:BindToClose. The problem is not even in game:BindToClose(?) It’s in the onPlayerRemoving function (?). You also changed the ipairs loop into a numeric for loop (which does the same thing) (?). I don’t know how that’s supposed to solve the problem that dataStore:GetAsync() is yielding forever.

Thank you, but I don’t think what you posted would solve my problem.

1 Like

Are you in studio? If so check if you have Datastore API Calls enabled

I am in Roblox Studio. Yes, I do have Datastore API Calls enabled. However, I tried on the website and it still had the same problem. I’ll edit OP to clarify that.

1 Like

I initially didn’t want to bump this post. However, after trying to understand what is wrong, I cannot find the solution. That is why I am bumping this post.

I think I know what the problem is. According to some documentation, GetAsync yields. From further testing, pcall doesn’t create a new thread, meaning that if I yield in a pcall, it would yield the current thread. This means that GetAsync would yield the thread forever.

I know the problem, but I don’t know how to fix it.

I fixed the script. Whenever saving the data, instead of returning the data in the pcall, I simply set a variable to it.

It’s also good practice to do it this way because GetAsync may return a nil value. Meaning, I cannot assume it will return a truthy value. Also (according to this code), it is better to do this way so my code won’t yield forever.

The solution goes like this:

local overrideData, success do
   success, _ = pcall(function()
      overrideData = dataStore:GetAsync(uniqueKey)
   end)
end

I also didn’t need to do that. I kept it there just incase the player doesn’t even have data.

1 Like