Global leaderboard doesn't refresh correctly

Hey I almost fixed my previous issue, however now I am stuck with the board not auto-refreshing, but it only refreshes after all players leave and join back, so like on a new server. I wanted it to auto refresh every x time, so like every 1 minute or something.

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetOrderedDataStore("cashds_1")

local surfaceGui = script.Parent
local sample = script:WaitForChild("Sample")
local sf = surfaceGui:WaitForChild("ScrollingFrame")
local uiListLayout = sf:FindFirstChildOfClass("UIListLayout") or Instance.new("UIListLayout")
uiListLayout.SortOrder = Enum.SortOrder.LayoutOrder
uiListLayout.Parent = sf

local existingLabels = {}  -- Use a dictionary to keep track of existing labels

local function savePlayerCash(player)
	print("saving player cash")
	local key = tostring(player.UserId)
	local success, result = pcall(dataStore.SetAsync, dataStore, key, player.leaderstats.Cash.Value)
	if not success then
		warn("[Global Leaderboard] Error saving cash for player", player.UserId, ":", result)
	end
end

local function getTopPlayers()
	print("getting top players")
	local success, pages = pcall(dataStore.GetSortedAsync, dataStore, false, 100)
	if not success then
		warn("[Global Leaderboard] Error getting top players:", pages)
		return nil
	end
	return pages:GetCurrentPage()
end

local function updateLeaderboard()
	print("[Global Leaderboard] Updating leaderboard at", os.date("%Y-%m-%d %H:%M:%S"))

	local top = getTopPlayers()
	if not top then
		return
	end

	-- Clear existing labels
	for _, label in pairs(existingLabels) do
		label:Destroy()
	end
	existingLabels = {}  -- Reset the dictionary

	for rank, entry in ipairs(top) do
		local userId = entry.key
		local cash = entry.value

		print("[Global Leaderboard] Player at rank", rank, "with UserId", userId, "has cash value", cash)

		local new = sample:Clone()
		new.Name = userId
		new.LayoutOrder = rank
		new.Parent = sf

		local playerNameLabel = new:WaitForChild("playerName")
		local rankLabel = new:WaitForChild("rank")
		local valueLabel = new:WaitForChild("value")

		playerNameLabel.Text = "Loading..."
		rankLabel.Text = "#" .. rank
		valueLabel.Text = cash

		existingLabels[userId] = new  -- Add the new label to the dictionary

		coroutine.wrap(function()
			local playerName = Players:GetNameFromUserIdAsync(userId)
			if playerName then
				playerNameLabel.Text = playerName
			else
				playerNameLabel.Text = "Unknown"
			end
		end)()
	end
end

local function onPlayerAdded(player)
	print("player added")
	player.Changed:Connect(function(property)
		if property == "Parent" and player.Parent == nil then
			savePlayerCash(player)
		end
	end)
end

Players.PlayerAdded:Connect(onPlayerAdded)

local function autoUpdateLeaderboard()
	print("updating leaderboard")
	while true do
		updateLeaderboard()
		wait(5) -- Update every 5 seconds
	end
end

autoUpdateLeaderboard()
2 Likes

What do you get in the output ?

1 Like

Nothing. It just prints that user of id something has loaded with x amount of cash. However, it works correctly only when players leave and join back. In the game, when I sell and get more cash, it doesn’t auto update, the prints keep stating the same, if the player had 10 when joined the game and then sold and now has 12 cash, it still says the player has 10 but when the player leaves and joins a new server it says now the player has 12 cash. So it just doesn’t auto-update during the game.

1 Like

I think you don’t need to pass the dataStore as argument :

local success, pages = pcall(dataStore.GetSortedAsync, false, 100)

You can also change this code to this code :

local function onPlayerAdded(player)
	print("player added")
	spawn(function()
		while true do
			savePlayerCash(player)
			wait(10) -- Saves player's cash every 10 seconds
		end
	end)
end

local function onPlayerRemoving(player)
	print("player removed")
	savePlayerCash(player)
end

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

Because you only save the player’s cash when he leaves the game.

1 Like
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetOrderedDataStore("cashds_1")

local surfaceGui = script.Parent
local sample = script:WaitForChild("Sample")
local sf = surfaceGui:WaitForChild("ScrollingFrame")
local uiListLayout = sf:FindFirstChildOfClass("UIListLayout") or Instance.new("UIListLayout")
uiListLayout.SortOrder = Enum.SortOrder.LayoutOrder
uiListLayout.Parent = sf

