Data store is not saving because of the server fast shutdown

Doesn’t BindToClose handle all types of server shutdown? If the shutdown is due to a system-wide outage (i.e. Roblox going down) wouldn’t it be prudent to use PlayerRemoving to capture player data as they are kicked, and not spawn concurrent threads in BindToClose?

2 Likes

i think if roblox going down the data store service will not work either

1 Like

Quite possibly, maybe not, it’s a good strategy to assume the worst-case scenario. That is why I think it not a good idea to throttle any currently executing process’ during the systems “Swan Song” by spawning more.

2 Likes

i modified the script based on your help
here is the new script :-

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

local DataStoreService = game:GetService("DataStoreService")
local DataBase = DataStoreService:GetDataStore("PlayersData")

local Assets = ReplicatedStorage:WaitForChild("Assets")
local PlayersGuns = Assets:WaitForChild("PlayersGuns")

local KickedPlayers = {}
local RequestsQueue = {}

local function WaitForBudget(RequestType , BudgetAmount)
	local Budget = DataStoreService:GetRequestBudgetForRequestType(RequestType)
	while Budget < BudgetAmount do
		Budget = DataStoreService:GetRequestBudgetForRequestType(RequestType)
		task.wait(5)
	end
end

local function SaveData(Player , SessionLock , BindToClose)
	local UserId = Player.UserId
	
	if table.find(KickedPlayers , Player.UserId) then
		print("player was kicked")
		table.remove(KickedPlayers , table.find(KickedPlayers , Player.UserId))
		return
	end
	
	table.insert(RequestsQueue , UserId)
	
	if SessionLock then
		local IsSucsuss , Data = nil
		repeat
			IsSucsuss , Data = pcall(function()
				if not BindToClose then
					WaitForBudget(Enum.DataStoreRequestType.UpdateAsync , 1)
				end
				DataBase:UpdateAsync(tostring(UserId).."Session" , function(OldVersion)
					return {
						["SessionLock"] = true,
						["LastSessionTime"] = os.time(),
					}
				end)
			end)
			if not IsSucsuss then
				warn(Data)
			end
		until IsSucsuss
	end
	
	local Screws = 0
	local MaxPositions = 6
	local PlayerDataFolder = Player:FindFirstChild("Data")
	
	if PlayerDataFolder then
		local PlayerScrews = PlayerDataFolder:FindFirstChild("Screws")
		local PositionStorage = PlayerDataFolder:FindFirstChild("MaxPotions")
		if PlayerScrews and PositionStorage then
			Screws = PlayerScrews.Value
			MaxPositions = PositionStorage.Value
		end
	end
	
	local GunParts = {}
	
	if Player:FindFirstChild("OwnedGunParts") and #Player.OwnedGunParts:GetChildren() > 0 then
		for i , GunPart in pairs(Player.OwnedGunParts:GetChildren()) do
			if not table.find(GunParts , GunPart.Name) then
				table.insert(GunParts , GunPart.Name)
			end
		end
	end
	
	local UsedGunParts = {}
	
	if Player:FindFirstChild("UsedGunPieces") then
		for i , Value in pairs(Player.UsedGunPieces:GetChildren()) do
			if Value.Name == "Front" then
				UsedGunParts["UsedFrontPiece"] = Value.Value
			elseif Value.Name == "Middle" then
				UsedGunParts["UsedMiddlePiece"] = Value.Value
			elseif Value.Name == "Back" then
				UsedGunParts["UsedBackPiece"] = Value.Value
			end
		end
	end
	
	local Gears = {}
	
	if Player:FindFirstChild("OwnedGears") and #Player.OwnedGears:GetChildren() > 0 then
		for i , Gear in pairs(Player.OwnedGears:GetChildren()) do
			if not table.find(Gears , Gear.Name) then
				table.insert(Gears , Gear.Name)
			end
		end
	end
	
	local UsedGear = nil
	if Player:FindFirstChild("UsedGear") then
		UsedGear = Player.UsedGear.Value
	end
	
	local Potions = {}

	if Player:FindFirstChild("OwnedPotions") and #Player.OwnedPotions:GetChildren() > 0 then
		for i , Potion in pairs(Player.OwnedPotions:GetChildren()) do
			for i = 1 , Potion.Value do
				table.insert(Potions , Potion.Name)
			end
		end
	end
	
	local Secsuss , ErrorMessage = nil
	
	repeat
		Secsuss , ErrorMessage = pcall(function()
			if not BindToClose then
				WaitForBudget(Enum.DataStoreRequestType.UpdateAsync , 6)
			end

			DataBase:UpdateAsync(tostring(UserId) , function(OldVersion)
				return {
					["Screws"] = Screws,
					["MaxPotions"] = MaxPositions,
				}
			end)
			
			DataBase:UpdateAsync(tostring(UserId).."GunParts" , function(OldVersion)
				return GunParts
			end)
			
			DataBase:UpdateAsync(tostring(UserId).."UsedGunParts" , function(OldVersion)
				return UsedGunParts
			end)
			
			DataBase:UpdateAsync(tostring(UserId).."Gears" , function(OldVersion)
				return Gears
			end)
			
			DataBase:UpdateAsync(tostring(UserId).."UsedGear" , function(OldVersion)
				return UsedGear
			end)
			
			DataBase:UpdateAsync(tostring(UserId).."Potions" , function(OldVersion)
				return Potions
			end)
			
		end)
	until Secsuss
	
	if SessionLock then
		local IsSucsess2 , Data2 = nil
		repeat
			if not BindToClose then
				WaitForBudget(Enum.DataStoreRequestType.UpdateAsync , 1)
			end
			IsSucsess2 , Data2 = pcall(function()
				DataBase:UpdateAsync(tostring(UserId).."Session" , function(OldVersion)
					return {
						["SessionLock"] = false,
						["LastSessionTime"] = os.time(),
					}
				end)
			end)
		until IsSucsess2
		print("yes")
	end
	if table.find(RequestsQueue , UserId) then
		table.remove(RequestsQueue , table.find(RequestsQueue , UserId))
	end
