Feedback On My DataStore Script

Hello!

So I have a script that takes care of my game’s leaderstats, saving data, and loading data. It works fine most of the time, except for some occasions (examples listed bellow). I would only like to know how I can improve and optimize my code so I can improve as a developer. :slight_smile:

Examples of some limitations/errors:

  • When I shut down servers, the player’s data does not save.
  • Sometimes I receive the “Too Many DataStore Requests” warning.
  • The code is overall messy and hard to read.

My script, located in ServerScriptService:

local DataStoreService = game:GetService("DataStoreService")
local CornerData = DataStoreService:GetDataStore("CornerData")

game.Players.PlayerAdded:Connect(function(player)
	
	-- leaderstats
	local leaderstats = Instance.new("Folder", player)
	leaderstats.Name = "leaderstats"
	
	local Wins = Instance.new("IntValue", leaderstats)
	Wins.Name = "Wins"
	Wins.Value = 0
	Wins.Parent = leaderstats
	
	-- InfoFolder
	local InfoFolder = Instance.new("Folder", player)
	InfoFolder.Name = "InfoFolder"
	
	local Coins = Instance.new("IntValue", InfoFolder)
	Coins.Name = "Coins"
	Coins.Value = 0
	
	local LavaDebounce = Instance.new("BoolValue", InfoFolder)
	LavaDebounce.Name = "LavaDebounce"
	LavaDebounce.Value = false
	
	local EquipedTitle = Instance.new("StringValue", InfoFolder)
	EquipedTitle.Name = "EquipedTitle"
	EquipedTitle.Value = "Newbie"
	
	local EquipedTrail = Instance.new("StringValue", InfoFolder)
	EquipedTrail.Name = "EquipedTrail"
	EquipedTrail.Value = "None"
	
	local SpeedLevel = Instance.new("IntValue", InfoFolder)
	SpeedLevel.Name = "SpeedLevel"
	SpeedLevel.Value = 0
	
	local JumpLevel = Instance.new("IntValue", InfoFolder)
	JumpLevel.Name = "JumpLevel"
	JumpLevel.Value = 0
	
	local CoinMultiplier = Instance.new("IntValue", InfoFolder)
	CoinMultiplier.Name = "CoinMultiplier"
	CoinMultiplier.Value = 1
	
	--Get Wins
	local data
	local success, errormessage = pcall(function()
		CornerData:GetAsync(player.UserId.."-Wins")
	end)
	
	if success then
		if CornerData:GetAsync(player.UserId.."-Wins") == nil then
			Wins.Value = 0
		elseif CornerData:GetAsync(player.UserId.."-Wins") ~= nil then
			Wins.Value = CornerData:GetAsync(player.UserId.."-Wins")
		end
	end
	
	--Get Coins
	local data2
	local success2, errormessage2 = pcall(function()
		CornerData:GetAsync(player.UserId.."-Coins")
	end)
	
	if success2 then
		if CornerData:GetAsync(player.UserId.."-Coins") == nil then
			Coins.Value = 0
		elseif CornerData:GetAsync(player.UserId.."-Coins") ~= nil then
			Coins.Value = CornerData:GetAsync(player.UserId.."-Coins")
		end
	end
	
	--Get EquipedTitle
	local data3
	local success3, errormessage3 = pcall(function()
		CornerData:GetAsync(player.UserId.."-EquipedTitle")
	end)

	if success3 then
		if CornerData:GetAsync(player.UserId.."-EquipedTitle") == nil then
			EquipedTitle.Value = "Newbie"
		elseif CornerData:GetAsync(player.UserId.."-EquipedTitle") ~= nil then
			EquipedTitle.Value = CornerData:GetAsync(player.UserId.."-EquipedTitle")
		end
	end
	
	local TitleGuiClone = game.ServerStorage.Titles.TitleGui:Clone()
	local EquipedTitle = player.InfoFolder.EquipedTitle
	TitleGuiClone.Parent = game.Workspace:WaitForChild(player.Name):WaitForChild("Head")
	
	for Number, Title in ipairs(game.ServerStorage.Titles:GetChildren()) do
		if Title.Name == EquipedTitle.Value then
			local ClonedTitle = Title:Clone()
			ClonedTitle.Parent = TitleGuiClone
		end
	end
	
	--Get SpeedLevel
	local data4
	local success4, errormessage4 = pcall(function()
		CornerData:GetAsync(player.UserId.."-SpeedLevel")
	end)

	if success4 then
		if CornerData:GetAsync(player.UserId.."-SpeedLevel") == nil then
			SpeedLevel.Value = 0
		elseif CornerData:GetAsync(player.UserId.."-SpeedLevel") ~= nil then
			SpeedLevel.Value = CornerData:GetAsync(player.UserId.."-SpeedLevel")
			local Character = game.Workspace:WaitForChild(player.Name)
			local Humanoid = Character:WaitForChild("Humanoid")
			Humanoid.WalkSpeed = 16 + (SpeedLevel.Value*3)
		end
	end
	
	--Get JumpLevel
	local data5
	local success5, errormessage5 = pcall(function()
		CornerData:GetAsync(player.UserId.."-JumpLevel")
	end)

	if success5 then
		if CornerData:GetAsync(player.UserId.."-JumpLevel") == nil then
			JumpLevel.Value = 0
		elseif CornerData:GetAsync(player.UserId.."-JumpLevel") ~= nil then
			JumpLevel.Value = CornerData:GetAsync(player.UserId.."-JumpLevel")
			local Character = game.Workspace:WaitForChild(player.Name)
			local Humanoid = Character:WaitForChild("Humanoid")
			Humanoid.JumpPower = 50 + (JumpLevel.Value*3)
		end
	end
	
	--Get EquipedTrail
	local data6
	local success6, errormessage6 = pcall(function()
		CornerData:GetAsync(player.UserId.."-EquipedTrail")
	end)

	if success6 then
		if CornerData:GetAsync(player.UserId.."-EquipedTrail") == nil then
			EquipedTrail.Value = "None"
		elseif CornerData:GetAsync(player.UserId.."-EquipedTrail") ~= nil then
			EquipedTrail.Value = CornerData:GetAsync(player.UserId.."-EquipedTrail")
		end
	end

	for Number, Trail in ipairs(game.ServerStorage.Trails:GetChildren()) do
		if Trail.Name == EquipedTrail.Value then
			local ClonedTrail = Trail:Clone()
			local Character = game.Workspace:WaitForChild(player.Name)
			ClonedTrail.Parent = Character.Head
			local attachment0 = Instance.new("Attachment", Character.Head)
			attachment0.Name = "TrailAttachment0"
			local attachment1 = Instance.new("Attachment", Character.HumanoidRootPart)
			attachment1.Name = "TrailAttachment1"
			ClonedTrail.Attachment0 = attachment0
			ClonedTrail.Attachment1 = attachment1
		end
	end
end)

