Save your player data with ProfileService! (DataStore Module)

The only main issue I have with ProfileService is the fact that if you teleport a lot of players from a different server to another after releasing, it usually will Session Lock and make the data load in about a minute instead of around 2-3 seconds (probably due to the overwhelming about of players saving). What I did to fix this was to edit the module and create a custom listener for the ProfileStore called “:ListenToReleaseAndSave” which would make the script wait for it to both Release and Save, and then teleport the player, and when I did this, data would load in 2-3 seconds (as opposed to about a minute)

I suggest to implement a function that allows you to listen when something gets saved as well as if it got released or not because as of right now you’re not able to tell if something saved recently or not. (I actually originally thought that :ListenToRelease would fire once it finishes releasing and saving but it only fires once it gets released)

EDIT: I’m a bit late on editing this, but this has been solved!

2 Likes

[03/30/2021] Great performance improvements for multi-place games utilizing teleports. Time to update again, lol. Big thanks for @Rawblocky’s report - it lead me to making interesting discoveries about ProfileService.

tldr: ProfileService loads slightly faster and does less developer console spam. Go update.

Github commit 1f83c0c

NEW METHOD - Profile:ListenToHopReady():

If you’ve been having problems with ProfileService after universe teleports, this method should make a huge difference:

Added a custom write queue:

ProfileService was relying too heavily on the UpdateAsync queue and, to my ignorance, it was not the safest thing to do either - apparently queued UpdateAsync calls are not guaranteed to be executed in the
order they were sent in. This shouldn’t have been a huge deal in most ProfileService using games because games would rarely grow a huge queue of UpdateAsync calls.

After you update, ProfileService will now use its own UpdateAsync queue system which will increase stability and will make ProfileService throw a lot less queue warnings! LETS GOOO!

Universe teleport testing place:

I’ve been doing some intense testing to make these improvements - you can check how ProfileService performs during teleports with this place file:

UniverseProfileTest.rbxl (49.9 KB)

You’ll need to publish this project to the primary place of a game (universe) and copy the UniverseProfileTest script to a second place within the same game. You’ll also need to change
these values to your place id’s:
image

Optionally, you can setup external analytics in LogHttp - it was useful for figuring out when the server experienced a BindToClose timeout:
image

12 Likes

Oh… Thanks for letting me know, I believed this was a datastore beta problem. I’m passing that on to a engineer to let them know it’s a overall DataStore problem.

1 Like

I put my data manager script in the server script service. I noticed it doesn’t work if it’s a module script. Why is that?

-- ProfileTemplate table is what empty profiles will default to.
-- Updating the template will not include missing template values
--   in existing player profiles!
local ProfileTemplate = {
    Cash = 0,
    Items = {},
    LogInTimes = 0,
}

----- Loaded Modules -----

local ProfileService = require(game.ServerScriptService.ProfileService)

----- Private Variables -----

local Players = game:GetService("Players")

local GameProfileStore = ProfileService.GetProfileStore(
    "PlayerData",
    ProfileTemplate
)

local Profiles = {} -- [player] = profile

----- Private Functions -----

local function GiveCash(profile, amount)
    -- If "Cash" was not defined in the ProfileTemplate at game launch,
    --   you will have to perform the following:
    if profile.Data.Cash == nil then
        profile.Data.Cash = 0
    end
    -- Increment the "Cash" value:
    profile.Data.Cash = profile.Data.Cash + amount
end

local function DoSomethingWithALoadedProfile(player, profile)
    profile.Data.LogInTimes = profile.Data.LogInTimes + 1
    print(player.Name .. " has logged in " .. tostring(profile.Data.LogInTimes)
        .. " time" .. ((profile.Data.LogInTimes > 1) and "s" or ""))
    GiveCash(profile, 100)
    print(player.Name .. " owns " .. tostring(profile.Data.Cash) .. " now!")
end

local function PlayerAdded(player)
    local profile = GameProfileStore:LoadProfileAsync(
        "Player_" .. player.UserId,
        "ForceLoad"
    )
    if profile ~= nil then
        profile:Reconcile() -- Fill in missing variables from ProfileTemplate (optional)
        profile:ListenToRelease(function()
            Profiles[player] = nil
            -- The profile could've been loaded on another Roblox server:
            player:Kick()
        end)
        if player:IsDescendantOf(Players) == true then
            Profiles[player] = profile
            -- A profile has been successfully loaded:
            DoSomethingWithALoadedProfile(player, profile)
        else
            -- Player left before the profile loaded:
            profile:Release()
        end
    else
        -- The profile couldn't be loaded possibly due to other
        --   Roblox servers trying to load this profile at the same time:
        player:Kick() 
    end
end

----- Initialize -----

-- In case Players have joined the server earlier than this script ran:
for _, player in ipairs(Players:GetPlayers()) do
    coroutine.wrap(PlayerAdded)(player)
