trying to save coins in my game, but datastore doesn’t work as well as not printing any errors or successes.
i have tried looking on the developer hub. no luck yet.
local DataStoreService = game:GetService("DataStoreService")
local CashSave = DataStoreService:GetDataStore("CashSave")
game.Players.PlayerAdded:Connect(function(player)
local leaderstats = Instance.new("Folder",player)
leaderstats.Name = "leaderstats"
local cash = Instance.new("IntValue",leaderstats)
cash.Name = "Cash"
local data
local success, errormessage = pcall(function()
data = CashSave:GetAsync(player.UserId.."-cash")
end)
if success then
cash.Value = data
else
print("there was an error while saving data!")
warn(errormessage)
end
end)
game.Players.PlayerRemoving:Connect(function(player)
local success, errormessage = pcall(function()
CashSave:SetAsync(player.UserId.."-cash",player.leaderstats.Cash.Value)
end)
if success then
print("Data successfully saved!")
else
print("there was an error while saving data")
warn(errormessage)
end
end)
This!
I like to just wait(1) if it’s studio so PlayerRemoving has time to fire (it throws a “Request added to queue” warning if you save anyways).
Also, implement a retry logic (retry until success or a certain amount of retries).
Add autosaving so players don’t lose their entire session when DataStores go down.
Add corruption handling, as far as I know Roblox can return an errormessage (string) instead of your actual data in rare cases.
unfortunately what I have tried hasn’t worked - I have changed my SetAsync to UpdateAsync, and Have added a game:BindToClose and i think I have done everything correctly, correct me if i’m wrong:
local DataStoreService = game:GetService("DataStoreService")
local CashSave = DataStoreService:GetDataStore("CashSave")
game.Players.PlayerAdded:Connect(function(player)
local leaderstats = Instance.new("Folder",player)
leaderstats.Name = "leaderstats"
local cash = Instance.new("IntValue",leaderstats)
cash.Name = "Cash"
local data
local success, errormessage = pcall(function()
data = CashSave:GetAsync(player.UserId.."-cash")
end)
if success then
cash.Value = data
else
print("there was an error while saving data!")
warn(errormessage)
end
end)
game.Players.PlayerRemoving:Connect(function(player)
wait(1)
local success, errormessage = pcall(function()
CashSave:UpdateAsync(player.UserId.."-cash",player.leaderstats.Cash.Value)
end)
if success then
print("Data successfully saved!")
else
print("there was an error while saving data")
warn(errormessage)
end
game:BindToClose(function()
wait(2)
print("Done")
end)
end)
Here, try this code. I normally try not to spoonfeed code but DataStores seem to be notoriously difficult to understand fully - through no fault of your own of course. I utilized the logic I meant to convey in my original post here along with the retry logic that @GEILER123456 posted about
local MaxDataStoreRetries = 3
local RunService = game:GetService('RunService')
local DataStoreService = game:GetService("DataStoreService")
local CashSave = DataStoreService:GetDataStore("CashSave")
-- we will use this table as a queue to ensure that UpdateAsync requests are processed before the server closes
local updateAsyncQueue = { }
-- this function does a few things:
-- 1. Safely wraps the callback in a protected call, which also warns in the output upon error (without interrupting the thread)
-- 2. Implements retry logic where if the protected call fails, we retry until it's either successful or the max attemps is reached
-- 3. Returns whether or not the callback was successful
local function DataStoreSafeCall(Callback)
local attempts = 0
local success = false
repeat
attempts += 1
success = xpcall(Callback, warn)
until success or attempts == MaxDataStoreRetries
return success
end
game.Players.PlayerAdded:Connect(function(player)
local leaderstats = Instance.new("Folder", player)
leaderstats.Name = "leaderstats"
local cash = Instance.new("IntValue", leaderstats)
cash.Name = "Cash"
local function getAsync()
cash.Value = CashSave:GetAsync(player.UserId .. "-cash")
end
if DataStoreSafeCall(getAsync) then
print('Loaded players data')
end
end)
game.Players.PlayerRemoving:Connect(function(player)
-- insert player into the Update queue
table.insert(updateAsyncQueue, player)
local function updateAsync()
-- the second argument for DataStore::UpdateAsync is not the new value itself, but rather a callback which *returns* the new value
-- the only argument for the callback is the last value in which was saved for the input key
-- this allows us to verify the old value before setting the new value
CashSave:UpdateAsync(player.UserId .. "-cash", function(oldValue)
return player.leaderstats.Cash.Value
end)
end
if DataStoreSafeCall(updateAsync) then
print('Saved players data')
end
-- locate the player's position in the update queue then remove them
local playerQueuePosition = table.find(updateAsyncQueue, player)
table.remove(updateAsyncQueue, playerQueuePosition)
end)
-- This prevents the server from closing until all UpdateAsync requests in the queue have either been processed or expired
-- Game::BindToClose should almost always be called in the global scope, not within a function or event
game:BindToClose(function()
repeat
-- I chose to use a RunService yield versus the global "wait()" function here
-- "wait()" is a task scheduler based function which can cause unexpected behavior
RunService.Stepped:Wait()
until #updateAsyncQueue == 0
end)
I tested this code in studio and it works. I will soon be releasing a DataStore module which will greatly simplify tasks like this, hopefully by the end of the summer.
for some reason it’s still not working for me. I have double checked that I have api services and everything. I guess there may be something wrong with my roblox studio… I guess i’ll restart the studio.
You need to make sure you’re incrementing leaderstats.Cash.Value via a server Script. If you’re trying to set the leaderstats.Cash value via the studio pane, you’ll have to make sure to switch to server mode first by pressing this button
Here’s the full rbxl file I used to test. It includes a proximity prompt with a server Script gibcash.rbxl (31.1 KB)
Hi! Correct me if I am wrong, but in this example, UpdateAsync() has the same result as SetAsync()? That is, you do not use the old value anywhere; instead, you set (return) the new value. Are there any advantages or other reasons why you prefer to use UpdateAsync() in this case?
You’re correct in saying that they would both do essentially the same thing here, and that in this specific case it actually wouldn’t make a difference whether you used SetAsync or UpdateAsync. However it’s good practice to favor UpdateAsync over SetAsync because you have less risk of over-writing data if multiple servers are attempting to write to the same key at once
Oh, I see! Do you have any idea how this collision will be resolved when > 1 server is trying to UpdateAsync()? And how is it different from SetAsync()? That is probably a tricky question.
I’m not sure as of right now but I’ll be sure to ping you when I find out, as I’m overdue to test that among other things. Ideally it would go in order of which request was created first, but there’s also the possibility of the order being the fastest to reach the DataStore server
I know SetAsync, in this regard, is more of a race than a queue