Save your player data with ProfileService! (DataStore Module)

Ok, but let me explain something to you. MessagingService may be unreliable, but if you notice in both of those posts, it says “exceeds the allowed limit”. Now the -2 billion seconds is definitely an issue, but perhaps that is the result of the errors in the developer’s code. If not, sure lets say MessagingService is unreliable and broken.
Secondly, your third point about MessagingService being useless is completely false. You should be constructing your game in a manner that knows stuff might not work and can continue running if and when those errors happen. It wouldn’t be an issue to use MessagingService in a situation like this as long as long as you know how to create your code with those issues in mind.

This is getting off topic, sorry loleris.

I guess saying that it’s useless is an overstatement, but in this case for ProfileService it’s useless.

Sure, that isn’t incorrect but I rather not work with something that can break when it’s not my fault, I rather be in control and know that my code will work 100% of the time not 99.99% or less.

I can understand why DataStore service will sometimes fail, and that’s fine we can handle it appropriately with the error message.

  • Ironically MessagingService doesn’t have any error message for us despite deliberately stating that “delivery is not guaranteed”, we expect it to be delivered but it’s not this is a major flaw in the API design.

  • The data limit isn’t nearly enough to do anything substantial and innovative. (We only have cross server chat, server list, and infinite player servers but that’s it from what I’ve seen)

  • The data budget is limiting and might not even be able to support large games (Top #10 Concurrent users)

With these 3 issues, to me it is useless and not worth using, it’s already hard enough to make games why make it even harder for ourselves? You can definitely use it don’t be discouraged to do so.

2 Likes

Well actually, I’m sorry for getting this thread off topic, I was just offering my opinion on an alternate way to do session locking.

Sorry if sound a bit stupid here if I don’t understand this module abit.

I’ve been using ProfileService for the past month in my upcoming game and I’m absolutely loving it. It makes things like trading system’s less vulnerable to things like duplication (of course if you don’t somehow mess up things). In your post and on your documentation, it states that

I used to love DataStore2 but stopped using it after I saw this because of how it doesn’t rely on the Player instance. Now, I’m planning to make a clan system using ProfileService but I’ve hit a roadblock. I want it so players can create clans on their own and invite members from different servers or if they are offline (i’ve already done this part with global updates). Now, the issue I have is with loading and saving clan data.

For example, if Player A is in Server A and Player B is in Server B, I would want to disable SessionLock and have it so data gets updated periodically. The issue is that I have a Clan Shop that uses a currency called Clan Points. Player A & B can be Clan Administrators and therefore both would be allowed to use the currency. How would I make sure that they don’t somehow manage to “duplicate” the clan points by buying stuff from the Clan Shop and not having it reflect in other servers the true amount of clan Points.

1 Like

This module is amazing, works perfectly with no data loss. I’m kinda confused on why I get this error though, because I’m not saving any userdata:

This only happens only when I join the game, not when I leave, though.

I also get some warnings on datastore throttles here and there but I don’t really care about them because the module still saves the data nicely.

This is what I’m saving, these are all tables with only booleans, strings, and numbers in them. I’ve been serializing my userdata for a while now, and I also saved in this format. The only difference is that I’m using ProfileService now

image

Edit: Nvm found it, I forgot to serialize a Color3

1 Like

I converted my game to using this module from DataStore2. So far I haven’t gotten any reports of data issues and the module is a lot easier to use than DataStore2 in my opinion.

2 Likes

does this module in any form use any functions like GetCurrentPages() for leaderboards because i am very interested switching from datastore2 to this

No, this module doesn’t use OrderedDataStores. You will have to implement OrderedDataStores on your own.

1 Like

Does this module support getting offline player’s data? If so, how would I go about getting and setting that data?

1 Like

I would be interested in this as well.

(without using DataStore Editor)

Yes. As long as you know the UserId, you can load up any offline player profile. You can learn more about loading profiles in official documentation and also reading the example code.

ProfileService itself does not know if it’s profiles belong to a Player and whether that player is online - all it takes is a unique DataStore key with a UserId to fetch a saved profile.

4 Likes

Beautiful module! :smile:
I’ve been looking for a module like this for quite a while, as, I personally despise standard DataStore APIs and intentionally put off all my DataStore work until I have to implement it. This is basically a perfect example of how I would expect datastores to work, and, I don’t really see myself not using this even for lighter projects.

It’s very feature packed, and, my only complaint is that on the documentation it’s a bit much to take in what is important to a general user and what isn’t, however, ignoring that, its a pretty brilliant utility. (My own projects really struggle with this as well, my documentation are even more of a mess)

This warning caught my eye.
image
If generally what some might consider hacky solutions aren’t a huge problem to you, perhaps a nice way to handle this more gracefully is via wrapping a __index/__newindex metamethod hook. Yielding within these hooks produces a predictable error message.

For example, you could create the following wrapper implementation to stop yielding (There is really no arguing this isn’t hacky in my opinion, the pass through of arguments and such is ridiculous):

local callUtility = setmetatable({}, {
	__index = function(_, callData)
		local func, arguments = table.unpack(callData) -- Extract the function and its arguments
		return table.pack(func(table.unpack(arguments))) -- Call the function with the given arguments, and pack up its results
	end
})

local someTargetFunction
local function someWrappedRepresentation(...)
	-- Pass the function and arguments through the metamethod, then unpack the results to the call
	return table.unpack(callUtility[{someTargetFunction, table.pack(...)}])
end

A much less hacky alternative implementation would be to use coroutines and simply monitor the coroutine.status result. This is personally what I use for publicized projects, vs the above for private projects (because there are just so many things that would upset people about the above haha).

1 Like

Thanks!

My framework uses a signal module with no yield support / yield catching in connected listeners for performance reasons - ProfileService is part of this framework module group and I’m trying to provide it as a public resource exactly like it is in my commercial project. ProfileService may evolve with my future projects and I’m going to continue providing it as a commercial-grade public resource.

4 Likes

Are you asking how to get the value of cash? You can just reference it normally. There are no special setter or getter functions for data in ProfileService.

1 Like

No… the title says what this module does and what it does clearly – it saves, loads, and session locks data.

That’s just some example code, don’t take it that seriously. What that function does is update the profile (aka where the data is) by adding the cash. For the client to see that change, you’d need a RemoteEvent to tell the client the cash’s new value whenever it changes

[10/01/2020] Profile:Reconcile() update!


Profile:Reconcile() is used to fill in missing string key and value pairs for Profile.Data from profile_template. Usage is shown in the updated example code:


ProfileService will now repeat the final save DataStore call indefinetly (After :Release() or game shutdown) until it succeeds - previously it only attempted to make this call once. This should further improve stability in very rare cases.


All tick() occurrences were replaced with os.clock()


Added handling for a scenario where ProfileService is run in studio without internet access - previously ProfileService would yield it’s API calls indefinetly in this rare scenario. A warning will now also be thrown.

15 Likes

So :Reconcile() will only add keys that are missing and will not overwrite existing keys based on type/value?

Will it also remove keys?

It looks like it only adds the keys. If you want to remove keys for whatever reason, I would recommend doing what @Kampfkarren did in Zombie Strike and use migration/revision modules for each major change in your data structure.

So, after reading through the docs and looking at the provided example, I’m already sold! I’m definitely planning to use this in my game, rather than the alternatives. I understand the concept of how to use ProfileService in conjunction with player data, but I’m a little confused on how I might use it for shared server data, that’s not tied to a specific player.

Take this hypothetical scenario:
My game has a shop which contains Roblox catalogue items. Each item is stored in a table, containing an AssetId, Name and Price. At any time, I should be able to add, update or remove these items. The server will be able to dynamically update the catalogue list without requiring a shutdown in order for all players to then see the updated catalogue; rather, it’ll just fire off a RemoteEvent with the updated data.

The structure of each item might look like this:

{
	Name = "Beautiful Hair for Beautiful People",
	AssetId = 16630147,
	Price = 100,
}
  1. What would be the best way to go about storing this data? One item per Profile or in a table in a single Profile? One item per Profile would be silly, as I’d need to know exactly which items are saved to know which key to enter. This would also take up many precious DataStore requests, as there could potentially be hundreds of items, and so requests would begin to fail due to rate-limting. Logically, you’d have a single Profile holding all catalogue items’ data.
  2. How would I have each server sync up to the catalogue, so that if I add, remove or update an item, all servers (eventually) receive the updated catalogue? Presumably this would use the Global Updates mechanism, but how would this work when all servers should be listening for updates at once?
  3. Is it possible to allow any server at any given time to create, update or delete items from the catalogue? Presumably the server will need to “steal” the session to do this?

This is only hypothetical, but I feel this could be a legitimate scenario (or something similar; e.g. promo codes), and would be very useful information to have, either explained or in the form of a tutorial or example.

1 Like

ProfileService is a specialised module for accessing data in one server at a time (and preventing collisions when players are hopping servers). You’ll have to use regular datastores for your problem.

Note that Roblox is now discouraging use of datastores for globally accessed data in favor of MessagingService

3 Likes