This module doesn’t work on clients because DataStores are server-only.
Is there a way I can detect if a player has just joined for the first time/has no data?
This is brilliant, im reading through your API right now and im already seeing so many areas where this can come in handy for modern games on Roblox… Blown away at how efficient it is, considering what its doing. Well done mate, very impressive, im for sure using this, thanks!
Is GlobalUpdates a good choice for making a messaging system with saving(as global updates are saved automatically) ?
If you are trying to use it as a real-time messaging system, no, since updates could take up to a full autosave cycle to be detected.
Is there anyway i can detect if the table Profile.Data
is changed?
I can use metamethod __newindex. Does setting a metamethod to the table will mess-up anything?
Yes, it will mess up things. A better way would be to put the Profile in a wrapper class or something and have setter methods that trigger side effects.
Shutdown handling is included. I encourage exploring the source code
[12/18/2020] - The good stuff update!
ProfileService module no longer yields when calling require(ProfileService)
!
Grab your update the easy way or the cool way.
Added Rojo support.
ProfileService.IssueSignal now provides profile_store_name
and profile_key
parameters so you wouldn’t be left in the dark.
Tables cannot be cyclic?
-- Check how many if it's beyond 4 million then probably reset it
local dataCounter = HttpService:JSONEncode(PlayerProfile)
print(Player.Name.." has "..string.len(dataCounter).." in their Data")
-- Sample Data
local SamplePlayerData = {
Stats = {
Coins = 0;
Level = 1;
EXP = 0;
MaxEXP = 0;
Quests = 0;
MaxQuests = 10;
Clan = "none"; -- defines if {Mage, Warrior, Tank}
Physical = 0;
Spell = 0;
Stamina = 0;
Banned = false;
};
Inventory = {
-- Weapons
-- Potions
--[[
Weapon = {
Spell = 0;
Physical = 0;
Delay = 1;
Class = 0;
Rarity = "Rare";
Tradeable = false;
Upgrades = 0;
MaxUpgrades = 99;
SellAmount = 0;
}
]]
--[[
Potion = {
Rarity = "Mythical";
Drinked = false;
Class = "none";
SellAmount = 0;
Tradeable = false;
}
]]
-- Armors
-- Helmet
-- Chestplate
-- Pants
--[[
Helmet = {
Rarity = "none";
Class = "none";
SellAmount = 0;
Tradeable = false;
Spell = 0;
Physical = 0;
Stamina = 0;
}
]]
--[[
Armor = {
Rarity = "none";
Class = "none";
SellAmount = 0;
Tradeable = false;
Spell = 0;
Physical = 0;
Stamina = 0;
}
]]
--[[
Pant = {
Rarity = "none";
Class = "none";
SellAmount = 0;
Tradeable = false;
Spell = 0;
Physical = 0;
Stamina = 0;
}
]]
Tools = {
Weapons = {
};
Potions = {
};
};
Armors = {
Helmets = {
};
Chestplates = {
};
Pants = {
}
};
Spells = {
}
};
Equipment = {
Helmet = "none";
Chestplate = "none";
Pants = "none";
Sword = "none";
Sword1 = "none";
Spell = "none";
Spell1 = "none";
};
}
Ouput
tables cannot be cyclic - Server - StatsService:194
14:19:27.223 Stack Begin - Studio
14:19:27.223 Script 'ServerStorage.Aero.Services.GameStats.StatsService', Line 194 - function PlayerAdded - Studio - StatsService:194
14:19:27.223 Stack End - Studio
what?
Output
ServerStorage.Aero.Services.GameStats.StatsService:151: attempt to index nil with 'Data' - Server - StatsService:151
14:22:07.819 Stack Begin - Studio
14:22:07.819 Script 'ServerStorage.Aero.Services.GameStats.StatsService', Line 151 - function KickPlayer - Studio - StatsService:151
14:22:07.819 Script 'ServerStorage.Aero.Services.GameStats.StatsService', Line 189 - function PlayerAdded - Studio - StatsService:189
14:22:07.819 Stack End - Studio
Code
local function KickPlayer(Player, PlayerProfile)
if Banned[tostring(Player.Name)] then
Player:Kick("You were banned automatically!")
elseif PlayerProfile.Data.Stats.Banned == true then
Player:Kick("You were banned by an Admin!")
else
print(Player.Name.." is allowed in the game!")
end
end
Caller of Function
local function PlayerAdded(Player)
local PlayerProfile = GameProfileStore:LoadProfileAsync(
"Player_"..Player.UserId,
"ForceLoad"
)
-- Baically Preventing Item loss and Duplicates or as Session Locking
if PlayerProfile ~= nil then
PlayerProfile:Reconcile() -- if there is something missing then fill it up
PlayerProfile:ListenToRelease(function()
-- might have loaded on another server
Profiles[Player] = nil
Player:Kick()
end)
if Player:IsDescendantOf(Players) == true then
-- woo hoo yes
Profiles[Player] = PlayerProfile
print("Data successfully Loaded!")
KickPlayer(Player, PlayerProfile)
else
-- oops
Player:Release()
end
else
-- Couldn't load it
Player:Kick()
end
-- Data Handling is done check if they are banned
KickPlayer(Player)
-- Check how many if it's beyond 4 million then probably reset it
local dataCounter = HttpService:JSONEncode(PlayerProfile)
print(Player.Name.." has "..string.len(dataCounter).." in their Data")
end
so HttpService:JSONEncode(Profile.Data
?
The Profile
object is cyclic because it has a reference to a ProfileStore
object that has a reference back to the Profile
. On the other hand, Profile.Data
is exactly what you set it to be and is not even allowed to be cyclic, so you can JSON encode it.
Great, that solves my problem the new problem is that is there an onUpdate()
function similar to DataStore2 ?
You will have to implement that yourself. One way to do it would be to have a container class for Profiles that has setter methods. Alternatively, you could use loleris’ new ReplicaService, which kind of builds that in for you.
but does it update also when I change some data?
If you mean the DataStore, no. ProfileService autosaves around every 90 seconds by default I believe, so changing data won’t immediately save.
I’m not saying anything about auto-save and its basically like this: change coins to 90 from 30 > does it fire when it updates like that?
Are you referring to this? Yes, your implementation could have a custom signal that fires whenever a setter method is called.
ProfileService
doesn’t have an Set()
method, alternatively if values like this Name = Value
have an .Changed()
then it basically solves all my problems or even better aa function that detects whether an table is updated!
…Please read my original reply. I believe I already answered that.
Anyways, personally, I believe that a custom change signal is more useful. Using Rodux as an example, it has a changed signal that simply gives you the old state and new state, making you check against old values to find differences. Although this isn’t necessarily a downside, custom signals for changes are more readable as opposed to a one-size-fits-all change signal.