Hi! I create a PlayerData folder within each Player, which contains various subsets, and within those subsets, there are further subsets depending on the type of data being stored, such as Inventory, Loadout or Settings. Some folders (usually the last child) have attributes assigned to them.
Upon the PlayerRemoving event, I serialize the entire PlayerData folder, including all its descendants, into a table and save it to DataStore. However I can’t reference the descendants during the PlayerRemoving event.
One approach I considered was saving the data into a table using custom read/write functions, but this would require a significant amount of recoding.
Is there a way to reference or replicate the PlayerData folder upon the PlayerRemoving event? Alternatively, would a change in the process itself be more optimal? I’d appreciate any advice on improving this workflow.
Is there any convincing reason as to why these destroyed instances need to have their descendants parented directly to nil in the first place? An unreferenced instance tree outside of the datamodel, is surely still perfectly eligible for GC. It seems as though we could have a variant of destroy that still treats these niche memory leakage concerns, without introducing unnecessary conflicts with the old player/character removal behavior, which only set the parent of the root instance to nil.
Hi, Why not just Clone() your PlayerData folder then reference the values in the cloned instance? It seems to work in my tests. Looks like it could be a good way to deal with this if you have everything in a Player.PlayerData folder (unlike myself) .
i don’t see how this is an issue, keep a table with updates that reflect whenever the backpack changes then save that, don’t do it when the player leaves… that’s how you get duplication glitches
Different developers code differently, not to mention how simple games used to be. Making the setting default to be Enabled is not the issue, but sunsetting it completely.
Also this same argument could be used for why this implementation was added in the first place, since you could possibly code the destruction of the player by yourself and clear all cache.
The backpack was used as an example, since it’s the most used type of code that would normally have this issue, but you can also refer to Values that are unique to a player, Folders with values that are used as (although poor) saving systems, you cannot simply say that the whole platform should adhere a change just because it can be coded in a different way.
Talking about duping, until a year ago (not sure now) you could enter two different servers with two different instances of the Roblox Player, making any protection utterly useless since the same account could be at different servers at the same time. Obviously this could be fixed with code, but relied completely on the developer themselves.
Welp I’ve tried this method before, but it doesn’t work because all the parents of PlayerData and its descendants are set to nil when the PlayerRemoving event triggers. This means that if I attempt to Clone() the PlayerData folder at that point, I can’t access its descendants.
That said, I can Clone() the PlayerData folder before the PlayerRemoving event. However, how would I detect when the player is about to leave in order to handle this properly?
Are you sure? It seems to work for me when I tested it.
local players = game:GetService("Players")
local function playerAdded(player)
local playerData = Instance.new("Folder")
playerData.Name = "PlayerData"
playerData.Parent = player
local cash = Instance.new("IntValue")
cash.Name = "Cash"
cash.Value = 50
cash.Parent = playerData
local cash = Instance.new("IntValue")
cash.Name = "Gems"
cash.Value = 100
cash.Parent = playerData
end
local function playerRemoving(player)
local clonedPlayerData = player.PlayerData:Clone()
task.wait(1) -- to allow parents of the original values to be set to nil by the new behavior
print(clonedPlayerData.Cash, clonedPlayerData.Cash.Value)
print(clonedPlayerData.Gems, clonedPlayerData.Gems.Value)
end
players.PlayerAdded:Connect(playerAdded)
players.PlayerRemoving:Connect(playerRemoving)
game:BindToClose(function() -- to allow player removing function to run
task.wait(2)
end)
To anyone currently wondering, the best alternative way to save data with these new changes would be to parent every piece of data that you want to save under a folder in another instance or service. Recommendations for such would be ReplicatedStorage (so that the client can access data too) and the ServerStorage (to ensure your data is save there).
When the player is removed, you can then first call for a save of all the data within the folder and than afterwards destroy the folder.
I was hoping my game was immune to this change, I tested today and it turns out my datastore script won’t work the way its coded. I’m testing it now. I don’t save much data, but the workaround that I’m using that seems to be working is to just create local variables for all of the data items before calling any async functions.
Its confusing to me what is meant by " This task is deferred and occurs after bound event connections are invoked, including PlayerRemoving ."
What does deferred mean? From my testing you don’t get much time to access the player before its gone. It seems any async call will finish after the player is gone(so if you have more than one async call to save data, the subsequent reference to the player’s children will throw an error)
If cycle through 4 or 5 values that I save in the player and put those into a table (or individual variables would probably work also), then use those variables to make my database async calls then it seems to work. (I guess its deferred long enough to save the values to variables, but not long enough to make more than one async call).
Somebody let me know if i’m doing this wrong or can explain what deferred means, how much time does this give you to access the instance.
abstract pseudo code with Enabled before fix and after ‘fix’:
So I reckon if we have it Enabled we don’t have to do any garbage collection of events connected to Player or Character instances when they leave or character is removed?
Why this update? You could’ve at least set it to disabled for us to make some changes on our script. After that, we can just enabled it. But no, it literally wiped out all of my player’s data. My datastore fires a save function whenever an item is added or destroyed from their inventory. Because of this, the script save function fires after all the items from their inventories being removed or destroyed which ended up saving an empty inventory. Damage has already been done and all of my players are not happy.
Can you explain more? I’m using the DataStore Editor and I don’t see that setting where I could revert the entire datastore rather than doing one by one on each player.
Apologies on the confusion earlier. I mixed up PlayerRemoving with AncenstryChanged. The initial issue was related to AncenstryChanged hence the inability to clone PlayerData upon the event signal fired. Thank you for the reassurance regarding PlayerRemoving. I’ve applied that but ultimately decided to go with DevFofo’s suggestion on handling player’s data entirely within ReplicatedStorage instead.
As DevFofo mentioned, if anyone has alternative methods, please share them—I’d love to find out if there areany more efficient ways to handle this
The issue was that making ‘Enabled’ the Default option negatively affected more experiences than we anticipated, where the experience code was not prepared for destroyed Character or Player, but did not select ‘Disabled’ mode.
If the ‘Enabled’ option doesn’t work for you today, consider switching it to ‘Disabled’ until you have time to adjust to changes instead of leaving the ‘Default’.
We are also in the process of developing a different transition option which will provide most of the benefit, but will keep the Character/Player connected to the children (but will destroy connections).
We will post an update once we select the best path to continue.
Awesome to hear it, although we are all very heated about it, as long we are indeed being heard and given fair options, no real damage should be caused.