So I tried to play my game and I’m stuck on waiting for last session to save how I handle my data and seems like the data from when I got afk kicked didn’t save but also I didn’t receive any errors…
I use warn() to make sure it gets logged when any problems happen else it would at least error and show up in logs too but no logs of it…
Here’s the code I wrote years ago and I still use that saves the data of each player.
function saveData(plr,data)
if not plr or not data then return;end;
local key=plr.UserId;local name=plr.Name;
local S;local reA=0;local R
repeat
S,R=pcall(function()statsData:SetAsync(key,data)end)
if not S then reA+=1;warn(`@{name}({key}) stats failed to save (Retrying {reA})`)end;
task.wait(5)until S;
if reA>0 then print(`@{name}({key}) stats saved`);end;
statsPlrs[plr.UserId]=nil;
end
1 Like
seems like error logs is useless when trying to find stats fails.
1 Like
This code is difficult to digest. I recommend giving Roblox’s official style guide a read, as well as watching this video on objective positive readability practices. When implementing data persistence, be sure to handle the event of server shutdowns. Server shutdowns occur when the last client disconnects from a game server, when an authorized developer manually shuts down all servers, or without notice due to vendor failure. This can be handled through game:BindToClose
1 Like
Is this a bot responding with random video links and that is not related!?
Is it possible that you’re trying to save “nil” or “false” after a bug with data corrupting after the player leaves?
Are you using game:BindToClose()?
They’re not random. They’re resourceful. You definitely need to clean up your code, it’s awful, lol
it looks like chatgpt, but it could be human? anyway i do agree it is quite hard to read, you mentioned no save happens when you are kicked, make sure you are using bind to close or some other detection to handle when the player abruptly disconnects
The player’s parent has nothing to do with this guard clause; removing it will only harm the robustness of the function
of course how else would i save data on shutdowns…
Nowhere in my reply did I deny your claim about the player’s parent being nil at this point in time. What I am refuting is its relevance to the guard clause. The player is given to the function, not its parent. Its parent is not involved in the check, nor does it influence the player’s truthiness
that’s how you cause errors with removing that line and then no ones data gets saved then.
Yup it appears I misread the code, my apolocheese. 
1 Like
There’s not enough context to solve your problem. Please send the rest of your script
1 Like
local saveThreads={}
local function onPlayerRemoving(plr)
local co=coroutine.create(save);coroutine.resume(co,plr);table.insert(saveThreads,co);
end
Players.PlayerRemoving:Connect(onPlayerRemoving);
game:BindToClose(function()
for _,plr in Players:GetPlayers()do onPlayerRemoving(plr)end;
for _,v in next,saveThreads do repeat task.wait()until coroutine.status(v)=='dead'end;print('saving threads finished.')
end)
save() formats the data and then fires saveData.
I’ve always used that else data wont save on shutdowns.
its not. there’s no reason to make it all chunky n everything would help if roblox even had better functions lile .map & .reduce to handle tables so much better lol and the variables are used correctly.
Your one-liners are objectively destructive to readability. What those resources would have you would not make your code “chunky”, it would decompress your spaghetti code. On another note, I can’t see any functional issues with the code you’ve given us; it’s still not enough. Please send your entire script
Players.PlayerMembershipChanged:Connect(function(plr)
local stats=statsPlrs[plr.UserId];
if stats.playtimePremiumST then
stats.playtimePremium+=(tick()-stats.playtimePremiumST);
stats.playtimePremiumST=nil;
end;
stats.playtimePremiumST=plr.MembershipType==Enum.MembershipType.Premium and tick()or nil;
end)
-- premium playtime stuff never had problems with past games too.
function saveData(plr,data)
if not plr or not data then return;end;
local key=plr.UserId;local name=plr.Name;
local S,R;local reA=0;
repeat
S,R=pcall(function()statsData:SetAsync(key,data)end);
if not S then reA+=1;warn(`@{name}({key}) stats({data}) failed to save (Retrying {reA})`)end;
task.wait(5)
until S; -- repeats save every 5 seconds until success
if reA>0 then warn(`@{name}({key}) stats saved after retry.`);end;
statsPlrs[plr.UserId]=nil;
end
-- main saving SetAsync function
local alreadySaving={}
function save(plr)
if table.find(alreadySaving,plr)then return end;table.insert(alreadySaving,plr);
local data={}
local S,R=pcall(function()
local stats=statsPlrs[plr.UserId];
local lastPlaytime=tick()-stats.joinTick;
data.stats=stats.stats;
data.lastJoinTick=stats.joinTick;
data.playtime=stats.playtime+lastPlaytime;
if stats.playtimePremiumST then
data.playtimePremium=stats.playtimePremium+(tick()-stats.playtimePremiumST);
else
data.playtimePremium=stats.playtimePremium;
end
data.playing=false;data.saveTick=tick();
end)
if S then
print(plr,'saving stats',data);return saveData(plr,data);
else
warn(plr,'unable to save invalid stats',data,R);
end;
end
-- gonna remake this to less code either way
local saveThreads={}
local function onPlayerRemoving(plr)
local co=coroutine.create(save);coroutine.resume(co,plr);table.insert(saveThreads,co);
end
-- threads the saves so it doesn't wait for each player to save 1 by 1 else it will run out of time.
-- tho i was alone when it failed.
Players.PlayerRemoving:Connect(onPlayerRemoving); -- save player stats on leave
game:BindToClose(function()
for _,plr in Players:GetPlayers()do onPlayerRemoving(plr)end;
for _,v in next,saveThreads do repeat task.wait()until coroutine.status(v)=='dead'end;print('saving threads finished.')
end) -- save everyone stats on shutdown.
gonna remake this too so i dont have to put a bunch of pcalls for gui since it can error if gui keeps looping and player leaves but added pcall and will create its own gui function with pcalls atp