Is there a way to improve the efficiency of my DataStores?

Hello everyone, hope all is well.

I’m currently finding it very hard to implement new values to save in a DataStore. Whenever I try to add something new, then nothing saves. How can I restructure this code, so it’s future-proof and efficient?

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local RemoteEvents = ReplicatedStorage:WaitForChild("RemoteEvents")
local SettingsRemote = RemoteEvents.Settings

local FOVslider = DataStoreService:GetDataStore("Settings", "FOV")
local IGMslider = DataStoreService:GetDataStore("Settings", "IGM")
local PUNswitch = DataStoreService:GetDataStore("Settings", "PUN")
local UNIXswitch = DataStoreService:GetDataStore("Settings", "UnixTimestamp")

local FOVData, IGMData, PUNData, UNIXData

local function AutoSave(plr)
	if plr then
		local leaderstats = plr:FindFirstChild("leaderstats")

		if leaderstats then
			local FOV, IGM, PUN, UNNI = leaderstats:FindFirstChild("FOV_VALUE"), leaderstats:FindFirstChild("IGM_VALUE"), leaderstats:FindFirstChild("PUN_VALUE"), leaderstats:FindFirstChild("UNIX_VALUE")

			if FOV and IGM and PUN and UNNI then
				local success, errorMessage = pcall(function()
					if FOV.Value ~= FOVData then
						FOVslider:SetAsync(plr.UserId, FOV.Value)
					end
					
					if IGM.Value ~= IGMData then
						IGMslider:SetAsync(plr.UserId, IGM.Value)
					end
						
					if PUN.Value ~= PUNData then	
						PUNswitch:SetAsync(plr.UserId, PUN.Value)
					end
					
					
					UNIXswitch:SetAsync(plr.UserId, UNNI.Value)
				end)

				if not success then
					warn("Error saving data for "..plr.Name.." ("..errorMessage..")")
				end
			end
		end
	end
end

Players.PlayerAdded:Connect(function(Player)
	-- FIELD OF VIEW
	local FOV = Instance.new("IntValue")
	local FOVdefaultValue = 50
	FOV.Name = "FOV_VALUE"
	FOV.Parent = Player:WaitForChild("leaderstats")	
	FOV.Value = FOVdefaultValue -- Default Value
	SettingsRemote:FireClient(Player, "FOV", FOV.Value) -- Default Value

	
	local success, err = pcall(function()
		FOVData = FOVslider:GetAsync(Player.UserId)
	end)
	SettingsRemote:FireClient(Player, "FOV", FOVData)

	if success and FOVData then
		FOV.Value = FOVData
	elseif FOVData == nil then
		FOV.Value = FOVdefaultValue
		print("Oh! You must be new! We're setting you up...")
		print("Welcome to Miles by the way. :)")
		print(" ")
	end

	-- IN GAME MUSIC
	local IGM = Instance.new("NumberValue")
	local IGMdefaultValue = 0.8
	IGM.Name = "IGM_VALUE"
	IGM.Parent = Player:WaitForChild("leaderstats")	
	IGM.Value = IGMdefaultValue -- Default Value
	SettingsRemote:FireClient(Player, "IGM", IGM.Value) -- Default Value

	
	local success, err = pcall(function()
		IGMData = IGMslider:GetAsync(Player.UserId)
	end)
	SettingsRemote:FireClient(Player, "IGM", IGMData)

	if success and IGMData then
		IGM.Value = IGMData
	elseif IGMData == nil then
		IGM.Value = IGMdefaultValue
	end
	
	-- POWER-UP NOTIFICATION
	local PUN = Instance.new("BoolValue")
	local PUNdefaultValue = true
	PUN.Name = "PUN_VALUE"
	PUN.Parent = Player:WaitForChild("leaderstats")	
	PUN.Value = PUNdefaultValue -- Default Value
	SettingsRemote:FireClient(Player, "PUN", PUN.Value) -- Default Value

	
	local success, err = pcall(function()
		PUNData = PUNswitch:GetAsync(Player.UserId)
	end)
	SettingsRemote:FireClient(Player, "PUN", PUNData)

	if success and PUNData then
		PUN.Value = PUNData
	elseif PUNData == nil then
		PUN.Value = PUNdefaultValue
	end
	
	-- UNIX TIMESTAMP SETTING (first join)
	local UNI = Instance.new("NumberValue")
	local UNIdefaultValue = 0
	UNI.Name = "UNIX_VALUE"
	UNI.Parent = Player:WaitForChild("leaderstats")	
	UNI.Value = UNIdefaultValue -- Default Value
	SettingsRemote:FireClient(Player, "UNI", UNI.Value) -- Default Value


	local success, err = pcall(function()
		UNIXData = UNIXswitch:GetAsync(Player.UserId)
	end)
	SettingsRemote:FireClient(Player, "UNI", UNIXData)

	if success and UNIXData then
		if UNIXData == 0 then -- New player, set OS Time
			UNI.Value = os.time()
		else
			UNI.Value = UNIXData
		end
	elseif UNIXData == nil then -- ^ ^ Assume new join ^ ^
		UNI.Value = UNIXData or 0
	end

	while task.wait(300) do
		print("Saved Data!")
		AutoSave(Player)
	end
end)