end

local function LoadCertainData(Key:string , Player:Player , DefualtData)
	local Data = nil
	repeat
		WaitForBudget(Enum.DataStoreRequestType.UpdateAsync , 1)
		local Sucsess = pcall(function()
			DataBase:UpdateAsync(Key , function(OldVersion)
				if OldVersion then
					Data = OldVersion
					return Data
				else
					return DefualtData
				end
			end)
		end)
	until (Sucsess and Data) or not PlayersService:FindFirstChild(Player.Name)
	return Data
end

local function LoadData(Player:Player)
	table.insert(RequestsQueue , Player.UserId)
	
	local Character = Player.CharacterAppearanceLoaded:Wait()
	
	for i , Object in pairs(Player.PlayerGui:GetDescendants()) do
		if Object:IsA("LocalScript") then
			Object.Enabled = false
		end
	end
	
	local SessionLock , SessionLockTime , IsSucsess , Error = nil
	
	repeat
		WaitForBudget(Enum.DataStoreRequestType.UpdateAsync , 1)
		IsSucsess , Error = pcall(function()
			DataBase:UpdateAsync(tostring(Player.UserId).."Session" , function(OldVersion)
				if OldVersion then
					SessionLock = OldVersion.SessionLock or false
					SessionLockTime = OldVersion.LastSessionTime or os.time()
					if SessionLock == true and (os.time() - SessionLockTime) < 1800 then
						table.insert(KickedPlayers , Player.UserId)
						Player:Kick("Failed to load your data , please try again later")
						if table.find(RequestsQueue , Player.UserId) then
							table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
						end
						return nil
					else
						SessionLock = false
						SessionLockTime = os.time()
						return {
							["SessionLock"] = true,
							["LastSessionTime"] = SessionLockTime,
						}
					end
				else
					SessionLock = false
					SessionLockTime = os.time()
					return {
						["SessionLock"] = true,
						["LastSessionTime"] = SessionLockTime,
					}
				end
			end)
		end)
		
	until (IsSucsess and SessionLock ~= nil and SessionLockTime) or not PlayersService:FindFirstChild(Player.Name)
	
	if SessionLock or not PlayersService:FindFirstChild(Player.Name) then
		if table.find(RequestsQueue , Player.UserId) then
			table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
		end
		return
	end
	
	local Folder = Instance.new("Folder")
	Folder.Name = "Data"
	Folder.Parent = Player
	
	local Screws = Instance.new("IntValue")
	Screws.Name = "Screws"
	Screws.Parent = Folder
	
	local PotionsStorage = Instance.new("IntValue")
	PotionsStorage.Name = "MaxPotions"
	PotionsStorage.Parent = Folder
	
	
	
	local DefaultPlayerData = {
		["Screws"] = 0,
		["MaxPotions"] = 6,
	}
	
	local PlayerData = LoadCertainData(tostring(Player.UserId) , Player , DefaultPlayerData)
	
	if not PlayersService:FindFirstChild(Player.Name) then
		if table.find(RequestsQueue , Player.UserId) then
			table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
		end
		return
	end
	
	Screws.Value = PlayerData.Screws
	PotionsStorage.Value = PlayerData.MaxPotions
	
	local GunPartsFolder = Instance.new("Folder")
	GunPartsFolder.Name = "OwnedGunParts"
	GunPartsFolder.Parent = Player
	
	local PlayerGunParts = LoadCertainData(tostring(Player.UserId).."GunParts" , Player , {"Default Chamber" , "Default Loading Port" , "Default Comb"})
	
	if not PlayersService:FindFirstChild(Player.Name) then
		if table.find(RequestsQueue , Player.UserId) then
			table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
		end
		return
	end
	
	if PlayerGunParts then
		for i , GunPart in PlayerGunParts do
			local Value = Instance.new("StringValue")
			Value.Name = GunPart
			Value.Parent = GunPartsFolder
		end
	end
	
	local PlayerUsedGunPieces = Instance.new("Folder")
	PlayerUsedGunPieces.Name = "UsedGunPieces"
	PlayerUsedGunPieces.Parent = Player
	
	local UsedPlayerGunParts = LoadCertainData(tostring(Player.UserId).."UsedGunParts" , Player , {
		["UsedFrontPiece"] = "Default Chamber",
		["UsedMiddlePiece"] = "Default Loading Port",
		["UsedBackPiece"] = "Default Comb",
	})
	
	if not PlayersService:FindFirstChild(Player.Name) then
		if table.find(RequestsQueue , Player.UserId) then
			table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
		end
		return
	end

	if UsedPlayerGunParts then
		local Value1 = Instance.new("StringValue")
		Value1.Name = "Front"
		Value1.Value = UsedPlayerGunParts.UsedFrontPiece
		Value1.Parent = PlayerUsedGunPieces
		local Value2 = Instance.new("StringValue")
		Value2.Name = "Middle"
		Value2.Value = UsedPlayerGunParts.UsedMiddlePiece
		Value2.Parent = PlayerUsedGunPieces
		local Value3 = Instance.new("StringValue")
		Value3.Name = "Back"
		Value3.Value = UsedPlayerGunParts.UsedBackPiece
		Value3.Parent = PlayerUsedGunPieces
	end
	
	local GearsFolder = Instance.new("Folder")
	GearsFolder.Name = "OwnedGears"
	GearsFolder.Parent = Player

	local PlayerGears = LoadCertainData(tostring(Player.UserId).."Gears" , Player , {})
	
	if not PlayersService:FindFirstChild(Player.Name) then
		if table.find(RequestsQueue , Player.UserId) then
			table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
		end
		return
	end

	if PlayerGears then
		for i , Gear in PlayerGears do
			local Value = Instance.new("StringValue")
			Value.Name = Gear
			Value.Parent = GearsFolder
		end
	end
	
	local PlayerUsedGear = LoadCertainData(tostring(Player.UserId).."UsedGear" , Player , "")
	
	if not PlayersService:FindFirstChild(Player.Name) then
		if table.find(RequestsQueue , Player.UserId) then
			table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
		end
		return
	end

	local Value = Instance.new("StringValue")
	Value.Name = "UsedGear"
	Value.Value = if GearsFolder:FindFirstChild(PlayerUsedGear) then PlayerUsedGear else ""
	Value.Parent = Player
	
	local PotionsFolder = Instance.new("Folder")
	PotionsFolder.Name = "OwnedPotions"
	PotionsFolder.Parent = Player

	local PlayerPotions = LoadCertainData(tostring(Player.UserId).."Potions" , Player , {})
	
	if not PlayersService:FindFirstChild(Player.Name) then
		if table.find(RequestsQueue , Player.UserId) then
			table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
		end
		return
	end

	if PlayerPotions then
		for i , Potion in PlayerPotions do
			if PotionsFolder:FindFirstChild(Potion) then
				local ExistedValue = PotionsFolder[Potion]
				ExistedValue.Value += 1
			else
				local Value = Instance.new("IntValue")
				Value.Name = Potion
				Value.Value = 1
				Value.Parent = PotionsFolder
			end
		end
	end
	
	for i , Object in pairs(Player.PlayerGui:GetDescendants()) do
		if Object:IsA("LocalScript") then
			Object.Enabled = true
		end
	end
	
