I noticed when I load data in tc and data ingame at the same time it doesn’t kick the one that loaded last (as in the one that joined first and was loaded before) like old profile service. Is there a new thing for that or is this how it is now? To make my question clearer the first one I join on loads data but the second one doesn’t load data or get kicked when trying to load it, instead it sits there and waits for my first one to leave/unload then loads on the second. Also does OnSessionEnd operate the same as ListenToRelease?
Yeah - that’s caused by a slower auto-save and MessagingService not working in studio. If data consistency is critical you should try using mock mode / making solutions with ProfileStore:GetAsync(), Profile:SetAsync() or ProfileStore:MessageAsync() for best version control when a session is live in-game.
ProfileStore would end the previous session after around 300 seconds which is the time it auto-saves and checks the session lock. ProfileService does this every 30 seconds and since it does not rely on MessagingService it can resolve session locks in studio as well.
In live game servers MessagingService will work properly for ProfileStore to handle session locks and the end result is up to x10 fewer DS calls than ProfileService.
Quite the inverse, many creators are probably making millions of Robux using loleris open sourced code, I feel it is normal for him to ask for donations (not even begging or forcing you).
If you want to be spoonfed a solution to your problem then you’re free to hire your own devs who will cost you money, or even make your own datastore handler instead of using an opensource one which you are blaming everything on…
the problem is that using instances/values as a source of truth is arguably a code smell.
The reason for this is that value objects grant no encapsulation, and thus you cannot guarantee the integrity of the data. ( its modifiable from everywhere, it can even be :Destroyed()
, parented somewhere else, renamed, etc )
This becomes an even more glaring concern if they are parented to the Player and you’re tying data saving to .PlayerRemoving
.
In PlayerRemoving, the player has already been :Removed() its character has already been set to nil, and you’re relying on the ValueObjects parented to the player to not have been garbage collected yet for everything to work properly. This is very fragile behavior to rely on.
Now, you mention storing them somewhere serversided thats not tied to the player (ServerScriptService, ServerStorage, ReplicatedStorage), but this solves only one of the previously mentioned concerns.
In addition, if you want to save anything slightly more complicated than basic primitives (like for example, a table), you would have to create a hierarchy of folders and subchildren that you would then need to translate back to a table format anyways, since that’s what you need to save to the DataStore.
To which I say, just skip the translation layer entirely and work directly with a table.
You can still use ValueObjects parented to the Player for convenience in feeding that information to the client and UI, its a perfectly valid use case.
ahhh that makes more sense ty
There is no known common practice out there to check DataStore versions before it’s “safe to load data”
And that is absurd. It’s like not having a backup parachute because “the main parachute won’t fail!”. If you have the possibility to create a fail safe system, especially handling something as delicate as player data, you’re obliged to implement it.
Here’s a very simple analogy: imagine if every time you attempted to open a place in Studio a HTTP request was made. If the system finds the game’s files, it pulls up your game, and when you close studio, it saves said files. If it, for whatever reason, does not find the files, it opens an empty baseplate instead. This empty baseplate is then saved as the most recent version of your game.
This is crazy bad practice.
and the need to go so far indicates something wrong.
This is incredibly vague and a poor attempt to wiggle out of responsibility.
If you’re unhappy with ProfileStore I encourage you to use ProfileService instead which has been successfully working in games with over 2.5B visits over the past 4 years with no breaking bugs found.
I have actually reported the same issue on ProfileService [1] [2] five months ago. My code remains mostly unchanged, and I have since transitioned to using ProfileStore. The same issue persists in both modules (presumably because it’s doing the same thing).
By the way; I imagine the reason why you have not heard of this issue is because no other developer has bothered to research and take into account data loss. The “2.5B visits” games treat their players as disposable (because they’re usually children) and either don’t have the means or want to put in the effort to investigate, because if little Timmy experiences data loss he’s just gonna find another game to play, the devs will write him off as the cost of operating business and ten more Timmys will take his place.
I’ll say this once again. The system only acknowledges data if the latest API request returns it. If that request fails or returns nil
improperly it leads to a 100% preventable loss of data.
Data versioning exists for a reason. Not using it is bad practice and makes you an abhorrent programmer.
CC @fork_lifted:
I feel it is normal for him to ask for donations
I would agree if the source code was exclusively loleris’. The second a single different programmer contributes to the source code it becomes a collaborative project, whose sole monetary benefactor remains the host. Imagine if the founder of Wikipedia funneled 100% of the donations towards himself, even though Wikipedia is made up of tens of thousands of collaborators from all around the world.
CC @arbitiu:
I don’t recommend instances being the source of truth for your data, you already have a perfectly fine and dandy
profile.Data
table you can update and pull data from directly, that doesn’t rely on finding children.
There is nothing wrong with this approach. It saves additional RemoteEvent calls to get data, allows for full server-client data sync with automatic getter, setter and listener functions (simply read .Value’s) and allows me to skip spending my time on building an entire replication service that essentially does the same thing, but more complicated (so with no net benefit).
If someone wants to keep their data fully “in the cloud”, there’s nothing wrong with that either. You can create aforementioned systems to read and update said data. I personally find it better if I can see the data with my own eyes instead of relying on console prints or worse, having to build my own display.
ontop of this players can change their username at any time which although being an uncommon scenario it isnt healthy to overlook (which means
rs.PlayerData[plr.Name]
would fail)
… this would never happen, live production or otherwise, because you cannot change player/character usernames in runtime (see link).
The reason for this is that value objects grant no encapsulation, and thus you cannot guarantee the integrity of the data. ( its modifiable from everywhere, it can even be
:Destroyed()
, parented somewhere else, renamed, etc )
Might aswell not use a map object for your game because you cannot guarantee it’s integrity (it’s modifiable from everywhere, it can be destroyed, …). If you’re not explicitly messing with it through code you’ve wrote, don’t have a rogue administrator with game edit access or a backdoor, it’s fine.
[…] To which I say, just skip the translation layer entirely and work directly with a table.
… and create your own getter/setter/listener API for something as trivial as int changes. Seriously, you don’t have to. Roblox does this for you (here).
Also, this, this and this are all good reads for whoever advocates for bloat like this.
That is seriously some self-righteous, defensive and in bad faith reply
I was never talking about literally changing the name property , but rather how users can pay R$1000 robux for a username change in their settings.
Despite not being recommended, conveniently for you, this argument doesn’t apply since you rely on undocumented behavior which makes the Name property from username changes not update until rejoin.
So yeah, your folders relying on pattern matching player names will work.
Aside from your defense mostly arguing against something you conveniently left out
No, I don’t use ReplicaService, don’t know why you’re accusing, I use value objects for UI to handle changes of state that way myself. I don’t have a problem with you using ValueObjects whatsoever, again it’s the fact you are using it as a source of truth for your data
Roblox’s documentation has plenty of examples that encourages caching player data in a table, even an entire section dedicated to using modulescripts as a cache, as it simplifies situations that would otherwise cause data loss like :BindToClose()
shutdowns.
This module is also equipped to handle these shutdowns… you know, if you actually use the profile.Data
as a cache, like you’re intended to.
You’re also missing out on autosaves because, again, you’re using a translated layer ontop of the table/cache that is supposed to handle the data for you
I am also confident in that it is way more convenient to have a modulescript that houses the table of profiles, which i can require from anywhere server-wise, in which I also don’t have to worry about unintended behavior
(like did you know that IntValues automatically math.rounds itself each time you update it? Why it matters? multiplications? divisions? implementing a % based xp boost?)
I wouldn’t rather have to create a table anyways, then translate them into instances, and then translate them back to a table to save them on quit.
It is pointless as Modulescripts house global state across scripts. You don’t need bindable events or bindable functions to communicate across scripts. You can just require the profiles from any server script. (and allows you to encapsulate, or in other words, control how this data can be changed, thus integrity)
You don’t need a replication service either, as I said, there is no problem to use ValueObjects to avoid remote calls
this is also NOT COMPLICATED TO DO you can just change your serverscript (that’s initializing profilestore) to a modulescript, return the table of profiles, and voilà a table cache!
this is to say that no, it is not over-engineering or “reinventing the wheel”
I know this isn’t directed at me, but I just find it incredibly ironic how you insist that player data is this delicate thing you must handle carefully, and then a couple lines later you trivialize handling said player data, “just use IntValues”, which I suspect is the root of your saving issues, the translation layer.
honestly just use something else at this point
this might be useful to you for now
that is quite literally how life works…
loleris is the creator of ProfileStore he has “all the rights” to it, and no one other than loleris publicly contributed to the source code
regarding other parts of your message just don’t use “Value” instances to store your players data, bad practice, if you just want to look at their data in studio you can just print(Profile.Data) and it’ll show you the whole table…
I think ProfileStore & other modules you mentioned (ReplicaService) are too “high level” for you.
just stop crying acting like it’s everyone else fault, you are the one using public resources
Hi there, I just want to remind you that you are not forced to use this module. If you believe ProfileStore has a problem, you could either fork it to fix the issue or make an entire datastore wrapper yourself.
What’s deep copy? I’m really new to DataStores, and I can’t really figure out how to overwrite old data consistently.
example of ‘shallow copy’
local tableA = { IntValue = 3, ArrayValue = { 1, 2, 3 } }
local tableB = {}
for key, value in tableA do
tableB[key] = value
end
print(tableA.IntValue) -- 3
print(tableB.IntValue) -- 3
print(tableA.ArrayValue[1]) -- 1
print(tableB.ArrayValue[1]) -- 1
tableA.ArrayValue[1] = 10
print(tableA.ArrayValue[1]) -- 10
print(tableB.ArrayValue[1]) -- 10
Notice array in tableB is also changed, because the array is shared with tableA, we need ‘deep copy’ to make any object values not shared.
I’ve always wondered, why does ProfileStore or ProfileService use Replica? isnt it much simpler to convert the data to objects then write to it? then when player leaves convert it back to table?
I found this code back than in a tutorial and have been using it ever since
local Module = {}
Module.Types = {
['boolean'] = 'BoolValue',
['string'] = 'StringValue',
['number'] = 'NumberValue'
}
function Module.TableToObject(Tbl, Prnt, Nst)
Nst = (Nst or 1)
if Nst > 30 then return end
for k,v in pairs(Tbl) do
if typeof(v) == 'table' then
local Fold = Instance.new('StringValue')
Fold.Name = tostring(k)
Fold.Value = shared.Encode(v)
Fold.Parent = Prnt
else
local val = Instance.new(Module.Types[typeof(v)] or Module.Types['string'])
val.Name = tostring(k)
val.Value = v
val.Parent = Prnt
end
end
end
function Module.ObjectToTable(Prnt, Tbl)
Tbl = Tbl or {}
for _,val in pairs(Prnt:GetChildren()) do
if val:IsA('Folder') then
Tbl[val.Name] = Module.ObjectToTable(val, {})
else
Tbl[val.Name] = val.Value
end
end
return Tbl
end
return Module
There was already a discussion about this here, but I wouldn’t say this is recommended. The thing with Replica is that you have the same data as on the client as the server; it’s just a table. (So imo it’s just more convenient that way)
This would get rid of autosaves.
I created a function in order to sync the profile with the player’s stats folder.
function DataManager:SyncProfileWithStats(player: Player)
local profile = DataManager.Profiles[player]
if not profile then return end
local function reconstructTable(folder: Instance)
local data = {}
for _, child in ipairs(folder:GetChildren()) do
if child:IsA("Folder") then
data[child.Name] = reconstructTable(child)
elseif child:IsA("ValueBase") then
data[child.Name] = child.Value
end
end
return data
end
local PlayerStats: Folder = player:WaitForChild("PlayerStats")
local newProfile = reconstructTable(PlayerStats)
DataManager.Profiles[player] = newProfile
warn("synced data: "..player.Name)
end
Is this alright to do, or would this be unsafe / cause problems? I want to use this so that it is easier for me to change values without using having to update each value and then the profile value on top of it, but I would like some advice before trying this out.
here’s an example scenario:
leaderstats.Gold.Value += 1
DataMgr:SyncProfileWithStats(Player) -- leaderstats folder converted into a table identical to profile table but with new data,
-- and profile is set to the new table.
I wonder if there’s a way to automatically reject :MessageAsync()
inside :MessageHandler()
.
Let me give an example:
Player1
wants to gift an item that unlocks at Level = 30
to Player2
, who is currently Level = 20
. In this case, I want :MessageHandler()
to reject the gift and return an indicator that the gift was not delivered, thereby preventing the item from being removed from Player1
’s data.
I see that :MessageAsync()
returns a boolean is_success
, which is tied to :MessageHandler()
's processed()
. However, if you omit processed()
from :MessageHandler()
, then:
ProfileStore will continue to iterate through other functions passed to
Profile:MessageHandler()
and will broadcast the samemessage
. Unprocessed messages will be broadcasted to new functions passed toProfile:MessageHandler()
and will continue to do so when a profile session is started another time (e.g. after a player joins the game again) untilprocessed()
is finally called.
This is not what I want. Instead, I need something like abort()
or reject()
to explicitly tell ProfileStore that the message should be completely discarded—not left pending until Player2
reaches Level = 30
and becomes eligible to accept the gift. Calling abort()
would then inform :MessageAsync()
that the message was rejected.
My question turned into a feature request, but hopefully, you get the idea.
That’s not entirely how :MessageAsync() works - it can also store messages for players that are offline and message handling may run days or months later.
Thank you for your reply. I see what you mean. But what about my example about gifting level-locked items? Is it possible with :MessageAsync()
? (I really don’t want messages to wait until Player2 reaches the required level)
:GetAsync() the recipient’s profile and evaluate giftability beforehand
Any Idea how ProfileStore could be used to create playerslots? I though up of sth but…
The problem I’m facing is not being able to reconcile slots, because, well they dont exist in the original data scheme. The original data only has 1 scheme for how a slot should look like, and a table for shared Data in between all slots.