Optimize datastore requests


That’s the issue I’m getting

Here’s the script:

local Players=game:GetService('Players')
local TempPart=script.Parent
--[[]]--
local SurfaceGui=TempPart.SurfaceGui
local MainFrame=SurfaceGui.Frame
--[[]]--
local Template=script['Template']
--[[]]--
local RecentlySaved={}
local DataStoreService=game:GetService('DataStoreService')
local GlobalLeaderboardB=DataStoreService:GetOrderedDataStore(":DataStore")
if not game["Run Service"]:IsStudio() then
	GlobalLeaderboardB=DataStoreService:GetOrderedDataStore("Game:DataStore")
end
--[[ SETTINGS ]]--
local CurrencyName="Best Time"
local ListSize=100
local UpdateEvery=120
local MinimumRequirement=100
--[[]]--
local _functions={}
_functions.returncurrency = function(v)
	local x=0
	for a, b in pairs(v:GetDescendants()) do
		if b.Name==CurrencyName then
			x=b.Value
			break
		end
	end
	return x
end
_functions.removefromrecentlysaved=function(PlayerName)
	for i, v in pairs(RecentlySaved) do 
		if v==PlayerName then
			table.remove(RecentlySaved,i)
			break
		end
	end
end
_functions.autoremoverecentsaved=function(PlayerName)
	spawn(function()
		wait(15)
		for i, v in pairs(RecentlySaved) do 
			if v==PlayerName then
				table.remove(RecentlySaved,i)
				break
			end
		end
	end)
end
_functions.returnplayerlist=function()
	local int = 0
	for i, v in pairs(Players:GetPlayers()) do 
		for _, j in pairs(v:GetDescendants()) do 
			if j.Name==CurrencyName and j.Value>MinimumRequirement then
				int+=1
				break
			end
		end
	end
	return int
end
_functions.clear=function()
	for i, v in pairs(MainFrame:GetChildren()) do 
		if v:IsA('Frame') then
			v:Destroy()
		end
	end
end
_functions.abbreviate=function(value,idp)
	if value < 1000 then
		return math.floor(value + 0.5)
	else
		local abbreviations = {"", "K", "M", "B", "T"}
		local ex = math.floor(math.log(math.max(1, math.abs(value)),1000))
		--[[]]--
		local abbrevs = abbreviations [1 + ex] or ("e+"..ex)
		local normal = math.floor(value * ((10 ^ idp) / (1000 ^ ex))) / (10 ^ idp)
		--[[]]--
		return ("%."..idp.."f%s"):format(normal, abbrevs)
	end
end
--[[]]--
Players.PlayerRemoving:Connect(function(Player)
	local PName=Player.Name
	if not table.find(RecentlySaved,Player.Name) then
		table.insert(RecentlySaved,Player.Name)
		local CurrentCurrencyAmount=_functions.returncurrency(Player)
		local Success,Errormsg=pcall(function()
			GlobalLeaderboardB:SetAsync(Player.UserId,CurrentCurrencyAmount)
		end)
		if not Success then warn(Errormsg) end
		_functions.removefromrecentlysaved(PName)
	else 
	end
end)
--[[]]--
while true do 
	task.wait()
	_functions.clear()
	repeat wait() until _functions.returnplayerlist()>0
	for i, Player in pairs(Players:GetPlayers()) do 
		local PName=Player.Name
		if not table.find(RecentlySaved,Player.Name) then
			table.insert(RecentlySaved,Player.Name)
			local CurrentCurrencyAmount=_functions.returncurrency(Player)
			local Success,Errormsg=pcall(function()
				GlobalLeaderboardB:SetAsync(Player.UserId,CurrentCurrencyAmount)
			end)
			if not Success then warn(Errormsg) end
			_functions.autoremoverecentsaved(PName)
		end
	end
	local Pages = GlobalLeaderboardB:GetSortedAsync(false, ListSize)
	local TopList = Pages:GetCurrentPage()
	for Rank, SavedData in ipairs(TopList) do
		local UserId = SavedData.key
		local CAmount=SavedData.value
		--[[]]--
		if CAmount>=MinimumRequirement then
			local Template=script['Template']:Clone()
			Template.Parent=MainFrame
			Template.LayoutOrder=Rank
			Template.Score.Text="".._functions.abbreviate(CAmount,1)
			local function SetRankText(n)
				local A={}
				A[1]=function()
					Template.Rank.Text="🥇"
				end
				A[2]=function()
					Template.Rank.Text="🥈"
				end
				A[3]=function()
					Template.Rank.Text="🥉"
				end
				if n<=#A then
					A[n]()
				else Template.Rank.Text="#"..n
				end
			end
			SetRankText(Rank)
			--[[]]--
			local User="Unknown"
			local Success,Errormsg=pcall(function()
				User=Players:GetNameFromUserIdAsync(UserId)
			end)
			Template.Username.Text=User
			Template.Name=User
		end
	end
	task.wait(UpdateEvery)
end

How would I optimize that?

Idk if this will fix the problem ur having

local Players = game:GetService('Players')
local TempPart = script.Parent
local SurfaceGui = TempPart.SurfaceGui
local MainFrame = SurfaceGui.Frame
local Template = script:WaitForChild('Template')
local RecentlySaved = {}
local DataStoreService = game:GetService('DataStoreService')
local CurrencyName = "Best Time"
local ListSize = 100
local UpdateEvery = 120
local MinimumRequirement = 100