end


game.Players.PlayerAdded:Connect(function(Player)
	if not table.find(RequestsQueue , Player.UserId) then
		task.spawn(LoadData , Player)
	end
end)
game.Players.PlayerRemoving:Connect(function(Player)
	if not table.find(RequestsQueue , Player.UserId) then
		task.spawn(SaveData , Player , true , false)
	end
end)

game:BindToClose(function()
	for i, Player in pairs(game.Players:GetChildren()) do
		if not table.find(RequestsQueue , Player.UserId) then
			task.spawn(SaveData , Player , true , true)
		end
	end
	repeat
		task.wait(1)
	until #RequestsQueue < 1
end)

while true do
	task.wait(60)
	for i , Player in ipairs(PlayersService:GetChildren()) do
		if not table.find(RequestsQueue , Player.UserId) then
			task.spawn(SaveData , Player , false , false)
		end
	end
end



if there is any issues with the new script , please inform it here because i really need help
@12345koip i am sorry for mentioning you but i really need your thoughts about the new script.

That’s not quite right.

Change that to this:

game:BindToClose(function()
    task.wait(if game:GetService("RunService"):IsStudio() then 5 else 30)
end)

And your queueing isn’t quite right.

You’re only adding to the queue here. Add my function from above and call it instead of that table.insert statement.
(Here’s the function for reference)

