Save your player data with ProfileService! (DataStore Module)

Is there a recommended way to tell the client when their data changes?

Currently, I do something like this with AeroGameFramework

-- SERVER
local profile = self:Get(player)

if (profile) then
    profile.Data.Cash += 50
end

-- Tell client their data changed
self:FireClient("DataUpdate", player, profile.Data)



-- CLIENT
local data = {}

DataService.DataUpdate:Connect(function(newData)
    data = newData or {}
end)
2 Likes

Can you not require the module on client? cuz it gave me an error saying BindToClose can only be called on the server. Stack Begin Script 'ReplicatedStorage.DataManager.ProfileService', Line 242

DataStoreService is a server-side only feature on Roblox, so ProfileService is server exclusive.

1 Like

This is probably one of the most unfair arguments I’ve seen going on in this thread. Bounding the data to the Player Instance doesn’t seem much a breaker, it’s what the entire module is dedicated for.

I’ve been using DataStore2 for a while and I’m debating whether it’s worth the transfer or not. People keep pointing out the 80 seconds hypothetical which I don’t think it terrible (at least because there’s nothing we can do about it).

I might test it out in a solo place for testing later on, but I remain unsure.

How would you make persistent servers or global leaderboards with DataStore2? I don’t want to use multiple systems to achieve variations of the same function.

2 Likes

There some problems I am facing where the ProfileService or my wrapper is yielding infinitely

wrapper-

local Rs = game:GetService("ReplicatedStorage");
local Players = game:GetService("Players");
local ProfileService = require(script:WaitForChild("ProfileService"));

local ProfileStore = ProfileService.GetProfileStore(
	"Player",
	{
		Cash = 0
	}
);

local Profiles = {};

local function MakeLeaderstats(player)
	local profile = Profiles[player];
	
	local leaderstats = Instance.new("Folder", player);
	leaderstats.Name = "leaderstats";
	
	local Cash = Instance.new("IntValue", leaderstats);
	Cash.Name = "Cash"
	Cash.Value = profile.Data.Cash
	
	local thread = coroutine.wrap(function()
		while true do
			Cash.Value = profile.Data.Cash
			game:GetService("RunService").Heartbeat:Wait();
		end
	end)
	thread();
end

function OnPlayerAdded(player)
	local profile = ProfileStore:LoadProfileAsync(
		"Player_" .. player.UserId,
		"ForceLoad"
	);
	
	profile:Reconcile();
	if profile ~= nil then
		profile:ListenToRelease(function()
			Profiles[player] = nil;
			player:Kick();
		end)
		
		if player:IsDescendantOf(Players) then
			Profiles[player] = profile;
			MakeLeaderstats(player);
		else
			profile:Release();
		end
	else
		player:Kick();
	end;
end;

for _,player in ipairs(Players:GetPlayers()) do
	coroutine.wrap(OnPlayerAdded)(player)
end
Players.PlayerAdded:Connect(OnPlayerAdded);

Players.PlayerRemoving:Connect(function(player)
	local profile = Profiles[player];
	if profile ~= nil then	
		profile:Release();
		Profiles[player] = nil;
		player:Kick();
	end
end)

local DataManager = {};

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

return DataManager;

script which isn’t working

local ProfileService = require(game.ReplicatedStorage:WaitForChild("DataManager"));
game.Players.PlayerAdded:Connect(function(player)
	print(player.Name) -- Doesn't fire
	print(ProfileService:Get(player).Cash) --Doesn't fire
end)

I made a post about this issue here

so a person suggested this solution

local ProfileService 

coroutine.wrap(function()
ProfileService = require(game.ReplicatedStorage:WaitForChild("DataManager"));
end)()

game.Players.PlayerAdded:Connect(function(player)
if not ProfileService then 
   repeat wait()  
       print("something") -- this prints infinitely  
   until ProfileService 
end
   print(player.Name) -- Doesn't fire
   print(ProfileService:Get(player).Cash) --Doesn't fire
end)

Refer to the official example (top post) - towards the bottom there’s a for loop that iterates over existing players before .PlayerAdded connection is made.

On a different note, your code that “waits for ProfileService” is loading your wrapper module (line 4, WaitForChild name parameter) - these are errors on your own end.

Isn’t this what you refering to?

Yes I know I want to use wrapper reffered as ProfileService how @okeanskiy’s tutorial showed.

Make sure your DataManager is in ReplicatedStorage.

image

Just a few questions as I’m rather new with using this module. With clans/factions having no session locking (not releasing the profile, not listening to release, and having different servers load the profile) could it be setup like this?

local module = {}
local ProfileService = require(script.ProfileService)
local ClanProfileStore = ProfileService.GetProfileStore("ClanSaveData",{})
local ClanProfile = ClanProfileStore:LoadProfileAsync("Clan","ForceLoad")	
function module.ReturnClanProfile()
	return ClanProfile
end
return module
--planning to add more functions, which is why I'm not just returning clanprofile

I’m planning to add real time cross server updates to these clans. Would using messaging service and informing all servers of new changes and then having those servers save that data be the most effective/efficient way to provide real time updates?

For example, in server A someone does something that makes their clan score go up by 3, another member of that same clan is in another server, server B, now server A would send a message to all running servers to update their clan score. This way any changes server B makes reflects the true amount of clan score they have (and of course server B would be doing the same for any changes it makes).

Could I use this with storing data that isn’t a players data?

ProfileService is designed for profiles that are acessed by one server at a time - this will not work.

Yeah, you can use ProfileService for anything other than players as long as the individual profiles are used on one server at a time - otherwise you’ll need to make custom code with update listeners and messagingservice.

2 Likes

How would I get the profile? Could i just use a string, like an ID? because what I understand is that it uses the player object? “Profiles[player]”

Edit:Nevermind, I didn’t realize how it was set up fully.

I’ve been wanting to experiment with ProfileService and took the time to do so today after looking at @EncodedLua’s tutorial.

Is there any reason why I should be getting the following warning:

The code is nearly identical to the script provided in the tutorial (except a few variable name changes.) Did I miss a step in the tutorial? Code can be provided if it helps solve this issue.

More info on UpdateAsync queue warnings can be found in the troubleshooting section and this thread.

From the info and personal experience I’ve gathered, such warnings are not dangerous at all as long as you don’t get like a ridiculous amount of them at a time (like 10 every second). I tried to avoid such warnings while coding ProfileService as much as possible, but there are cases where it’s necessary to put a request on queue immediately.

1 Like

Thank you, I feel like an idiot for missing this portion of the documentation.

I’ve also stumbled upon the script not working in Studio (works just fine in a regular game session), is this just something I’ll have to get used to in regards to testing?

found it in the documentation, go figure

1 Like

Not sure if this was answered but if you update the ProfileTemplate, will old players be able to get that data saved, or are there special steps needed to be taken for that to be possible?

1 Like

You can use the Reconcile() method on a Profile, this will compare the existing profile data structure with the data template being used and will fill in missing values.

API - ProfileService (madstudioroblox.github.io)

2 Likes

Thank you for leading me to the API, should’ve clicked on that to start with.

Since you seem experienced with this, do I need to edit any of the module settings or should I be fine keeping them as default?