While loops lagging script, how to substitute them?

Hello, I’ve been working on a NPC car and when things were going well I noticed that after 30 - 60 seconds in game the fps starts to decrease, I know that this happens because every frame I start some while loops, but they break after some conditions gets false, I don’t see why broken loops would cause lag.

Function that runs the loops:

RunService.Heartbeat:Connect( function()
	
	local raycastParams = RaycastParams.new()

	raycastParams.FilterDescendantsInstances = {}
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist

	local RaycastResult = game.Workspace:Raycast(

		rayOrigin.Position,
		rayOrigin.CFrame.LookVector * 10,
		raycastParams
	)

	if RaycastResult and RaycastResult.Normal.Y < 1 and RaycastResult.Instance.CanCollide == true then

		canSpeedUp = false
		canBrake = true

		while canBrake do

			if slowDown(throttle, 0.02) == false then break end

			task.wait(0.02)
		end
		
	else
		
		canBrake = false
		canSpeedUp = true

		while canSpeedUp and throttle.Value < maxThrottle do

			if canBrake == false then

				speedUp(throttle)
			end

			task.wait(0.25)
		end
	end
	
	for _, child in pairs(car:GetChildren()) do
		
		if child:IsA("BasePart") then
			
			local connection = child.Touched:Connect(function() end)

			for _, part in ipairs(child:GetTouchingParts()) do

				recoverSteer(part)
			end
			
		elseif child:IsA("Folder") then
			
			for _, folderChild in pairs(child:GetChildren()) do
				
				local connection = folderChild.Touched:Connect(function() end)

				for _, part in ipairs(folderChild:GetTouchingParts()) do

					recoverSteer(part)
				end
			end
		end
	end
	
	if math.abs(rayOrigin.CFrame.LookVector.Z - desiredSteer) > 0.001 and turning == false then
		
		alingSteer(desiredSteer, 0.4)
		
	elseif turning == false then
		
		ChangeSteer:Fire(0)
	end
end)

If the loops really can’t stay in the code what should i use to substitute them? I tried to remove the loops but this makes who got better fps accelerate faster.

1 Like