local queue = {} --the table of queued data is necessary

local function queueRequest(player: Player): ()
    table.insert(queue, player.UserId) --add to request queue

    --wait until the request is at the front of the queue
    repeat
        task.wait(1)
    until
        table.find(queue, player.UserId) == 1

    --remove request from queue
    table.remove(queue, 1)

    --wait extra
    task.wait(1)
end
1 Like

i just wanted to make the wait more dynamic to make the bindtoclose function just wait for the script to save every thing and then close , so the queue system is to make it save in serial instead of saving every players data in the same time to make less requests to the data store , now i understand why my new version have minor issues , thank you so you much for your help again.

1 Like

does not this may cause it to do not save the data on bindtoclose ? i do not mean the function it is self i mean when the server shut down for unknown reason ?

Yes, but you already have a Players.PlayerRemoving connection, which fires whenever the player disconnects from the game, including if they get kicked for server shutdown.

1 Like

is there a way to prevent this ? because if it fails the player will need to wait 30 min to enter the game again

i think this should work :-

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

local DataStoreService = game:GetService("DataStoreService")
local DataBase = DataStoreService:GetDataStore("PlayersData")

local Assets = ReplicatedStorage:WaitForChild("Assets")
local PlayersGuns = Assets:WaitForChild("PlayersGuns")

