How do I optimize this script to properly generate mazes without causing freezes?

I want to make my game no longer cause long freezes, and prevent the game from generating dead ends in between segments that happen to connect to each other indirectly.

The issue is as stated in the goal. I need to get the lag spikes caused by dead end generation to cease (when normal segments can’t generate and has to generate a dead end, randomly causes script execution exhaustion for no visible reason) and I have no idea where it could be coming from, other than the random segment generator function maybe infinitely looping. Dead ends were working as intended up until I changed how segment generation chance works to be based on individual spawn chances instead of category based spawn chances. Dead ends should only be spawning if the specified exit of a segment reached a length beyond a number of times, or if another segment is in the way, and Dead ends should never generate more than once in the same place.

For more information: BoundingBoxes in each segment serves to prevent other segments from clipping into it, and EndGuard is a special case version of BoundingBoxes which covers individual exits, and only takes effect when a dead end tries to generate inside of it.

I have tried so many solutions that I have been burnt out trying them all. No sort of roblox documentation is going to help, I’m afraid.

All of these issues I have ran into almost always originated from the MapGeneration script, since it is the main script that handles all of the generation, and the segments themselves are certainly not the problem.

Code:

local SS = game:GetService("ServerStorage")
local sfolder = workspace:FindFirstChild("Segments")
sfolder.Parent = SS
local live = sfolder.Live:GetChildren()
local dead = sfolder.Dead:GetChildren()
local pool_live = {}
local pool_dead = {}

function addMarbles(p, t)
	for _,v in ipairs(t) do
		local q = v:GetAttribute("SpawnChance")
		if q and math.floor(q) >= 1 then
			for n=1, math.floor(q) do
				table.insert(p, v)
			end
		end
	end
