This leaderboard system is loading the same pages over and over until it overloads. I want this to load only the necessary pages until it finished listing all of them. This leaderboard system takes code from the Roblox resources leaderboard model.
I’m not sure why it is repeatedly loading, but I found out that it is happening during the :AdvanceToNextPageAsync() function.
Leaderboard Script:
local replicatedstorage = game:GetService("ReplicatedStorage")
local datastoreservice = game:GetService("DataStoreService")
local services = replicatedstorage:FindFirstChild("Services")
local leaderboardsystem = require(services:FindFirstChild("LeaderboardSystem"))
local winsLeaderboardData = datastoreservice:GetOrderedDataStore("Wins")
local shredsLeaderboardData = datastoreservice:GetOrderedDataStore("Shreds")
local donationsLeaderboardData = datastoreservice:GetOrderedDataStore("Donations")
local winsBoardModel = workspace:FindFirstChild("WinsLeaderboard")
local shredsBoardModel = workspace:FindFirstChild("ShredsLeaderboard")
local donationsBoardModel = workspace:FindFirstChild("DonationsLeaderboard")
local winsBoard = leaderboardsystem.new(winsBoardModel, winsLeaderboardData, true, "Wins")
local shredsBoard = leaderboardsystem.new(shredsBoardModel, shredsLeaderboardData, true, "Shreds")
local donationsBoard = leaderboardsystem.new(donationsBoardModel, donationsLeaderboardData, false)
winsBoard:Init()
shredsBoard:Init()
donationsBoard:Init()
Leaderboard Module:
local MarketplaceService = game:GetService("MarketplaceService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local modules = ReplicatedStorage:FindFirstChild("Modules")
local formatLargeNumber = require(modules.formatLargeNumber)
local retryAsync = require(modules.retryAsync)
local USER_ICON_TEMPLATE = "rbxthumb://type=AvatarHeadShot&id=%d&w=60&h=60"
local ROBUX_TEMPLATE = utf8.char(0xE002) .. " %s"
local DISPLAY_COUNT = 100
local UPDATE_INTERVAL = 120
local UPDATE_MAX_ATTEMPTS = 3 -- Make up to 3 attempts (Initial attempt + 2 retries)
local UPDATE_RETRY_PAUSE_CONSTANT = 1 -- Base wait time between attempts
local UPDATE_RETRY_PAUSE_EXPONENT_BASE = 2 -- Base number raised to the power of the retry number for exponential backoff
local leaderboard = {}
leaderboard.__index = leaderboard
function leaderboard.new(board, datastore, autoUpdate, dataName)
local newBoard = setmetatable({}, leaderboard)
newBoard.Board = board
newBoard.DataStore = datastore
newBoard.AutoUpdate = autoUpdate
newBoard.DataName = dataName
newBoard.DisplayFrames = {}
newBoard.UserIdUsernameCache = {}
return newBoard
end
function leaderboard:Init()
self:CreateDisplayFrames()
task.spawn(function()
while true do
if self.AutoUpdate and not RunService:IsStudio() then
self:UpdateData()
end
self:RefreshLeaderboardAsync()
task.wait(UPDATE_INTERVAL)
end
end)
end
function leaderboard:UpdateData()
for _, player in pairs(Players:GetPlayers()) do
if player:GetAttribute(self.DataName) == 0 then continue end
retryAsync(function()
self.DataStore:UpdateAsync(player.UserId, function()
return player:GetAttribute(self.DataName)
end)
end, UPDATE_MAX_ATTEMPTS, UPDATE_RETRY_PAUSE_CONSTANT, UPDATE_RETRY_PAUSE_EXPONENT_BASE)
end
end
function leaderboard:GetUsernameFromUserIdAsync(userId: number): string
if self.UserIdUsernameCache[userId] then
return self.UserIdUsernameCache[userId]
else
local success, result = pcall(function()
return Players:GetNameFromUserIdAsync(userId)
end)
if success then
self.UserIdUsernameCache[userId] = result
return result
else
return string.format("<unknown%d>", userId)
end
end
end
-- Update a display frame with the specified userId and robuxAmount
function leaderboard:UpdateDisplayFrameInfoAsync(displayFrame: any, userId: number?, data: number?)
if userId and data then
local displayUsername = self:GetUsernameFromUserIdAsync(userId)
local displayAmount = string.format(ROBUX_TEMPLATE, formatLargeNumber(data))
local displayIcon = string.format(USER_ICON_TEMPLATE, userId)
displayFrame.UserDisplay.NameLabel.Text = displayUsername
displayFrame.UserDisplay.IconLabel.Image = displayIcon
displayFrame.DataLabel.Text = displayAmount
displayFrame.Name = displayUsername
displayFrame.Visible = true
else
displayFrame.Visible = false
end
end
-- Create necessary display frames to be used on the leaderboard
function leaderboard:CreateDisplayFrames()
for i = 1, DISPLAY_COUNT do
local isEven = i % 2 == 0
local displayFrame = self.Board.Template:Clone()
displayFrame.BackgroundTransparency = if isEven then 0.9 else 1
displayFrame.LayoutOrder = i
displayFrame.UserDisplay.RankLabel.Text = tostring(i)
displayFrame.Visible = false
displayFrame.Parent = self.Board.Board :: any
self.DisplayFrames[i] = displayFrame
end
end
-- Retreive the top <DISPLAY_COUNT> donors and display their information on the leaderboard
function leaderboard:RefreshLeaderboardAsync()
local success, result = pcall(function()
-- DataStorePages support up to 100 items per page, so page size must be clamped in the case of DISPLAY_COUNT > 100
return self.DataStore:GetSortedAsync(false, math.min(DISPLAY_COUNT, 100))
end)
if not success then
warn(string.format("Failed to retrieve leaderboard data because: %s", result))
return
end
local pages = result :: DataStorePages
local top = {}
-- Pull items from the pages object until we have enough to satisfy DISPLAY_COUNT or run out of pages
repeat
local currentPage = pages:GetCurrentPage()
for _, data in currentPage do
table.insert(top, data)
end
if pages.IsFinished or #currentPage == 0 then
break
else
pages:AdvanceToNextPageAsync()
end
until #top >= DISPLAY_COUNT
for i = 1, DISPLAY_COUNT do
local donorData = top[i]
local displayFrame = self.DisplayFrames[i]
if donorData then
local userId = donorData.key
local dataAmount = donorData.value
self:UpdateDisplayFrameInfoAsync(displayFrame, userId, dataAmount)
else
self:UpdateDisplayFrameInfoAsync(displayFrame, nil, nil)
end
end
end
return leaderboard