Tool saving script can't handle over 10 items/in dire need of optimization

Hello! i’m bullet, currently i’ve been running into an issue with optimizing a datastore that’s key to the game i’m working on, as it needs your tools to be saved for when you rejoin, it works but the thing is you can only have 10 tools before it goes over the datastore request queue and overall the script just needs some dire help.

Script
local toolsDS = dss:GetDataStore("ToolsData")
local plr 
local debounce = true

local toolsFolder = game.ServerStorage.ToolsFolder

game.Players.PlayerAdded:Connect(function(player)
	
	plr = player
	
	local toolsSaved = toolsDS:GetAsync(plr.UserId .. "-tools") or {}

	for i, toolSaved in pairs(toolsSaved) do
	local char = plr.Character or plr.CharacterAdded:Wait()
		if plr.Backpack:FindFirstChild(toolSaved) == nil and char:FindFirstChild(toolSaved) == nil and toolsFolder[toolSaved] ~= nil then 
			toolsFolder[toolSaved]:Clone().Parent = plr.Backpack
			toolsFolder[toolSaved]:Clone().Parent = plr.StarterGear 
		end
	end

	plr.CharacterRemoving:Connect(function(char)
		char.Humanoid:UnequipTools()
	end)
end)

game.Players.PlayerRemoving:Connect(function(plr)

	local toolsOwned = {}

	for i, toolInBackpack in pairs(plr.Backpack:GetChildren()) do
		table.insert(toolsOwned, toolInBackpack.Name)
	end

	local success, errormsg = pcall(function()

		toolsDS:SetAsync(plr.UserId .. "-tools", toolsOwned)
	end)
	if errormsg then warn(errormsg) end
end)

while wait(0.1) do
	if plr ~= nil and debounce == true then
		debounce = false
		plr:WaitForChild("BoughtSomething").Changed:Connect(function()
			if plr:WaitForChild("BoughtSomething").Value == true then
				local char = plr.Character or plr.CharacterAdded:Wait()
				char.Humanoid:UnequipTools()
				local toolsOwned = {}

				for i, toolInBackpack in pairs(plr.Backpack:GetChildren()) do
					table.insert(toolsOwned, toolInBackpack.Name)
				end

				local success, errormsg = pcall(function()
					toolsDS:SetAsync(plr.UserId .. "-tools", toolsOwned)
				end)
				
				if errormsg then warn(errormsg) end
					
				task.wait()
				plr:WaitForChild("BoughtSomething").Value = false
			end
		end)
		task.wait()
		wait(1)
		debounce = true
	end
end
Issues that come up after having 10 tools

thanks for reading, help is appreciated!

You’re saving to the data store way too frequently (1/10 of a second in that while loop).

There’s a 6 second cooldown between write requests for the same key, so it’s throttling the data store usage, causing requests to be placed in a queue until it can process it. If the queue limit is exceeded (30 requests), the request is dropped entirely and that’s how data loss happens.

Page that shows all the data store limits:

Instead of auto saving every 0.1 seconds, you could try something like 5 minutes (300 seconds), so then you wouldn’t throttle the data usage.

A more optimized version might look something like this:

local Players = game:GetService("Players")
local toolsDS = dss:GetDataStore("ToolsData")

local AUTOSAVE_INTERVAL = 5 * 60 -- How frequently it auto saves (5 * 60 = 5 minutes)

-- You'll want a table to store all the player's data rather than
-- updating a single plr variable
local sessionData = {}

local toolsFolder = game.ServerStorage.ToolsFolder

local function onBoughtSomething(player)
    local boughtSomething = player:FindFirstChild("BoughtSomething")

    if boughtSomething and boughtSomething.Value == true then
        local character = player.Character or player.CharacterAdded:Wait()
        local playerDataKey = player.UserId .. "-tools"

        character.Humanoid:UnequipTools()

        local toolsOwned = {}

        for i, toolInBackpack in ipairs(player.Backpack:GetChildren()) do
	    table.insert(toolsOwned, toolInBackpack.Name)
        end
        
        sessionData[playerDataKey] = toolsOwned

        boughtSomething.Value = false
    end
end

