Data Store ModuleScript

Hello Devs!

Today I’ve made this Data Store ModuleScript and I want to learn more about this topic and how I can improve my methods to storing/loading player data.

Made this with a basic understandment of “Session Locking” concept, but I still having doubts about the viability of this code and if a did it correctly.

The “PlayerDataService” is another module I also made to handle in-game checking and changing data, but it doesn’t interfer in this module I’m sharing now. The same for “DefaultPlayerData” ModuleScript.

The playerData:GetInfo() is a method that returns a dictionary with the values I want to save in data store.

Whatever tips not including data store topic is very appreciated as well! :slight_smile:

Here is the code:

local DataStoreService = game:GetService("DataStoreService")
local ServerScriptService = game:GetService("ServerScriptService")
local MemoryStoreService = game:GetService("MemoryStoreService")

local PlayerDataService = require(ServerScriptService.Modules.PlayerDataService)
local DefaultPlayerData = require(ServerScriptService.Modules.PlayerDataService.PlayerData.DefaultPlayerData)

local DataStoreModule = {
	Settings = {
		MaxTries = 6,
	}
}
DataStoreModule.__index = DataStoreModule

local function Try(f, ...)
	local success, result
	for try = 1, DataStoreModule.Settings.MaxTries do
		success, result = pcall(f, ...)
		if success then break end
		task.wait(2)
	end
	print(success, result, ...)
	return success, result
end

function DataStoreModule.new(name: string)
	local newDataStore = setmetatable({}, DataStoreModule)
	newDataStore.DataStore = DataStoreService:GetDataStore(name)
	newDataStore.MemoryStore = MemoryStoreService:GetSortedMap(name)
	
	return newDataStore
end

function DataStoreModule:StartPlayerSession(player: Player)
	local success, sessionJobId = Try(self.MemoryStore.GetAsync, self.MemoryStore, player.UserId)
	if success and sessionJobId and sessionJobId ~= game.JobId then
		return player:Kick("Your player has joined twice. Please rejoin")
	end
	Try(self.MemoryStore.SetAsync, self.MemoryStore, player.UserId, game.JobId, 3000000)
end

function DataStoreModule:EndPlayerSession(player: Player)
	local success, sessionJobId = Try(self.MemoryStore.GetAsync, self.MemoryStore, player.UserId)
	if success and sessionJobId == game.JobId then
		Try(self.MemoryStore.RemoveAsync, self.MemoryStore, player.UserId)
	end
end

function DataStoreModule:LoadData(player: Player)
	local success, result = Try(self.DataStore.GetAsync, self.DataStore, player.UserId)
	
	if not success then
		return player:Kick("Failed to load your data. Please rejoin")
	end
	
	local playerData = PlayerDataService:GetPlayerData(player)
	
	for key, value in result or {} do
		playerData[key] = value
	end

	return playerData
end

function DataStoreModule:SavePlayerData(player: Player)
	local playerData = PlayerDataService:GetPlayerData(player)
	local success, result = Try(self.DataStore.SetAsync, self.DataStore, player.UserId, playerData:GetInfo())
	if not success then
		warn(result)
	end
end

return DataStoreModule

Edit: I’ve forgot to add a little code of how I’m using this Module. Here is:

PlayersService.PlayerAdded:Connect(function(player: Player)
	PlayerDataStore:StartPlayerSession(player)
	local loadedData = PlayerDataStore:LoadData(player)
    -- ...
end)

PlayersService.PlayerRemoving:Connect(function(player: Player)
	PlayerDataStore:SavePlayerData(player)
	PlayerDataStore:EndPlayerSession(player)
	-- ...
end)
1 Like

You don’t have to use MemoryStoreService for session locking. You can use metadata inside of an UpdateAsync call to see if the session is locked or not.

For example:

--> player joins
--> datascript checks if data is locked by calling updateAsync and reading metadata
--> if locked, does nothing and kicks player.
--> if unlocked, it locks it (using return updateAsync function), and lets the server script do whatever it wants w the data

If you’re deadset on using MemoryStoreService, go for it, but I suggest putting it all under DataStoreService since you no longer have to worry about two services being down, and only have to worry about one.

Also, there will never be a case where you are ending a player session without saving data. You can create a boolean parameter in your save function that will end the player session.

function DataStoreModule:SavePlayerData(player: Player, endSession: boolean)
	local playerData = PlayerDataService:GetPlayerData(player)
	local success, result = Try(self.DataStore.SetAsync, self.DataStore, player.UserId, playerData:GetInfo())
	if not success then
		warn(result)
	end
	
	if not endSession then
		return
	end

	local success, sessionJobId = Try(self.MemoryStore.GetAsync, self.MemoryStore, player.UserId)
	if success and sessionJobId == game.JobId then
		Try(self.MemoryStore.RemoveAsync, self.MemoryStore, player.UserId)
	end
end

Also I suggest not retrying 6 times. DataStore calls either fail or don’t. I think the max number of times should be 3, with 10 seconds in between them. 6 times in 12 seconds is going to spam throttle warnings anytime DataStoreService is down for the entire platform.

And if the 3 tries are up and the player has no data due to errors, don’t kick them! Most likely the entire platform is having data issues. I suggest creating some sort of “freeplay” mode where the player can play but they are told that their data will not save due to issues outside of your control.

2 Likes

Thank you so much for the tips! I’ll consider all them next time I edit this module.

Also, I’ve looked on Data Stores documentation page and found out that metadata are included on GlobalDataStore:SetAsync() too. Are there any issue or point I should consider in continue using GlobalDataStore:SetAsync() instead GlobalDataStore:UpdateAsync() in this case?

In my opinion, nobody should ever be using SetAsync because UpdateAsync is essentially “Get” and “Set” combined.

You can use metadata for SetAsync but it’s useless because you won’t be able to read the previous metadata (to see if it’s locked), which is why UpdateAsync is a must.

The absolute only alternative to this problem in the scope of only using DataStoreService is using Get and then Set, but that’s exactly what UpdateAsync does.

1 Like

I’m actually struggling with datastore currently. I’ve always saved data using a regular script and instances. But now I’m needing to save more than just one thing to the datastore. I need to save players inventory without limitations. Essentially, each player can keep how ever many items they so choose. Suppose they pickup a pickaxe from the ground that has 45 health. they later find another pickaxe that has 13 health. Its not efficient for me to just create 100 int values just incase the player picks up 100 pickaxes with all of them having different health. I know this is code review and I’m not asking for help. I’m just saying what I have going on as datastore is so dang confusing.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.