What should I do for my badge system so it doesn't overload

So in my game from defeating players you gain a stat called souls, and the more souls you have there are milestones that award you with your accomplishments (e.g reach 1,000 souls and gain a badge called soul destroyer or smth).

So what I did was I made a changed event on souls that checks if you have enough to be awarded before awarding you. And if you do have enough souls then it checks if you already have the badge and if you don’t it awards you with the badge.

Okay now where the problem lies is that the check part is being overused. UserHasBadgeAsync is being used way too often, and I even made a check to see if changed has ran 10 times for each player before checking. TO NO AVAIL. Even this system causes throttling. Example Below:

souls.Changed:Connect(function()
num += 1
if num >= 10 then
num = 0
-- for loop through a table of soul badges and requirements
-- check for badge on each one
end
end)

(Sorry for the bad indenting, but I’m sure it isn’t too difficult to understand this piece of code)

And you may be thinking “Why not just create a throttling function for UserHasBadgeAsync?” WELL I DID YOU SEE ITS RIGHT HERE:

function CheckBadgeOwnership(player, badgeId)
	if not player then
		return "Error"
	end

	badgeRequests[player.UserId] = badgeRequests[player.UserId] or { count = 0, lastRequestTime = 0 }
	local userRequests = badgeRequests[player.UserId]
	
	if userRequests.cd and os.time() < userRequests.cd + 5 then
		return "Cooldown"
	else
		if userRequests.cd and os.time() > userRequests.cd + 5 then
			badgeRequests[player.UserId].cd = nil
			userRequests.cd = nil
		end
	end

	local BADGE_THROTTLE_LIMIT = 30
	if userRequests.count >= BADGE_THROTTLE_LIMIT then
		badgeRequests[player.UserId].cd = os.time()
		badgeRequests[player.UserId].count = 0
		return "Error"
	end
	
	badgeRequests[player.UserId].count += 1
	local hasBadge = badgeservice:UserHasBadgeAsync(player.UserId, badgeId)

	return hasBadge
end

And yet we’re still having the same warning messages below:


Does anyone know what I could try doing in my code to make this less of an occurring warning? I don’t want this warning to spam my error reports on my game.

why not just check when the player joins and store it in a bool value and just update the bool value when the player gets it

Maybe something like this could work?

local BadgeService = game:GetService("BadgeService")
local BadgeCache = {}

function CheckBadgeOwnership(player, badgeId)
	if not player then
		return "Error"
	end

	if typeof(BadgeCache[player.UserId]) ~= "table" then
		BadgeCache[player.UserId] = {}
	end

	local hasBadge = false
	local info = BadgeCache[player.UserId][badgeId]

	if info == nil then
		hasBadge = BadgeService:UserHasBadgeAsync(player.UserId, badgeId)

		BadgeCache[player.UserId][badgeId] = {
			["hasBadge"] = hasBadge,
			["lastCheck"] = os.time(),
		}
	else
		hasBadge = info.hasBadge
	end

	return hasBadge
end

it basically just stores if the user has the badge so it can be quickly checked.

this approach, you minimize the number of calls to UserHasBadgeAsync and avoid throttling much

local BadgeService = game:GetService("BadgeService")
local HttpService = game:GetService("HttpService")

local badgeRequests = {}
local badgeRequirements = {
    [123456789] = 1000, -- Example badge ID and requirement
    [987654321] = 5000,
}

local function CheckBadgeOwnership(player, badgeId)
	if not player then
		return "Error"
	end

	badgeRequests[player.UserId] = badgeRequests[player.UserId] or { count = 0, lastRequestTime = 0, badges = {} }
	local userRequests = badgeRequests[player.UserId]
	
	-- Check cache first
	if userRequests.badges[badgeId] ~= nil then
		return userRequests.badges[badgeId]
	end
	
	-- Throttling mechanism
	local BADGE_THROTTLE_LIMIT = 30
	if userRequests.count >= BADGE_THROTTLE_LIMIT then
		if os.time() < userRequests.lastRequestTime + 60 then
			return "Cooldown"
		else
			userRequests.count = 0
		end
	end
	
	userRequests.count += 1
	userRequests.lastRequestTime = os.time()
	
	-- Call UserHasBadgeAsync
	local hasBadge = BadgeService:UserHasBadgeAsync(player.UserId, badgeId)
	userRequests.badges[badgeId] = hasBadge

	return hasBadge
end

local function AwardBadges(player, souls)
	for badgeId, requirement in pairs(badgeRequirements) do
		if souls >= requirement then
			if CheckBadgeOwnership(player, badgeId) == false then
				BadgeService:AwardBadge(player.UserId, badgeId)
			end
		end
	end
end

-- Batch processing
local function ProcessPlayers()
	while true do
		for _, player in pairs(game.Players:GetPlayers()) do
			local souls = player:FindFirstChild("souls")
			if souls then
				AwardBadges(player, souls.Value)
			end
		end
		wait(10) -- Adjust the interval as needed
	end
end

game:GetService("RunService").Stepped:Connect(ProcessPlayers)

Why would you connect that to Renderstepped?? Thats a completely unnecessary addition.