Save your player data with ProfileService! (DataStore Module)

Quoting my reply to the other guy who said something similar;

I know you need to require modules for them to run, but at least until a few days ago you were also able to run normal code at least for stuff like ProfileService

Modules never act unless told to, that’s kinda the point of the name module

Alright, whatever. I used to use the ProfileService data managing script in a ModuleScript, so even though all your facts are obvious and correct my point still stands, at least for me. I’m not trying to discuss if this has been like this forever or if it has changed, I just wanted to ask if it just stopped working because of something with Roblox or if it was related with ProfileService itself.

@loleris Is it possible to set metadata to the profile as you are session locking it? Suppose I want to make a pet copy counter and I want to have one server as the host for global updates. If I use the ForceLoad or Steal, every new server will become the host which I doubt is good for performance. If I use Cancel I risk the possibility of it never being unlocked, and if I use Repeat I risk the possibility of it yielding forever.

The only workaround I see that allows me to use ProfileService for this scenario is if I were to have the ability to set a metatag at the time the metatag for the session server is set.

My code so far (I am assuming it won’t work because the metatag wouldn’t be instantly updated):

-- Attempt to claim each plushie type's session if its not already locked.
-- Only one server can handle the global updates for each plushie profile.
-- Different servers may share this responsibility, but only a single server
-- will have each plushie type.
local function createPlushieProfile(plushieType)
	local profile = ProfileStore:LoadProfileAsync("Plushie_" .. plushieType, function(placeId, gameId)
		return "Steal"
	end)

	if profile == nil then
		-- Another server has already claimed a lock on this profile.
		print(string.format("Plushie type %d is hosted by another server.", plushieType))
		return nil
	end

	PlushieProfiles[plushieType] = profile
	print(string.format("Plushie type %d is now hosted by this server.", plushieType))

	profile:SetMetaTag("LastClaimed", os.time())
	profile:ListenToRelease(function()
		PlushieProfiles[plushieType] = nil
		print(string.format("Plushie type %d is now being hosted by another server.", plushieType))
	end)

	local globalUpdates = profile.GlobalUpdates
	for i, update in ipairs(globalUpdates:GetActiveUpdates()) do
		local updateId, updateData = unpack(update)
		globalUpdates:LockActiveUpdate(updateId)
	end

	for i, update in ipairs(globalUpdates:GetLockedUpdates()) do
		handleLockedUpdate(globalUpdates, unpack(update))
	end

	globalUpdates:ListenToNewActiveUpdate(function(updateId, updateData)
		globalUpdates:LockActiveUpdate(updateId)
	end)

	globalUpdates:ListenToNewLockedUpdate(function(updateId, updateData)
		handleLockedUpdate(globalUpdates, unpack(updateId, updateData))
	end)
end

for plushieType, _ in ipairs(Plushies) do
	local profile = ProfileStore:ViewProfileAsync("Plushie_" .. plushieType)
	if profile == nil then
		print(string.format("Plushie type %d has been created for the first time.", plushieType))
		createPlushieProfile(plushieType)
	else
		-- Prevent this server from trying to steal this profile from another server.
		-- If this check isn't made, every time a new server is made, the profile's session
		-- lock would transfer to that new server. Adding a delay to the claim time ensures
		-- that the server who has the session lock gets a minimum turn time.
		local lastClaimed = profile:GetMetaTag("LastClaimed")
		local secondsSinceClaimed = os.time() - lastClaimed
		print(string.format("Plushie type %d was claimed %s ago.", EasyUtil:toTime(secondsSinceClaimed)))
		if secondsSinceClaimed > 100 then
			createPlushieProfile(plushieType)
		end
	end
end

Is that full code of playerdataservice? Its an module? If its full code and module where is return PlayerDataService on the end line of the code? Your calling PlayerDataService on player parent, do PlayerDataService an folder or a module?

In a follow up to my previous post, I have decided to try using MessagingService to determine if the session is still alive. Any thoughts are gratefully appreciated.

Figured out the issue, it was not Profile Service as the source but rather another piece of code I had saving data combined with Profile Service taking up the key requests

No. ProfileService is NEVER a good idea for cross-server communication.

All it takes to make an item copy counter is to have a single key you UpdateAsync and increment the values with the copies created in a single server with some buffering so you’d call as infrequently as possible… And hope that it works. There is little data (to my knowledge) on DataStore keys being stable being written with thousands of UpdateAsync requests. It should certainly not be a feature you heavily rely on.

Okay thank you. I have resorted to using a memory story to update the copies quickly and then updating the dataStore when possible. If the copies are not found in the memory store I then check the dataStore for the backup.

how do i prevent data from being auto saved? I’m trying to make a system that requires the player to click an object to save their progress (like undertale), instead of automatically saving the progress.

@loleris Would this be a correct and efficient way to create Replicas on the client that will never be destroyed and need to be referenced at all times?

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ReplicaController = require(ReplicatedStorage.ReplicaController)

local REPLICA_NAMES = {
	"PlayerSession",
	"PlushieCopies",
	"SeasonPassRewards",
	"Leaderboards",
	"Matches"
}

local Replicas = {}
for i, replicaName in ipairs(REPLICA_NAMES) do
	ReplicaController.ReplicaOfClassCreated(replicaName, function(replica)
		Replicas[replicaName] = replica
		print("Replica: " .. replica:Identify())
	end)
end

ReplicaController.RequestData()
for i, replicaName in ipairs(REPLICA_NAMES) do
	while not Replicas[replicaName] do
		task.wait()
	end
end

return Replicas

I also noticed that you can do replica:ArrayInsert({}, value) but not replica:SetValue({}, value)

There’s DataStoreService, You can use that to save player data, but I don’t think that using datastore editors is useful as it doesn’t work on Event Room

how can you set async?
there isn’t something like DataStoreService.SetAsync

Instead of a function, you set the keys directly. If you’re looking for some sort of set async, then look at my resource because it uses ProfileService and features that function.

1 Like

how would I go about checking a users data from one server while they are in another server

You could just view their profile from the server you’re in

local ProfileService = require(Path.to.ProfileService)

local ProfileTemplate = {}
local UserId = 561097949 --The UserId of the player who's data you want to access

local ProfileStore = ProfileService.GetProfileStore("ExampleStore", ProfileTemplate)
local PlayerProfile = ProfileStore:ViewProfileAsync(tostring(UserId))
local PlayerData = PlayerProfile.Data

--Do whatever you want with PlayerData (you can't change it since the Profile is in view mode)
2 Likes

thanks ill try it out, my bad for not noticing there was a viewprofile function

Is profile service suitable for making stuff like a clan system?

I am getting [ProfileService]: Roblox API services available - data will be saved while testing in an unpublished empty baseplate.

Running this code in the command bar yields error 404 instead of error 403.

local status, message = pcall(function()
	game:GetService("DataStoreService"):GetDataStore("____PS"):SetAsync("____PS", os.time())
end)
print(status, message)

Output:

false HTTP 404 (Not Found)

Sorry if I’m just not using this correctly but I encountered an error with ProcessReciept.

I used your example code and modified it a bit to be inline with what I want to do.
My code:

Here’s a video that shows the problem:

The error doesn’t cause anything out of the ordinary, but I have a feeling they aren’t supposed to appear. I would appreciate if someone could help me fix it.