Help making a loop run fast without lag

I’ve looked at several forum posts and I can’t find a good solution.

I have this giant loop that loads a players “vehicle” and transfers it from build mode to flight mode. It runs at an okay speed, but it lags the entire game if the player has a build that has more than 400-700 blocks.

Is there any way I can fix this? I have tried coruntining everything, but that just broke a whole bunch of functions.

for i, v in pairs(plot:GetChildren()) do
			
			if v.ClassName ~= "SelectionBox" and v.ClassName ~= "SurfaceGui" then
				local clone = game.ReplicatedStorage.Items:FindFirstChild(v.Name)
				
				if clone then
					clone = clone:Clone()
					
					
				else
					if string.find(string.lower(v.Name),"customshape") then
						clone = v:Clone()
						
						clone:SetAttribute("Health",100)
				
						local weld = Instance.new("WeldConstraint",clone.W1)
						weld.Part0 = clone.W1
						weld.Part1 = clone.W2
					else
						clone = nil
					
					end
				
				end
				
				
				if clone then
					
					pcall(function()
						clone:SetPrimaryPartCFrame(v:GetPrimaryPartCFrame())
					end)
					
					pcall(function()
						
						
						
						if clone:FindFirstChild("Config") then
							clone.Config:ClearAllChildren()


							for o,b in pairs(v.Config:GetChildren()) do
								local clone2 = b:Clone()
								clone2.Parent = clone.Config
							end
						end

						for o, b in pairs(clone:GetChildren()) do
							if b.ClassName == "Part" or b.ClassName == "WedgePart" or b.ClassName == "MeshPart" or b.ClassName == "CornerWedgePart" or b.ClassName == "UnionOperation" then
								b.Material = v:FindFirstChild(b.Name).Material
								b.Color = v:FindFirstChild(b.Name).Color
								b.Transparency = v:FindFirstChild(b.Name).Transparency
							end
						end
					end)
					if clone then
						clone.Parent = model
					end

					
				end
				
				v:Destroy()
			end
		end
	
