Accessing player data from other scripts

Player data takes time to load, and sometimes it’s not ready whenever scripts need to access it, I’m looking for a good way to solve this

An idea I have

A signal that fires whenever a player is fully loaded, and other scripts won’t do anything with the player before this signal

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Signal = require(ReplicatedStorage.Signal)
local loadedSignal = Signal.new()

local PlayerService = {}
PlayerService.LoadedSignal = loadedSignal

function PlayerService.SetupData(player) -- called once when player joins
	local playerInfo = {}
	wait(2) -- yielding placeholder
	loadedSignal:Fire(player, playerInfo)
end

return PlayerService
local PlayerService

local PetService = {}

function PetService.SetupPlayer(player, playerInfo)
	
end

PlayerService.LoadedSignal:Connect(PetService.SetupPlayer)

return PetService

I’m sure there is a better way to this, but can’t think of one

also thinking about this but for now my solutions bets in Data:Get(Player: any) basically gets player data and returns it

I do that a lot, but has some flaws, like what happens if the data isn’t already loaded when that function is called, so you would load and return the data, but what if the function is called by another script while the data is still loading

since I am using ProfileService I use

local Profiles = {}

function something:Get(Player)
    -- ReadonlyArray.includes()
    if not Profiles[Player.Name] then
        return Profiles[Player.Name]
    end
end

why would you get it when it is not loaded just load it once you load it in the event

Don’t know what you mean, I wasn’t talking about both the example and using :Get()

I was refering to something like

local playerDatas = {}

local PlayerService = {}

function PlayerService.LoadData(player)
   local playerData = {}
   wait(2) -- imagine this as the datastore yielding
   playerDatas[player] = playerData 
   return playerData 
end

function PlayerService.GetData(player)
   return playerDatas[player] or PlayerService.LoadData(player)
end

return PlayerService

if two different scripts call PlayerService.GetData() at similar times it would end up loading the data twice

to fix this in the past I’ve done

local playerDatas = {}

local PlayerService = {}

function PlayerService.LoadData(player)
   local playerData = {}
   playerData.Loaded = false
   playerData.LoadedSignal = Signal.new()
   playerDatas[player] = playerData

   
   wait(2) -- imagine this as the datastore yielding
   playerData.Loaded = true
   playerData.LoadedSignal:Fire()
   return playerData 
end

function PlayerService.GetData(player)
   local playerData = playerDatas[player] or PlayerService.LoadData(player)

   if not playerData.Loaded then
      playerData.LoadedSignal:Wait()
   end

   return playerData
end

return PlayerService

I’m not sure if this is a good way to do it though

how many scripts does get the data from the module?

Any script that would require player data, so potentially infinite as any scripts dealing with the player would likely need to get the data, but the bug could happen with any count if it’s above only one

to me checking the player’s name in a map or table is proof that it already exists

that isn’t the issue, the yielding is the issue, if there was no yielding that would work fine, but that isn’t the case with datastores

yielding can be addressed with pcalls unless its a custom datastore wrapper for instance here is something I wrote

-- Make a class since we don't want new stuff
local PlayerService
do
	PlayerService = {}
	function PlayerService:constructor()
		self.PlayerDatas = {}
	end
	function PlayerService:LoadData(Player)
		-- Player Data
		local PlayerData = {}
		-- Checks if the Player Data is already there
		local _0 = self.PlayerDatas
		local _1 = Player.Name
		local _2 = _0[_1]
		if not (_2 ~= 0 and _2 == _2 and _2 ~= "" and _2) then
			local _3 = self.PlayerDatas
			local _4 = Player.Name
			local _5 = PlayerData
			-- ▼ Map.set ▼
			_3[_4] = _5
			-- ▲ Map.set ▲
		end
		-- returns it
		local _3 = self.PlayerDatas
		local _4 = Player.Name
		return _3[_4]
	end
	function PlayerService:GetData(Player)
		-- Checks if it exists and returns it
		local _0 = self.PlayerDatas
		local _1 = Player.Name
		local _2 = _0[_1]
		if not (_2 ~= 0 and _2 == _2 and _2 ~= "" and _2) then
			return nil
		else
			local _3 = self.PlayerDatas
			local _4 = Player.Name
			return _3[_4]
		end
	end
end
return {
	PlayerService = PlayerService,
}

You aren’t understanding what I’m saying at all

Player Joins, and all scripts call this function at the same time on player added

Script A calls :GetData(player) – data doesn’t exist, load data
Script B calls :GetData(player) – data hasn’t finished loaded from Script A, so Script B loads data too
Script C calls :GetData(player) – data hasn’t loaded yet from either A or B so it loads again

as you can see data was loaded 3 times because of yielding

I’ll likely just go with my first idea as I don’t have to worry about yielding at all

What I do is whenever I load the data of a player, I add the player into a data loading queue and when the data is finally loaded, I remove it out of the queue. On my DataService.Client:Get() method, I check if the data is currently loading and yield until it is finally loaded.

function DataService.Load(dataStore, player)
    DataLoadingQueue[player.Name] = {DataLoaded = RemoteSignal.new()}
    local profile = dataStore:LoadProfileAsync(("%s_%s"):format("Player_", player.UserId), "ForceLoad")
end
function DataService.Client:Get(player)
    -- Player's data is in loading queue
    if (DataLoadingQueue[player.Name] ~= nil) then
        DataLoadingQueue[player.Name].DataLoaded:Wait()
    end

    return HttpService:JSONEncode(self.Server:Get(player))
end
1 Like