How can I access ProfileService from two different scripts?

I use ProfileService for my game, World War Tycoon. 99% of the time, it works extremely well, but about 1% of the time, there is an issue where a player joins the game, their stats do not load whatsoever, and this error is thrown:

After looking through the ProfileService thread, there were a few people who had a similar issue, like below

In my usage of ProfileService, I have ProfileService initialized + first called to get a player’s stat saves. One of the stats then refers to a ModuleScript, to see if the player is eligible for daily rewards based on their rank in the game, based on the last time they played, which is also saved into ProfileService.

Leaderboard code snippet:

local ProfileService = require(game.ServerScriptService.profileService.PlayerDataHandler)
local getCash = require(game:GetService("ServerScriptService").stats.getCash)
game.Players.PlayerAdded:Connect(function(Player)
	ProfileService:playerAdded(Player)
	local stats = createStat("IntValue",Player,"leaderstats",0)
	local cash = createStat("IntValue",stats,"Cash",getCash.get(Player,true),Player)
---- rest of code here
end)

ModuleScript Code Snippet:

local ProfileService = require(game.ServerScriptService.profileService.PlayerDataHandler)
function getCash.get(plr:Player,init,gamepass)
	local lastPlay = ProfileService:Get(plr,"lastPlayed")
	if os.time() - lastPlay >= dailyRewardsTimer then
		print(tostring(plr).."'s played exactly a day ago!")
	end	
end

So, technically, ProfileService is accessed by two different scripts, one a regular server script, and one a ModuleScript that is called by that same server script a little later. It seems like this has a very minor chance of error, but I want there to be next to 0% error, especially as the game scales up.

How would I fix this issue?

3 Likes

Just handle all of the ProfileService functions in that one ModuleScript.

1 Like

All the scripts call to the central PlayerDataHandler script (which is also a ModuleScript), and use all the functions there, like Get, Update, etc.

2 Likes

Yeah, so like I said, just handle all of the ProfileService functions in that one ModuleScript.

Example if you need one:

Leaderboard code:

local getCash = require(game:GetService("ServerScriptService").stats.getCash)
game.Players.PlayerAdded:Connect(function(Player)
	getCash.playerAdded(Player)
	local stats = createStat("IntValue",Player,"leaderstats",0)
	local cash = createStat("IntValue",stats,"Cash",getCash.get(Player,true),Player)
	---- rest of code here
end)

ModuleScript:

local ProfileService = require(game.ServerScriptService.profileService.PlayerDataHandler)
function getCash.get(plr:Player,init,gamepass)
	local lastPlay = ProfileService:Get(plr,"lastPlayed")
	if os.time() - lastPlay >= dailyRewardsTimer then
		print(tostring(plr).."'s played exactly a day ago!")
	end	
end

function getCash.playerAdded(plr:Player)
	ProfileService:playerAdded(plr)
end
3 Likes

ah, I see what you mean now. I will give it a shot!

1 Like

Moving it to all one central ModuleScript has seemed to have fixed this issue. Thank you! I will be marking your answer as the solution

2 Likes

Coming back to this. The issue is even more extremely rare, but it still occurs…

Granted, this was the first time in almost a month I encountered this.

Are there any other scripts that access ProfileService? Could you also show me the code that loads the profile?

There are two scripts, one in ReplicatedStorage and one in ServerStorage. The ReplicatedStorage script is supposed to be the “connector” that all the other scripts that reference ProfileService pull info from.

ReplicatedStorage script:

local connect = {}
local ProfileService = require(game.ServerScriptService.profileService.PlayerDataHandler)

function connect:Init(Player)
	ProfileService:playerAdded(Player)
end

function connect:Update(player,statName,newVal)
	ProfileService:Update(player,statName,function(currVal)
		if newVal > currVal or statName == "Cash" then
			return newVal
		else
			return currVal
		end	
	end)
end

function connect:Get(Player,statName)
	local data = ProfileService:Get(Player,statName)
	return data
end

function connect:Leaving(Player)
	ProfileService:Leaving(Player)
end

function connect:PlayerAdded(Player)
	ProfileService:playerAdded(Player)
end

return connect

All scripts pull from that one, and that script pulls from the script that loads the profile

As for the code that loads the profile, it’s pretty much the same exact code from the main ProfileService thread, but here’s the loader, PlayerDataHandler:

-- Not sure why spacing is weird in this code?
local PlayerDataHandler = {}
--local logger = require(game.ServerScriptService.stats.otherLogs)

