I was making a datastore script for @ProvenData and for some reason the data isn’t saving. Here is the script:
local module = {}
local dataStoreService = game:GetService("DataStoreService")
local dataStore = dataStoreService:GetDataStore("PlayerData")
local err = warn
local playerData = {}
local defaultDataID = 0
function loadData(player)
local key = "key-"..player.UserId
local data = nil
local success, errorDebug = pcall(function()
data = dataStore:GetAsync(key)
end)
if success then
if data then
table.insert(playerData, data)
else
data = {defaultDataID, player.Name}
table.insert(playerData, data)
end
else
err(errorDebug)
end
end
function saveData(player)
local key = "key-"..player.UserId
local data = nil
local index = 0
for i, v in ipairs(playerData) do
if v[2] == player.Name then
data = v
index = i
break
end
end
print(1) -- So I can see where the script is stopping at
if data then
print(2) -- So I can see where the script is stopping at
local success, errorMessage = pcall(function()
print(3) -- So I can see where the script is stopping at
dataStore:UpdateAsync(key, function(oldData)
print(4) -- So I can see where the script is stopping at
local previousData = oldData or {defaultDataID, player.Name}
if data[1] == previousData[1] then
data[1] = data[1] + 1
return data
else
return nil
end
end)
print(5) -- So I can see where the script is stopping at
end)
print(6) -- So I can see where the script is stopping at
table.remove(playerData, index)
if success then
print("Data successfully saved")
else
err(errorMessage)
end
end
end
game.Players.PlayerAdded:Connect(loadData)
game.Players.PlayerRemoving:Connect(saveData)
--[[game:BindToClose(function()
for i, v in next, game:GetService("Players"):GetChildren() do
coroutine.wrap(function()
saveData(v)
end)
end
end)]]--
for i, v in next, game:GetService("Players"):GetChildren() do
coroutine.wrap(function()
loadData(v)
end)()
end
return module
I’ve also added prints to see where the script gets stops at.
For some reason, when I leave the game it only prints 1, 2, and 3 then it stops. Can someone please tell me why the data isn’t saving, and how to fix the issue?
EDIT: Also, here is how the data I’m trying to save looks like: {0, Username, "Random string"}. 0 Is how many times the player has left the game, Username is self explanatory, and the string at the end of the table is the playerdata (It’s to test if the datastore script works).
I set data to nil, but then I try to find the player data associated with the leaving player, then when that data is found, we set local data to be equal to the player data associated with the leaving player then we break out of the loop.
I put an or after it, and then I put in this {defaultDataID, player.Name}. So if oldData happens to be nil roblox automatically uses the optional data {defaultDataID, player.Name}.
Have you considered trying to use the lua debugger? Set a breakpoint at the top of saveData and step through the code, line-by-line, inspecting your variabels and seeing if they are what you expect.
I tried to simplify your code a bit, and maybe fixed a bug or two. Check the -- @nicemike40 comments for my thoughts. No guarantees that this works, but it should be easier to reason about:
local module = {}
local dataStoreService = game:GetService("DataStoreService")
local dataStore = dataStoreService:GetDataStore("PlayerData")
local err = warn
-- @nicemike40 using a weak-keyed map from player objects to data so that
-- the entry is cleaned up if you leave the game
local playerData = setmetatable({}, {__mode = "k"})
local defaultDataID = 0
function loadData(player)
local key = "key-"..player.UserId
local data = nil
local success, errorDebug = pcall(function()
data = dataStore:GetAsync(key)
end)
if success then
-- @nicemike40 just a simplification
playerData[player] = data or {defaultDataID, player.Name}
else
err(errorDebug)
end
end
function saveData(player)
local key = "key-"..player.UserId
local data = playerData[player]
print(1) -- So I can see where the script is stopping at
if data then
print(2) -- So I can see where the script is stopping at
local success, errorMessage = pcall(function()
print(3) -- So I can see where the script is stopping at
dataStore:UpdateAsync(key, function(oldData)
print(4) -- So I can see where the script is stopping at
-- @nicemike40 I didn't understand your logic in this function,
-- why would you ever want to return nil? I simplified it:
if oldData then data[1] = oldData[1] + 1 end
return data
end)
print(5) -- So I can see where the script is stopping at
end)
print(6) -- So I can see where the script is stopping at
-- @nicemike40 map is weak, so the entry is removed from the map only if
-- the player object is actually destroyed
if success then
print("Data successfully saved")
else
err(errorMessage)
end
end
end
game.Players.PlayerAdded:Connect(loadData)
game.Players.PlayerRemoving:Connect(saveData)
-- @nicemike40 see my comment below
--[[game:BindToClose(function()
for i, v in next, game:GetService("Players"):GetChildren() do
coroutine.wrap(function()
saveData(v)
end)
end
end)]]--
-- don't use next lol
for _, player in pairs(game:GetService("Players"):GetPlayers()) do
-- @nicemike40 hmmm... be careful here. I don't think it's a problem, but
-- you should be aware that you're calling a yielding function (loadData)
-- inside this coroutine, which means that if a player leaves while you're
-- still loading their data, you might have a data race as saveData tries
-- to access the same player data while loadData is still running.
--
-- The same issue might just happen normally with PlayerRemoving/
-- PlayerAdded, but I'm not sure those run in separate coroutines so
-- the problem is less pronounced.
coroutine.wrap(function()
loadData(player)
end)()
end
return module
@nicemike40 So I happened to fix the issue by using this modulescript:
local dataModule = {}
local err = warn
function dataModule:save(key, data, dataStore, defaultOldData, typeOf)
if data then
local success, errorDebug = pcall(function()
dataStore:UpdateAsync(key, function(oldData)
local previousDataId-- = oldData[1] or oldData.DataId or defaultOldData
if oldData then
previousDataId = oldData[1]
else
if oldData then
if not oldData[1] then
if oldData.DataId then
previousDataId = oldData.DataId
end
end
end
end
if not previousDataId then
previousDataId = defaultOldData[1] or defaultOldData.DataId
end
local currentDataId = data[1]
local where = "A"
if not currentDataId then
where = "B"
currentDataId = data.DataId
end
if not currentDataId then
warn("Could not save data. Please go to game.ServerScriptService.DataModule, and read the comments at the top of the script!")
return nil
end
if previousDataId == currentDataId then
if where == "A" then
data[1] = data[1] + 1
end
if where == "B" then
data.DataId = data.DataId + 1
end
return data
else
return nil
end
end)
end)
if success then
print("Successfully saved data of type: "..typeOf)
else
err(errorDebug)
end
return success, errorDebug
end
end
function dataModule:load(key, dataStore)
local data = nil
local success, errorDebug = pcall(function()
data = dataStore:GetAsync(key)
end)
if success then
else
err(errorDebug)
end
return {["success"] = success, ["errorDebug"] = errorDebug, ["data"] = data}
end
return dataModule
Now, to answer your questions on why I return nil, it’s because if the script detects that the data could not load, it will return nil thus not saving the data.
So basically, if I had a million cash in the game, and the script couldn’t load my cash, the loaded cash would be 0, so if I would leave the game it would override the million cash with 0, thus causing dataloss. Thats why we return nil if the data could not load. Because update async doesn’t save the data if we return nil.
-- don't use next lol
Also, why should I not use next lol?
-- @nicemike40 using a weak-keyed map from player objects to data so that -- the entry is cleaned up if you leave the game setmetatable({}, {__mode = "k"})
Where should I go, to learn more about metatables?