--	end)
	
	--coroutine.resume(starter1)
	--functionlist[#functionlist+1] = starter1
	
	pcall(function()
		p.Character.Humanoid.Jump = true
	end)
	
	local plane = model:Clone()
	plane.Parent = workspace
	
	local angles = plane:GetPivot()-plane:GetPivot().Position
	
	if plane:FindFirstChild("Seat") then
		local seat = plane:FindFirstChild("Seat")
		print(seat.Name)
		angles = seat.Seat.CFrame-seat.Seat.CFrame.Position
	end
	angles = CFrame.fromEulerAnglesYXZ(angles.X,angles.Y,angles.Z+math.rad(90)) 
	
	local mass = Instance.new("NumberValue",plane)
	mass.Name = "Mass"
	mass.Value = 0
	
	for i, v in pairs(plane:GetDescendants()) do
		if v.ClassName == "Script" then
			v.Enabled = true
		elseif v.ClassName == "Part" or v.ClassName == "Wedge" or v.ClassName == "Seat" then
			mass.Value += v.Mass
		end
	end
	
	--for i, v in pairs(plane:GetChildren()) do

	--	for _,w in pairs(v:GetChildren()) do
	--		pcall(function()
	--			w.Anchored = false
	--		end)
	--	end

	
	--end
	

	for i, v in pairs(plane:GetChildren()) do


		if math.random(1,5) == 1 then
			game.ReplicatedStorage.Events.Misc.ProgressBars.UpdateProgressBar:FireClient(p,i,#plane:GetChildren())
		end
		
		if #plane:GetChildren() >= 500 then
			if math.random(1,45) == 1 then
				wait(.08)
			end
		end
		
		for o,b in pairs(v:GetChildren()) do
			
			if string.find(string.lower(b.ClassName),"part") or string.find(string.lower(b.ClassName),"union") then
				local originalsize = b.Size
				--b.Size = b.Size + Vector3.new(.05,.05,.05)
				
				
				local touching
				--b.Size = originalsize 
				if v.Name == "Motor" or v.Name == "Motor2" then
					if v.Config.Legacy.Value == true then
						if b.Name == "Base" then
							touching = getAdjacentParts(b,{Position = v.Shape.Position,Size = Vector3.new(3,3,3)})
						end
					else
						touching = getAdjacentParts(b)
					end
				else
					touching = getAdjacentParts(b)
				end
				
			
				
				if not touching then
					touching = {}
				end
				for p,n in pairs(touching) do
					if n:IsDescendantOf(plane) then
						if not n:IsDescendantOf(v) then
							if n.Name ~= "Shape" and b.Name ~= "Shape" then
								local weld = Instance.new("WeldConstraint",n)
								weld.Part0 = n
								weld.Part1 = b
							end
						end
					end
				end
			end
		
		end
		
		
			
			
		if v.Name == "Motor" then
			
			if v.Config.Visible.Value == false then
				v.Base.CanCollide = false
				v.Turnable.CanCollide = false
			end
			
		end
		
		if v.Name == "InvisibleBlock" then
			if v.Config.CanCollide.Value == false then
				for _,q in pairs(v:GetChildren()) do
					if q:IsA("Part") then
						q.CanCollide = false
					end
				end
			end
		end
		
		if v.Name == "InvisibleWingPanel" then
			if v.Config.CanCollide.Value == false then
				for _,q in pairs(v:GetChildren()) do
					if q:IsA("Part") then
						q.CanCollide = false
					end
				end
			end
		end
		
	end
	
	--local starter2 = coroutine.create(function()
		
		if location == "Water" then
			plane:PivotTo(CFrame.new(workspace.Spawns.Water:GetChildren()[math.random(1,#workspace.Spawns.Water:GetChildren())].Position)*angles)
		end

		for i, v in pairs(plane:GetChildren()) do

			for _, t in pairs(v:GetChildren()) do
				pcall(function()
					t.Anchored = false
				end)
			end
		end
		
		local functionsetups = {}

		for i, v in pairs(plane:GetChildren()) do
			if math.random(1,25) == 1 then
				game.ReplicatedStorage.Events.Misc.ProgressBars.UpdateProgressBar:FireClient(p,i,#plane:GetChildren())
			end
			
			if math.random(1,75) == 1 then
				task.wait(0.05)
			end

		




			local s, e = pcall(function()
				if setups[v.Name] then
					local spawned = coroutine.create(function() setups[v.Name](p,v) end)
					coroutine.resume(spawned)
					table.insert(functionsetups,(#functionsetups+1),spawned)
					--wait(0)
					--coroutine.close(spawned)
				end

			end)
		
			for _,w in pairs(v:GetChildren()) do
				pcall(function()
					w:SetNetworkOwner(p)
				end)
			end
		
			if s then
				--	print("all objects on client side")
			else
				--	print(e)
			end
		end
		wait()
		for i, v in pairs(functionsetups) do
			coroutine.close(v)
		end

This code is enormous. Have you tried breaking it into sections and profiling each one to see how much time each section takes?

The first loop runs pretty quick. The second loop (the one halfway through) is the loop that causes the most trouble and time. The third loop takes less than a second.

Calculating the mass is as easy as plane.PrimaryPart.AssemblyMass, assuming all parts are connected and all models’ PrimaryPart property is set.

Also, there’s a lot of optimization needed for your code. Example, the amount of GetChildren calls can be lowered by using temporary variables that store the returned value of that function, especially this:
image

Thank you so much for spending the time to look through my code. Do you know any other spots I should improve?

1

Utilize Object:IsA("ClassName"). Instead of doing this:

string.find(string.lower(b.ClassName),"part") or string.find(string.lower(b.ClassName),"union")

Do this:

b:IsA("BasePart") -- Any object that inherits BasePart, such as WedgePart, Part, UnionOperation, etc.

2

Try string patterns. From this:

v.Name == "Motor" or v.Name == "Motor2"

to this:

string.match(v.Name, "^Motor%d*$")
-- Or this shorthand:
v.Name:match("^Motor%d*$")

-- ^ - Test if the name's first character is the same as the character after this anchor.
-- %d - Check if the character at this position is a number.
-- %d* - Check if the characters at this position and onward are numbers.
-- $ - Test if the name's last character is the same as the character before this anchor.

-- See https://create.roblox.com/docs/reference/engine/libraries/string for string captures and patterns.

3

This is unnecessary:

if v.Name == "Motor" then -- This

		if v.Config.Visible.Value == false then
			v.Base.CanCollide = false
			v.Turnable.CanCollide = false
		end

	end

	if v.Name == "InvisibleBlock" then -- This
		if v.Config.CanCollide.Value == false then
			for _,q in pairs(v:GetChildren()) do
				if q:IsA("Part") then
					q.CanCollide = false
				end
			end
		end
	end

	if v.Name == "InvisibleWingPanel" then -- This
		if v.Config.CanCollide.Value == false then
			for _,q in pairs(v:GetChildren()) do
				if q:IsA("Part") then
					q.CanCollide = false
				end
			end
		end
	end

Simplify to this:

if v.Name == "Motor" then
		if v.Config.Visible.Value then -- If visible, ignore with continue (this keyword is only applicable in loops).
			continue
		end
		
		v.Base.CanCollide = false
		v.Turnable.CanCollide = false
	elseif v.Name == "InvisibleBlock" or v.Name == "InvisibleWingPanel" then
		if v.Config.CanCollide.Value then
			continue
		end
		
		for _, q in v:GetChildren() do
			if q:IsA("BasePart") then
				q.CanCollide = false
			end
		end
	end

4

You’re using too many pcalls to mask potentially error-prone pieces of code, which shouldn’t be done too much (if the pcalled code stops early due to an error, there may be leftover data unhandled, such as new Instances lying around somewhere, contributing to memory leaks).

Always validate the values and objects instead of band-aiding.


There are many more improvements need, but instead of listing them, I just made fixes to your code and sent it here. Haven’t tested the code, and I’m drained of energy after being up for hours, so let me know any problems with this:

local repStore = game:GetService("ReplicatedStorage")

local items = repStore:WaitForChild("Items")

for _, v in plot:GetChildren() do
	if v:IsA("SelectionBox") or v:IsA("SurfaceGui") then
		v:Destroy()
		continue -- Destroy and continue?
	end
	
	local itemName = v.Name
	local targetItem, clone = items:FindFirstChild(itemName), nil

	if targetItem then
		clone = targetItem:Clone()
	else
		if string.find(string.lower(itemName),"customshape") then
			clone = v:Clone()

			clone:SetAttribute("Health",100)

			local weld = Instance.new("WeldConstraint",clone.W1)
			weld.Part0 = clone.W1
			weld.Part1 = clone.W2
		else
			v:Destroy()
			continue -- Destroy and continue?
		end
	end
	
	if not clone then
		continue -- Destroy and continue?
	end

	clone:SetPrimaryPartCFrame(v:GetPrimaryPartCFrame())

	if clone:FindFirstChild("Config") then
		clone.Config:ClearAllChildren()

		if v:FindFirstChild("Config") then
			for _, b in v.Config:GetChildren() do
				local clone2 = b:Clone()
				clone2.Parent = clone.Config
			end
		end
	end

	for _, b in clone:GetChildren() do
		if b:IsA("BasePart") then
			local t = v:FindFirstChild(b.Name)
			
			if not t then continue end
			
			b.Material = t.Material
			b.Color = t.Color
			b.Transparency = t.Transparency
		end
	end
	
	clone.Parent = model

	v:Destroy()
end

if not p.Character then
	repeat
		task.wait()
	until p.Character
end

local humanoid = p.Character:FindFirstChildWhichIsA("Humanoid")

if humanoid then
	humanoid.Jump = true
end

local plane = model:Clone()
local planeObjects = plane:GetChildren()
local planeObjectCount = #planeObjects

local angles = plane:GetPivot() - plane:GetPivot().Position

plane.Parent = workspace

if plane:FindFirstChild("Seat") then
	local seat = plane:FindFirstChild("Seat")
	print(seat.Name)
	angles = seat.Seat.CFrame-seat.Seat.CFrame.Position
end
angles = CFrame.fromEulerAnglesYXZ(angles.X,angles.Y,angles.Z+math.rad(90)) 

local mass = Instance.new("NumberValue",plane)
mass.Name = "Mass"
mass.Value = 0

for i, v in plane:GetDescendants() do
	if v:IsA("Script") then
		v.Enabled = true
	elseif v:IsA("BasePart") then
		mass.Value += v.Mass
	end
end

for i, v in planeObjects do
	if math.random(1,5) == 1 then
		repStore.Events.Misc.ProgressBars.UpdateProgressBar:FireClient(p,i,planeObjectCount)
	end

	if planeObjectCount >= 500 then
		if math.random(1,45) == 1 then
			wait(.08)
		end
	end

	for _, b in v:GetChildren() do

		if not b:IsA("BasePart") then
			continue
		end
		
		local originalsize = b.Size
		--b.Size = b.Size + Vector3.new(.05,.05,.05)

		local touching
		--b.Size = originalsize 
		if v.Name:match("^Motor%d*$") and v.Config.Legacy.Value and b.Name == "Base" then
			touching = getAdjacentParts(b, {
				Position = v.Shape.Position,
				Size = Vector3.one * 3
			})
		else
			touching = getAdjacentParts(b)
		end
		
		if not touching then
			continue -- Skip the next for loop.
		end
		
		for _, n in touching do
			if (not n:IsDescendantOf(plane)) or n:IsDescendantOf(v) then
				continue
			end
			
			if not (n.Name == "Shape" or b.Name == "Shape") then
				local weld = Instance.new("WeldConstraint",n)
				weld.Part0 = n
				weld.Part1 = b
			end
		end
	end
	
	if v.Name == "Motor" then
		if v.Config.Visible.Value then -- If visible, ignore with continue (this keyword is only applicable in loops).
			continue
		end
		
		v.Base.CanCollide = false
		v.Turnable.CanCollide = false
	elseif v.Name == "InvisibleBlock" or v.Name == "InvisibleWingPanel" then
		if v.Config.CanCollide.Value then
			continue
		end
		
		for _, q in v:GetChildren() do
			if q:IsA("BasePart") then
				q.CanCollide = false
			end
		end
	end
end

--local starter2 = coroutine.create(function()

if location == "Water" then
	plane:PivotTo(CFrame.new(workspace.Spawns.Water:GetChildren()[math.random(1,#workspace.Spawns.Water:GetChildren())].Position)*angles)
end

for _, v in planeObject do
	for _, t in v:GetChildren() do
		if t:IsA("BasePart") then
			t.Anchored = false
		end
	end
end

local functionsetups = {}

for i, v in planeObjects do
	if math.random(1,25) == 1 then
		game.ReplicatedStorage.Events.Misc.ProgressBars.UpdateProgressBar:FireClient(p,i,planeObjectCount)
	end

	if math.random(1,75) == 1 then
		task.wait(0.05)
	end

	local s, e = pcall(function()
		if setups[v.Name] then
			local spawned = coroutine.create(function() setups[v.Name](p,v) end)
			coroutine.resume(spawned)
			table.insert(functionsetups,(#functionsetups+1),spawned)
			--wait(0)
			--coroutine.close(spawned)
		end

	end)

	for _,w in pairs(v:GetChildren()) do
		if w:IsA("BasePart") then
			w:SetNetworkOwner(p)
		end
	end

	if s then
		--	print("all objects on client side")
	else
		--	print(e)
	end
end

wait()

for i, v in functionsetups do
	coroutine.close(v)
end
2 Likes

Thank you very much! I just had to make a few variable adjustments and everything was working. I won’t mark this as the solution in case I need more help with the code. Is that okay?

1 Like

You don’t have to reply right now because I understand you’re tired, but do you know how I could get the entire thing to run faster, or am I good where I am right now? For reference, it takes this about 7-15 seconds to fully load and the chat might stop or become really slow during that time. This jet (being made by one of the biggest community members in the game) has about 3000ish parts total.
image

Lag may be caused by loading a large model into the Workspace, which will lag the server itself. The script you have may be just around 1-2 percent of that lag (probably even less), so the script is not the culprit, especially that the model is 3000~ parts total.

One example of lag when moving parts from somewhere to the workspace is loading a massive save file in Lumber Tycoon 2. This may take seconds, enough for players to notice.

So basically I can’t do anything about the lag?

Kind of. The only other ways I know of are to combine as many parts as possible (through unions) or by using a 3D modelling software (like Blender) to make segments of the model (and export each segment as one mesh) to reduce the amount of parts and meshes being copied/moved over.

This may cause a slightly larger loading time for clients, but it may reduce lag in runtime.

1 Like

Okay I definitely can not do that. Thank you so much for the help though! I’ll mark your code revamp suggestion as a solution. Cheers!

1 Like

Hey! Sorry for coming back so late. Another part of the lag is the server setting up all the functional blocks. That is the bottom most loop. Do you know how I could make that process speed up? Again, these problems only happen with giant and detailed builds but I want things to be fast no matter what.

By the way, this one dude with a massive build keeps spawning in and it freezes the server completely for a couple seconds. Is there another way I can get around this?

I suppose it’s because you’re firing a RemoteEvent for a progress bar, apparently. I believe this is too heavy for the server to deal with, and I don’t think a progress bar is one valid purpose for RemoteEvents.

I’ve changed this code a bit, but it may not work exactly the way you expect it to (since I removed the lines for the progress bar and the creation of coroutines):

local steps = 0

for i, v in planeObjects do
	steps += 1
	
	if steps >= 100 then
		game:GetService("RunService").Stepped:Wait()
		steps = 0
	end
	
	if setups[v.Name] then
		task.spawn(function()
			local pass, err = pcall(setups[v.Name], p, v)

			if not pass then
				warn(err)
			end
		end)
	end

	for _,w in v:GetChildren() do
		if w:IsA("BasePart") then
			w:SetNetworkOwner(p)
		end
	end
end

Oh, yeah, the functions for setup, I don’t know how optimized they are. Wouldn’t you mind also posting those functions?

image
the entire setup module is just full of block names and how the script should prepare them to be interacted with

Also quick note, if I try to make the loop you sent run a little easier with wait(), then a lot of the blocks that need to be set up won’t end up setting up.

So with this loop, either the players build loads correctly and the server dies for a few seconds, or the players build gets messed up and the server runs fine. There probably is a very obvious solution, but I am really bad at optimization (totally not obvious).

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