end

----- Connections -----

Players.PlayerAdded:Connect(PlayerAdded)

Players.PlayerRemoving:Connect(function(player)
    local profile = Profiles[player]
    if profile ~= nil then
        profile:Release()
    end
end)
1 Like

modules only run when they have been required for the first time, but your script doesn’t appear to return anything anyways (modules must return a table with all of its publicly accessible functions and publicly accessible values), so it might as well be a server script

How should I set up profile service for client replication with ReplicaService?

Your post was deleted. Please help.

I’m pretty sure there’s an example in the ReplicaService page.

I know that but how would I go about making a profile and replicate that to the client. The example just shows how to set up a class token. Could you help by showing me an example?

Can anyone show me an example of how to use GlobalUpdates? The API docs confuse me :sweat_smile:

Shout out to okeanskiy and his amazing video that is explaining how it works.
Watch it here: Global Updates: ProfileService Tutorial Part 2 (Roblox Studio) - YouTube

1 Like

umm Im kinda of a noob so i do not know much about how to deal with my problem, so i have the data saving fine for the most part but for some reason one of the data key thingies gets deleted or something and turns into nil for some reason. but its just one of the data thingies(im basically saying that if there was levels coins and exp to save, exp for some reason just gets nil SOMETIMES for some reason.) that turns into nil. The other ones like level and coins never did turn into nil. i do not know why it happenned, i set the script up with the video of okeanskiy. I hope you can help me.

local ServerScriptService = game:GetService("ServerScriptService")
local RS = game:GetService("ReplicatedStorage")

local ProfileService = require(RS.ProfileService)

local UiColorTable = {{1,1,134},{10,124,134},{12,255,247},{26,255,129},{23,135,6},{167,255,2}
,{255,255,3},{255,158,1},{255,74,14},{255,0,0},{255,6,72},{255,6,160},{96,0,103}}

local profileStore = ProfileService.GetProfileStore(
	"Player",{
		UiColorTheme = UiColorTable[math.random(1,13)];
		Sectoriat = 0;
		Level = 1;
		Exp = 0;
	}
)

local Profiles = {}

local function onPlayerAdded(player)
	local profile = profileStore:LoadProfileAsync(
		"Player_" .. player.UserId,
		"ForceLoad"
	)
	if profile then
		profile:ListenToRelease(function()
			Profiles[player] = nil
			player:Kick()
		end)
		
		if player:IsDescendantOf(PlayerService)then
			Profiles[player] = profile
		else
			profile:Release()
		end
	else
		player:Kick()
	end
end

local function onPlayerRemoved(player)
	local profile = Profiles[player]
	
	if profile then
		profile:Release()
	end
end

PlayerService.PlayerAdded:Connect(onPlayerAdded)
PlayerService.PlayerRemoving:Connect(onPlayerRemoved)

local DataManager = {}

function DataManager:Get(player)
	local profile = Profiles[player]
	
	if profile then
		return profile.Data
	end
end

function DataManager:GetProfileStore()
	return profileStore
end

return DataManager```

Note: Sectoriat is basically coins.

What about it isn’t working? Also use ``` on the top and below code.

Well as i stated, for some reason exp just keeps on turning into nil while the others have no problem, btw i have never changed any of the values to nil in my scripts so im kinda scared like if this happened to me a few times already, what would happen when i release my game. People would lose their exp and worst part is since it turns into nil i can’t do anything with exp since it causes a lot of errors like some parts of the scripts would just stop. Thats why i thought i would ask it here.

Did you add the Exp key into the profile template after originally testing it? If so, you’ll need to call :Reconcile() upon finding a profile to ensure that they have the most updated template.

Well then, i will try to write reconcile function , if i can’t get it to work hopefully you guys can help me.

I just want to point out that this method of saving player data has probably saved me from potential major headache, since working with datastores is a very delicate and tedious process that every new developer on the Roblox platform will eventually have to tackle with.

If done incorrectly, you could accidentally shoot yourself on the foot in the future. Having to deal with hundreds if not thousands of player lodging complaints at you on why their data is suddenly gone while having to rework on your hard-worked but to failed datastore system is a situation I and anyone else wouldn’t want to be in.

In this case, thank you very much for creating this ProfileService. As an inexperienced developer I had underestimated just how much this Service could save me not just time but also health.

7 Likes

I’m putting my player inventory data in an inventory folder. But this loop isn’t working and I don’t know what to do… please help

	for i, v in ipairs(profile.Data.Inventory) do
		local InvItem = Instance.new("IntValue")
		InvItem.Value = v
		InvItem.Name = i
		InvItem.Parent = Inventory
	end

What is the ‘Inventory’ and try to use pairs not ipairs for the loop.

Ok inventory is a folder in player