Thank you for correcting me. You went a little overboard there with all the minor mistakes though. I was a bit rushed to create that code. Also, I was trying to show him both ways, with a table and how he was using the value objects. I was explaining in his current code that I had replied to that he needed to create a value object for that code to work and that if he wanted to use values, that was how he could utilize it. One of the reasons I like to use a value object to say that the data is loaded is because it can replicate to the client and server that the data is loaded. There is no significant deficiencies in using a value object to show that the data is loaded. However, I do like to use tables to organize the playerâs data, like I said in my previous posts. But, like I said, tables are probably the most efficient and easy to organize.
I donât think it was overboard. You would be surprised to come to understand how these minor changes have a relatively major impact on your gameâs code and developing good practice in the future, whether for Roblox or other programming languages.
This is fairly ambiguous in your code though, because the table is never actually used and it looks more dependent on the ValueObjects. Thatâs why I raised this.
The code sample I supplied eliminates ValueObjects (well, only really at the DataReady part) and gives you a quick, easy and efficient way to access information.
A table is far more efficient for this use case. You can also access it via a local variable or exposed function without depending on the existence of an instance.
As for what you said about ValueObjects not supposedly causing deficiencies, yes it does. You have to rely on several factors regarding this object, including where itâs located in the hierarchy and you need to ensure it exists in the location you need.
Furthermore, with the above in mind, keep in mind that ValueObjects are instances. Instances incur a cost for all the process they invoke or participate in initially, as well as any done afterward. A table scraps the expense of an instance entirely.
Iâm just curious - why does this part of my code not work? The saving doesnât work, it keeps printing âFailed to save stats for skilpus3000â
while game.Players:FindFirstChild(player.Name) ~= nil do
-- Saving:
local playerStatsToSave = {rescues.Value, tokens.Value}
local success = attemptRequest(function()
return stats:SetAsync(key, playerStatsToSave)
end, 0) -- Doesn't retry.
if success then
print('Successfully saved stats for', player.Name);
else
print('Failed to save stats for', player.Name);
end
wait(10)
end
wrap the autosaving with a new thread, this isnt the most optimized way, but it sure does work.
remember to wrap the :SetAsync() with a pcall, or xpcall.
you can do major optimizations to this script, like changing from making new threads into other ways.
like:
while wait(60*5) do
for _,v in pairs(game:GetService("Players"):GetChildren()) do
local playerStatsToSave = {v.leaderstats.Points.Value, v.leaderstats.Tokens.Value}
stats:SetAsync("player-" .. v.UserId, playerStatsToSave) -- remember to wrap this in a pcall or xpcall.
end
end
this is a possible solution, not the best solution but it would work.
What is your key
variable here?
This means that a truth-y value is not being returned by the method attemptRequest. Keep in mind that SetAsync has no return value. Could you post the code behind attemptRequest?
The purpose of wrapping the autosave in a new thread is to effectively make it a background process that wonât interfere with the execution of other threads. Keep in mind that nothing after a while loop runs until the loop is broken either by meeting itâs condition or explicit usage of the break operator.
You didnât include a pcall despite saying that it should be wrapped. You are correct though. Iâm not sure why xpcall is applicable here though (I donât know what it is either), a regular pcall is fine for this case.
Lastly, like I told Faultsified, please use the condition of a while loop properly. Donât implicitly pass it by using wait. Thatâs bad code. Donât rely on wait to return a truth-y value.
I can provide the whole Script for easier understanding, but this is your version I implemented
local DataStoreService = game:GetService("DataStoreService")
local stats = DataStoreService:GetDataStore("Stats")
local function attemptRequest(request, retries)
local retries = retries or 0;
local success, data = pcall(function()
return request();
end);
if (success) then
return data;
elseif (not success and retries ~= 0) then
return attemptRequest(request, retries - 1);
else
error('Unable to get requested data!');
end
end
game.Players.PlayerAdded:Connect(function(player)
-- Get saves:
local key = "player-" .. player.userId
local savedPlayerStats = attemptRequest(function()
return stats:GetAsync(key);
end, 5); -- Retries 5 times maximum.
local leaderstatsFolder = Instance.new("Folder",player)
leaderstatsFolder.Name = "leaderstats"
local tokens, points;
if (savedPlayerStats) then -- Player has been here before:
tokens = Instance.new("NumberValue",leaderstatsFolder)
tokens.Name = "Tokens"
points= Instance.new("NumberValue",leaderstatsFolder)
points.Name = "Points"
points.Value = savedPlayerStats[1]
tokens.Value = savedPlayerStats[2]
else -- New player:
tokens = Instance.new("NumberValue",leaderstatsFolder)
tokens.Name = "Tokens"
points= Instance.new("NumberValue",leaderstatsFolder)
points.Name = "Points"
points.Value = 0
tokens.Value = 0
end
while game.Players:FindFirstChild(player.Name) ~= nil do
-- Saving:
local playerStatsToSave = {rescues.Value, tokens.Value}
local success = attemptRequest(function()
return stats:SetAsync(key, playerStatsToSave)
end, 0) -- Doesn't retry.
if success then
print('Successfully saved stats for', player.Name);
else
print('Failed to save stats for', player.Name);
end
wait(10)
end
end)
game.Players.PlayerRemoving:Connect(function(player)
-- MAIN STATS:
local points= player:WaitForChild("leaderstats"):FindFirstChild('Points')
local tokens = player:WaitForChild("leaderstats"):FindFirstChild('Tokens')
-- Saving:
if (points and tokens) then
local key = "player-" .. player.userId
local playerStatsToSave = {points.Value, tokens.Value}
local success, err = attemptRequest(function()
return stats:SetAsync(key, playerStatsToSave)
end, 2) -- Retries twice.
if success then
print('Successfully saved stats for', player.Name, 'upon leaving.');
else
warn('Failed to save stats for', player.Name, 'upon leaving.')
end
end
end)
SetAsync doesnât return anything. You should return both the success and data gotten from attemptRequest, then use the first return (success).
A xpcall, is basically pcall, but with the functionallity to get the error message and format it, like this:
local suc, res = xpcall(function()
x("Hello World!") -- this will error
end, function(err)
error("The example errored! Full Error: " .. err) -- would print something like attempted to index x, a nil value
end)
I didnt think about the idiom, I just slapped that code together fast.
most code I make, I have the while-wait idiom as a rule of thumb, I never rely on wait() in loops, Iâve stumbled across multiple times that it breaks.
How exactly would that look like? I am all new to pcalls and I donât have too much experience with DataStore, Im still trying to wrap my mind around how this works, lol
Sorry for the late response! It seems I had completely forgotten about this.
What you can do is send back the return values from the pcall that wraps the request function.
local function attemptRequest(request, retries)
local retries = retries or 0
local completed = false
local response
repeat
retries = retries - 1
local success, reply = pcall(request)
if success then
completed = success
response = reply
else
wait(1) -- Give time between a request if it doesn't succeed
end
until completed or retries <= 0
return completed, response
end
With this new function, you can use your new return values within your code to handle what should happen when a requested attempt fails.
For example, in your PlayerAdded function, your script may change to something like this:
local Players = game:GetService("Players") -- Canonical way to get a service
Players.PlayerAdded:Connect(function (player)
-- Reminder that userId is deprecated in favour of UserId
local key = string.format("player-%d", player.UserId) -- Being fancy?
local success, savedPlayerStats = attemptRequest(function ()
return stats:GetAsync(key)
end, 5) -- Would recommend a lower value to preserve your request budget
if success then
-- Request was successful
if savedPlayerStats then
-- Player has stats saved
else
-- A non-truth-y value was returned (no stats)
end
end
-- Your other code
end)
Thanks alot! Sorry for the late response, Ive been really sick But thanks!