Extreme lag spikes, from a single heartbeat function

Hello, so I was testing my code, when I runned into a massive lag spike with over 3k pings, obviously you can’t play, like that.
But I think I know it’s origin, even though I can’t find a “real” solution to it.
Here’s the script:

local function search(tbl)
			table.sort(tbl, function(a,b)
				return (killer.PrimaryPart.Position - a.Character.PrimaryPart.Position).Magnitude < (killer.PrimaryPart.Position - b.Character.PrimaryPart.Position).Magnitude
			end)
			
			self.currentTarget = tbl[1]
			
			RunService.Heartbeat:Connect(function()
				if self.currentTarget == nil then return end
				
				local NewPath = Path.new(killer)

				NewPath:Run(self.currentTarget.Character.PrimaryPart.Position)
				
				NewPath._events.WaypointReached.Event:Connect(function()
					zoneParameters1.Position = killer.PrimaryPart.Position - Vector3.one
					zoneParameters2.Position = killer.PrimaryPart.Position - Vector3.one
					
					
					zone1 = Zone.new(zoneParameters1)
					zone2 = Zone.new(zoneParameters2)
				end)
			end)
			
			return tbl
		end

I use ZonePlus and SimplePath in this module. Thanks in advance to everyone who’s going to help!

1 Like

Can we see the code which runs this function?

Surely, @hya123456h !

char limit

That’s all the code, kinda:

function Killer.new(killer, maxDistance)
	killer.PrimaryPart.Anchored = false
	killer.PrimaryPart.CanCollide = false

	local self = {}
	self.maxDistance = maxDistance or 100
	self.currentTarget = nil
	self.cooldownInSeconds = 5
	self.zoneParams = {}

	function self:MoveTo(pos1,pos2)
		local Distance = (pos1 - pos2).Magnitude

		if Distance > self.maxDistance then
			return warn("pos1 is more than "..tostring(self.maxDistance).." studs away, and it's over the max, please, change the max distance or use 2 fewer distances")
		else
			local NewPath = Path.new(killer)

			if killer.PrimaryPart.Position ~= pos1 then
				killer.PrimaryPart.Position = pos1
			end

			NewPath:Run(pos2)
		end
	end


	function self:FindNearestTarget(zoneParameters1, zoneParameters2)
		local zone1 = Zone.new(zoneParameters1)
		local zone2 = Zone.new(zoneParameters2)
		
		local function search(tbl)
			table.sort(tbl, function(a,b)
				return (killer.PrimaryPart.Position - a.Character.PrimaryPart.Position).Magnitude < (killer.PrimaryPart.Position - b.Character.PrimaryPart.Position).Magnitude
			end)
			
			self.currentTarget = tbl[1]
			
			RunService.Heartbeat:Connect(function()
				if self.currentTarget == nil then return end
				
				local NewPath = Path.new(killer)

				NewPath:Run(self.currentTarget.Character.PrimaryPart.Position)
				
				NewPath._events.WaypointReached.Event:Connect(function()
					zoneParameters1.Position = killer.PrimaryPart.Position - Vector3.one
					zoneParameters2.Position = killer.PrimaryPart.Position - Vector3.one
					
					
					zone1 = Zone.new(zoneParameters1)
					zone2 = Zone.new(zoneParameters2)
				end)
			end)
			
			return tbl
		end
		
		local playersInZone = {}
		
		zone1.playerEntered:Connect(function(player)
			if table.find(playersInZone, player) == nil then
				table.insert(playersInZone, player)
				
				task.spawn(search, playersInZone)
			end
		end)
		
		zone1.playerExited:Connect(function(player)
			if table.find(playersInZone, player) then
				table.remove(playersInZone, table.find(playersInZone, player))
				
				if self.currentTarget == player then
					self.currentTarget = nil
				end
			end
		end)
		
		zone2.playerEntered:Connect(function(player)
			player.Character.Humanoid:TakeDamage(100)
			
			player.Character.Humanoid.Died:Connect(function()
				self.currentTarget = nil
			end)
		end)

		task.spawn(function()
			while task.wait(self.cooldownInSeconds) do
				if self.currentTarget == nil then
					local randomPosition = Vector3.new(math.random(5,100), 0, math.random(5,100))

					local NewPath = Path.new(killer)

					NewPath:Run(randomPosition)

					NewPath._events.WaypointReached.Event:Connect(function()
						zoneParameters1.Position = killer.PrimaryPart.Position
						zoneParameters2.Position = killer.PrimaryPart.Position

						zone1 = Zone.new(zoneParameters1)
						zone2 = Zone.new(zoneParameters2)
					end)

				else
					return
				end
			end
		end)
		
	end



	return self
end
1 Like

Anyone knows how to solve this, I still need help, please. Thanks to all.

It’s risky to create a connection within an infinite loop, you’ll need to be very careful about controlling the rate that they’re created when doing so