local function onPlayerAdded(player)
    local playerDataKey = player.UserId .. "-tools"
    local savedData = nil

    sessionData[playerDataKey] = {}

    local success, errorMessage = pcall(function()
        savedData = toolsDS:GetAsync(playerDataKey)
    end)

    if success then
        if savedData then
            local character = player.Character or player.CharacterAdded:Wait()
            local backpack = player:FindFirstChild("Backpack")
            local starterGear = player:FindFirstChild("StarterGear")

            for i, toolName in ipairs(savedData) do
                local isInBackpack = backpack and backpack:FindFirstChild(toolName)
                local isInCharacter = character:FindFirstChild(toolName)
                local tool = toolsFolder:FindFirstChild(toolName)
                
                if not isInBackpack and not isInCharacter and toolToClone then
                    if backpack then
                        tool:Clone().Parent = backpack
                    end

                    if starterGear then
                        tool:Clone().Parent = starterGear
                    end
                end
            end

        end

        sessionData[playerDataKey] = savedData
    else
        warn(errorMessage)
    end

    player.CharacterRemoving:Connect(function(character)
        local humanoid = character:FindFirstChildWhichIsA("Humanoid")
        if humanoid then
            humanoid:UnequipTools()
        end
    end)

    -- I don't know when you add the BoolValue to the player, so this could be changed
    -- to your preference depending on it
    local boughtSomething = player:WaitForChild("BoughtSomething")
    local debounce = false

    boughtSomething.Changed:Connect(function()
        if not debounce then
            debounce = true

            onBoughtSomething(player)

            debounce = false
        end
    end)
end

local function saveData(playerDataKey)
    local toolsOwned = sessionData[playerDataKey]
    
    if toolsOwned then
        local success, errorMessage = pcall(function()
            toolsDS:SetAsync(playerDataKey, toolsOwned)
        end)

        if errorMessage then
            warn(errorMessage)
        end
    end
end

local function onPlayerRemoving(player)
    local playerDataKey = player.UserId .. "-tools"

    saveData(playerDataKey)

    sessionData[playerDataKey] = nil
end

local function autoSave()
    while true do
        for _, player in ipairs(Players:GetPlayers()) do
            local playerDataKey = player.UserId .. "-tools"
            
            saveData(playerDataKey)
        end        

        task.wait(AUTOSAVE_INTERVAL)
    end
end

-- Resumes the auto save thread
local autosaveRoutine = coroutine.create(autoSave)
local success = coroutine.resume(autosaveRoutine)

if not success then
	warn("Could not resume auto save routine")
end

-- In case the player joins before the script fully loads, we call onPlayerAdded
-- for players currently in game
for _, player in ipairs(Players:GetPlayers()) do
    onPlayerAdded(player)
end

Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(onPlayerRemoving)

Edit: fixed the typo on the GetAsync call. Thanks, @MysteriousVagabond, for pointing that out.

1 Like

Isn’t there a better way to do this without looping that is more responsive, can’t you just detect whenever a child is added to a player’s backpack and detect whenever it’s removed as well instead of using a while loop? Just go through a for loop of all players backpack and create a connection with childadded and childremoved, whenever a player joins just make a new connection for them too.

An example of this is with roblox’s example using collection service
https://developer.roblox.com/en-us/api-reference/function/CollectionService/GetTagged
Just replace collection service with Players, get tagged with :GetPlayers(), and use :PlayerAdded() + :PlayerRemoved()

1 Like

sorry, still doesn’t save over 10 items

Insert the items into a table, then save the table. This helps a lot with this sort of thing.

items are inserted into a table, looks for " toolsOwned " in the script and you’ll find it
it’s under the bought something function and the save data function

When I run @Coderius’s code, everything seems to work fine (although the GetAsync call has a typo); however, I noticed you mentioned that it only loads 10 tools. The built-in toolbar is limited to 10 tools, but if you press the tilde key, it’ll open up your backpack and show you any other tools you may have. Using Coderius’s code, I was able to save and load 32 tools without any problem.

2 Likes

hm, that’s weird, it might be because i’m testing it in studio, i’ll try it in game rq

edit: tried it, still does not work, do you leave and rejoin to test how many tools you can save?

I ran it in a test server in studio, added tools to my backpack, toggled the “BoughtSomething” BoolValue, left the server, then closed the server. When I restarted the test server, it loaded all the tools I had put in my backpack.

1 Like

fixed the said typo in my code, it works perfectly now, going to mark that post as solution, thank you!