I’m assuming you’re confused as to why I said this, as Roblox does not shut down the server before the last player leaves. However, the BindToClose
function will do this.
So, I think the best way to accomplish this would be to, for one, do what @MrLonely1221 suggested with the Try package, and also to have a boolean variable (or use what I later suggested), which will determine if a data store request is being validated. You would then have to do two things:
- Write to the data store when requested to do so. This might be a player leaving or an automated save process. If this fails, retry the process for up to 3 tries (or whatever limit you specify).
- Read from the data store to make sure the player’s data is there and is correctly written (this means doing a
rawequal
on two tables, if that’s how you’re storing data, or just using the ==
conditional operator). If the data does not match, you will need to retry writing the data again.
- Optionally yield for 5 seconds. This might make sure that there’s no data corruption, but through my testing it is not necessary. This should only be implemented if you are doing this in multiple threads, where frame times could be unreliable.
These two (or three) steps would functionally run when the player’s data saves, but it’s more important when the player leaves. The only issue is that the game will close any coroutines, or any script for that matter, when the server closes. That’s when BindToClose
comes into play. However, you will need to modify the implementation to do so. The way I can see it working is to have a table of pending data requests that will simply be a dictionary, indexed by the data store key, and the value would be set to the target (most recently updated value of the data store in your case). Of course, you can index these however you want to, but preferably this would be the easiest way to manage data requests with the function. Here, let’s assume your table is named “PendingDataStoreRequests”:
--BindToClose function. Please place accordingly into a server script that has these variables implemented.
for key, value in pairs(PendingDataStoreRequests) do
local result
local success
repeat
task.wait(1)
success, result = pcall(pfr.Store.GetAsync, pfr.Store, key)
print("Waiting for data to save...")
until success and result and ((typeof(result)=="table" and rawget(result)==rawget(value)) or result == value)
--The data should be saved now.
end
the way I formatted the code is by copying the tab character from a previous snippet
In this code snippet, there’s a table called “PendingDataStoreRequests,” that is present. Remember earlier in this post when I mentioned the method to save data? Well, this table will be implemented into there, and when something is written to the data store, the key and value is also written to the table. The key is removed from the table when the value is saved successfully. The BindToClose
function will stop the code from stopping until the value is written, by detecting if there’s anything left and then verifying that it is correct.
However, I’m not very confident that this will work, since BindToClose
might be in a very special thread that does not close, but other threads do close. If this is unfortunately the case, you could simply save the remaining players’ data by looping through the players in the BindToClose
function, which might be simpler to begin with. I’m not sure which method is better.
The only issue you may have with this compared to professional open-source models like ProfileService and DataStore2 is that there isn’t any session locking. There is no way to tell how the data written is from the same server yet. I’m not sure if this will be an issue for you if you are simply storing currency, but it might be an issue if you have inventory items or trading implemented in your game. I would recommend adding session locking by writing to MemoryStoreService
every hour the player plays the game, but there’s probably a better method out there to prevent session locking, and I’m not confident that what I said would work very well.
Overall, this is quite a simple solution, and it should fit your needs. If you have any further questions, feel free to ask them.