Save your player data with ProfileService! (DataStore Module)

I’m assuming that in 98.04% of cases, teleporting will succeed and profiles will be released on .PlayerRemoved before the next server gets to loads them.

If I don’t release the profile before teleporting a player then I wont have to reload their profile in the unlikely chance that the teleport fails.

And on the off chance that datastores are working slowly for whatever reason then the worst outcome is that I have to wait a little bit longer on the second server for the profile to be released on the first server. ProfileService will handle the retrying.

I didn’t think of this before but, if you follow the basic usage of kicking the player from the server when their profile gets released then you cannot call :release() before trying to teleport the player.

This is already defined in the official documentation.
Screenshot_1
ProfileService will not be angry at you for calling Profile:Release() multiple times. Also, you will need to alter the example code to not kick players on release when a teleport is imminent. Let me know if releasing before teleports help your issue.

3 Likes

I don’t actually have any issue, it’s just speculation and planning.

For now I will just accept the 5% chance of up to 15 extra seconds. ‘up to’: meaning it can be less?
If the game gets players I will reevaluate this. It might be less of a problem in my game since players can view interstitials while teleporting and loading.

I also want to know how this scales:

If every player in the server owns two profiles will it be 20-36% of the datastore call budget? And I assume this is also dependent on the autosave interval used?

Yeah it would scale linearly with increased profile count per player and not per player count itself as the available budget increases with more players. Use the upper value to be safe.

1 Like

Below (ProfileStore.Mock) should be WipeProfileAsync I guess

Can someone make an example of using metatags as purchase confirmation?
I dont need the whole thing… Just how the ending would look like before returning PurchaseGranted

Like this? Havent tested it.

profile:SetMetaTag(receiptInfo.ProductId, true)
profile:Save()
Confirm that profile.MetaData.MetaTagsLatest[receiptInfo.ProductId] exists
profile.MetaData.MetaTags[receiptInfo.ProductId] = nil (Will MetaTagsLatest also update?)
return Enum.ProductPurchaseDecision.PurchaseGranted

Snippet from Madwork:

-- PURCHASE CONFIRM:
function PlayerProfile:PurchaseIdCheck(purchase_id) --> "GrantProduct" / "PurchaseConfirmed" / "Yield" / "NotProcessed"
	VerifyString(purchase_id, "purchase_id")
	if self._view_mode == true then
		error("[PlayerProfileService]: Can't confirm purchases for player profile in view mode")
	elseif self._entity == nil then
		return "NotProcessed"
	else
		local meta_data = self._profile.MetaData
		local local_purchase_ids = meta_data.MetaTags["ProfilePurchaseIds"]
		local saved_purchase_ids = meta_data.MetaTagsLatest["ProfilePurchaseIds"] or {}
		if local_purchase_ids == nil then
			local_purchase_ids = {}
			meta_data.MetaTags["ProfilePurchaseIds"] = local_purchase_ids
		end
		if table.find(local_purchase_ids, purchase_id) == nil then
			while #local_purchase_ids >= sett_PurchaseIdHistory do
				table.remove(local_purchase_ids, 1)
			end
			table.insert(local_purchase_ids, purchase_id)
			return "GrantProduct"
		elseif table.find(saved_purchase_ids, purchase_id) == nil then
			return "Yield"
		else
			return "PurchaseConfirmed"
		end
	end
end

(PlayerProfileService is an extension of ProfileService in Madwork - you should be able to remake this function for your needs though; Variable sett_PurchaseIdHistory is set to 30)
The purchase receipt callback must be yielded by a loop that calls this function every second.
Make the function return “NotProcessed” when Profile:IsActive() == false at which point you would break the loop.

8 Likes

Thanks, I figured it out. got the loading times down to 1 - 2 seconds.

1 Like

A bit confused on how to access the data store player-key. Trying to read someone’s store with the DataStore Editor 3.0 made by @sleitnick. How should I go about this?

The name of the DataStore is the ProfileStore’s name, and the key is the Profile’s key.

1 Like