If self.currentTarget says nil for even 1 second, 60 WaypointReached.Event connections will be created (assuming you have 60 fps)

Okay, so how do I handle it in a different way, then?

The answer would depend on why are you creating it within a loop in the first place

If you want to create a connection whenever currentTarget is not nil, then you can use a value instance to detect when it changes and run the code inside the value instance’s Changed connection

What’s the type of the currentTarget value? I can write example code if you wish

Oh, sorry I forgot to mention that I tried with :GetProperyChangedSignal("Position") (for the primarypart), and it didn’t fire, same for the .Changed, and about the example, well, sure! Thanks alot @JohhnyLegoKing

1 Like
RunService.Stepped:Connect(function()

Disconnecting the connection if currentTarget is not nil could also work to fix the problem:

function Killer.new(killer, maxDistance)
	killer.PrimaryPart.Anchored = false
	killer.PrimaryPart.CanCollide = false

	local self = {}
	self.maxDistance = maxDistance or 100
	self.currentTarget = nil
	self.cooldownInSeconds = 5
	self.zoneParams = {}

	function self:MoveTo(pos1,pos2)
		local Distance = (pos1 - pos2).Magnitude

		if Distance > self.maxDistance then
			return warn("pos1 is more than "..tostring(self.maxDistance).." studs away, and it's over the max, please, change the max distance or use 2 fewer distances")
		else
			local NewPath = Path.new(killer)

			if killer.PrimaryPart.Position ~= pos1 then
				killer.PrimaryPart.Position = pos1
			end

			NewPath:Run(pos2)
		end
	end


	function self:FindNearestTarget(zoneParameters1, zoneParameters2)
		local zone1 = Zone.new(zoneParameters1)
		local zone2 = Zone.new(zoneParameters2)

		local function search(tbl)
			table.sort(tbl, function(a,b)
				return (killer.PrimaryPart.Position - a.Character.PrimaryPart.Position).Magnitude < (killer.PrimaryPart.Position - b.Character.PrimaryPart.Position).Magnitude
			end)

			self.currentTarget = tbl[1]

			local connection
			connection = RunService.Heartbeat:Connect(function()
				if self.currentTarget == nil then return end

				connection:Disconnect()

				local NewPath = Path.new(killer)

				NewPath:Run(self.currentTarget.Character.PrimaryPart.Position)

				NewPath._events.WaypointReached.Event:Connect(function()
					zoneParameters1.Position = killer.PrimaryPart.Position - Vector3.one
					zoneParameters2.Position = killer.PrimaryPart.Position - Vector3.one


					zone1 = Zone.new(zoneParameters1)
					zone2 = Zone.new(zoneParameters2)
				end)
			end)

			return tbl
		end

		local playersInZone = {}

		zone1.playerEntered:Connect(function(player)
			if table.find(playersInZone, player) == nil then
				table.insert(playersInZone, player)

				task.spawn(search, playersInZone)
			end
		end)

		zone1.playerExited:Connect(function(player)
			if table.find(playersInZone, player) then
				table.remove(playersInZone, table.find(playersInZone, player))

				if self.currentTarget == player then
					self.currentTarget = nil
				end
			end
		end)

		zone2.playerEntered:Connect(function(player)
			player.Character.Humanoid:TakeDamage(100)

			player.Character.Humanoid.Died:Connect(function()
				self.currentTarget = nil
			end)
		end)

		task.spawn(function()
			while task.wait(self.cooldownInSeconds) do
				if self.currentTarget == nil then
					local randomPosition = Vector3.new(math.random(5,100), 0, math.random(5,100))

					local NewPath = Path.new(killer)

					NewPath:Run(randomPosition)

					NewPath._events.WaypointReached.Event:Connect(function()
						zoneParameters1.Position = killer.PrimaryPart.Position
						zoneParameters2.Position = killer.PrimaryPart.Position

						zone1 = Zone.new(zoneParameters1)
						zone2 = Zone.new(zoneParameters2)
					end)

				else
					return
				end
			end
		end)

	end



	return self
end
1 Like

Thanks alot! This works, could explain me, why RunService.Stepped works and RunService.Heartbeat no? Really, I don’t know much about RunService (only Heartbeat and Renderstepped which are kinda the same).

Thanks too you’ve been very helpful, and I learned something from you too, thanks again!

1 Like

Stepped is 60 times a second. Heartbeat is 32,246,234 times a second.
lol, it’s not that bad but it is way way more …

Stepped is a beautiful thing, literally adjusts itself per fps.
You codes should make use of this. Slower don’t always mean worse.

Okay, okay, it was working until I saw that it does slowly increse in memory and pings. Any other solutions?

Increase in memory and pings is better than locking up or not working at all.
This will very much help if you have more NPCs also.

Nevermind, it works with what @JohhnyLegoKing said.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.