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
?
i think if roblox going down the data store service will not work either
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.
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
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.
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.
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.
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:
- You add requests to queue but never remove them. You need to remove them when they’re finished.
- You don’t queue data requests on leave, which is really important. It’s kind of the main point. Dont bother queueing get requests.
- Using 6 different data store keys greatly impacts the budget and save times. Try to only use 1 where possible.
- 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
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.