Save your player data with ProfileService! (DataStore Module)

No the server only closes if theres no players left. What if theres 11 players left after this one player leaves? I guess I’ll manually save

In my PlayerRemoving function such as deleting current game state caches related to that player and other various functions. I understand that :Kick() fires PlayerRemoving so listening to releases are safe, but I heard that there are times where PlayerRemoving won’t fire despite the player leaving and therefore important functions won’t fire. Is there anything in ProfileService that could cause this? I asked this a few months back:

https://devforum.roblox.com/t/does-shutting-down-a-server-cause-all-the-players-in-that-server-to-fire-the-players-removing-event/315082

Just a concern on mine, having players leave the game and not firing PlayerRemoving could cause some serious bugs.

Hey,
main cause of this is Game Shutdown (mainly in studio) and I don’t know if ProfileService is listening to :BingToClose() automatically but you can try to look for it or implement it yourself.

1 Like

Found another bug (happened after 1.4 Million visits)

ServerScriptService.Modules.ProfileService:747: attempt to index nil with ‘ActiveSession’ ServerScriptService.Modules.ProfileService, line 747
ServerScriptService.Modules.ProfileService, line 538
ServerScriptService.Modules.ProfileService

ProfileService does BindToClose for you, and does it pretty well, you shouldn’t have to worry.

For more advanced use:

If you wanna do something after a profile is released completely, let’s say have a soft shutdown scheme on your game while using ProfileService, then you can check out :ListenToServerHopReady which fires after a profile is actually fully released.

In that scenario, have your own BindToClose and loop through all profiles and add a ListenToServerHopReady handler which teleports the player into another server.

@MarmDev

Can ProfileService be used to save data that is not attached to a player? Such as a server system

quoting from the original post,

  • Made for ambitious projects - ProfileService is a profile object abstraction detached from the Player instance - this allows the developer to create profiles for entities other than players, such as: group-owned houses, savable multiplayer game instances, etc.

yes

1 Like

Hey loleris, I have a question. I’m running into a “Resolved profile corruption” warning that wipes out the players data. I figured out from looking at your code that the it’s because I needlessly encode my players saved data to JSON string before saving it to the data store.

It looks like I need to edit your code to add a JSON decoding step if the datastore returns just a string. I’m going to edit the script around line 652 to add a (type()== string ) case. Please let me know if I’m about to make a big mistake.

This library is such a great contribution to the community. Thanks so much!

Ideally you wouldn’t have to fork ProfileService for changes so you could easily update the module with future updates. You could do a double query on two coroutines for your old data and new profiles for different DataStore keys whenever a player joins, wait until both queries finish (at basically no time penalty compared to loading just the new profile) and then merge the data if it wasn’t merged before.


local result1, result2
local is_finished1, is_finished2 = false, false

-- 2 asynchronous tasks:

coroutine.wrap(function()
  -- Do something that yields
  -- Don’t forget to wrap basic DataStore calls in pcall here
  result1 = 1
  is_finished1 = true
end)()

coroutine.wrap(function()
  -- Do something that yields
  -- Don’t forget to wrap basic DataStore calls in pcall here
  result2 = 1
  is_finished2 = true
end)()

-- Waiting until all tasks finish:

while is_finished1 == false or is_finished2 == false do
  RunService.Heartbeat:Wait()
end
4 Likes

I built off your suggestion, and deployed my save data refactor to my live game. No one has reported any problems yet. Thanks so much loleris!

2 Likes

Can you use this module in a class? or is that not required? Can I call it multiple times?
Thank you!

From the troubleshooting section:

Failure to prevent these data types may result in silent data loss, silent errors, fatal errors and overall failure to save data.

Would it make sense / be possible to report these as warnings? In my case, I was inadvertently trying to use non-sequential number keys rather than string keys for one of my tables and the result was that the Profile stored these as numbers but the datastore converted them to strings.

Does :Reconcile() remove old data? For example if my data table is this:

local DataToSave = {
Coins = 0,
Diamonds = 5,
}

But then I decide I don’t want to use Diamonds as a currency anymore so I remove it from the table like so:

local DataToSave = {
Coins = 0,
}

Will Diamonds get removed from the players data upon reconciling? Or will it just stop it from getting added to new player data?

It won’t get removed. You would need to have your own function for that.

local function ReconcileWithExtraKeyRemoval(Profile)
    Profile:Reconcile()
    
    local ProfileData = Profile.Data
    for key in pairs(ProfileData) do
        if DataTemplate[key] == nil then
            ProfileData[key] = nil
        end
    end
end

You can’t remove indexes from nested tables (sub tables if you don’t know what that means) because that means that you would not be able to have things like weapons, for example:

Profile.Data.Weapons = {
    "Sword",
    "Axe",
    "Hammer"
}

These values don’t exist in the data template,
– so looking for extra “sub tables” would remove things like these.

1 Like

Hey! With the recent datastore v2 announcements, do you plan on updating this module utilizing the new features roblox provides?

11 Likes

I’m also wondering the same, this seems like quite a big update and hopefully it can open up doors to more features or at least optimise the current features.

Seeing UserIds get tagged for GDPR requests would be really useful. Especially if roblox decide to automate GDPR requests provided the right data is tagged.

Is there anyway to use :GetOrderedDataStore in ProfileService? I’ve made an entire game framework with ProfileService, but ran into a problem when making global leaderboards. Has anyone found a way to accomplish this? I’m genuinely not sure, been here for about 2 hours trying to script it. But can’t find anyway, all help appreciated. Thanks!

[08/14/2021]

I grinded this 10 hours non-stop. Even I think that I deserve some praise. Anyways here’s the moment we’ve been waiting for…

Github commit 565ddd8

ProfileService - DataStore v2 edition

(But backwards-compatible, lol)


Full DataStore v2 support. You’re not going to miss a thing.

I’ve written all the explanations in the docs, so there’s not much I can really say here… Epic DSv2 speedrun?

I’d say the safety of this module is around 99% - I did extensive artificial testing plus all the original tests that helped ProfileService stay 99.99% germ-proof through every other update. I will react to error reports pretty fast.


Summary:

New ProfileStore methods:

New Profile members:

New Profile methods:


As always, you may update the module through github or the Roblox library.

168 Likes

bro a datastore update isnt worth ur eyesight

Anyways, amazing module, nice work!

8 Likes