PSManager (CURRENTLY NOT UPDATED) - Automating ProfileService + know when your Profile.Data has been changed!

please note, that this module is no longer updated at the time being, and has bugs. use at your own risk

Introduction

Hello, my fellow developers!

Many of you use ProfileService, to store data in your games. Although it’s easy to use, I have made some custom function(s) that (I think) will make it much easier to use.


Features

PSManager includes functions such as, OnDataValueChanged, which you can use to detect when changes are made to your Profile.Data!!!11!1!

amazing, I know. you can hold your applause for now

Anyway, some other features, such as automatically loading profile stores + profiles, releasing them, etc.
Profiles + ProfileStores can be directly indexed from the module, e.g

PSManager[storeName] -- store
PSManager[storeName][plr] -- profile

And LinkDataToValue (see below at documentation section to see how it works)


IMPORTANT:

please read the documentation at the very bottom for a FULL list of all the features. Thanks!


In order to use PSManager, you’ll need to do the following:

Installation

Get the module via Roblox, which should include 2 separate modules, the main module, PSManager, and PSManagerSettings. Module functionality should be pretty self-explanatory, we’ll get to those in a bit.

Parent your modules to anyway you want for now, and complete the following step below.

Important - Do this every time you insert PSManager into Roblox Studio (Updating, Installing)

Upon inserting the modules into Roblox Studio, expand PSManager in the explorer, and set the _PSManagerSettings object value to your PSManagerSettings module, like so:

(where PSManagerSettings is parented to doesn’t matter, as long as _PSManagerSettings.Value is set to it. it is recommended that you do not put it in the PSManager object, because when updating you may accidentally delete your settings.)

Remember to delete the PSManagerSettings that comes with the updated version, and keep your old one with your settings already in it.


Configuration & Set-up

In order for PSManager to auto-load player profiles, we’ll need to go open the PSManagerSettings module. Upon opening the module, you should be seeing something like this:

Let’s focus on PROFILE_STORE_DEFAULTS. Right now, we have a template in it, we’ll call it StoreDefaultData.

The current name for this ProfileStore is "Currency". The table that it is assigned to is our StoreDefaultData.

By default, we have 2 things in it. (only the _playerProfileKey is required in order for this to work)

  1. _playerProfileKey
  2. Coins

_playerProfileKey is the ProfileKey we’ll be using to automatically load a player’s Profile from the specified ProfileStore, which in our case, is "Currency".

We won’t touch it for now, but in a nutshell, you can add special keywords that will be replaced with certain text, such as:

Screenshot 2021-08-24 at 1.41.54 PMPlayer.UserId

(gonna make a new paragraph for _playerProfileKey because it’s a bit more complicated, scroll down to the Documentation section for that)


Coins is just a placeholder value, the value that is assigned to it is the default. You can add more by just doing something like this:

We’ll stick with just Coins for this tutorial, you can adjust the following example code to fit your needs


And lastly, DEFAULT_NRH is the default not_released_handler used when calling LoadProfileAsync(), we won’t be touching it for now, and leaving it at the default, "ForceLoad"

Setup - Finished!

That’s about it for setting up, if set up correctly, ProfileStore's and player Profile's will be automated upon game start and when a player joins/leaves.


Usage

So, after configuring your StoreDefaultData's, we can now use PSManager!
We can start by requiring the module (make sure you have it in your place file already if you haven’t, links are at the top and bottom)

local SSS = game:GetService("ServerScriptService")
local PSManager = require(SS:WaitForChild("PSManager"))

Let’s start with a simple leaderstats script.

local PLRS = game:GetService("Players")

PLRS.PlayerAdded:Connect(function(plr)
    local leaderstats = Instance.new("Folder")
    leaderstats.Name = "leaderstats"
    leaderstats.Parent = plr
    
    local Coins = Instance.new("NumberValue")
    Coins.Name = "Coins"
    Coins.Parent = leaderstats
end)

To make this tutorial extra simple, we’ll just make it so that the player gets +10 coins every second. First, we’ll call :FetchProfile() on PSManager to get the player’s Currency Profile

Side note

You could also just index the profile directly as mentioned above, FetchProfile just has a built-in wait-until-player-loaded yield

local plrCurrencyProfile = PSManager:FetchProfile("Currency",plr) -- storeName, plr as index

And then, we set the value of coins to the player’s saved Coins value

Coins.Value = plrCurrencyProfile.Data.Coins

And finally, we use our fancy OnDataValueChanged function to detect when Coins has been changed

plrCurrencyProfile:OnDataValueChanged("Coins",function(updatedCoins)
    Coins.Value = updatedCoins
end)

And that is our basic usage, done! Whenever we want to change data, we just get the Profile and write to Profile.Data, or use the AddValue function (in documentation below) for quick editing of number values, like Coins.

