I’m using a very similar template, it should work perfectly
What is the function/method to check if a player has no existing profiles before / first time their profile being made?
I don’t think there is any. But you can check profile.loadCount (something like that) or put a value in your template firstTime = true and when you load it you check it,if true then do first time code and then turn it to false
That gonna work nw charrrrrrrrrrrr
Hi!, i have a question about retrieve value from modulescript to serverscript where:
on module script:
local profilestore = {}
profilestore.Profiles = {}
function weapon.getattack(player: Player)
local profile = uarthlein.Profiles[player]
if not profile then return end
return profile.Data.WeaponAttackend
then in serverscript;
function Attack()
local player = players:GetPlayerFromCharacter(tool.Parent)
print(profilestore.WeaponAttack)
end
but it didnt return a weaponattack value
quick question does :IsActive()
reflect wether or not the player’s data is open on another server (or within the same server)
ProfileStore fails to properly deliver user data (in my use case).
My game’s data loading follows a very simple process: it fetches a profile, then applies said profile’s “Data” to a mirror Folder object with it’s own Values. If anything in this process throws an error, the folder will not be created, the data will not be set, written to, saved (so overwritten) and the player will not be able to access the game.
Despite this, ProfileStore sometimes loads the default template despite some Profiles having up to 400 previous sessions of fully populated data. This results in an unconditional data loss.
Other times a default template is loaded and the affected user rejoins the game. The proper, populated profile loads in, and remains loaded in. Other times it is again overwritten by the default template. This seems to vary on a per-server basis.
I have yet to discover reproduction steps to trigger this event.
I’ve tested the following scenarios:
- commented out
profile:EndSession()
andProfiles[plr] = nil
when exiting = data does not save, but loads on rejoin successfully. Nothing else happens. - commented out
profile:EndSession()
andProfiles[plr] = nil
when exiting, then rejoined the same server = ProfileStore errors that “[profile] is already loaded in this session”. Nothing else happens. - set “profile” to
nil
after fetching, then attempted to set ValueBase values =attempt to index nil with 'Data'
. Data successfully loads upon rejoin and after removingprofile = nil
. Nothing happens. - stored a numeric table with gaps in the profile = nothing happens.
- indexed a table with
true
= ProfileStore errors. Nothing happens.
I’ve gone through tens of pages of errors present in my game. I’ve also scrolled through many errors output by ProfileStore. There is not a single error that would otherwise be registered, such as “Incorrect data handling”, which would result in an overwrite or similar. Simply put, there are no errors, other than the odd “code 0: internal server error” or “[profile] is already loaded in this session”.
My Data is saved only when a valid Folder object exists. If it exists, it is guaranteed to be 100% the data loaded in from a ProfileStore profile. If anything throws an error in ProfileStore, or somewhere down the line setting data, the rest of the code will not compile, the “Loaded” value (check “Code & Functions”) will never change to “true”, and the Player will not be able to spawn in.
Yet players are able to spawn in successfully. This means there have been no errors or warnings output when loading the data from ProfileStore and setting it in my own code. From a simple logical observation, ProfileStore simply fails to deliver the proper populated data, which causes my code to (incorrectly) set the player’s profile data to the blank template. This data is then saved, which causes the aforementioned permanent data loss - because the folder has been loaded correctly.
The code I’ve used in my own system is almost identical to the example code given in this thread:
Code & Functions
local PROFILE_TEMPLATE = {
Money = 80,
Bounty = 0,
...
}
local PlayerStore = ProfileStore.New("PlayerData", PROFILE_TEMPLATE)
local Profiles: {
[player]: typeof(PlayerStore:StartSessionAsync()),
} = {}
local function PlayerAdded(player,folder)
-- Start a profile session for this player's data:
local profile = PlayerStore:StartSessionAsync("Player_" .. player.UserId, {
Cancel = function()
return player.Parent ~= game.Players
end,
})
-- Handling new profile session or failure to start it:
if profile ~= nil then
profile:AddUserId(player.UserId) -- GDPR compliance
profile:Reconcile() -- Fill in missing variables from PROFILE_TEMPLATE (optional)
profile.OnSessionEnd:Connect(function()
Profiles[player] = nil
player:Kick(`Data saved successfully! Please rejoin :)`)
end)
if player.Parent == game.Players then
Profiles[player] = profile
folder.Money.Value = profile.Data.Money
folder.Bounty.Value = profile.Data.Bounty
...
else
-- The player has left before the profile session started
profile:EndSession()
Profiles[player] = nil
end
else
-- This condition should only happen when the Roblox server is shutting down
player:Kick("Server shut down - data saved successfully!")
end
end
local function PlayerAdded2(plr)
local folder = rs.PlayerData["_t"]:Clone()
folder.Name = plr.Name
PlayerAdded(plr,folder)
-- parent folder if fully loaded, so if off chance bug, empty template isnt made and isnt saved
folder.Parent = rs.PlayerData
folder:WaitForChild("Loaded")
folder.Loaded.Value = true
end
local function DataSaveOnExit(plr)
local profile = Profiles[plr]
if profile ~= nil then
if rs.PlayerData:FindFirstChild(plr.Name) then
local folder = rs.PlayerData[plr.Name]
profile.Data.Money = folder.Money.Value
profile.Data.Bounty = folder.Bounty.Value
...
folder = nil
end
profile:EndSession()
Profiles[plr] = nil
end
end
Events
game.Players.PlayerAdded:Connect(function(plr)
PlayerAdded2(plr)
end)
game.Players.PlayerRemoving:Connect(function(plr)
DataSaveOnExit(plr)
end)
-- In case Players have joined the server earlier than this script ran:
for _, player in ipairs(game.Players:GetPlayers()) do
task.delay(0,function()
PlayerAdded2(player)
end)
end
TL;DR
After meticulous research and testing it appears that ProfileStore loads my default template, ignoring any previously saved data, and my code applies the blank data to the user when loading in, causing total data loss. Because of the way my data loading system is structured it’s highly improbable it’s due to user error, since if at any point the getting, setting or reading data errored an affected player would not be able to access the game, which in turn would not trigger data save. The data loss has already happened before any data was set.
How often did it happen? Did you find any particular steps after which your problem occured every time?
I’ve reviewed your code.
Problem #1:
I saw a line “folder = nil”, but never saw “folder:Destroy()”. If your code doesn’t destroy folders then it means it might be saving old player data on leave when the same player rejoins the same game session because of folder duplicates for the same player.
Problem #2:
“DataSaveOnExit()” should be bound to the “profile.OnLastSave” signal individually for every profile instead of PlayerLeaving - Your save on exit function may not succeed in scenarios such as server shutting down where ProfileStore will start ending session on it’s own and your changes to “profile.Data” will be too late.
I don’t recommend instances being the source of truth for your data, you already have a perfectly fine and dandy profile.Data
table you can update and pull data from directly, that doesn’t rely on finding children.
ontop of this players can change their username at any time which although being an uncommon scenario it isnt healthy to overlook
(which means rs.PlayerData[plr.Name]
would fail)
this is to say that profilestore doesn’t seem to be the problem in this code
Just a question, could you use a PackageLink/Package or similar so it can automatically update when you release an update?
TopBarPlus seems to do this so i’d appreciate it if ProfileStore can also automatically update
Why can be instances a problem here? As far as I know instances are safe as long as they are inside of ServerScriptService or ServerStorage. So loading all data from the table and storing this data in a Value on the Server wouldn’t be a problem in my opinion.
How often: happens to about 1 in roughly 100 players, about once a week. Additionally, some users can get wiped by simply rejoining the game while other users can get wiped by logging back on months later: case in point one of my players, whose last save file was populated a couple weeks ago, and in a single next save file - completely defaulted, which means it wasn’t an issue with server cached data.
As for repro steps, I did manage to find a 100% reproducible method. This was how the system used to work:
- A folder with the player’s name was created.
- It was parented to PlayerData.
- Data loading was initialized.
This would then trigger DataSaveOnExit, as it’s conditions are:
- a folder with the player’s name exists,
- it’s parented to PlayerData.
However, if something went wrong, e.g. an error was output, the data would not replicate to the folder - at which point the folder would still exist, as it’s own default template (mirroring ProfileStore), and would be saved to ProfileStore on DataSaveOnExit, so it was always saving the default template if the data loading process failed.
However however, I have since fixed this and shifted the data loading logic around to ensure this doesn’t happen:
- A folder with the player’s name is created.
- Data loading is initialized.
- It gets parented to PlayerData.
In this way when data loading happens before the folder is actually parented it ensures that any unloaded/improperly loaded data will never save, because the code does not compile, so the folder never gets parented, so it never triggers DataSaveOnExit (conditions not met: parented/exist). Which is what leads me to believe the fail point happens before any of my code runs, and the data is being loaded correctly, but the content of the data is invalid.
Problem 1:
My mistake! The function does remove the folder, I somehow deleted it while cross-posting to this thread. The function looks more like this:
local function DataSaveOnExit(plr)
local profile = Profiles[plr]
if profile ~= nil then
if rs.PlayerData:FindFirstChild(plr.Name) then
local folder = rs.PlayerData[plr.Name]
profile.Data.Money = folder.Money.Value
profile.Data.Bounty = folder.Bounty.Value
...
folder = nil
end
----------------------------- I deleted this on accident, but it is live in production
if rs.PlayerData:FindFirstChild(plr.Name) then
rs.PlayerData[plr.Name]:Destroy()
end
----------------------------- /end
profile:EndSession()
Profiles[plr] = nil
end
end
Although looking at it now, it definitely can be rewritten to just :Destroy()
the folder
instead of this condition check, so I will also implement that.
Problem 2:
I’ll implement the functionality you described, although I don’t think that would be the issue, since then it would be serving outdated data, and not a blank template. Additionally all the players that have reported this issue to me have denied experiencing this through an update/server shutdown/ROBLOX maintenance.
I’d like to reiterate the major issue here: the data loads in inconsistently or incorrectly. Players are:
- loading in empty template data after not having played the game long enough for all servers to restart,
- flip-flopping between their correct “old” data and incorrect, empty “current” data when rejoining if affected.
Assuming the flipping to the correct old data happens due to my possible faulty folder caching that means my server has correctly saved data, cached it locally, then an empty template was loaded through ProfileStore, which causes the flip flop effect - both server and client are confused between which folder they should pull data from. If the cached data did not exist, it would still result in data loss.
And in a game where players are consistently rejoining the same server or server hopping said folder caching would very quickly cause critical failures completely unrelated to ProfileStore, I would have noticed, and the caching issue would not exist (if it even does; refer to “Problem 1”).
I appreciate your response.
EDIT:
After some tinkering I’ve managed to create a 100% reproducible method which seems to do exactly what I’m describing, and it’s in ProfileStore:
Explanation?
--- on line 605:
if version ~= nil then
local success, error_message = pcall(function()
loaded_data, key_info = profile_store.data_store:GetVersionAsync(profile_key, version)
end)
if success == false and type(error_message) == "string" and string.find(error_message, "not valid") ~= nil then
warn(`[{script.Name}]: Passed version argument is not valid; Traceback:\n` .. debug.traceback())
end
else
---------------------- right here
loaded_data, key_info = profile_store.data_store:GetAsync(profile_key) -- version is not nil, but loaded_data is nil (GetAsync failed?)
end
loaded_data = transform_function(loaded_data) -- parses the nil loaded_data to line 507
----------------------/
Repro
-- On line 507:
local once = false -- very simple bool check
local function UpdateAsync(profile_store, profile_key, transform_params, is_user_mock, is_get_call, version) --> loaded_data, key_info
--transform_params = {
-- ExistingProfileHandle = function(latest_data),
-- MissingProfileHandle = function(latest_data),
-- EditProfile = function(lastest_data),
--}
local loaded_data, key_info
local next_in_queue = WaitInUpdateQueue(SessionToken(profile_store.Name, profile_key, is_user_mock))
local success = true
local success, error_message = pcall(function()
local transform_function = function(latest_data)
---------------------- ADDED IN THIS BLOCK
if not once then
once = true
latest_data = nil -- latest_data is nil
end
----------------------/
local missing_profile = false
local overwritten = false
local global_updates = {0, {}}
if latest_data == nil then
missing_profile = true -- this flag obviously gets set
...
Steps:
- playtest a game once, change some savable data then leave,
- update the module with the small code block,
- playtest the game again - any previous data will be disregarded and an empty template will load - leave the game,
- remove the code block,
- playtest again - the empty template will now again save and load properly.
And in my use case:
- ProfileStore loads the provided default template (empty),
- my code compiles without error,
- ProfileStore outputs no error or warning during this process.
I can’t accept edits to the module itself as a repro - A repro is a piece of code that would demonstrate bad behavior while using the module as is just like you were using the module as is.
There’s always a chance that ProfileStore has a problem just like there’s always a chance someone using ProfileStore has made a mistake (And I’ve followed through a lot of people initially thinking ProfileStore was the problem). However this is a free resource and I can’t put in the hours to review what everyone has written, so I can only accept either bug reproduction code (e.g a piece of code where it proves data did not load properly through using the ProfileStore API) or pull requests to ProfileStore source and explanation why those changes are proposed. I can also investigate more thorough explanations of observed bad behavior.
I can’t accept edits to the module itself as a repro - A repro is a piece of code that would demonstrate bad behavior while using the module as is just like you were using the module as is.
I can only accept either bug reproduction code (e.g a piece of code where it proves data did not load properly through using the ProfileStore API)
I can’t exactly snap my fingers and make ProfileStore fail to pull latest data on demand without altering the source code itself, because this happens in production, with several different mystery factors that I cannot reasonably replicate.
I can also investigate more thorough explanations of observed bad behavior.
Safe to assume there’s no possibility of this as I’m using the code provided in this thread down to a T, unless that code is also faulty.
I can only accept either bug reproduction code […] or pull requests to ProfileStore source and explanation why those changes are proposed
For sure, let me fix your code while you’re begging for donations and pocketing the entire profit from my (and other creators’!) collaborative effort. Your grift scheme is up there with Bethesda and having modders fix their games for them.
For anyone else experiencing this issue (hopefully not), I recommend you check for existing DataStore versions before assuming a lack of latest data means a lack of data in general. This fiasco could have been avoided had ProfileStore been cautiously checking for whether a single save version existed before assuming there is absolutely none and wiping entire profiles. DataStoreService has a handy ListVersionsAsync()
method which can return you both specific info on every saved version (even deleted ones) and the amount, which you can then use to determine whether previous save files exist or not.
For example, if ListVersionsAsync()
returns nil
, that means there is no previously saved data for the given user. If it returns one or several versions you can guarantee that player has previously played the game before, they have a save file to load (cough ProfileStore), and if your DataStore for whatever reason returns nil
on GetAsync()
you can simply roll back to the most recent version instead of creating a fresh save file.
It baffles me that every DataStore tutorial and resource I’ve seen is so careless about potential GetAsync()
issues. If GetAsync()
fails to deliver data it’s automatically assumed there is simply no data, so an empty save file is created, which is a complete disaster for any game that wants to take itself seriously.
Inb4 “GetAsync() can’t fail! It guarantees to deliver data, nil
or error!” HTTP is a finnicky thing and can absolutely be impacted by both technical and physical (real life) difficulties. It’s not magic.
ProfileStore uses UpdateAsync exclusively for normal operation with session locks. :GetAsync() is only used for ProfileStore:GetProfile() (Not present in basic usage example code).
There is no known common practice out there to check DataStore versions before it’s “safe to load data” and the need to go so far indicates something wrong.
If you’re unhappy with ProfileStore I encourage you to use ProfileService instead which has been successfully working in games with over 2.5B visits over the past 4 years with no breaking bugs found.
i haven’t looked into the new code, and I think he goes quite rough in the tone and accusation, but regarding xLawOut’s concern, usually we can get data roughly like this
local profile_data
local success, error_message = pcall(function() profile_data = datastore:GetAsync(profile_key) end)
if success and not profile_data then
it means we really has a new player
if not success then
it means we can check error_message
and we should try again to get the player data
if success and profile_data then
we get the player data, we may further check if the data is corrupted, etc before using it.
I’m using ProfileService still, the relevant code is more involved than I wrote above.
So i think I would also want to ask about the not success
case, whether the module would retry, or it would assume we have a new player thus overwritten it with brand new data?
Retrying after DataStore errors is one of the cases ProfileService/ProfileStore modules handle.
I noticed when I load data in tc and data ingame at the same time it doesn’t kick the one that loaded last (as in the one that joined first and was loaded before) like old profile service. Is there a new thing for that or is this how it is now? To make my question clearer the first one I join on loads data but the second one doesn’t load data or get kicked when trying to load it, instead it sits there and waits for my first one to leave/unload then loads on the second. Also does OnSessionEnd operate the same as ListenToRelease?
Yeah - that’s caused by a slower auto-save and MessagingService not working in studio. If data consistency is critical you should try using mock mode / making solutions with ProfileStore:GetAsync(), Profile:SetAsync() or ProfileStore:MessageAsync() for best version control when a session is live in-game.
ProfileStore would end the previous session after around 300 seconds which is the time it auto-saves and checks the session lock. ProfileService does this every 30 seconds and since it does not rely on MessagingService it can resolve session locks in studio as well.
In live game servers MessagingService will work properly for ProfileStore to handle session locks and the end result is up to x10 fewer DS calls than ProfileService.