I am getting error “tables cannot be cyclic”, how do I fix.
Code (this is not the full code). The code is inside a module script called Data Manager which is parent of profile service module:
local function PlayerAdded(Plr)
local PlayerProfile = ProfileStores.PlayerData:LoadProfileAsync(
"Plr_"..Plr.UserId,
"ForceLoad"
)
if (not PlayerProfile) then
Plr:Kick("Data failed to load.")
else
PlayerProfile:Reconcile()
PlayerProfile:ListenToRelease(function()
Profiles.PlayerData[Plr] = nil
Plr:Kick()
end)
if (Plr:IsDescendantOf(Players)) then
Profiles.PlayerData[Plr] = PlayerProfile
Bindables.OnPlayerProfileLoaded:Fire(Plr,PlayerProfile) -- Error is here
else
PlayerProfile:Release()
end
end
end
Leaderstats (a different script):
DataManager:OnPlayerProfileLoaded(function(Plr,Profile) -- This listens to the bindable event fired
local Wins = Plr:WaitForChild("leaderstats"):WaitForChild("Wins")
Wins.Value = Profile.Data.Wins
end)
If you want the full scripts I can private message them to you.
Edit: This only seems to be happening when I pass the profile as an argument. If I pass for e.g an empty table it works. How do I fix this?
BindableEvents can’t send live references to objects (objects are just tables with data). A BindableEvent will attempt to make a copy of a table, but, in the case of copying a Profile object, it will fail due to circular references being used in the ProfileService system.
Consider using a custom signal module (instead of BindableEvents) like MadworkSignal which can pass live references - It’s part of the ReplicaService package.
Edit: Replaced pointer(...) with coroutine.wrap(pointer)(...). I don’t think this will have any performance problems because the event will only fire when player joins.
Just one question will this be compatible into making game leaderboards and uses API like GetOrderedDataStores() and does session locking actually prevent this type of issues, second, does it have functions like Increment() or Set() because it’s just so long to write profile.Data.Cash = profile.Data.Cash + numberHere and lastly, do you have a function that sets table’s or not or it just automatically just saves it when it is inserted? if not then I’d like an example from you
ProfileService is NOT for making leaderboards / creating ordered OrderedDaraStores. It’s not designed for it nor it would make it easy to do it. OrderedDaraStores don’t need nearly as many precautions (messing them up is rarely a tragedy) and are easy to implement on their own.
local data = profile.Data
data.Cash += number_here
ProfileService keeps Profile.Data saved to the DataStore - you can put mostly any kind of values inside and it will save as long as Profile:IsActive() returns true or until the profile is released.
and the loading code the same code you gave but edited
-- Init profile service
local GameProfileStore = ProfileService.GetProfileStore(
"PlayerData",
PlayerDataSample
)
local function Debug(profile)
if profile ~= nil then
print("Successfully loaded data!")
else
print("Failed to load Data")
end
end
local function KickPlayer(Player)
local PlayerData = GameProfileStore:LoadProfileAsync(
"Player_" .. Player.UserId,
"ForceLoad"
)
if PlayerData ~= nil then
PlayerData:Reconcile() -- Fill in missing variables from ProfileTemplate (optional)
PlayerData:ListenToRelease(function()
Profiles[Player] = nil
-- The profile could've been loaded on another Roblox server:
Player:Kick()
end)
if Player:IsDescendantOf(Players) == true then
Profiles[Player] = PlayerData
-- A profile has been successfully loaded:
Debug(PlayerData)
else
-- Player left before the profile loaded:
PlayerData: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()
end
spawn(function()
if BannedPlayers[tostring(Player.Name)] then
Player:Kick("You are permanently Banned from this game, you are not welcome.")
elseif PlayerData.Stats.Banned == true then
Player:Kick("You are permanently Banned from this game, you are not welcome.")
else
print(Player.Name.." is authorized for this game!")
end
end)
end
local function PlayerAdded(Player)
print("Loading Data for "..Player.Name)
local PlayerData = GameProfileStore:LoadProfileAsync(
"Player_" .. Player.UserId,
"ForceLoad"
)
if PlayerData ~= nil then
PlayerData:Reconcile() -- Fill in missing variables from ProfileTemplate (optional)
PlayerData:ListenToRelease(function()
Profiles[Player] = nil
-- The profile could've been loaded on another Roblox server:
Player:Kick()
end)
if Player:IsDescendantOf(Players) == true then
Profiles[Player] = PlayerData
-- A profile has been successfully loaded:
Debug(PlayerData)
else
-- Player left before the profile loaded:
PlayerData: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()
end
btw the last function without the end isn’t the bug the loading is
Saving same as the old code
local function PlayerRemoved(Player)
local Backpack = Player.Backpack
local profile = Profiles[Player]
if profile ~= nil then
profile:Release()
end
end
Would be helpful if you read through the troubleshooting page first as it seems you’re trying to store Roblox instances which are not serializable (“Path” member).
Yes. It seems like you are saving too much to the DataStore for your items. Simply save the weapon’s identifier and any metadata, and store all of the extra stuff (Path,ClassFor,Type,ChanceToGet,MaxUpgrades,Debounce,CanDamage,IsTradeable) for items in a database.
This module is not for making leaderboards (normal datastore works fine for it) or even currency. This module used mainly to save things like inventory for example.
Really cool! I just found out about ProfileService and ReplicaService after looking for clean open source solutions for data in new games. Why re-invent the wheel when someone else has done the work for you? I’ll definitely be using these. Thanks!
Is the Profile.Data saved in a single key or multiple keys? Because what if I reach the max data store limit, does this automatically distributes it over to several keys, or data fails to save?
Asking this because in my game there will be an inventory system with no limit of the number of items, each with their own properties saved, and I am not planning to add any sort of compression on the data that will be saved. Do I need to worry of it reaching the limit or is it too high that no one will reach it?
ProfileService uses a single DataStore key - the limit for storing under one profile is less than 4 megabytes. 4 megs is a heck ton of data, but you should make minimal estimates on how much data you’re going to take up. Consider stacking data that can be stacked and not make small redundant differences in objects that are still more or less the same.
Using the ProfileService, it’s giving me an error through the Service’s line of code
13:53:44.591 BindToClose can only be called on the server. - Client - Profile Service:1511
13:53:44.591 Stack Begin - Studio
13:53:44.591 Script 'ReplicatedStorage.Modules.Profiles.Profile Service', Line 1511 - Studio - Profile Service:1511
13:53:44.592 Stack End - Studio
Here’s the line of code, if needed.
game:BindToClose(function()
if UseMockDataStore == true then
return -- Ignores OnCloseTasks if ProfileService is running on MockDataStore
end
ProfileService.ServiceLocked = true
-- 1) Release all active profiles: --
-- Clone AutoSaveList to a new table because AutoSaveList changes when profiles are released:
local on_close_save_job_count = 0
local active_profiles = {}
for index, profile in ipairs(AutoSaveList) do
active_profiles[index] = profile
end
-- Release the profiles; Releasing profiles can trigger listeners that release other profiles, so check active state:
for _, profile in ipairs(active_profiles) do
local is_active = profile._profile_store._loaded_profiles[profile._id] == profile
if is_active then
on_close_save_job_count = on_close_save_job_count + 1
coroutine.wrap(function() -- Save profile on new thread
SaveProfileAsync(profile, true)
on_close_save_job_count = on_close_save_job_count - 1
end)()
end
end
-- 2) Yield until all active profile jobs are finished: --
while on_close_save_job_count > 0 or ActiveProfileLoadJobs > 0 or ActiveProfileSaveJobs > 0 do
RunService.Heartbeat:Wait()
end
return -- We're done!
end)
Is it because im calling the profile service in a local script or something? Can’t seem to find out what’s the cause of this.