Possibly add a short wait() after RunService.Heartbeat:Connect( function() because the Runservice.Heartbeat event fires every frame.

I removed the whiles and placed a wait in heartbeat, now takes longer to lag, like after 1 - 2 minutes.

There are a couple of things here you shouldn’t be doing.

  1. Remove the while loops from the code.

Heartbeat updates every frame, meaning every frame a new while loop is added to the computation. You should make some simple if then checks for speedUp() and slowDown().
EX:

if RaycastResult and RaycastResult.Normal.Y < 1 and RaycastResult.Instance.CanCollide == true then
		canSpeedUp = false
		canBrake = true

		slowDown(throttle, 0.02)
	else
		canBrake = false
		canSpeedUp = true

		if throttle.Value < maxThrottle then
			speedUp(throttle)
		end
	end
end
  1. You are making new touched connections every frame.

You should make the touch connections outside of RunService.Heartbeat, we can save memory here.

for _, child in pairs(car:GetChildren()) do
	if child:IsA("BasePart") then
		local connection = child.Touched:Connect(function() end)
	elseif child:IsA("Folder") then
			for _, folderChild in pairs(child:GetChildren()) do
				local connection = folderChild.Touched:Connect(function() end)
			end
		end
	end
end

RunService.Heartbeat:Connect(function()
end)

It is a bad idea to place wait (task.wait()) inside RunServer (and worse inside a loop as in your case). You can take those loops out in coroutines.

Creating connections at every step of the game is another very bad idea. This means a huge memory leak. Try to do it outside or be very careful when you do it inside RunServer.

I hope this has been helpful.


local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {}
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist

local canBrake
local co1 = coroutine.create(function ()
	while canBrake do
		if slowDown(throttle, 0.02) == false then break end
		task.wait(0.02)
	end
end)

local canSpeedUp
local co2 = coroutine.create(function()
	while canSpeedUp and throttle.Value < maxThrottle do
		if canBrake == false then
			speedUp(throttle)
		end
		task.wait(0.25)
	end
end)

RunService.Heartbeat:Connect( function()
	local RaycastResult = game.Workspace:Raycast(
		rayOrigin.Position,
		rayOrigin.CFrame.LookVector * 10,
		raycastParams
	)

	if RaycastResult and RaycastResult.Normal.Y < 1 and RaycastResult.Instance.CanCollide == true then
		canSpeedUp = false
		canBrake = true
		coroutine.resume(co1)
	else
		canBrake = false
		canSpeedUp = true
		coroutine.resume(co2)
	end

	for _, child in pairs(car:GetChildren()) do
		if child:IsA("BasePart") then
			for _, part in ipairs(child:GetTouchingParts()) do
				recoverSteer(part)
			end
		elseif child:IsA("Folder") then
			for _, folderChild in pairs(child:GetChildren()) do
				for _, part in ipairs(folderChild:GetTouchingParts()) do
					recoverSteer(part)
				end
			end
		end
	end

	if math.abs(rayOrigin.CFrame.LookVector.Z - desiredSteer) > 0.001 and turning == false then
		alingSteer(desiredSteer, 0.4)
	elseif turning == false then
		ChangeSteer:Fire(0)
	end
end)

for _, child in pairs(car:GetChildren()) do
	if child:IsA("BasePart") then
		local connection = child.Touched:Connect(function() end)
	elseif child:IsA("Folder") then
		for _, folderChild in pairs(child:GetChildren()) do
			local connection = folderChild.Touched:Connect(function() end)
		end
	end
end
1 Like

I know about that, if i remove the whiles then acceleration will be every frame, so a player with 30 fps will speed up slower than a 60 fps player:

Maybe this is the cause of the lag, because the whiles break after the variables change, I’ll try to move the for loops to outside the heartbeat, thank you for the answer. :slightly_smiling_face:

The loops wouldn’t break after i change the canSpeedUp or the canBrake variables?

That was just an idea on how you can fix your code. For a more accurate solution you should give us more information on what your code should do.

I removed the for loops and I noticed that they were inside the heatbeat because they’re not basePart.Touched:Connect() they use :GetTouchingParts, I need to run this every frame otherwise I’ll get the touching parts only when I runned the loop.

It’s an npc car, i know i could make this with pathfinding but i made it with constraints, here’s the full script:

-- Services

local RunService = game:GetService("RunService")

-- Bindable events

local ChangeThrottle = game.ReplicatedStorage.BindableEvents.Car.ChangeThrottle
local ChangeSteer = game.ReplicatedStorage.BindableEvents.Car.ChangeSteer

-- Configuration

local maxThrottle = 1
local maxReverseThrottle = -0.7

-- Variables

local car = script.Parent
local rayOrigin = car.RayOrigin

local desiredSteer = math.round(rayOrigin.CFrame.LookVector.Z)

local configuration = car.Configuration
local throttle = configuration.Throttle

local canSpeedUp = false
local canBrake = false

local turning = false

-- Functions

local function speedUp(throttle)

	if throttle.Value >= 0 then -- Speed up

		ChangeThrottle:Fire(0.05)

	else -- Brake

		ChangeThrottle:Fire(0.1)
	end
end

local function slowDown(throttle, intensity)

	if throttle.Value > 0 then

		ChangeThrottle:Fire(-intensity)

	else

		ChangeThrottle:Fire(intensity)
	end

	if throttle.Value <= intensity and throttle.Value >= -intensity then

		ChangeThrottle:Fire(0)

		return false

	else

		return true
	end
end

local function alingSteer(desiredSteer, sensibility)
	
	if desiredSteer ~= 0 then
		
		if rayOrigin.CFrame.LookVector.Z < desiredSteer then

			ChangeSteer:Fire(-sensibility)

		else

			ChangeSteer:Fire(sensibility)
		end
		
	elseif desiredSteer == 0 then
		
		if rayOrigin.CFrame.LookVector.Z < desiredSteer then

			ChangeSteer:Fire(sensibility)

		else

			ChangeSteer:Fire(-sensibility)
		end
	end
end

local function steerTo(direction, turnWaitTime, recoveringSteer)
	
	turning = true
	
	ChangeSteer:Fire(direction)

	task.wait(turnWaitTime)
	
	if recoveringSteer == nil then
		
		desiredSteer = math.round(rayOrigin.CFrame.LookVector.Z)
	end
	
	while true do

		task.wait(0.05)

		alingSteer(desiredSteer, 0.4)
		
		if math.abs(rayOrigin.CFrame.LookVector.Z - desiredSteer) < 0.5 then

			turning = false

			break
		end
	end
end

local function randomizeSteer(touch)
	
	local randomizer = math.random(-1, 1)

	if randomizer ~= 0 then
		
		if randomizer == 1 then
			
			steerTo(1, touch:GetAttribute("RightTurnWait"))
			
		elseif randomizer == -1 then
			
			steerTo(touch:GetAttribute("LeftTurnDirection"), touch:GetAttribute("LeftTurnWait"))
		end
		
		return true
		
	else
		
		return false
	end
end

local function recoverSteer(touch)
	
	if touch.Name == "SteerRecover" and turning == false then
				
		steerTo(touch:GetAttribute("Direction"), 0.05, true)
	end
end

-- Event functions

car.Structures.MainStructure.Touched:Connect( function(touch)
	
	if touch.Name == "Turn" and touch:GetAttribute("Obrigatory") == false then
		
		local turnChance = math.random(0, 1)

		if turnChance > 0 then

			if touch:GetAttribute("Direction") == 0 then
				
				randomizeSteer(touch)
				
			else
				
				steerTo(touch:GetAttribute("Direction"), touch:GetAttribute("TurnWaitTime"))
			end
		end
		
	elseif touch.Name == "Turn" then
		
		if touch:GetAttribute("Direction") == 0 then
			
			while true do
				
				task.wait(0.1)
				
				if randomizeSteer(touch) then
					
					break
				end
			end
			
		else
			
			steerTo(touch:GetAttribute("Direction"), touch:GetAttribute("TurnWaitTime"))
		end
	end
end)

for _, child in pairs(car:GetChildren()) do

	if child:IsA("BasePart") then

		local connection = child.Touched:Connect(function() end)

		for _, part in ipairs(child:GetTouchingParts()) do

			recoverSteer(part)
		end

	elseif child:IsA("Folder") then

		for _, folderChild in pairs(child:GetChildren()) do

			local connection = folderChild.Touched:Connect(function() end)

			for _, part in ipairs(folderChild:GetTouchingParts()) do

				recoverSteer(part)
			end
		end
	end
end

RunService.Heartbeat:Connect( function()
	
	local raycastParams = RaycastParams.new()

	raycastParams.FilterDescendantsInstances = {}
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist

	local RaycastResult = game.Workspace:Raycast(

		rayOrigin.Position,
		rayOrigin.CFrame.LookVector * 10,
		raycastParams
	)

	if RaycastResult and RaycastResult.Normal.Y < 1 and RaycastResult.Instance.CanCollide == true then

		canSpeedUp = false
		canBrake = true
		
		if canBrake then
			
			slowDown(throttle, 0.02)

			task.wait(0.02)			
		end
		
	else
		
		canBrake = false
		canSpeedUp = true
		
		if canSpeedUp and throttle.Value < maxThrottle then
			
			if canBrake == false then

				speedUp(throttle)
			end

			task.wait(0.25)
		end
	end
	
	if math.abs(rayOrigin.CFrame.LookVector.Z - desiredSteer) > 0.001 and turning == false then
		
		alingSteer(desiredSteer, 0.4)
		
	elseif turning == false then
		
		ChangeSteer:Fire(0)
	end
end)

All of the work is already done for you in the physics engine! It’s as simple as setting reasonable values for the constraints, and then setting values in a Script. No interpolation should be needed.

Additionally, you shouldn’t be firing remotes every frame. Let the server interpolate things like this instead of the client.

See HingeConstraint.MotorMaxAcceleration and HingeConstraint.MotorMaxTorque.

They’re bindable events, i think they don’t lag.

Actually it’s a server script

So if i set throttle to 1 the car will speed up smoothly?

Heres the script that recieves the fires:

-- Services

local Players = game:GetService("Players")

-- Bindable events

local ChangeThrottle = game.ReplicatedStorage.BindableEvents.Car.ChangeThrottle
local ChangeSteer = game.ReplicatedStorage.BindableEvents.Car.ChangeSteer

-- Variables

local car = script.Parent
local seat = car.Seat

local configuration = car.Configuration
local throttle = configuration.Throttle
local steer = configuration.Steer

local motor1 = car.Structures.WheelMount.Cylindrical1
local motor2 = car.Structures.WheelMount2.Cylindrical2
local motor3 = car.Structures.SteerPart.Cylindrical3
local motor4 = car.Structures.SteerPart2.Cylindrical4

local steerServo1 = car.Structures.SteerPart.SteerHinge1
local steerServo2 = car.Structures.SteerPart2.SteerHinge2

local maxSpeed = 10
local steerAngle = 40

local wheelFriction = 3

local player

-- Utility functions

local function changeWheelsPhysics(density, friction, elasticity, frictionWeight, elasticityWeight)

	for _, wheel in pairs(car.Wheels:GetChildren()) do

		local CustomPhysics = PhysicalProperties.new(density, friction, elasticity, frictionWeight, elasticityWeight)

		wheel.CustomPhysicalProperties = CustomPhysics
	end
end

local function setMotorVelocity(velocity)

	motor1.AngularVelocity = -velocity
	motor2.AngularVelocity = velocity
	motor3.AngularVelocity = -velocity
	motor4.AngularVelocity = velocity
end

-- Event functions

ChangeThrottle.Event:Connect( function(increment)

	throttle.Value = throttle.Value + increment

	if increment == 0 then

		throttle.Value = 0
	end

	setMotorVelocity(throttle.Value * maxSpeed)
end)

ChangeSteer.Event:Connect( function(desiredSteer)

	steer.Value = desiredSteer

	steerServo1.TargetAngle = steerAngle * steer.Value
	steerServo2.TargetAngle = steerAngle * steer.Value
end)

-- Tasks

changeWheelsPhysics(1, 3, 1, 1, 1)

Well, sorry, i didn’t notice that you removed only the child.Touched:Connect(function() end) from the heartbeat, and the whiles didn’t caused any lag i don’t know why.

Oh, that’s fine then.

If you set it to anything it should accelerate smoothly towards your target, provided you set up the constraints right.

If you set MotorMaxAcceleration to a stupidly high number then it will try it’s absolute hardest to reach your target speed, and if you also set MotorMaxTorque to a stupidly high number it’ll immediately reach your target speed.

1 Like