Save your player data with ProfileService! (DataStore Module)

How do I teleport to other places if I just get kicked when the profile gets released?

Do I need to make some sort of Teleporting variable in the player and change the code to this?

profile:ListenToRelease(function()
    if player.Teleporting.Value == false then
        Profiles[player] = nil
        player:Kick()
    end
end)

Hello, I’m starting to use ProfileService and I love it. Sorry if this has been answered elsewhere – do you plan to add Wally support? Thank you!

5 Likes

I have a question about this, how can I save the tools in this part?

I have no idea how to add the tools to that part, I suppose it is like that but I don’t think it works:

local DataManager = require(game.ServerScriptService:WaitForChild("DataStore"):WaitForChild("Configurations"):WaitForChild("DataManager"))

game.Players.PlayerAdded:Connect(function(player)
	local Profile = DataManager:Get(player, true)
	local Backpack = playe:WaitForChild("Backpack")

	Backpack.ChildAdded:Connect(function()
	 for _, Tools in pairs(Backpack:GetChildren()) do
	Profile.Data.Items = Tools
	end
end)

First of all, you can’t save Instances with ProfileService (and with any DataStore Module actually).

What you could do is save the tools name and when you need to load them, you could just simply fetch the tool name in a folder containing all the tools available.

The folder should be something like this in the workspace:
Screen Shot 2021-10-23 at 16.24.27 PM

local DataManager = require(game.ServerScriptService:WaitForChild("DataStore"):WaitForChild("Configurations"):WaitForChild("DataManager"))

game.Players.PlayerAdded:Connect(function(player)
	local Profile = DataManager:Get(player, true)
	local Backpack = player:WaitForChild("Backpack")

	for _, ToolName in pairs (Profile.Items) do
		local ToolToClone = game.Workspace.Tools:FindFirstChild(toolName):Clone()
		ToolToClone.Parent = Backpack
	end
end)

game.Players.PlayerRemoving:Connect(function(player)
	local Profile = DataManager:Get(player, true)
	local Backpack = player:WaitForChild("Backpack")

	local ToolsToSave = {}
	for _, Tool in pairs (Backpack:GetChildren()) do
		ToolsToSave[#ToolsToSave+1] = Tool.Name
	end

	Profile.Items = ToolsToSave
end)

1 Like

I know, I forgot to put the .Name

I did it directly from the script that handles the ProfileService

function cachedProfiles:Get(player, yield)
	local profile = cachedProfiles[player]
	if yield and not profile then
		repeat task.wait(0.1)
			profile = cachedProfiles[player]
		until profile or (not player.Parent)
	end
	if profile then
		return profile
	end
end

and to get the tools (this is a server script):

Players.PlayerAdded:Connect(function(player)
	local Profile = DataManager:Get(player, true)
		for _, Tools in pairs(Profile.Data.Tools) do
			if not toolsFolder:FindFirstChild(Tools) then continue end
			local Tool = toolsFolder[Tools]:Clone()
			Tool.Parent = player:WaitForChild("Backpack")
			local ToolGear = toolsFolder[Tools]:Clone()
			ToolGear.Parent = player:WaitForChild("StarterGear")
		end
	end)
1 Like

ProfileService calls release on the profiles almost instantaneously, long before my code has a chance to call ListenToHopReady(). How would I work around this? My first thought would be to yield the profile’s release, but that doesn’t sound like a very good idea…

Since it seems like the profile is being removed from your profile table before your BindToClose call happens.

My solution to this:

On your :ListenToRelease call on PlayerAdded, check if ProfileService.ServiceLocked is true, that means the game is shutting down, if yes, then listen to :ListenToServerHopReady and inside that handler teleport the player.

Edit: you also need to have a counter for players that haven’t been teleported yet, and then yield until that counter is 0 on a :BindToClose call. Please don’t call :GetPlayers every Heartbeat though.

That should work.

Sadly all that is a side effect of ProfileService not using deferred events, if it did this wouldn’t be an issue.

1 Like

ListenToHopReady() will execute even after the profile enters the ready-hop-state

1 Like

Afaik his problem seems to be that :ListenToRelease makes his Profiles table be empty right away, so when he goes over the profiles to listen for hop ready, there’s nothing.

  • ProfileService’s :BindToClose runs, profiles start to be released
  • :ListenToRelease on every profile runs, profile is removed from Profiles table
  • His own :BindToClose call is now fired
  • No profiles on his Profiles table are found, TO BE EVEN LISTENING FOR.

Unlike ScriptSignals, :BindToClose fires in the order of first to last connected. Bad behaviour right? Sadly it can’t be changed :/

image
image

Fixing this is a question of making ProfileService yield for a Heartbeat fire (or deferred resume) before releasing all profiles.

Edit: Made this into a Pull Request.

Is there a signal to detect if data has changed?

what color are u using for ur background?

Seems like default dark theme.

My dark mode is more of a dark gray but theirs seems to be more of a dark blue

No, because of the way that ProfileService works, that wouldn’t be a well designed thing to add.

ProfileService is in a way pretty raw, you’re expected to handle signals for data yourself.

You can always wrap Profiles in another more higher level class; after all, you’re probably only gonna really need to use the main profile reference sometimes. Inside that higher level class you can have custom methods for handling specific data, and keep signals for changes, etc.

You can always make that higher level class redirect to a real profile indexes, so you’re able to use everything with no issues other than indexing speed, but that’s not that important for stuff like DataStores.

That’s GitHub, not Roblox Studio.

2 Likes

I’m trying to understand how to use global updates. Just watched okeanskiy’s tutorials on it but I still have some questions.

  1. Is it possible to save instances as data when adding a new active update?
  2. I’m not sure of any way to figure out how to determine which global update id has what data. If I wanted to clear or edit a certain update, how do I get the id of that update?
  3. Is it possible to have functions or yields in data when adding a new active update?

also wondering if it’s possible to handle lock and handle an active update within one forloop, for example:

for _,Update in pairs (globalUpdates:GetActiveUpdates()) do
	globalUpdates:LockActiveUpdate(Update[1])
	module.HandleLockedUpdate(player, globalUpdates, Update)
end

instead of

for _,Update in pairs (globalUpdates:GetActiveUpdates()) do
	globalUpdates:LockActiveUpdate(Update[1])
end

globalUpdates:ListenToNewLockedUpdate(function(ID, Data)
	module.HandleLockedUpdate(player, globalUpdates, {ID, Data})
end)

How would I get loaded profiles from another script?

So I ran into this error randomly, which thankfully happened to my Profile so I became aware of it. I haven’t had any other instances of this happening to players. This problem would keep occurring with the same error when I would rejoin the same server where the error first occurred. Joining a different/new server would fix my Profile through Force-Loading, but if I have a game with 100 player servers, there might not be any other servers for a player to join.

What is the best method to resolve this so it doesn’t happen again?

This is my PlayerAdded code:

Text version:

local function PlayerAdded(player)
    local profile = GameProfileStore:LoadProfileAsync(
        "Player_" .. player.UserId,
        "ForceLoad"
    )
    if profile ~= nil then
        profile:Reconcile() --This replaced the InitializeProfileTemplate(profile) function
        profile:ListenToRelease(function()

            OnProfileReleased(player, profile)
            
            Profiles[player] = nil
            -- The profile could've been loaded on another Roblox server:
            player:Kick("Profile might've been loaded on another Roblox server.")
        end)
        if player:IsDescendantOf(Players) == true then
            Profiles[player] = profile
            -- A profile has been successfully loaded:
            OnProfileLoaded(player, profile)
        else
            -- Player left before the profile loaded:
            profile:Release()
        end
    else
        -- The profile couldn't be loaded possibly due to other
        --   Roblox servers trying to load this profile at the same time:
        player:Kick("Profile couldn't be loaded.") 
    end
end
Players.PlayerRemoving:Connect(function(player)
		local profile = Profiles[player]
		if profile ~= nil then
            -- Don't release profile until Inventory is finished working
            if player:GetAttribute("InventoryIsBusy") ~= false then
                repeat RunService.Stepped:Wait()
                    if DEBUG_PRINT then
                        warn("Waiting for [" .. player.Name .. "]'s Inventory to not be busy.")
                    end
                until player:GetAttribute("InventoryIsBusy") == false
			end
            profile:Release()
		end
    end)