Save your player data with ProfileService! (DataStore Module)

For mock profiles all I have to do is

local RunService = game:GetService("RunService")
local GameProfileStore = ProfileService.GetProfileStore("PlayerData", ProfileTemplate)
if RunService:IsStudio() == true then
  GameProfileStore = GameProfileStore.Mock
end

right?
or is there another step I have to take to make it so the profile is mock so when testing in studio the data doesn’t save

1 Like

Is there a method that will wait till the profile.Data is loaded? For example, sometimes client loads fast and the profile.Data hasn’t been created yet so Client stuff do not work sometimes.

I used ReplicaService and waited for the replica of the class for the data to be created.

My code looks like this on the client:

-- wait for replicas to be created, then create player data
ReplicaController.ReplicaOfClassCreated(DATA_CLASS_TOKEN, function(replica)
	-- get player object
	local replicaPlayer = replica.Tags.Player
	
	-- create and store data object
	local dataObject = Data.new(replicaPlayer, replica)
	data[replicaPlayer] = dataObject

	-- handle memory cleanup upon deletion
	replica:AddCleanupTask(function()
		dataObject:Destroy()
		data[replicaPlayer] = nil
	end)
	
	-- handle and clear async binds
	local binds = asyncBinds[replicaPlayer]
	
	if binds then
		-- call functions
		for _, func in ipairs(binds) do
			func()
		end
		
		-- clear binds
		asyncBinds[replicaPlayer] = nil
	end
end)

Note that Data is a custom Class that handles a specific player’s data, and loads the data by supplying a Player and a Replica into the constructor.

The important thing here, is where I say “handle and clear async binds”. The reason this is important is because I have a function on the client which is:

--[[
	Method: waitForDataAsync
	
	Waits for data to exist, then calls the function, will throw if the supplied player does not exist
	
	@param {function} func: function to call when data exists, typically something containing .getValue() or .bindToChange()
	@param {Player? or String?} otherPlayer: name or object of another player to get the data from, nil will default to the local player
]]
function DataCache.waitForDataAsync(func, otherPlayer) --> throws if supplied player does not exist
	-- get player object
	otherPlayer = otherPlayer or Player

	if type(otherPlayer) == "string" then
		otherPlayer = Players[otherPlayer]
	end
	
	-- player does not exist: throw an error
	assert(otherPlayer ~= nil, "Supplied player does not exist")
	
	-- asynchronously wait for the player data to exist
	-- if it exists,
	xpcall(function(...)
		-- attempt to error
		getDataObject(...)
		
		-- no error means we can continue
		func()
	end, function(err)
		-- error means data does not exist, and we need to wait for it to exist, then call the function
		-- this is done by storing the function into a table, of which all the functions in it will be called when the data is created
		local binds = asyncBinds[otherPlayer]
		
		if not binds then
			binds = {}
			asyncBinds[otherPlayer] = binds
		end
		
		binds[#binds+1] = func
	end, otherPlayer)
end

Basically, it will call the supplied function once the supplied player’s data loads. It will call the supplied function if the data already exists. I think you can make something like this given the code samples I have shown, but if you need any help let me know.

3 Likes

" Updating the template will not include missing template values in existing player profiles! ".
How would I be able to make updates to already existing player profiles when I want to add for example a new currency?

Hey,
use profile:Reconcile() in your PlayerAdded function.

1 Like

Thank you man you saved my life

Hey, I was wondering how should I go about making a global profile.
Idea is to make Global Stat Tracker.

Hey watch video from @okeanskiy about Global Updates or make topic in #help-and-feedback:scripting-support.

1 Like

Hey! So I’ve been making a inventory system with ProfileService, and essentially I have the empty table called: “Inventory” under data, all of the data saving works perfectly. But I can’t figure out how to add values to the table. For example:

local testTable = {
-- Already called variables.
Blue = 1
Red = 36
}

-- Both of these already exist.
print(testTable.Blue)
print(testTable.Red)

-- This doesn't exist, but when its called it'll create a value.
testTable.Yellow = 12

print(testTable.Yellow)

But! Whenever I try this with profileservice, like this:

local DataManager = require(game.ReplicatedStorage.DataManager)
local Items = game.ReplicatedStorage.Items

local function onItemAdded(obj)
	obj.Main.ProximityPrompt.Triggered:Connect(function(plr)
		local Data = DataManager:Get(plr)
		if Data then
			print(Data)
			Data.Inventory[obj.Name] += 1
		end
	end)
end

I end up getting an error:

IGNORE “Wheat”, that was used for testing earlier.

Anyway, the main goal is to be able to pickup a box in the workspace that has a proximity prompt. When the proximity prompt is triggered, you’ll be given the time. But for some reason, I’m not able to call an entirely new value?

1 Like

Can you also show your DataManager Module Script?

Yes, the data manager works currently.

local Players = game:GetService("Players")

local ProfileService = require(game.ReplicatedStorage.ProfileService)

local ProfileStore = ProfileService.GetProfileStore(
	"Player",
	{
		Cash = 0;
		LoginTimes = 0;
		Avatar = {};
		Inventory = {};
	}
)
		
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(Players) then
			Profiles[player] = profile
		else
			profile:Release()
		end
	else
		player:Kick()
	end
end

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

Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(onPlayerRemoving)

local DataManager = {}

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

I just noticed something, “avatar” doesn’t appear whenever I call for the data?

You are missing profile:Reconcile()

Okay, I think I figured it out. Is there any way I can wipe all of the stuff inside of my profile? Its full of a lot of random stuff when I was adding the reconcile

Well yeah there is built in function for that or you can use sleitnick DataStore Editor that works also fine :).

Yes true! I completely forgot about that plugin. Just a quick question though, where do I find the name of the datastore? Nevermind, I’m dumb. FOUND IT!

DataStore Name

Player Key Name

2 Likes

Wow, this is really helpful. I thought this was way to confusing and advanced for me, but it is really easy to use after you get going. To anybody thinking about adding this, DO IT. If you think it is too hard check out the youtube tutorials, they help a lot.

After sometime in game (10-30 mins) I get kicked from the game. Is there a reason for this? The only time I ever kick is in my ProfileService script.
Screen Shot 2021-06-04 at 11.26.05 AM

This happened to me too, seems to be patched now,

1 Like

The only reason that would happen to you if you kick the player whenever the profile:ListenToRelease call back was called, which is when another server tries to force load a profile. In your case, you may probably joined the game again - having 2 sessions - which ProfileService swears to protect you from.

2 Likes