end
function rollMarbles(p, removeResult, default)
	if #p == 0 then
		warn("Pool is empty. Resorting to default, if any.")
		return default
	end
	
	local r = p[math.random(1,math.max(1, #p))]
	if removeResult then
		while table.find(p, r) do
			table.remove(p, table.find(p, r))
		end
	end
	return r
end
addMarbles(pool_live, live)
addMarbles(pool_dead, dead)
print("live #:", #pool_live)
print("dead #:", #pool_dead)

local maze = workspace:FindFirstChild("Maze")
local beginning = maze:FindFirstChild("Spawn"):FindFirstChild("Beginning")
local ev = game:GetService("ReplicatedStorage"):FindFirstChild("Recam")

-- Settings
local config = {
	max_segment_count = script:GetAttribute("MaxSegmentCount"),
	max_segment_combo = script:GetAttribute("MaxSegmentCombo"),
	max_branches = script:GetAttribute("MaxBranches"),
	delay_multiplier = script:GetAttribute("DelayMultiplier"),
	
	manual_gen = script:GetAttribute("DebugMode")
}
local segmentCount = 0
local threads = 0


local function subfunc(func, ...) -- Run a function independently from the current thread.
	local rem = Instance.new('BindableEvent')
	rem.Event:Connect(func)
	rem:Fire(...)
end

function connecteval(root, obj, ignoreCollisions)
	local rot = CFrame.Angles(0, math.rad(root.Orientation.Y), 0)
	obj:SetPrimaryPartCFrame(obj:GetPrimaryPartCFrame() * rot)
	local oy = math.rad(root.Orientation.Y)
	local increment = Vector3.new(-math.sin(oy), 0, -math.cos(oy))
	print(increment)
	local final = root.Position + increment
	local distance = final - obj.PrimaryPart.Position
	
	if ignoreCollisions == nil then
		ignoreCollisions = obj:GetAttribute("IgnoresCollisions")
	end
	print("Ignoring Collisions:", ignoreCollisions)
	
	local oldParent = obj.Parent
	obj.Parent = maze.Segments
	if config.manual_gen then
		ev:FireAllClients(obj.BoundingBoxes.Main)
	end
	
	for _,p in ipairs(obj:GetDescendants()) do
		if not p:IsA("BasePart") then
			continue
		end
		
		p.Position += distance
		if config.manual_gen then
			p.CanCollide = false
		end
		
		if p:FindFirstAncestor("BoundingBoxes") or p:FindFirstAncestor("EndGuard")
			or p:FindFirstAncestor("Connections") then
			
			p.Transparency = config.manual_gen and .8 or 1
		end
		
		local collisionG = ignoreCollisions and "EndGuard" or "BoundingBoxes"
		print("Collision Focus:", collisionG)
		
		local attempts = {}
		p.Touched:Connect(function()end)
		for _,b in ipairs(p:GetTouchingParts()) do
			local db = b.Parent.Parent
			local droot = root:FindFirstAncestorOfClass("Model")
			
			table.insert(attempts, "\nBoundery: " .. b.Parent.Name .. "\nColliding: " .. p.Parent.Name .. "\nSegment: " .. db.Name)
			if b.Parent.Name == collisionG and p.Parent.Name == "BoundingBoxes"
				and db ~= obj and db.Parent.Parent == maze and
				(not b:IsDescendantOf(droot)) then
				
				obj.Parent = oldParent
				return false,final
			end
		end
		
		print(#attempts, "attempts:", attempts)
	end
	
	return true,final
end
function generate(start, segment, ignoreCollisions)
	local obj = segment:Clone()
	local success,final = connecteval(start, obj, ignoreCollisions)
	local curcon,sub
	
	if success or ignoreCollisions then
		local cs = obj:FindFirstChild("Connections")
		if cs then
			curcon = cs:FindFirstChild("End")
			sub = cs:FindFirstChild("Sub")
		end
	else
		obj = sfolder.Dead.Dead_Wall:Clone()
		_,final = connecteval(start, obj, true)
	end
	
	if not obj:GetAttribute("IsDeadEnd") then
		segmentCount += 1
		print("s:", segmentCount)
	end
	
	return curcon, sub, success, final
end
function randGenerate(start, n)
	local lpool_live,lpool_dead = table.clone(pool_live),table.clone(pool_dead)
	local sid,temp, curcon,ocs,success,final
	
	while not success do
		print({lpool_live, lpool_dead})
		
		local variant
		if segmentCount < config.max_segment_count then
			if n <= config.max_segment_combo then
				variant = lpool_live
			else
				variant = lpool_dead
			end
		else
			variant = lpool_dead
		end
		
		temp = rollMarbles(variant, false)
		print("v:", #variant)
		
		while variant == lpool_live and n == config.max_segment_combo and not temp:GetAttribute("ContainsSubEnds") do
			temp = rollMarbles(variant, true, sfolder.Dead.Dead_Wall)
			print("v:", #variant)
			wait(.071)
			if temp == sfolder.Dead.Dead_Wall then
				break
			end
		end
		
		curcon,ocs,success,final = generate(start, temp)
		if temp == sfolder.Dead.Dead_Wall and not success then
			warn("Cannot spawn fallback. Imma just skip this one.")
			break
		end
	end
	
	return curcon, ocs, n, success, final
end
function fullGenerate(start)
	threads += 1
	local curcon = start
	
	for n=1,config.max_segment_combo+1 do
		if config.manual_gen then
			ev.OnServerEvent:Wait()
		else
			wait(.1 * config.delay_multiplier)
		end
		
		local subcon
		local newcon, ocs, n, success, final = randGenerate(curcon, n)
		
		if not (newcon or ocs) then
			print("dead end")
			break
		end
		
		if ocs then
			subcon = ocs:GetChildren()
			for _,sv in ipairs(subcon) do
				subfunc(fullGenerate, sv)
				wait(.05)
			end
		end
		
		if newcon then
			curcon = newcon
		end
		
		subcon = subcon or {}
		
		print(newcon, ocs, n, success, final)
	end
	
	threads -= 1
	if threads <= 0 then
		ev:FireAllClients()
	end
end

if not config.manual_gen then
	wait(8)
	for n=3,1,-1 do
		print(n)
		wait(1)
	end
end
fullGenerate(beginning)

If you need to copy the game, just request it and I’ll PM you the game link.

Bump (I really need a solution for this).