local KickedPlayers = {}
local RequestsQueue = {}

local function WaitForBudget(RequestType , BudgetAmount)
	local Budget = DataStoreService:GetRequestBudgetForRequestType(RequestType)
	while Budget < BudgetAmount do
		Budget = DataStoreService:GetRequestBudgetForRequestType(RequestType)
		task.wait(5)
	end
end

local function AddRequestToQueue(Player:Player , IsLoading)
	table.insert(RequestsQueue , Player.UserId)
	
	repeat
		task.wait(1)
	until table.find(RequestsQueue , Player.UserId) == 1 or (IsLoading == true and not PlayersService:FindFirstChild(Player.Name))
end

local function SaveData(Player , SessionLock)
	local UserId = Player.UserId
	
	if table.find(KickedPlayers , Player.UserId) then
		print("player was kicked")
		table.remove(KickedPlayers , table.find(KickedPlayers , Player.UserId))
		return
	end
	
	if SessionLock then
		local IsSucsuss , Data = nil
		repeat
			IsSucsuss , Data = pcall(function()
				WaitForBudget(Enum.DataStoreRequestType.UpdateAsync , 1)
				DataBase:UpdateAsync(tostring(UserId).."Session" , function(OldVersion)
					return {
						["SessionLock"] = true,
						["LastSessionTime"] = os.time(),
					}
				end)
			end)
			if not IsSucsuss then
				warn(Data)
			end
		until IsSucsuss
	end
	
	local Screws = 0
	local MaxPositions = 6
	local PlayerDataFolder = Player:FindFirstChild("Data")
	
	if PlayerDataFolder then
		local PlayerScrews = PlayerDataFolder:FindFirstChild("Screws")
		local PositionStorage = PlayerDataFolder:FindFirstChild("MaxPotions")
		if PlayerScrews and PositionStorage then
			Screws = PlayerScrews.Value
			MaxPositions = PositionStorage.Value
		end
	end
	
	local GunParts = {}
	
	if Player:FindFirstChild("OwnedGunParts") and #Player.OwnedGunParts:GetChildren() > 0 then
		for i , GunPart in pairs(Player.OwnedGunParts:GetChildren()) do
			if not table.find(GunParts , GunPart.Name) then
				table.insert(GunParts , GunPart.Name)
			end
		end
	end
	
	local UsedGunParts = {}
	
	if Player:FindFirstChild("UsedGunPieces") then
		for i , Value in pairs(Player.UsedGunPieces:GetChildren()) do
			if Value.Name == "Front" then
				UsedGunParts["UsedFrontPiece"] = Value.Value
			elseif Value.Name == "Middle" then
				UsedGunParts["UsedMiddlePiece"] = Value.Value
			elseif Value.Name == "Back" then
				UsedGunParts["UsedBackPiece"] = Value.Value
			end
		end
	end
	
	local Gears = {}
	
	if Player:FindFirstChild("OwnedGears") and #Player.OwnedGears:GetChildren() > 0 then
		for i , Gear in pairs(Player.OwnedGears:GetChildren()) do
			if not table.find(Gears , Gear.Name) then
				table.insert(Gears , Gear.Name)
			end
		end
	end
	
	local UsedGear = nil
	if Player:FindFirstChild("UsedGear") then
		UsedGear = Player.UsedGear.Value
	end
	
	local Potions = {}

	if Player:FindFirstChild("OwnedPotions") and #Player.OwnedPotions:GetChildren() > 0 then
		for i , Potion in pairs(Player.OwnedPotions:GetChildren()) do
			for i = 1 , Potion.Value do
				table.insert(Potions , Potion.Name)
			end
		end
	end
	
	AddRequestToQueue(Player , false)
	
	local Secsuss , ErrorMessage = nil
	
	repeat
		Secsuss , ErrorMessage = pcall(function()
			WaitForBudget(Enum.DataStoreRequestType.UpdateAsync , 1)

			DataBase:UpdateAsync(tostring(UserId) , function(OldVersion)
				return {
					["Screws"] = Screws,
					["MaxPotions"] = MaxPositions,
				}
			end)
			
			DataBase:UpdateAsync(tostring(UserId).."GunParts" , function(OldVersion)
				return GunParts
			end)
			
			DataBase:UpdateAsync(tostring(UserId).."UsedGunParts" , function(OldVersion)
				return UsedGunParts
			end)
			
			DataBase:UpdateAsync(tostring(UserId).."Gears" , function(OldVersion)
				return Gears
			end)
			
			DataBase:UpdateAsync(tostring(UserId).."UsedGear" , function(OldVersion)
				return UsedGear
			end)
			
			DataBase:UpdateAsync(tostring(UserId).."Potions" , function(OldVersion)
				return Potions
			end)
			
		end)
	until Secsuss
	
	if SessionLock then
		local IsSucsess2 , Data2 = nil
		repeat
			WaitForBudget(Enum.DataStoreRequestType.UpdateAsync , 1)
			IsSucsess2 , Data2 = pcall(function()
				DataBase:UpdateAsync(tostring(UserId).."Session" , function(OldVersion)
					return {
						["SessionLock"] = false,
						["LastSessionTime"] = os.time(),
					}
				end)
			end)
		until IsSucsess2
		print("yes")
	end
	
	if table.find(RequestsQueue , UserId) then
		table.remove(RequestsQueue , table.find(RequestsQueue , UserId))
	end
