Why is my player data table nil when it should be printing the data?

Hello Developers,

Happy Friday! So I was watching a tutorial on Youtube about Profile Service + Data Replication. I was following along and writing the code exactly how the person in the video does it, however my code is not working.

the reason it isn’t working is because everytime I print a player’s data it’s printing nil, but I don’t know why. Can anyone help?

Init Script (ServerScriptService):

local ServerStorage = game:GetService("ServerStorage")
local SystemsFolder = ServerStorage:WaitForChild("Systems")

local DataService = require(SystemsFolder:WaitForChild("DataService"))
DataService:Init()

TestScript Script (ServerScriptService):


local DataService = require(game:GetService("ServerStorage"):WaitForChild("Systems"):WaitForChild("DataService"))

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local GetDataRemoteFunction = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("GetData")

GetDataRemoteFunction.OnServerInvoke = function(Player, value )
	local profile = DataService:GetPlayerProfile(Player, false)
	if profile then
		return profile.Data
	end
	return nil
end

DataService Module (ServerStorage>Systems):

 
local Players = game:GetService("Players")

local ServerStorage = game:GetService("ServerStorage")
local ServerModules = ServerStorage:WaitForChild("Modules")
local ProfileService = require(ServerModules:WaitForChild("ProfileService"))

local ProfileTemplate = {
	
	-- game values
	Cash = 0,
	Gems = 0,
	
	-- analytic
	LogInTimes = 0,
	TotalGameTime = 0,
}

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

local Profiles = {}
local JoinTimes = {}

local function FloorNumber(number, decimalPlaces)
	decimalPlaces = decimalPlaces or 2 -- this is saying that if decimalPlaces is not given, just make it "2"
	return math.floor(number * math.pow(10, decimalPlaces)) / math.pow(10, decimalPlaces)
end

local function OnPostProfileLoad(player, profile)
	profile.Data.LogInTimes += 1
	local totalLogins = profile.Data.LogInTimes
	print( string.format('%s has logged in %s time%s', player.Name, totalLogins, (totalLogins > 1) and 's' or '') )
	print( ' This player has played a total of ' ..profile.Data.TotalGameTime..' seconds before joining this server.' )
	--					  ^				   ^	   ^------------------------.
	--			  player time here    specific login number here		time or time(s) depending on the number of login times
	-- ex)
	-- SPOOK_EXE has logged in 1 time.
	-- SPOOK_EXE has logged in 2 times.
	JoinTimes[player] = tick()
end

local function PlayerAdded(player)
	local profile = GameProfileStore:LoadProfileAsync(tostring(player.UserId))
	if profile then
		profile:AddUserId(player.UserId) -- GDPR compliance
		profile:Reconcile() -- putting all missing template values into the active data ( optional )
		profile:ListenToRelease(function()
			Profiles[player] = nil
			-- The profile could've been loaded on another Roblox server:
			player:Kick('Profile has been loaded onto another Server.')
		end)
		if player:IsDescendantOf(Players) then
			OnPostProfileLoad(player, profile)
			Profiles[player] = profile
		else
			-- Player left before the profile loaded:
			profile:Release()
		end
	else
		player:Kick('Failed to Load Data : OnPlayerAdded_1')
	end
	
end

--Lets say you have a Admin Panel and you try to edit the player's data, it will kick them from the server

-- // Module // --
local Module = {}

function Module:GetPlayerProfile(Player, Yield)
	local profile = Profiles[Player]
	if Yield and not profile then
		repeat task.wait(0.1)
			profile = Profiles[Player]
		until profile or (not Player.Parent)
	end
end

function Module:Init()
	
	for _, player in ipairs(Players:GetPlayers()) do
		coroutine.wrap(PlayerAdded)(player)
	end
	Players.PlayerAdded:Connect(PlayerAdded)
	
	Players.PlayerRemoving:Connect(function(player)
		local profile = Profiles[player]
		if profile ~= nil then
			--add the duration they've played to the data
			local duration = tick() - JoinTimes[player]
			profile.Data.TotalGameTime += duration
			profile.Data.TotalGameTime = FloorNumber(profile.Data.TotalGameTime, 3)
			print( string.format('%s has played a total of %s seconds this session, totalling the total to %s seconds', player.Name, FloorNumber(duration), profile.Data.TotalGameTime))
			JoinTimes[player] = nil
			--release the profile so other games can load it
			profile:Release()
		end
	end)
end

return Module

GetData Local Script (StarterPlayer>StarterPlayerScripts):


local HTTPService = game:GetService("HttpService")

local Players = game:GetService("Players")
local LocalPlayer = Players.LocalPlayer

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local GetDataRemoteFunction = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("GetData")

local Interface = LocalPlayer.PlayerGui:WaitForChild('Interface')

local activeData = nil

coroutine.wrap(function()
	while task.wait(1) do
		activeData = GetDataRemoteFunction:InvokeServer()
		print(activeData)
		Interface.Label.Text = "Data: ".. (activeData and HTTPService:JSONEncode(activeData) or "No Data") 
	end
end)()

Additionall, here’s the place file:
ProfileService Data Service game.rbxl (67.7 KB)