local dataTemplate = {
	Cash = 0,
	Kills = 0,
	Deaths = 0,
	Rebirths = 0,
	lastPlayed = 0,
	singlePlayerBase = {},
	twoPlayerBase = {},
}

local ProfileService = require(game.ServerScriptService.profileService.ProfileService)
local Players = game:GetService("Players")

local ProfileStore = ProfileService.GetProfileStore(
	"PlayerProfile",
	dataTemplate
)

local Profiles = {}

function PlayerDataHandler:playerAdded(player)
	local profile = ProfileStore:LoadProfileAsync("Player_"..player.UserId)
	
	if profile then
		profile:AddUserId(player.UserId)
		profile:Reconcile()
		
		profile:ListenToRelease(function()
			Profiles[player] = nil
			
			player:Kick("To protect your data, you were kicked from the game.")
		end)
		
		if not player:IsDescendantOf(Players) then
			profile:Release()
		else
			Profiles[player] = profile
		end
	else
		player:Kick("There was an error loading your data, and for your protection, you were kicked from the game. Please try again!")
		--logger.dataFail(player,"Data did not load correctly upon joining")
	end
end

function PlayerDataHandler:Leaving(player)
	if Profiles[player] then
		Profiles[player]:Release()
	end
end

function PlayerDataHandler:Init()
	game.Players.PlayerRemoving:Connect(function(player)
		if Profiles[player] then
			Profiles[player]:Release()
		end
	end)
end

local function getProfile(player)
	assert(Profiles[player], string.format("Profile does not exist for %s", player.UserId))
	
	return Profiles[player]
end

-- Getter/Setter methods
function PlayerDataHandler:Get(player, key)
	local profile = getProfile(player)
	assert(profile.Data[key], string.format("Data does not exist for key: %s", key))
	
	return profile.Data[key]
end

function PlayerDataHandler:Set(player, key, value)
	print(tostring(player),tostring(key),tostring(value))
	local profile = getProfile(player)
	assert(profile.Data[key], string.format("Data does not exist for key: %s", key))
	
	assert(type(profile.Data[key]) == type(value))
	
	print("saved data successfully for "..key.." at value "..value)
	
	profile.Data[key] = value
end

function PlayerDataHandler:Update(player, key, callback)
	local profile = getProfile(player)
	
	local oldData = self:Get(player, key)
	local newData = callback(oldData)
	
	self:Set(player, key, newData)
end

return PlayerDataHandler

In addition, when I centralized the script that gets pulled from, I forgot to remove

local ProfileService = require(game.ServerScriptService.profileService.PlayerDataHandler)

from a script. It never does anything with that line of code, but I assume it could cause problems.

1 Like

Yeah, this is most likely the problem.

Wait a day or so, and check your error reports for a ProfileService error. The reason this bug is happening is most likely due to a race condition, where one module would load quicker than another.

Also, merry Christmas!

1 Like

Guess I will have to wait a few days and see if anyone reports it again - it was very rare to begin with however.

Merry Christmas to you too!

1 Like

There’s a section called Error Report on your game’s analytics page. You can check for errors that occur, which is probably more reliable than waiting for people to report it.

1 Like

Just checked. I removed that line of code from the game very late last night, but who knows how many servers have updated.

That being said, here is the frequency of the error within the last day:

And here is the amount of players we have gotten over the same(ish) time period:

As you can see, it continues to be of very low frequency, oftentimes near zero, but it still seems to happen… occasionally. Some of the errors associated with it, are multiple associated to the same player, especially when the player keeps leaving and rejoining. A couple of times of this and going to different servers fixes it. I do not know if old servers have something to do with this.

That being said, if we look at the most recent error report for this issue, it was 4 incidents out of 1079 plays - a .003% occurrence rate.

This is the only code where you’re ever requiring ProfileService right? Make sure you don’t have any other scripts also loading the profile.

There is nothing besides that centralized ModuleScript requiring ProfileService now.

Still seems like the issue is occurring, albeit at a rate of far less than 1%.

Based on the error message seen in the console seen in my initial post, how could I setup ProfileService to just kick players who have that error toggled?

Go to whatever line that error occurs and you’ll see how the error handling works.

1 Like

Profiles get “released” when players leave.




image

I am thinking of just throwing a “kick” statement in there that tells the player to try a different server or wait a few minutes, but that’s not really an ideal solution