end

local function LoadCertainData(Key:string , Player:Player , DefualtData)
	local Data = nil
	repeat
		WaitForBudget(Enum.DataStoreRequestType.UpdateAsync , 1)
		local Sucsess = pcall(function()
			DataBase:UpdateAsync(Key , function(OldVersion)
				if OldVersion then
					Data = OldVersion
					return Data
				else
					return DefualtData
				end
			end)
		end)
	until (Sucsess and Data) or not PlayersService:FindFirstChild(Player.Name)
	return Data
end

local function LoadData(Player:Player)
	AddRequestToQueue(Player , true)
	
	local Loaded = Player.CharacterAppearanceLoaded:Wait()
	
	for i , Object in pairs(Player.PlayerGui:GetDescendants()) do
		if Object:IsA("LocalScript") then
			Object.Enabled = false
		end
	end
	
	local SessionLock , SessionLockTime , IsSucsess , Error = nil
	
	repeat
		WaitForBudget(Enum.DataStoreRequestType.UpdateAsync , 1)
		IsSucsess , Error = pcall(function()
			DataBase:UpdateAsync(tostring(Player.UserId).."Session" , function(OldVersion)
				if OldVersion then
					SessionLock = OldVersion.SessionLock or false
					SessionLockTime = OldVersion.LastSessionTime or os.time()
					if SessionLock == true and (os.time() - SessionLockTime) < 1800 then
						table.insert(KickedPlayers , Player.UserId)
						Player:Kick("Failed to load your data , please try again later")
						if table.find(RequestsQueue , Player.UserId) then
							table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
						end
						return nil
					else
						SessionLock = false
						SessionLockTime = os.time()
						return {
							["SessionLock"] = true,
							["LastSessionTime"] = SessionLockTime,
						}
					end
				else
					SessionLock = false
					SessionLockTime = os.time()
					return {
						["SessionLock"] = true,
						["LastSessionTime"] = SessionLockTime,
					}
				end
			end)
		end)
		
	until (IsSucsess and SessionLock ~= nil and SessionLockTime) or not PlayersService:FindFirstChild(Player.Name)
	
	if SessionLock or not PlayersService:FindFirstChild(Player.Name) then
		if table.find(RequestsQueue , Player.UserId) then
			table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
		end
		return
	end
	
	local Folder = Instance.new("Folder")
	Folder.Name = "Data"
	Folder.Parent = Player
	
	local Screws = Instance.new("IntValue")
	Screws.Name = "Screws"
	Screws.Parent = Folder
	
	local PotionsStorage = Instance.new("IntValue")
	PotionsStorage.Name = "MaxPotions"
	PotionsStorage.Parent = Folder
	
	
	
	local DefaultPlayerData = {
		["Screws"] = 0,
		["MaxPotions"] = 6,
	}
	
	local PlayerData = LoadCertainData(tostring(Player.UserId) , Player , DefaultPlayerData)
	
	if not PlayersService:FindFirstChild(Player.Name) then
		if table.find(RequestsQueue , Player.UserId) then
			table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
		end
		return
	end
	
	Screws.Value = PlayerData.Screws
	PotionsStorage.Value = PlayerData.MaxPotions
	
	local GunPartsFolder = Instance.new("Folder")
	GunPartsFolder.Name = "OwnedGunParts"
	GunPartsFolder.Parent = Player
	
	local PlayerGunParts = LoadCertainData(tostring(Player.UserId).."GunParts" , Player , {"Default Chamber" , "Default Loading Port" , "Default Comb"})
	
	if not PlayersService:FindFirstChild(Player.Name) then
		if table.find(RequestsQueue , Player.UserId) then
			table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
		end
		return
	end
	
	if PlayerGunParts then
		for i , GunPart in PlayerGunParts do
			local Value = Instance.new("StringValue")
			Value.Name = GunPart
			Value.Parent = GunPartsFolder
		end
	end
	
	local PlayerUsedGunPieces = Instance.new("Folder")
	PlayerUsedGunPieces.Name = "UsedGunPieces"
	PlayerUsedGunPieces.Parent = Player
	
	local UsedPlayerGunParts = LoadCertainData(tostring(Player.UserId).."UsedGunParts" , Player , {
		["UsedFrontPiece"] = "Default Chamber",
		["UsedMiddlePiece"] = "Default Loading Port",
		["UsedBackPiece"] = "Default Comb",
	})
	
	if not PlayersService:FindFirstChild(Player.Name) then
		if table.find(RequestsQueue , Player.UserId) then
			table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
		end
		return
	end

	if UsedPlayerGunParts then
		local Value1 = Instance.new("StringValue")
		Value1.Name = "Front"
		Value1.Value = UsedPlayerGunParts.UsedFrontPiece
		Value1.Parent = PlayerUsedGunPieces
		local Value2 = Instance.new("StringValue")
		Value2.Name = "Middle"
		Value2.Value = UsedPlayerGunParts.UsedMiddlePiece
		Value2.Parent = PlayerUsedGunPieces
		local Value3 = Instance.new("StringValue")
		Value3.Name = "Back"
		Value3.Value = UsedPlayerGunParts.UsedBackPiece
		Value3.Parent = PlayerUsedGunPieces
	end
	
	local GearsFolder = Instance.new("Folder")
	GearsFolder.Name = "OwnedGears"
	GearsFolder.Parent = Player

	local PlayerGears = LoadCertainData(tostring(Player.UserId).."Gears" , Player , {})
	
	if not PlayersService:FindFirstChild(Player.Name) then
		if table.find(RequestsQueue , Player.UserId) then
			table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
		end
		return
	end

	if PlayerGears then
		for i , Gear in PlayerGears do
			local Value = Instance.new("StringValue")
			Value.Name = Gear
			Value.Parent = GearsFolder
		end
	end
	
	local PlayerUsedGear = LoadCertainData(tostring(Player.UserId).."UsedGear" , Player , "")
	
	if not PlayersService:FindFirstChild(Player.Name) then
		if table.find(RequestsQueue , Player.UserId) then
			table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
		end
		return
	end

	local Value = Instance.new("StringValue")
	Value.Name = "UsedGear"
	Value.Value = if GearsFolder:FindFirstChild(PlayerUsedGear) then PlayerUsedGear else ""
	Value.Parent = Player
	
	local PotionsFolder = Instance.new("Folder")
	PotionsFolder.Name = "OwnedPotions"
	PotionsFolder.Parent = Player

	local PlayerPotions = LoadCertainData(tostring(Player.UserId).."Potions" , Player , {})
	
	if not PlayersService:FindFirstChild(Player.Name) then
		if table.find(RequestsQueue , Player.UserId) then
			table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
		end
		return
	end

	if PlayerPotions then
		for i , Potion in PlayerPotions do
			if PotionsFolder:FindFirstChild(Potion) then
				local ExistedValue = PotionsFolder[Potion]
				ExistedValue.Value += 1
			else
				local Value = Instance.new("IntValue")
				Value.Name = Potion
				Value.Value = 1
				Value.Parent = PotionsFolder
			end
		end
	end
	
	if table.find(RequestsQueue , Player.UserId) then
		table.remove(RequestsQueue , table.find(RequestsQueue , Player.UserId))
	end
	
	if PlayersService:FindFirstChild(Player.Name) then
		for i , Object in pairs(Player.PlayerGui:GetDescendants()) do
			if Object:IsA("LocalScript") then
				Object.Enabled = true
			end
		end
	end
	