Also, here’s the video that I was watching this off of: https://www.youtube.com/channel/UCEwgh-qSICxQ0dHpwBOCXIg

Video of my problem:

1 Like

This is one of the downsides in my opinion of using other people’s modules. I would recommend you get used to DataStoreService yourself.

If you really want to stick with using this foreign Module, I would recommend you check out their devforum. You might find your answer there, but again, if not, you should simply create your own DataStore modules.

Hope this is helpful in a way.

Backtrack. The client is receiving nil from the RemoteFunction invocation so check if the server is receiving anything. I added a print to the RemoteFunction’s OnServerInvoke and it is also getting nil, so we have to backtrack further to GetPlayerProfile. Now in this case, the profile does print.

So let’s analyse your flow:

Client invokes server → Server calls DataModule to get a player profile → DataModule should return a profile → RemoteFunction should return the data.

If you look at it in terms of returns only, then:

DataModule should return a profile to OnServerInvoke → OnServerInvoke should return a dictionary to the invoking client.

The client gets nothing, the server handling OnServerInvoke is also receiving nothing but DataModule can in fact see the profile! So what’s going on? Simple answer is that you forgot to return it to OnServerInvoke. Really helps to look at your logic flow to see where you might’ve missed something.

Take a look at your GetPlayerProfile function:

function Module:GetPlayerProfile(Player, Yield)
	local profile = Profiles[Player]
	print(profile)
	if Yield and not profile then
		repeat task.wait(0.1)
			profile = Profiles[Player]
		until profile or (not Player.Parent)
	end
end

There’s no return statement! Functions will give back nil if you don’t specify a return value, so GetPlayerProfile is returning nothing and so OnServerInvoke likewise has nothing to give back to the client. See where the problem is and what caused it?

Just add return profile at the bottom and you’re fine.

function Module:GetPlayerProfile(Player, Yield)
	local profile = Profiles[Player]
	if Yield and not profile then
		repeat task.wait(0.1)
			profile = Profiles[Player]
		until profile or (not Player.Parent)
	end
+	return profile
end

image

Much better.


@hans5915 When it comes to ProfileService, it’s main intention is to handle some of the technicals for you but you’re still mainly responsible for writing your own data layers. ProfileService is an exceptional module compared to other DataStore modules and is used by novice and advanced developers alike. I don’t think it’s very helpful to tell developers not to use modules and reinvent wheels on their own.

3 Likes

How many programmers use their own created DataStore modules, do you think? Do a lot of people do it? I heard it’s common to use open sourced modules for your own games.

Personally, I don’t use one, and I’m probably never going to use one. I think that everyone using the same open-source datastore module could introduce a few problems, for example, if an exploit is discovered for that module, all of the games that use it are vulnerable. But, you don’t need to make a custom system, I personally don’t see the point of using some large module to handle something that’s only a few lines long at times, but I think everyone should have atleast a bit of experience with datastores before using a module, so you can actually diagnose any issues.

And, again, this is my opinion, but, it feels better to me to know that I made that, and didn’t need to use any open-source resources to do so.

1 Like

This is incredible. Thank you for the simple explanation, and I appreciate you taking the time to help me. Thanks so much!

I certainly agree. I plan to delve into learning everything in Lua programming, I know in order to be successful with improvement and to be actually knowledgeable and to create full-scale games it would be imperative to know exactly what everything is doing, especially if I ever use an open sourced module. I see the problem you point out though, that’s good input. Thanks.

1 Like

This feels more or less like a non-point. In theory any vulnerability that comes about from a well written data module, such as ProfileService, places fault on the game’s programmer. A data package like ProfileService shouldn’t be required from client-sided environments, and so the module’s actual server-sided implementation becomes the topic of discussion. ProfileService uses DataStoreService, if an exploit was discovered it would be a bigger problem than just ProfileService module. I would agree with you if this wasn’t Roblox development or you were writing data to something external with HttpService.

This is a good point. In certain situations it really just isn’t needed. For example, our games track leaderboard stats on their own because we aren’t overly worried about session-locking the data (we use ProfileService and then a seperate backup key that isn’t tied to any module).

There is no shame in using open-sourced modules. That is why they exist. You do not have to write a data system from scratch for every game you create. If you see a module on the DevForum that suits your use-case and is open for widespread usage, then you should feel welcome to use it. Read about its benefits and why it might serve your purpose. A lot of modules increase accessibility for new scripters. ProfileService does this plentifully because you don’t have to worry about the odd cases of data failure outside of what is almost uncontrollable. No writing DataModel BindToClose or UpdateAsync individually. It can be a lot easier to understand than sitting down and learning the actual good mannerisms of data writing when you first start out. That’s just my opinion.

1 Like

Unless it’s something that I’m really unfamiliar with that I have tried before personally, I wouldn’t use other developers module.

There’s always a plus side to making your own scripts; You know what you did and if there would ever happen to come an error, you would be able to catch it and solve it.

Edit: It also saves on performance most of the times. I remember using PartCache, and seeing 2000 lines of Code. I managed to make my own “PartCache”, and all it took was 200 lines. So yeah, creating your own things might be able to save on performance too!

1 Like