Function using while true do loop too much

Hey, pretty much the title. The function moves a part in a random direction, it works well with about 10 parts but once it starts doing 20 - 30 Parts it starts to mess up.

I believe its because of the many loops despite using break whenever the part is no longer there.

Not quite sure how to achieve the same effect but without loops.

Thanks in advanced!

function RandomMove(Part, Speed, DecisionMakingTime: number, forwardAllowed: boolean, directionAllowed: boolean)
	local Map = workspace.Map:GetChildren()[1]
	Part.PrimaryPart.AlignPosition.Position = Vector3.new(3, Map.Position.Y, Map.Position.Z)

	local forward = 0
	local direction = 0

	local y_direction = 0
	local z_direction = 0

	local MoveCooldown = false

	local max_y = Map.Size.Y / 2
	local max_z = Map.Size.Z / 2

	local Decide
	local Move

	Decide = coroutine.create(function()
		while true do
			if Part.PrimaryPart then
				if forwardAllowed then
					forward = math.random(1, 2)
				end

				if directionAllowed then
					direction = math.random(1, 2)
				end

				task.wait()
				task.wait(DecisionMakingTime)
			else
				break
			end
		end
	end)

	coroutine.resume(Decide)

	Move = coroutine.create(function()
		while true do
			wait()
			if Part.PrimaryPart then
					local f = 0 -- forward
					local t = 0 -- turn

					if MoveCooldown == false then
						if forward == 1 then -- forward
							f = Speed
							y_direction = y_direction + Speed
						elseif forward == 2 then -- Backward
							f = -Speed
							y_direction = y_direction - Speed
						end

						if direction == 1 then -- Left
							t = -Speed
							z_direction = z_direction + Speed
						elseif direction == 2 then -- right
							t = Speed
							z_direction = z_direction - Speed
						end
					end

					if Part.PrimaryPart == nil then return end
					local newPosition = Part.PrimaryPart.Position + Vector3.new(0, f, t)

					if math.abs(newPosition.Y - Map.Position.Y) <= max_y and math.abs(newPosition.Z - Map.Position.Z) <= max_z then
						Part.PrimaryPart.AlignPosition.Position = newPosition
					end
			else
				break
			end
		end
	end)
	coroutine.resume(Move)
end

for i,v in pairs(workspace.MovingParts:GetChildren()) do
	-- There's about 20 Moving Parts.
	RandomMove(v, 10, 0.1, true, true)
end

You may look into this:

1 Like

Hey, thank you I’ll check it out! Do you have any suggestions on how I can implement RunService to avoid my previous problem?

it starts to mess up

What do you mean by that? FPS drops?

loops despite using break

Generally you want to avoid using while true loops and breaks, you should make use of the condition in the loop and do while Part.PrimaryPart do instead.

wait()

I see you used task.wait() in the 1st function so im not sure why you went for wait() in the 2nd function? On another note I believe task.wait() and RunService.Heartbeat:Wait() are practically the same although I would prefer heartbeat for an empty wait

This might be because of wait() throttling. Replace the wait() with task.wait() to avoid this issue.

Now, let’s talk about the while true do loop: the fact of the matter is that you’re starting two while true loops in separate coroutines for every part. This is a little awkward. To your credit, it’s not exactly easy to separate the while true do loop creation from the random move, since each part needs to keep track of its own state. What you need is a cohesive way for your behavior (what’s in the while true do loops) and your state (foward, direction, movecooldown, etc). to interact. What you have now fits that description, but it’s not very elegant. I think it would be more elegant to use object oriented programming here (if you’re not familiar with that, do read the link!).

Consider the following structure:

-- Moving part class; perhaps make a separate modulescript for this that returns MovingPart.
local MovingPart = {}
MovingPart.__index = MovingPart

function MovingPart.new(part)
    local self = {
        Part = part,
        Speed = 10,
        ForwardAllowed = true,
        DirectionAllowed = true,
        DecisionMakingTime = 0.1,
        CanDecide = true,

        _forward = 0,
        _direction = 0,
        _moveCooldown = false,
        - -etc.
    }
    setmetatable(self, MovingPart)

    return self
end

function MovingPart:Decide()
    -- code that sets state
end

function MovingPart:Move()
    -- code that moves the part according to state
end

-- Main script
local movingParts = {}

for _, part in workspace.MovingParts:GetChildren() do
    table.insert(movingParts, MovingPart.new(part))
end