end


game.Players.PlayerAdded:Connect(function(Player)
	task.spawn(LoadData , Player)
end)
game.Players.PlayerRemoving:Connect(function(Player)
	if not table.find(RequestsQueue , Player.UserId) then
		task.spawn(SaveData , Player , true)
	end
end)

game:BindToClose(function()
	task.wait(5)
	repeat
		task.wait(1)
	until #RequestsQueue < 1
end)

while true do
	task.wait(60)
	for i , Player in ipairs(PlayersService:GetChildren()) do
		if not table.find(RequestsQueue , Player.UserId) then
			task.spawn(SaveData , Player , true)
		end
	end
end



``` i know that there is some minor changes like the addtoqueue function not being called at the top of the save function but this is because the data is not cashed so it gets the data first then waits for the queue

i tested it and it takes 9 sec to save one player data !

Don’t bother checking the request budget on BindToClose. also, you have 3 different data stores which will significantly add to the time it takes to save data.

You also need to add more waiting at the end of your BindToClose to give the last few requests time to process.

1 Like

actually is has 6 , i made like that because if it failed to save or load the data the player does not lose every thing

i tested it with 3 players , it loaded the data fine but did not save the data for all the players it saved it for only 1 !

i hope that i am not bothering you but i really need help , it sometimes stuck in the queue , i do not if this happens in both saving and loading but i am sure this happens in loading

A few issues:

  1. You add requests to queue but never remove them. You need to remove them when they’re finished.
  2. You don’t queue data requests on leave, which is really important. It’s kind of the main point. Dont bother queueing get requests.
  3. Using 6 different data store keys greatly impacts the budget and save times. Try to only use 1 where possible.
  4. You’re still using multiple threads to save data on leaving, so the thread issue still is present. Thr queue should stop this, but task.spawn is
    unneccessary because connections will run in separate threads if you havent changed signal behaviour.

The most important part here is fixing your queue implementation.

I’m going to sleep now so i might not reply for a few hours sorry

1 Like

Just save the data and leave it at that. You’re trying to do too many things and you just don’t have all the time in the world when they leave. No task spawns or if add this, if add that, no bind on close. just simply save the data on player.remove. Done.

i remove the requests in the function it self not in the addrequesttoqueue function , i can combined them in one key but this will make it if that key got lost or corrupted all the data will be lost and also what about the characters limit ? , how i forgot this ? i am gonna remove the unnecessary task.spawn

i wish if it was this easy but these additions make it more secure and safe

Keys can’t get ‘lost’. They’ll stay until you remove them. Only version history can get ‘lost’ - it gets deleted after 30 days.

Using multiple keys isn’t the way to prevent data loss or corruption. You should autosave regularly, and you can have a function that checks data integrity for corruption. You can retrieve older data versions with ListVersionsAsync and GetVersionAsync. If you really want, you can have a backup data store.

The limit for a data store key is 4,194,304 bytes, which is about 4 megabytes. The data you’re saving doesn’t appear to come even close to this limit, but only once you have data that could hit the limit should you start using multiple keys.

1 Like