local GlobalLeaderboardB = DataStoreService:GetOrderedDataStore(game["Run Service"]:IsStudio() and ":DataStore" or "Game:DataStore")

local function returnCurrency(player)
    for _, descendant in pairs(player:GetDescendants()) do
        if descendant.Name == CurrencyName then
            return descendant.Value
        end
    end
    return 0
end

local function removePlayerFromRecentlySaved(playerName)
    for i, name in ipairs(RecentlySaved) do
        if name == playerName then
            table.remove(RecentlySaved, i)
            break
        end
    end
end

local function autoRemoveFromRecentlySaved(playerName)
    task.spawn(function()
        wait(15)
        removePlayerFromRecentlySaved(playerName)
    end)
end

local function returnPlayerList()
    local count = 0
    for _, player in ipairs(Players:GetPlayers()) do
        if returnCurrency(player) > MinimumRequirement then
            count = count + 1
        end
    end
    return count
end

local function clearMainFrame()
    for _, child in ipairs(MainFrame:GetChildren()) do
        if child:IsA('Frame') then
            child:Destroy()
        end
    end
end

local function abbreviateValue(value, idp)
    if value < 1000 then
        return math.floor(value + 0.5)
    else
        local abbreviations = {"", "K", "M", "B", "T"}
        local ex = math.floor(math.log(math.max(1, math.abs(value)), 1000))
        local abbrevs = abbreviations[1 + ex] or ("e+"..ex)
        local normal = math.floor(value * ((10 ^ idp) / (1000 ^ ex))) / (10 ^ idp)
        return ("%."..idp.."f%s"):format(normal, abbrevs)
    end
end

local function setRankText(template, rank)
    local rankSymbols = {"🥇", "🥈", "🥉"}
    template.Rank.Text = rank <= 3 and rankSymbols[rank] or "#" .. rank
end

Players.PlayerRemoving:Connect(function(player)
    local playerName = player.Name
    if not table.find(RecentlySaved, playerName) then
        table.insert(RecentlySaved, playerName)
        local currentCurrencyAmount = returnCurrency(player)
        local success, errorMsg = pcall(function()
            GlobalLeaderboardB:SetAsync(player.UserId, currentCurrencyAmount)
        end)
        if not success then
            warn(errorMsg)
        end
        removePlayerFromRecentlySaved(playerName)
    end
end)

while true do
    task.wait()
    clearMainFrame()
    repeat
        wait()
    until returnPlayerList() > 0
    
    for _, player in ipairs(Players:GetPlayers()) do
        local playerName = player.Name
        if not table.find(RecentlySaved, playerName) then
            table.insert(RecentlySaved, playerName)
            local currentCurrencyAmount = returnCurrency(player)
            local success, errorMsg = pcall(function()
                GlobalLeaderboardB:SetAsync(player.UserId, currentCurrencyAmount)
            end)
            if not success then
                warn(errorMsg)
            end
            autoRemoveFromRecentlySaved(playerName)
        end
    end

    local pages = GlobalLeaderboardB:GetSortedAsync(false, ListSize)
    local topList = pages:GetCurrentPage()
    for rank, data in ipairs(topList) do
        local userId, cAmount = data.key, data.value
        if cAmount >= MinimumRequirement then
            local template = Template:Clone()
            template.Parent = MainFrame
            template.LayoutOrder = rank
            template.Score.Text = abbreviateValue(cAmount, 1)
            setRankText(template, rank)
            local success, username = pcall(function()
                return Players:GetNameFromUserIdAsync(userId)
            end)
            template.Username.Text = success and username or "Unknown"
            template.Name = template.Username.Text
        end
    end
    task.wait(UpdateEvery)
end

There’s a lot going on there, and honestly it’s quite hard to read. Reviewing the code, here’s a list of things you can do:

  • Use an approach where one script handles all of the leaderboards. From what I can see, this script only handles one, and you’re likely to have more. You will need to set up a system where different leaderboards can format their text differently (i.e., A rank leaderboard will start with “#” and a cash leaderboard will start with “$”). This will be shown in the example video below.

  • Only save to the OrderedDataStore when their data changes. Set up listeners that save that specific data to the OrderedDataStore when it changes. Do this where you create the leaderstats. This will remove the need for A LOT:
    :: PlayerRemoving event
    :: The need to save people’s data to the OrderedDataStore when leaderboards reload.
    :: The RecentlySaved table and functions.

  • Set up a system to ignore test dummies. When testing with multiple clients in studio, their userIds will be negative which causes leaderboard to break. Make sure to ignore saving their data to the OrderedDataStore by checking if their UserId is less than 0.

  • Remove the nested SetRankText() function from the loop. There are multiple ways to go about doing this, but I would put a table outside the loop and index their rank. This negates the need for a nested table and unecessary if statements.

local ranks = {
    [1] = "🥇",
    [2] = "🥈",
    [3] = "🥉",
}

Template.Rank.Text = ranks[Rank] or `#{Rank}`

My own leaderboard system using OOP, Knit, and Promises. Hopefully this can add to how you should ideally structure your leaderboard system.


2 Likes