local existingLabels = {}  -- Use a dictionary to keep track of existing labels

local function updateLeaderboard()
	print("[Global Leaderboard] Updating leaderboard at", os.date("%Y-%m-%d %H:%M:%S"))
	
	local top = dataStore:GetSortedAsync(false, 100)
	
	for _, label in ipairs(existingLabels) do
		label:Destroy()
	end
	
	table.clear(existingLabels)

	for rank, entry in ipairs(top) do
		local userId = entry.key
		local cash = entry.value

		print(`[Global Leaderboard] Player at rank {rank} with UserId {userId} has cash value {cash}`)

		local new = sample:Clone()
		new.Name = userId
		new.LayoutOrder = rank
		new.Parent = sf

		local playerNameLabel = new:FindFirstChild("playerName")
		local rankLabel = new:FindFirstChild("rank")
		local valueLabel = new:FindFirstChild("value")

		playerNameLabel.Text = "Loading..."
		rankLabel.Text = `#{rank}`
		valueLabel.Text = cash

		table.insert(existingLabels, new)
		
		local playerName = Players:GetNameFromUserIdAsync(userId)
			
		if playerName then
			playerNameLabel.Text = playerName
		else
			playerNameLabel.Text = "Unknown"
		end
	end
end

local function save(player:Player)
	
	--[[
	We don't need to do all the checks here to prevent data loss.
	If this was to control the player's main data, we would
	use many more precautions. But since this store is just
	meant to mimic player data, we do not need to do that.
	]]
	
	local success, result = pcall(dataStore.SetAsync, dataStore, player.UserId, player.leaderstats.Cash.Value)
	if success then
		print(`Successfully saved data for @{player.Name}.`)
	else
		warn(result)
	end
end

local function autoUpdateLeaderboard()
	print("updating leaderboard")
	while true do
		local success, result = pcall(updateLeaderboard)
		
		if not success then
			warn("Failed to update leaderboard.")
		end
		
		task.wait(300) -- Update every 5 minutes
	end
end

task.spawn(autoUpdateLeaderboard)
Players.PlayerRemoving:Connect(save)

Try this code.
I also lengthened the time between accessing the store to stay within budget and also not cause errors from throttling, etc. We don’t want to spam the store with requests.




You do. There are two ways of calling a method:

  • Dot notation: does not pass itself as a parameter.
  • Colon notation: does pass itself as a parameter.

Since we would normally use colon notation and we aren’t here, we need to pass the store itself as a parameter since the receiving subprogram expects it.

1 Like

image

Now nothing appeared on the leaderboard itself.

Can you modify the warn statement to include the error message? Just to know where it went wrong.

This kinda fixed the issue, however at one point it changed the value from my cash value to 0 on leaderboard and after a second it updated to the correct one

try this

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetOrderedDataStore("cashds_1")

local surfaceGui = script.Parent
local sample = script:WaitForChild("Sample")
local sf = surfaceGui:WaitForChild("ScrollingFrame")
local uiListLayout = sf:FindFirstChildOfClass("UIListLayout") or Instance.new("UIListLayout")
uiListLayout.SortOrder = Enum.SortOrder.LayoutOrder
uiListLayout.Parent = sf

local existingLabels = {}  -- Use a dictionary to keep track of existing labels

local function savePlayerCash(player)
	local key = tostring(player.UserId)
	local success, result = pcall(dataStore.SetAsync, dataStore, key, player.leaderstats.Cash.Value)
	if not success then
		warn("[Global Leaderboard] Error saving cash for player", player.UserId, ":", result)
	end
end

local function getTopPlayers()
	local success, pages = pcall(dataStore.GetSortedAsync, dataStore, false, 100)
	if not success then
		warn("[Global Leaderboard] Error getting top players:", pages)
		return nil
	end
	return pages:GetCurrentPage()
end

local function updateLeaderboard()
	print("[Global Leaderboard] Updating leaderboard at", os.date("%Y-%m-%d %H:%M:%S"))

	local top = getTopPlayers()
	if not top then
		return
	end

	-- Clear existing labels
	for _, label in pairs(existingLabels) do
		label:Destroy()
	end
	existingLabels = {}  -- Reset the dictionary

	for rank, entry in ipairs(top) do
		local userId = entry.key
		local cash = entry.value

		print("[Global Leaderboard] Player at rank", rank, "with UserId", userId, "has cash value", cash)

		local new = sample:Clone()
		new.Name = userId
		new.LayoutOrder = rank
		new.Parent = sf

		local playerNameLabel = new:WaitForChild("playerName")
		local rankLabel = new:WaitForChild("rank")
		local valueLabel = new:WaitForChild("value")

		playerNameLabel.Text = "Loading..."
		rankLabel.Text = "#" .. rank
		valueLabel.Text = cash

		existingLabels[userId] = new  -- Add the new label to the dictionary

		coroutine.wrap(function()
			local playerName = Players:GetNameFromUserIdAsync(userId)
			if playerName then
				playerNameLabel.Text = playerName
			else
				playerNameLabel.Text = "Unknown"
			end
		end)()
	end
