Save your player data with ProfileService! (DataStore Module)

Can someone actually show something made with this?

2 Likes

My experience makes extensive use of both ProfileService and ReplicaService. I haven’t had a ton of concurrent users yet, but ProfileService has made working with datastores easy and painless. Highly recommended!

I quoted the specific part of using it for group oriented things which is what I’d like to see someone make an example of.

Im having a failrate (over 60s loadtime) for at a minimum 1 in 10,000 playerjoins.
Im using

Profile Manager:



function ProfileManager:Get(player)
	local profile = Profiles[player]

	if profile then
		return profile.Data
	else
		return nil
	end
end

default implementation provided by loleris
ProfileService is a version taken from 3 months ago (maybe the Sep. 10 update from the model, but I just got the Sep. 30 update from Github, I’ll see how it goes)

This issue is ongoing. This is the point of failure (reading data, and retrying until not nil)


How can I set up readings to diagnose the point of failure in ProfileService?
I have analytics.

1 Like


explain this and idk what to do

Roblox currently maintains DataStore key versions up to 30 days

2 Likes

Yo, i wanna make a Auction House system but is ProfileService and Update features is a good thing to use (i don’t know if the 6 sec cooldown of set methods on datastore would be great) or should i use MemoryStoreService ?

Yeah uh that’s not really like a flex, their examples suck.

Wait, is the profile needed to be locked in this stage? If you’re just showing like coins that won’t change or anything you might be able to use :ViewProfileAsync (if that exists which I remember it doing)

My imagination says your game is a game on which there’s a little lobby which lets you choose a server to join and might show things like stats even if those can’t be modified in that lobby. The worse thing that could happen by using ViewProfile would be players being temporality that they don’t have something even though they do, the profile just didn’t release / save until that data was in there.

I have a little issue @loleris, I’m trying to use GlobalUpdates:ChangeActiveUpdate() but with this function it requires the update_id of the ActiveUpdate and to get the ID you have to use GlobalUpdates:GetActiveUpdates() which also requires the player to be online. The only reason I’m using ChangeActiveUpdate() is to modify an offline user ActiveUpdate. Is there an alternative method to getting the ActiveUpdate ID’s?

Inside the ProfileStore:GlobalUpdateProfileAsync() function that you pass as an argument both GlobalUpdates:ChangeActiveUpdate() and GlobalUpdates:GetActiveUpdates() can be called freely regardless of whether the profile is active in a server or not - These methods are available for the GlobalUpdates object passed as the only argument to the callback function
image

1 Like

Correct. However, some stats still do change, like daily rewards and such.

Hi, I was wondering if you can do:

require(5331689994)

Will this work?

Hello, I am using profile service and using the sample code provided, but apparently when calling profile:Release() the method is missing.

So u have to make sure that you call :Rlease() is on player in a table data like this

Local PlayersProfile = {} – this hold player datas profile

So shen player leavd
PlayersProfile[plr].Profile:Release()

Hey, I’m loving this module so far, except that I’m having some issues with template implementation.

I want players to have randomised values in some of their data keys when they first join (random races, character size, etc etc) and this does not seem to work properly with this module as the template is defined once with no option to redefine it. This means that the math.random and Random:NextNumber functions run once at runtime, then keep those values for the rest of the server uptime. Therefore, all new players in the same server will have the same data to start instead of different random values.

Does anyone know of a workaround/fix to this?

After the profile loads check for a flag (e.g. if Profile.Data.IsFirstLoad ~= true then).
You can react to the absence of the flag and make your one time alterations and
finally set the flag (e.g. Profile.Data.IsFirstLoad = true)

1 Like

This is implemented now, but I’m running into another issue: I get a warning on game start and nothing works. I’ve never tried ProfileService before, so this could just be a silly mistake that I made.

Here’s the warning:

Warning
[ProfileService]: DataStore API error [Store:"PlayerData";Key:"Player_664884959"] - "104: Cannot store Dictionary in data store. Data stores can only accept valid UTF-8 characters."

And here’s my code for handling ProfileService (I am using Knit, and storing the template in a separate module. Most of the code is pulled directly from the ProfileService documentation.)

Main Module
-- Get Knit, services and modules
local knit = require(game.ReplicatedStorage.Packages.Knit)
local ProfileService = require(script.Parent.Parent.Modules.ProfileService)
local DataTemplate = require(script.Parent.Parent.Modules.DataTemplate)
local Players = game:GetService("Players")

-- Internal variables
local ProfileStore = ProfileService.GetProfileStore(
	"PlayerData",
	DataTemplate:GetTemplate()
)

local Profiles = {}

-- Make the service
local DataService = knit.CreateService {
	Name = "DataService",
	Client = {}
}

-- Random value function
local function RandomValueSet(profile : any)
	-- On first join, make a new template for random values to set
	if profile.Data.FirstLoad == true then
		profile.Data.FirstLoad = false
		local template = DataTemplate:GetTemplate()
		profile.Data.SkinTone = template.SkinTone
		profile.Data.Name = template.Name
		profile.Data.Face = template.Face
	end
end

-- Data loading function
local function PlayerAdded(player : Player)
	local profile = ProfileStore:LoadProfileAsync("Player_" .. player.UserId)
	if profile ~= nil then
		profile:AddUserId(player.UserId) -- GDPR compliance
		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! Set random values if first load.
			RandomValueSet(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("Unable to load data. Try rejoining :)") 
	end
end

-- Data retrieval function
function DataService:GetValue(player : Player, key : string)
	if Profiles[player] then
		if Profiles[player].Data[key] then
			return Profiles[player].Data[key]
		else
			warn("Player " .. player.Name .. " does not have data for value " .. key .. "!")
		end
	else
		warn("Player " .. player.Name .. " has no profile!")
	end
end

-- Data overwriting function
function DataService:SetValue(player : Player, key : string, value : any)
	if Profiles[player] then
		Profiles[player].Data[key] = value
	else
		warn("Player " .. player.Name .. " has no profile!")
	end
end

-- On init, connect functions and retrieve data for existing players
function DataService:KnitInit()
	for i, v in pairs(Players:GetPlayers()) do
		task.spawn(PlayerAdded, v)
	end
	
	Players.PlayerAdded:Connect(PlayerAdded)
	
	Players.PlayerRemoving:Connect(function(player)
		local profile = Profiles[player]
		if profile ~= nil then
			profile:Release()
		end
	end)
end

return DataService
Template Module
local knit = require(game.ReplicatedStorage.Packages.Knit)
local DataTemplate = {}

-- Internal variables
local storage = require(knit.ServerModules.Storage)
local nametable = storage.NameDataBase
local skintonetable = storage.SkinToneDataBase
local facetable = storage.FaceDataBase

-- Retrieval function
function DataTemplate:GetTemplate()
	return {
		FirstLoad = true,
		Tissues = 0,
		SkinTone = skintonetable[math.random(1, 16)],
		Name = nametable[math.random(1, #nametable)],
		Face = facetable[math.random(1, #facetable)],
		Curse = "None",
		Clothing = "Junguard",
		SpawnLocation = "Cavern",
		DataValues = {}
	}
end

return DataTemplate
1 Like

Roblox logs do not provide good enough reasons why something can’t be saved in the DataStore, but you can probably easily figure this out with the help of troubleshooting page of ProfileService wiki - Saving data which Roblox cannot serialize.

1 Like

ListenToRelease should always be used and ListenToHopReady just as an addition, since its more for teleports