game.Players.PlayerRemoving:Connect(function(player)
	--Save Wins
	local success, errormessage = pcall(function()
		CornerData:SetAsync(player.UserId.."-Wins",player.leaderstats.Wins.Value)
	end)
	
	--Save Coins
	local success2, errormessage2 = pcall(function()
		CornerData:SetAsync(player.UserId.."-Coins",player.InfoFolder.Coins.Value)
	end)
	
	--Save EquipedTitle
	local success3, errormessage3 = pcall(function()
		CornerData:SetAsync(player.UserId.."-EquipedTitle",player.InfoFolder.EquipedTitle.Value)
	end)
	
	--Save SpeedLevel
	local success4, errormessage4 = pcall(function()
		CornerData:SetAsync(player.UserId.."-SpeedLevel",player.InfoFolder.SpeedLevel.Value)
	end)
	
	--Save JumpLevel
	local success5, errormessage5 = pcall(function()
		CornerData:SetAsync(player.UserId.."-JumpLevel",player.InfoFolder.JumpLevel.Value)
	end)
	
	--Save EquipedTrail
	local success6, errormessage6 = pcall(function()
		CornerData:SetAsync(player.UserId.."-EquipedTrail",player.InfoFolder.EquipedTrail.Value)
	end)
end)

As you can see, the code is very repetetive…
Any feedback is appreciated!

Thank you! :smiley:

Holy, where do I even get started?

First of all, you’re using different keys for one datastore that are all related to one player. Now a more efficient way to do this is to either save tables or use datastore scopes. How does this help your code? Well, by using GetAsync/SetAsync once on one datastore or on multiple different datastores, you prevent throttling the game’s data store usage (this might be incorrect with datastore scopes so don’t quote me on that). This also reduces the required use of pcalls (an expensive function), which further optimizes your code. Also, please cache your data received from GetAsync(), if statements like these will only increase the throttling.

if CornerData:GetAsync(player.UserId.."-EquipedTitle") == nil then
	EquipedTitle.Value = "Newbie"