end

local function onPlayerAdded(player)
	player.Changed:Connect(function(property)
		if property == "Parent" and player.Parent == nil then
			savePlayerCash(player)
		end
	end)
end

Players.PlayerAdded:Connect(onPlayerAdded)

local function autoUpdateLeaderboard()
	while true do
		updateLeaderboard()
		wait(60) -- Update every 60 seconds (1 minute)
	end
end

game:GetService("RunService").Heartbeat:Connect(autoUpdateLeaderboard)

I changed the code to how it was and I only changed what TomoT said about the playeradded and removed and it kinda fixed the issue. Your fix made it all uh well not work so are you sure it all requires a change not just the part when player joins? Also are you sure this works globally? I wanted it to work you know like a global leaderboard

I’m taking parts from my own global leaderboard, which works perfectly (for me), and trying to apply them to your leaderboard. Like I said before, can you try and make it print what the error actually was? It’s hard to debug without it. What do you mean TomoT “kind of” fixed the issue? What doesn’t work with it? I would really like to try and help further.

image

It looks like it didn’t success on updating the leaderboard. The warn came from here. Also, what I meant is I used the same script I originally posted in this topic, changed only what TomoT said to change when player joins (just the part) and uh it kinda worked for the refreshing, but it sometimes bugs out and the value of a player can be set to 0 for a brief moment and then goes back to what it should be, also idk why but the first 3 places aren’t colored.

Also one more thing, the value was stuck for like good 3 seconds like this:
image
(im the guy with 0)
image
then it went to this. I’m not sure if it should work like that (im back to the old “working” version of the script, the one I told you above that i “fixed” based on tomot comment, while im still trying to find out why yours doesn’t work)

I’m aware, I asked if you could make it show the error, so something like

if not success then
    warn("Failed with error: "..result)
end

For the refreshing, do you mean that the refreshed values are outdated? Do they not show at all? They won’t colour because you didn’t write that in your script. The delay might be to do with the fact you are using a coroutine. For my leaderboard, I didn’t use a coroutine, and it took about half a second between each entry showing, just for context.

Uh I found another problem right now. I have no idea what is wrong because everything was working on my old script but now out of nowhere my values keep glitching. Every refresh it keeps changing from 34 to 38 then 34 then 38 like old value to new value everytime it refreshes.
Moving onto your version and the warn, i found this error
image

I feel a little bit stupid… it’s so simple… XD

I forgot to get the current page.

Change the main iterator a bit:

--from:
for rank, entry in ipairs(top) do

--to this:
for rank, entry in ipairs(top:GetCurrentPage()) do

I think the issue with the other script is how short the save interval is. This not only busts through your budget incredibly fast, but may cause incorrect values to save.

Looks like the leaderboard started to show up again. However same issue with auto refreshing doesn’t work. Maybe I should just do what TomoT said? To add a while loop when players enter? Also, are you sure it works as a GLOBAL leaderboard? When 2 players are on different servers and both sell, does it update for both of them? I tried checking on a vip server but that’s when it glitched my values.

I think you need to try debouncing your leaderboard.

local db = false
local function updateLeaderboard()
    if db then return nil end
    db = true
    --update leaderboard code goes here
    db = false
end

You could also try checking if their frame already exists:

if sf:FindFirstChild(userId) then continue end



Yes. That’s the whole reason we are using a data store for this. An OrderedDataStore just means that it will order the store, and multiple servers can retrieve (GetSortedAsync) the data.

Also, please make sure you don’t spam update your leaderboard, it will cause so many issues when it comes to actual data saving and memory usage. You might need to decide whether it’s better to have a constantly updated leaderboard or good memory usage and player’s data saving working well.

Just refresh the leaderboard periodically, use a loop that calls the updateLeaderboard function at regular intervals using wait(timeInSeconds) .
Make sure the leaderboard updates are triggered only when necessary, such as when a player’s cash value changes or when a new player joins.