Alternatively, we can also use the LinkDataToValue function to update Profile data when a ValueBase has been changed. (See below in the documentation section)

Profiles and everything such as releasing, saving, etc, are all automated if you set up PSManagerSettings properly.


Conclusion

That’s about it. Below are the download links, and documentation.

Enjoy!


Links


Documentation

PSManager

PSManager:FetchProfile()

Returns a player’s Profile from the specified ProfileStore. Has a built-in yielding function for player data to fully load.

PSManager:FetchProfile(<Player> player, <string> ProfileStoreName)
PSManager:WaitForPlayerLoaded()

Yields the current thread until the player has loaded all the Profiles from the preset ProfileStores (will not yield if data is already loaded)

PSManager:WaitForPlayerLoaded(<Player> player)
PSManager:FetchProfileStore()

Returns a ProfileStore with the specified ProfileStoreName string

PSManager:FetchProfileStore(<string> ProfileStoreName)
PSManager:AddValue()

Adds value to a number value in the specified Player's specified ProfileStore.

PSManager:AddValue(<Player> Player, <string> StoreName, <string> ValueName, <number> AddValue)

Profile

Profile:OnDataValueChanged()

Fires the specified callback function when a change is made to Profile[ValueName], and passes the new assigned value as the first (and only) argument.

Also returns a Connection object, which can be used to “disconnect” the callback using the Connection:Disconnect() function

Profile:OnDataValueChanged( <string> ValueName ,function( <any> UpdatedValue )
    print("ValueName was changed to", UpdatedValue)
end)
Profile:LinkDataToValue()

Makes a value inside Profile.Data to constantly be at the value of the specified ValueObject (ValueBase).

Profile:LinkDataToValue(<string> ValueName, <ValueBase> ValueObject)

Misc

_playerProfileKey

As mentioned before, this is the ProfileKey we’ll be using to access our Profile. There are 2 special text functions/formats you can use (currently) to insert certain text, such as a player’s UserId and/or Name.

They are as follows:
Screenshot 2021-08-24 at 1.41.54 PMPlayer.UserId
Screenshot 2021-08-24 at 1.45.55 PM
Player.Name

These keywords (or whatever you want to call them) do not require spaces before or after them. You can just put them into your string, and it will automatically be converted.

For example, this
Screenshot 2021-08-24 at 1.46.25 PM
Would become: "Currency-464902990"

Side note (for custom keywords)

You can also add your own (only do so if you know how to) keywords if desired, under the function (in PSManagerSettings), PROCESS_PROFILE_KEY_DICTIONARY > dictionary (Player object is passed through as the first argument)

18 Likes

Well oof, I was about to make something like this too but you did it first so :sad:

Can you also release the source code on github?

Ah, I was going to but I got too lazy with the documentation, pull requests, that kinda stuff :sweat_smile: . I don’t really use GitHub, but you can get the source code from the roblox file though.

EDIT: Fixed, but had to use a slightly ugly workaround. If anybody has any ideas, plz let me know kthxbye

There’s currently a critical issue where the profile is not getting released when a player leaves, please stand by whilst I fix this.

1 Like

I suggest the following changes within the loadPlayer function of PSManager. Hope this helps. I haven’t tested it extensively, so it is possible that I have made an oversight, but I have tested and verified that it works for access to nested tables. The advantage of this is that you will now actually recieve an RBX signal. In addition, if you just want to detect any arbitrary change to the player data, that is too possible now. If you only care about certain fields, then the keypath and the key are passed as parameters for the callback.

local accessSignal = Instance.new("BindableEvent")

-- recursively sets proxies for table-values in the base table.
local function getProxyListener(t, keyNamePath : string?)
	keyNamePath = keyNamePath or "Base"

	local prox = newproxy(true)
	local proxMeta = getmetatable(prox)

	proxMeta.__index = function(self, k)
		local val = t[k]

		if type(val) == "table" then
			return getProxyListener(val, keyNamePath .. "." .. k)
		else
			return val
		end
	end

	proxMeta.__newindex = function(self, k, v)
		if (type(v) == "table") or (type(v) == "function") then error("Cannot assign a table or function") end

		t[k] = v

		accessSignal:Fire(keyNamePath, k, v)
	end

	return prox
end

store[plr] = table.freeze(setmetatable({
	Data = getProxyListener(profile.Data, "Data"),

	GetTrueData = function()
		return profile.Data
	end,`Preformatted text`
	OnDataChanged = accessSignal.Event
},{
	__index = function(t,k)
		return profile[k]
	end
}))
1 Like

Why I can’t require module it says module does not return exactly 1 value.

1 Like

Having the same issue. PSManager returns a studio error stating that it can’t be required because it doesn’t return a value. Is this gonna get fixed… or what?

1 Like