SettingsRemote.OnServerEvent:Connect(function(Player, Type, Value)
	local leaderstats = Player:FindFirstChild("leaderstats")
    
    if Type == "FOV" then
	if leaderstats then
		local FOV = leaderstats:FindFirstChild("FOV_VALUE")

		if FOV then
			FOV.Value = Value
		end
        end
    elseif Type == "IGM" then
        if leaderstats then
            local IGM = leaderstats:FindFirstChild("IGM_VALUE")

            if IGM then
                IGM.Value = Value
            end
        end
    elseif Type == "PUN" then
        if leaderstats then
            local PUN = leaderstats:FindFirstChild("PUN_VALUE")

            if PUN then
                PUN.Value = Value
            end
		end
	elseif Type == "UNI" then
		if leaderstats then
			local UNIXX = leaderstats:FindFirstChild("UNIX_VALUE")

			if UNIXX then
				UNIXX.Value = Value
			end
		end
    end
end)

Players.PlayerRemoving:Connect(function(Player)
	AutoSave(Player)
end)

Thanks, Aki

(yes I’m very bad at DataStores, if anything they’re one of my worst nightmares)

3 Likes

Your code looks well structured and efficient, except one thing that can cause DataStores to not save entirely
Oftentimes playerRemoving will randomly not fire because the server just closes unexpectedly (network errors, shutdowns, etc). To ensure the data is saved when server shuts down, you can use game:BindToClose() to keep the server open and save the data for each player left in the server

game:BindToClose(function()
for i, player in pairs(game.Players:GetPlayers()) do
AutoSave(player)
end
end) 

You can find more info at the documentation: DataModel | Documentation - Roblox Creator Hub
Oh yea, you might consider saving data in tables when there is a lot of values to save. I only see 4 values that need saving so it’s not necessary for now. I can help you to do that though if you want :slight_smile:

2 Likes

Your code looks reasonably good, but I’m missing a few important things here for the database. As the person above mentioned before you need to create AutoSaves for internal roblox problems. Also an important thing is to do safeCall ( DataStores - Beginners to Advanced ) and in case the date is not saved correctly it repeats the save until success, hope I helped :slight_smile:

1 Like

If your game hasnt released yet, I advise using profile service (especially if you want to add trading in the future)

1 Like

AAAA more things to learn :smiley:
Trading won’t be a thing in-game (unless my friend decides otherwise), though if you don’t mind me asking, what are the benefits to ProfileService? I’ve heard about it many times, never could wrap my finger around it. :0

I’d really appreciate the help! I really suck at DataStores unfortunately heh. I’m trying to add another value, SFX. For some reason it was preventing everything from saving, I’m not sure why. I copied the same structure to no avail.

You should condense all the players’ settings into one DataStore. If you have other data you can save, you can just save a dictionary of their other data (money, xp, etc.) with their settings. You can save something like this to a generalized datastore:


DataStoreService:SetAsync(UserId, {
   General = {
      Money = 10,
      XP = 100 -- just examples
   },
   Settings = {
      FOV = 100,
      UNIXSwitch = true,
      -- other values here
   }
})

(I put a table directly in that SetAsync function to visualize what you could be doing in general for all savable data, but you can just put the players’ data from a module, i.e., DSS:SetAsync(UserId, Module.Data[UserId])

You can save, modify, and load them into a ModuleScript. That’s what I do for my game. Within that ModuleScript, you can have functions that use self to modify the data, or you can just directly index the module with other scripts.

Also, just like the user said above me said, use :BindToClose to save DataStores, as that will prevent data loss when servers are shut down.

You can also add an autosave feature as well. If you create a function that saves a players’ data, you can just call that function in a loop for that autosave. I recommend 60 seconds.

And of course, save when they leave the game using a PlayerRemoving event.

If you trust someone else’s work, I also recommend ProfileService, as it is pretty reliable and has been used in many games.

1 Like

Oh man this is quite a bit. I’m probably going to take a very long time trying to construct everything, I really want to see how this ProfileService works anyways. Does this mean I have to rework everything if I try to utilize it?

If you aren’t too confident in writing your own DataStore handling, I definitely would go with ProfileService. You may need to rewrite some code. I recommend loading the data as you would normally with this old DataStore system, putting all of the data in one table (along with any general data you have too, like I put above), and saving that with ProfileService.

If your game isn’t out, you can just switch to ProfileService directly as you wouldn’t have to worry about restoring players’ data.

1 Like

Alright, I’ll try my best. Hopefully, it won’t be too complicated.

Best of luck! The documentation has some good examples and the thread has some answers to good questions, so it shouldn’t be too too hard.

1 Like

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