elseif CornerData:GetAsync(player.UserId.."-EquipedTitle") ~= nil then
	EquipedTitle.Value = CornerData:GetAsync(player.UserId.."-EquipedTitle")
end

How you would cache the data is by using a variable to store the data received from GetAsync(), for instance,

local succ, dataRecieved  = pcall(CornerData.GetAsync, CornerData, player.UserId.."-EquipedTitle")
if dataReceived == nil then
	EquipedTitle.Value = "Newbie"
elseif dataReceived  ~= nil then
	EquipedTitle.Value = CornerData:GetAsync(player.UserId.."-EquipedTitle")
end

These are the problems I could only find with the amount of time I have to spare. The rest of your code looks fine, just remember to apply these changes. Happy coding!

2 Likes

Thank you so much for your answer! I’ll try these out later! :smiley:

Hey there!

First, take a look at how many requests you are sending:

image

image

All that stuff could be made with only 2 requests, one to load and another to save the data.

Keep in mind:

  • Use game:BindToClose to make sure the server won’t close before save the data.
  • Send as few requests as possible.
  • Make sure the data has loaded before saving it.
  • You can have multiples tries when saving or loading data.
  • Use Dictionaries.

As you are saving multiple information, it is a good idea to use Dictionaries. I made this piece of code as an example:

local DataStoreService = game:GetService("DataStoreService")
local CornerData: DataStore = DataStoreService:GetDataStore("CornerData")

local player_default_data = {
   Wins = 0,
   Coins = 0,
}

local function load(key, attempts)
   for i = 1, (attempts or 1) do
      local received, result = pcall(function()
         local dataReceived = CornerData:GetAsync(key)
         return dataReceived
      end)

      if received then
         print('Loaded')
         return result or player_default_data  -- if the data is empty, will return the default data
      else
         warn(result)  -- if it fails, "result" will be the error message
      end
   end
end

local function save(key, data, attempts)
   for i = 1, (attempts or 1) do
      local success, errorMessage = pcall(function()
         CornerData:SetAsync(key, data)
      end)

      if success then
         print('Saved')
         break
      else
         warn(errorMessage)
      end
   end
end

game.Players.PlayerAdded:Connect(function(player)
   local dataIsLoading = Instance.new('BoolValue', player)  -- when saving data, we will check for this value, because we can't save the data before loading it
   dataIsLoading.Name = 'DataIsLoading'

   local playerData = load(player.UserId, 3)  -- trying to load 3 times

   if playerData then
      dataIsLoading:Destroy()  -- data loaded, we can delete the value

      local infoFolder = Instance.new('Folder', player)
      infoFolder.Name = 'InfoFolder'

      local coins = Instance.new('IntValue', infoFolder)
      coins.Name = 'Coins'
      coins.Value = playerData.Coins

      local Wins = Instance.new('IntValue', infoFolder)
      Wins.Name = 'Wins'
      Wins.Value = playerData.Wins
   else
      warn('DATA FAILED TO LOAD')
      player:Kick('Your data failed to load, please rejoin.')  -- avoiding dataloss
   end
end)

game.Players.PlayerRemoving:Connect(function(player)
   local infoFolder = player:FindFirstChild('InfoFolder')
   local playerDataLoaded = player:FindFirstChild('DataIsLoading') == nil  -- checking if the value is not inside the player

   if infoFolder and playerDataLoaded then
      local player_data = {
         Wins = infoFolder.Wins.Value,
         Coins = infoFolder.Coins.Value,
      }

      save(player.UserId, player_data, 3)  -- trying to save 3 times
   end
end)


-- If you don't use it, the server will close before saving the data and resulting in dataloss
game:BindToClose(function()
   wait(5)  -- change this value if you want
end)

Hope it helps :slight_smile:

1 Like

Thank you very much for the reply! I am in the process of implementing the changes to my game. I just have a question; does this method use the same keys as before? I have some players who have data from before and it would be a shame if they were to loose it all… Thanks again! :smiley:

Oh, if you already have a game with players, maybe replacing the code is not a good idea, since you would need both ways (the one you are already using + dictionaries). To be honest, I don’t have experience with updating a datastore architecture, so I’m not the best to talk about it :confused:

But I think it wouldn’t be great because you would need both ways :slight_smile:

3 Likes

It’s perfectly fine, thanks for all your help and time! I’ll be sure to use dictionaries in my future games and I will definitely use game:BindToClose() since that is my main issue! :smiley:

Hope you have a good day!

1 Like