Infinite yield possible on WaitForChild("leaderstats")

I’m working on an obby game, and I’m trying to fetch the player’s leaderstats when they join the game. However, it often fails to retrieve the data, resulting in an error that unfortunately breaks my entire script. Since I’m running this on the server, it immediately stops for the entire server.

When you join the game for the first time, it works as expected. But when you quickly leave and rejoin the game, the problems start to appear.

Helpful to know: I’m using ProfileService, ReplicaService, and Knit (Knit is only used in the datastore code). For anyone that will be-able to make this fail-proof, you are a life-saver.

The problem lies here.

players.PlayerAdded:Connect(function(player)
	local plot = findSmallestPlot()
	if plot then
		assignPlotToPlayer(player, plot)
		spawnPlayerOnPlot(player, plot)
		handleStageObject(player, plot)
	end
	
	player.CharacterRemoving:Connect(function(Character)
		task.defer(Character.Destroy, Character)
	end)
	
	task.wait(5)
	
	local Stage = player:WaitForChild("leaderstats").Stage -- Here (You see task.wait() cuz i've been trying to fix this)
	Stage.Changed:Connect(function(newValue)
		placePlayerBack(player, plot)
		stageChanged(player, plot)
	end)
end)

This is the entire code:

local PhysicsService = game:GetService("PhysicsService")
PhysicsService:RegisterCollisionGroup("Character")
PhysicsService:RegisterCollisionGroup("OwnerStage")
PhysicsService:CollisionGroupSetCollidable("Character", "OwnerStage", true)

local serverScriptService = game:GetService("ServerScriptService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local players = game:GetService("Players")

local DataService = require(serverScriptService.Services.DataService)
local Manager = require(serverScriptService.Services.DataService.Data)
local Plots = workspace.Obby.Platforms
local Debounce = false

local playerDebounce = {} 

-- Character Cancollide
local function setCharacterCollisionGroup(descendant)
	if descendant:IsA("BasePart") and descendant.Parent:IsA("Character") then
		descendant.CollisionGroup = "Character"
	end
end

local function playerReplica(player, plot)
	DataService:GetReplica(player):andThen(function(playerDataReplica)
		local playerStage = playerDataReplica.Data.Stage
		local currentStage = replicatedStorage.obbyStages.DCO:FindFirstChild(playerStage)
		if currentStage then
			local clonedStage = currentStage:Clone()
			clonedStage.Name = "ClonedStage"
			clonedStage:PivotTo(plot.Platform:FindFirstChild("StartPart").CFrame)
			clonedStage.Parent = plot.Platform.Stage
		end
	end)
end

-- Finds the smallest available plot
local function findSmallestPlot()
	local smallestPlot = nil
	local smallestPlotNumber = math.huge

	for _, plot in ipairs(Plots:GetChildren()) do
		if plot:GetAttribute("Taken") then continue end
		local plotNumber = tonumber(plot.Name)
		if plotNumber and plotNumber < smallestPlotNumber then
			smallestPlot = plot
			smallestPlotNumber = plotNumber
		end
	end

	return smallestPlot
end

-- Assigns a plot to a player and spawns their stage
local function assignPlotToPlayer(player, plot)
	plot:SetAttribute("Taken", true)
	plot:SetAttribute("Owner", player.UserId)
	playerReplica(player, plot)
end

-- Spawns a player on their assigned plot and sets up collision groups
local function spawnPlayerOnPlot(player, plot)
	player.CharacterAdded:Connect(function(character)
		character:PivotTo(plot.Platform.StartPosition.CFrame * CFrame.new(0, 3, 0))
		for _, descendant in character:GetDescendants() do
			setCharacterCollisionGroup(descendant)
		end
		character.DescendantAdded:Connect(setCharacterCollisionGroup)
	end)
end

-- Removes a player from their plot and cleans up
local function removePlayerFromPlot(player)
	for _, plot in Plots:GetChildren() do
		if plot:GetAttribute("Owner") == player.UserId then
			plot:SetAttribute("Taken", nil)
			plot:SetAttribute("Owner", nil)
			for _, child in plot.Platform.Stage:GetChildren() do
				if child.Name == "ClonedStage" then
					child:Destroy()
					break
				end
			end
			print("Plot has been removed from " .. player.Name .. "!") -- On release verwijderen
		end
	end
end

local function handleStageObject(player, plot)	
	local stageFolder = plot:WaitForChild("Platform").Stage
	local currentConnection

	stageFolder.ChildAdded:Connect(function(instance)
		local endPosition = instance:FindFirstChild("EndPosition")
		if endPosition then
			if currentConnection then
				currentConnection:Disconnect()
			end

			currentConnection = endPosition.Touched:Connect(function(hit)
				local hitPlayer = players:GetPlayerFromCharacter(hit.Parent)
				if not hitPlayer then return end
				
				if playerDebounce[hitPlayer.UserId] then
					return
				end
				
				if plot:GetAttribute("Owner") == hitPlayer.UserId then
					print(hitPlayer.UserId)
					print(hitPlayer.Name)
					
					playerDebounce[hitPlayer.UserId] = true

					local profile = Manager.Profiles[player]
					if not profile then return end
					local leaderstatStage = player:WaitForChild("leaderstats").Stage

					profile.Data.Stage += 1
					leaderstatStage.Value = profile.Data.Stage
					task.wait(0.5)
					playerDebounce[hitPlayer.UserId] = nil
				end
			end)
		end
	end)
end

local function stageChanged(player, plot)
	for _, child in plot.Platform.Stage:GetChildren() do
		if child.Name == "ClonedStage" then
			child:Destroy()
			break
		end
	end
	playerReplica(player, plot)
end

local function placePlayerBack(player, plot)
	player.Character:PivotTo(plot.Platform.StartPosition.CFrame * CFrame.new(0, 3, 0))
end

-- Main logic
players.PlayerAdded:Connect(function(player)
	local plot = findSmallestPlot()
	if plot then
		assignPlotToPlayer(player, plot)
		spawnPlayerOnPlot(player, plot)
		handleStageObject(player, plot)
	end
	
	player.CharacterRemoving:Connect(function(Character)
		task.defer(Character.Destroy, Character)
	end)
	
	task.wait(5)
	
	local Stage = player:WaitForChild("leaderstats").Stage
	Stage.Changed:Connect(function(newValue)
		placePlayerBack(player, plot)
		stageChanged(player, plot)
	end)
end)

players.PlayerRemoving:Connect(function(player)
	removePlayerFromPlot(player)
	task.defer(player.Destroy, player)
end)
1 Like

It doesn’t look like you’re creating leaderstats anywhere in the script.
Unless you’re creating it in another script, that could be why.

1 Like

The datastore code:

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Knit = require(ReplicatedStorage.Packages.Knit)
local Promise = require(Knit.Util.Promise)
local SaveStructure = require(script.SaveStructure)
local ProfileService = require(Knit.Util.ProfileService)
local ReplicaService = require(Knit.Util.ReplicaService)
local Manager = require(script.Data)

local Profiles = Manager.Profiles
local Replicas = Manager.Replicas
local PlayerProfileClassToken = ReplicaService.NewClassToken("PlayerData")
local ProfileStore = ProfileService.GetProfileStore("PlayerData", SaveStructure)

local DataService = Knit.CreateService {Name = "DataService"}

function DataService:GetReplica(Player: Player)	
	return Promise.new(function(Resolve, Reject)
		assert(typeof(Player) == "Instance" and Player:IsDescendantOf(Players), "Value passed is not a valid player")

		if not Profiles[Player] and not Replicas[Player] then
			repeat 
				if Player then
					wait()
				else
					Reject("Player left the game")
				end
			until Profiles[Player] and Replicas[Player]
		end

		local Profile = Profiles[Player]
		local Replica = Replicas[Player]
		if Profile and Profile:IsActive() then
			if Replica and Replica:IsActive() then
				Resolve(Replica)
			else
				Reject("Replica did not exist or wasn't active")

			end
		else
			Reject("Profile did not exist or wasn't active")
		end
	end)
end

local function giveLeaderstats(player)
	local profile = Manager.Profiles[player]
	if not profile then return end

	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local Stage = Instance.new("IntValue")
	Stage.Name = "Stage"
	Stage.Value = profile.Data.Stage
	Stage.Parent = leaderstats

	local hiddenStats = Instance.new("Folder")
	hiddenStats.Name = "hiddenStats"
	hiddenStats.Parent = player

	local Deaths = Instance.new("IntValue")
	Deaths.Name = "Deaths"
	Deaths.Value = profile.Data.Deaths
	Deaths.Parent = hiddenStats
end

local function PlayerAdded(Player: Player)
	local StartTime = tick()
	local Profile = ProfileStore:LoadProfileAsync("Player_" .. Player.UserId)

	if Profile then
		Profile.Data.Stage = 1 -- Verwijderen wanneer klaar
		Profile:AddUserId(Player.UserId)
		Profile:Reconcile()
		Profile:ListenToRelease(function()
			Profiles[Player] = nil
			Replicas[Player]:Destroy()
			Replicas[Player]= nil
			Player:Kick("Profile was released")

		end)

		if Player:IsDescendantOf(Players) == true then
			Profiles[Player] = Profile
			giveLeaderstats(Player)
			local Replica = ReplicaService.NewReplica({
				ClassToken = PlayerProfileClassToken,
				Tags = {["Player"] = Player},
				Data = Profile.Data,
				Replication = "All"
			})

			Replicas[Player] = Replica
			warn(Player.Name.. "'s profile has been loaded. ".."("..string.sub(tostring(tick()-StartTime),1,5)..")")
		else
			Profile:Release()
		end
	else
		Player:Kick("Profile == nil") 
	end
end

function DataService:KnitInit()
	for _, player in ipairs(Players:GetPlayers()) do
		task.spawn(PlayerAdded, player)
	end

	Players.PlayerAdded:Connect(PlayerAdded)

	Players.PlayerRemoving:Connect(function(Player)
		if Profiles[Player] then
			Profiles[Player]:Release()
		end
	end)
end

function DataService:KnitStart()

end

return DataService

I’m not familiar with this profile system but from what I see you have a player added function which gets the stage, which you pointed out is where the error lies, but also another one that calls giveLeaderstats and creates the leaderstats folder. Is the first player added function trying to access the leaderstats before the second one actually creates it?

I made some sort of a workaround, it kind of works, it just needs some tweaks, still thanks all for the help