Save your player data with ProfileService! (DataStore Module)

Ahhh yes, thats when I updated I left the old one in there, wow will remove that right now!

1 Like

Hey, I know this doesn’t have anything to do with the module really but I was wondering how did you setup the website for your documentation and stuff. I see the same thing being done with other modules and I was wondering how did they do it.

1 Like

Github pages and Material for MkDocs

2 Likes

Is the kicking of the player necessary on ListenToRelease? If you use ListenToHopReady to use teleport service, the player gets kicked immediately when you release the profile. I just want to make sure that removing or delaying the kick on ListenToRelease doesn’t compromise security versus dupe or what not.

Kicking is for when the profile is acessed externally - if the profile is active before you call release in the same session you can create an exception and not kick the player.

In my own universe game I’ll probably teleport players to the hub place when their profile is ForceLoaded / Stolen externally.

Should there be any concerns with adjusting UpdateAsync to instead use another place to save data at through HttpService such as Firestore?

I hadn’t directly looked into how frequently ProfileService updates the saved data that could risk hitting a limit but I do like to continue using ProfileService for its session locking and keeping the latest version safely updated at all times. I don’t see ProfileService using GetRequestBudgetForRequestType to throttle itself so I would assume it’s very light in how much it submits an update.

ProfileService needs enough auto-save frequency to comply with external ForceLoad requests (Until they try to steal the profile) and notify the session about the release immediately after the last save.

I don’t see a problem with replacing the UpdateAsync calls with custom getters.

1 Like

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