Alright, so people seem to be confused on whether this is really worth switching to or not, so I’ve decided to write a little ‘review’ of sorts:

  1. How efficient is this?
    Obviously, this module was made with low footprint in mind. It doesn’t alter your Profile.Data whatsoever, and it’s as efficient as it gets. Is it perfect? no, there’s always room for improvement and little stuff to nitpick, but those barely got any impact. As it is, this module is as good as it gets.
  2. Why should I use this?
    This module was made with issues like item duplication loopholes in mind. It prevents a lot of issues such as item-duplication through session locking (which is arguably the most important use of this module), and handles a lot of other issues for you. It has signals, which allows you add logs for when your data gets corrupted, datastore completely fails, etc, Which I’ve found very useful.
  3. But does it actually work?
    Well, you can’t really say much about that, because I didn’t really have any item-duplication issues previously, and I still don’t with this. But after discussing with loleris in DMs, I can guarantee that it does it’s job as efficiently as possible. We talked about detecting whether a player has joined from a “corrupted” (crashed) server, since there could be an issue when a player joins another server, and yet their profile wasn’t released from an old one due to that server crashing and thus being unable to release a profile. We eventually came to a solution where you can detect if a player’s profile has been locked in just >80 seconds, which is really efficient if you ask me. Obviously you could use stuff like MessagingService to perform a ping operation on a server and checking if it’s “alive” or not that way, but as Roblox states, Delivery is best effort and not guaranteed. Make sure to architect your game so delivery failures are not critical., it’s not a guaranteed and trustworthy method to use. But hey, even if you can’t get instant server-crash detection, 80 seconds is still more than enough!
  4. Is this easy to setup for my game?
    Short answer: Yes.
    Long answer: It depends.
    If you’re making a game from scratch and are going to use this from the getgo, then you don’t have to worry about anything. The API page is well-documented and has great examples, allowing for an easy setup and usage of this module.
    But for some people like me, who’ve rewritten their module from scratch to support this, here’s my take on it:
    This module is based off of what it calls profiles, so if your data-handling module isn’t set up properly, it could take you some time to switch from default data to profiles. Either way, it’s really easy to just make a wrapper of this module, allowing you to for instance imitate DataStore as much as possible.
    My data-handling module looks like this:
    image
    , where in the script it’s just local DataStoreService = require(script.Wrapper).
    Easy to setup, right?

The only thing you need to get used to is the methods a profile has, such as Profile:ListenToRelease, Profile:Release etc, but those are really easy to understand and get used to, especially with the examples provided and the API page.

Hopefully I went through everything most people are interested about, but if you feel like I missed something out, feel free to ask!

12 Likes

I really appreciate this module. After using it for a bit, I think it’s easily the best method of managing player data that is publicly available. I looked a bit at your other work and was wondering if the character anti-cheat or other aspects of your Madwork system will eventually be made publicly available? There is a lack of good movement anti-cheats out there and yours seems great so far.

1 Like

Thanks!

Madwork is a commercial project, so you can only expect gradual open sourcing of few of it’s modules over the course of next few months.

2 Likes

In my opinion, this is a great module, minus the fact of possibly waiting 80 seconds for data to load. If I were in the user’s shoes, someone who wouldn’t know about how the code works behind the scenes, I sure would find it massively inconvenient to wait 80 seconds to load into the game. This takes away from user experience and in my opinion, will make players not want to join your game if they know they could just join another fun game they like that takes seconds to load. Sure that other server might not have the features that ProfileService provides, but if the player knows they can get into the action faster in another game I am sure they would choose that over a game that takes over a minute to load your data.

You should really think of a way on how to avoid this 80 second wait altogether.

The 80 second wait is hypothetical and HIGHLY unlikely in normal conditions - some people have been highlighting it too much without explaining the actual slim odds of even encountering it.

When you actually get the 80 second wait, then we can have constructive discussions about it.

5 Likes

Wouldn’t MessagingService only really fail when either, it’s hit its limits, or all the other Roblox services are failing as well due to an outage? Whifh would include DataStoreService.

Probably, but wiki says as it shouldn’t be relied upon when it comes to important stuff.

“ Delivery is best effort and not guaranteed. Make sure to architect your game so delivery failures are not critical.”

I think you are misinterpreting this line, because this way of doing things is true for other things. I see no issue of using MessagingService as a first attempt and if that fails, fall back to the methods already in place.

I’ve already talked to loleris about it, don’t remember the reply because the whole convo was pretty long, so you’d have to ask him about it. Either way 80 (theoretical) seconds isn’t bad, adding substantially more footprint just to reduce some seconds isn’t the best choice.

1 Like

80 seconds is really bad. Imagine if you were a non-developer user as I mentioned previously. You would most likely not want to play anymore when you know other games can load faster. But I mentioned all that before. Even though this 80 second thing is hypothetical, it could still happen. It lowers user experience in my opinion.

I also disagree with where you state 80 seconds is “really efficient” because that’s completely false.

Other modules dont even offer anything. Also, take this case into consideration:

  • Player joins
  • Player’s data is session locked
  • You ping the server from where their data was session locked from
  • MessagingService fails and you are unable to validate whether that ping request didn’t work because the server crashed, or because MessagingService failed.

If you have ideas to implement something <80 seconds that is also reliable, then be my guest. If not, then this is the best you’re going to get.

2 Likes