while true do
    for i, movingPart in movingParts do
        if not movingPart then -- In case any get destroyed
            table.remove(movingParts, i)
            continue
        end
        
        if movingPart.CanDecide then
            movingPart:Decide()
        end

        movingPart:Move()

        task.wait()
    end
end

Note that the above is untested and incomplete; it’s just an idea to get you started.

I expanded upon your Oop!
Tested and working.
I’m not sure what the intended result was, but we have some random movement!

@OP, there was a plenty of unused code here too. MoveCooldown, z_direction & y_direction were calculated and not used in your code.

I would move away from while true do and look more into using RunService in the future too.
I hope this was helpful! :wink:

-- get runservice for heartbeat or renderstepped connections
-- easier than coroutine
local Run = game:GetService("RunService")
-- * assuming the map does not change, move map outside of the function, store it once.
-- cleans up your function
local Map = workspace.Map:WaitForChild("MapPart")
local max_x = Map.Size.X / 2
local max_y = Map.Size.Y / 2
local max_z = Map.Size.Z / 2

-- Moving part class
local MovingPart = {}
MovingPart.__index = MovingPart

function MovingPart.new(model, attributes)
	attributes = attributes or {}
	
	local self = {}
	self.Model = model
	self.PrimaryPart = model.PrimaryPart
	self.Elapsed = 0
	self.Speed = attributes["Speed"] or 10
	self.DecisionTime = attributes["DecisionTime"] or 0.5
	
	self.DontAllowForward = attributes["DontAllowForward"] or false
	self.DontAllowDirection = attributes["DontAllowDirection"] or false
	
	self.Forward = false
	self.Direction = false
	
	setmetatable(self, MovingPart)
	return self
end

function MovingPart:Decide()
	if (not self.DontAllowForward) then
		self.Forward = math.random(0,1) == 0
	end
	if (not self.DontAllowDirection) then
		self.Direction = math.random(0,1) == 0
	end
end

function MovingPart:Move(dt)
	-- multily speed by dt for consistant speeds across framerates
	local speed = self.Speed * dt
	local f = self.Forward and speed or -speed
	local t = self.Direction and -speed or speed
	
	local newPosition = self.Model:GetPivot().Position + Vector3.new(f, 0, t)
	
	if math.abs(newPosition.X - Map.Position.X) <= max_x and math.abs(newPosition.Z - Map.Position.Z) <= max_z then
		self.Model:PivotTo(CFrame.new(newPosition))
	end
end

-- Main script
local AllMovingParts = {}
local random = Random.new()
for _, part in workspace.MovingParts:GetChildren() do
	local movingPartClass = MovingPart.new(part,
		{
			Speed = random:NextNumber(1,20),
			DecideTime = random:NextNumber(0.1,1)
		}
	)
	table.insert(AllMovingParts, movingPartClass)
end

Run.Heartbeat:Connect(function(dt)
	for i, class in AllMovingParts do
		if (not class.Model) or (not class.PrimaryPart) then
			continue
		end
		
		class.Elapsed += dt
		if class.Elapsed > class.DecisionTime then
			class.Elapsed -= class.DecisionTime
			class:Decide()
		end
		
		class:Move(dt)
	end
end)

For heartbeat, especially if you only want something to run once every n seconds, while true do loops work fine as a replacement. For renderstepped and presimulation, they do not. Note that these two are functionally very similar, in that they both run every frame during the heartbeat stage:

while true do
    print("Hi")
    task.wait()
end

and

RunService.Heartbeat:Connect(function()
    print("Hi")
end)

The only difference is that the while true do loop runs in the delayed threads section and the heartbeat one runs in the deferred threads section, and thus, each frame, what’s in the while true do loop runs before what’s connected to the heartbeat event. This is not a big enough difference to be generally useful, and there are better ways to dictate the order at which things run every frame than to rely on this. So, for all intents and purposes, they’re the same.

The reason we prefer RunService is because using RunService events gives us more control over when in the frame things are run. Need something run before rendering? Use :BindToRenderStep(), where you can even specify a priority value to determine when your code is run in relation to other code bound to render step, including roblox’s default player input and camera controls. Need something done before physics simulation? Use RunService.PreSimulation. Got something that can wait until after physics simulation? Use RunService.Heartbeat.

But since code in while true do task.wait() end loops runs in the heartbeat stage anyway, as long as you don’t need the code to happen before rendering or physics simulation, you can get away with using the loop, espeically if you want to specify a delay inside of the task.wait().

1 Like

Great Elaboration of the RunService! Thank you for Clarifying.

1 Like