Yes, I switched to using EasyProfile for my Profile Service Manager. You can see my post here asking about value changes. It seems to work pretty well but with the occasional reports of it being slow every so often but that’s with any game
The documentation seems to be broken so here is my code to manage it, just to save you some time:
local DataErrorMessage = "Could not load data, try again shortly. If issue persists, open a ticket!"
local LoadType = "ForceLoad" --"Steal"
local gameProperties, srv = _G.GameProperties, _G.GameServices
local profileService = require(script:WaitForChild("EasyProfile"))
local playerTemplate = require(script:WaitForChild("PlayerTemplate"))
local DataKey = gameProperties.DataKey
local ProfileStore = profileService.CreateProfileStore(
DataKey,
playerTemplate,
"<default_key>-<userid>"
)
local PSManager = {
Profiles = {}
}
local function HandleGlobalKey(globalKey, profile) --> Value, Key, KeyId
local value, key, keyId = globalKey.Value, globalKey.Key, globalKey.KeyId
if not keyId then
return
end
if key == "Warning" then
if value == "reset" then
profile.Data.Warnings = {}
elseif value == "remove" then
local warning = value.warning and tonumber(value.warning)
if not warning or not profile.Data.Warnings[warning] then
return
end
table.remove(profile.Data.Warnings, warning)
elseif typeof(value) == "table" then
table.insert(profile.Data.Warnings, {
admin = value.admin,
reason = value.reason,
warnedAt = value.sendTime
})
end
end
end
local function PlayerAdded(player : Player)
local start_time, timeJoined = tick(), os.time()
ProfileStore:LoadProfileAsync(player, true, LoadType):After(function(success, playerProfile, ...)
--print(tick() - start_time.."s")
if not success then
player:Kick(DataErrorMessage)
return
end
local metaData = playerProfile:GetMetaData()
if metaData.ProfileActiveSession.jobId ~= game.JobId then
player:Kick("Profile is already loaded in another server, please wait before rejoining! If this continues, open a ticket!")
return
end
--/ Create leaderstats
playerProfile:CreateProfileLeaderstats(player, {"KOs", "WOs", "Cash"})
local profile = playerProfile.Profile
profile.TimeJoined = timeJoined
local dummy = {
Data = {
TimeJoined = profile.TimeJoined
}
}
local callbacks = {}
--/ Value Changing
dummy.Data.__index = function(_,index)
return profile.Data[index]
end
dummy.__index = function(_,index)
if (index ~= "Data") then
return profile[index]
end
end
dummy.Data.__newindex = function(_,index,value)
profile.Data[index] = value
local callback = callbacks[index]
if not callback or typeof(callback) ~= "table" then return end
for _, callback in callback do
coroutine.wrap(callback)(value)
end
end
dummy.__newindex = function(_,index,value)
profile[index] = value
end
setmetatable(dummy.Data,dummy.Data)
setmetatable(dummy,dummy)
function dummy:OnDataValueChanged(index, callback)
if (not callbacks[index]) then
callbacks[index] = {}
end
table.insert(callbacks[index],callback)
local Connection = {}
function Connection:Disconnect()
table.remove(callbacks[index],table.find(callbacks[index],callback))
end
return Connection
end
--/ Global Keys
for _, globalKey in playerProfile:GetGlobalKeys() do
coroutine.wrap(HandleGlobalKey)(globalKey, profile)
end
playerProfile.GlobalKeyAdded:Connect(function(globalKey)
HandleGlobalKey(globalKey, profile)
end)
PSManager.Profiles[player] = dummy
end)
end
for _, player in srv.players:GetPlayers() do
coroutine.wrap(PlayerAdded)(player)
end
srv.players.PlayerAdded:Connect(PlayerAdded)
srv.players.PlayerRemoving:Connect(function(player : Player)
if PSManager.Profiles[player] ~= nil then
PSManager.Profiles[player] = nil
ProfileStore:UnclaimSessionLock(player, nil)
end
end)
function PSManager:WaitForPlayerLoaded(player : Player) : Profile
local profile = self.Profiles[player] if profile then return profile end
repeat task.wait() profile = self.Profiles[player] until profile return profile
end
function PSManager:FetchProfile(...) : Profile
return self:WaitForPlayerLoaded(...)
end
function PSManager:GetProfileStore()
return ProfileStore
end
function PSManager:GetPlayerTemplate()
return playerTemplate
end
function PSManager:GetOfflinePlayerStore(userId : number) : Profile
if not userId or not tonumber(userId) then
return
end
userId = tonumber(userId)
return ProfileStore:GetProfileAsync(
profileService.IsProfileKeyValid(userId, ProfileStore)
):Await()
end
return PSManager
Hope that helps!