Unusual script exhaustion when trying to generate a chunk for my new procedurally generated voxel world script

This is my fourth iteration of my voxel world generating script. The goal with this iteration is to be able to make it generate infinitely instead of being limited to a certain world size. (Within Roblox limits of course.)

There is a crash that happens when it’s doing the final generation. Everything else from the Perlin worms and the height maps seems to work fine but when it’s trying to actually create the world (Or the visual test part for now) it seems to crash. I don’t imagine it being because it’s trying to generate a lot at once because for now it’s only trying to generate one chunk. And even my previous iterations have generated more at once than that.

Another thing I did notice is it does not place a single block so something must be going wrong on the first attempt it’s trying to place a block. Is it some weird loop that’s happening that I am not seeing? Is it something else that I am just looking at the wrong areas for?

Here is the script I have so far.

wait(6)

--Settings--
local Settings = {
	BlockSize = 6,
	NoiseScale = 14,
	ChunkSize = 16,--Size in blocks
	WorldMaxHight = 20--100,
}
--Settings--

--Services--
local RaritySytem = require(game.ServerScriptService.RaritySystem)
local AdvancedPerlinNoise = require(game.ServerScriptService.PerlinNoise)
--Services--

--Constants--
local Blocks = game.Lighting.Blocks
local MaxOpperations = 128000
local worldseed = math.random(-1000000,1000000)
--Constants--

--Values--
local WorldChunks = {}
local Opperations = MaxOpperations
--Values--

--Functions--
function RunServerCooldown()--Special thingy that lets the server catch up after running a large opperation. May eventurally remove this if it is no longer needed as optimisations are made.
	if Opperations > 0 then
		Opperations = Opperations - 1
		--print(Opperations)
	else
		Opperations = MaxOpperations
		game:GetService("RunService").Heartbeat:Wait()
		--print("Cooldown")
	end
end

function ChangeMessage(MessageText)
	for index,child in pairs (game.Players:GetChildren()) do
		if child:IsA("Player") then

			if child.PlayerGui:FindFirstChild("MessageGui") ~= nil then child.PlayerGui:FindFirstChild("MessageGui"):Destroy() end

			if MessageText ~= false and MessageText ~= nil then
				local gui = script.MessageGui:Clone()
				gui.Parent = child.PlayerGui
				gui.TextLabel.Text = MessageText
				gui.Enabled = true
			end

		end
	end
end

function GetChunk(Xpos:number,Zpos:number)
	print(WorldChunks)
	for index = 1,#WorldChunks do
		local Chunk = WorldChunks[index]
		if Chunk.Position == Vector2.new(Xpos,Zpos) then
			return Chunk
		end
	end
	return nil
end

function FindNeighborBlock(xGridPos,zGridPos,xPos,zPos,yPos,TargetBlockType:string)
	
	local Chunk = GetChunk(xGridPos,zGridPos)
	if Chunk then
		local Grid = Chunk.Grid
		
		print("Pos = "..tostring(xPos)..", "..tostring(yPos)..", "..tostring(zPos))
		print(Grid)
		
		local TargetLocation = Grid[xPos][zPos][yPos]

		if Grid[xPos][zPos][yPos+1] ~= nil and Grid[xPos][zPos][yPos+1].BlockName == TargetBlockType then--Up
			--print("Found "..LookingFor.."!")
			return true
		end
		
		if Grid[xPos][zPos][yPos-1] ~= nil and Grid[xPos][zPos][yPos-1].BlockName == TargetBlockType then--Down
			--print("Found "..LookingFor.."!")
			return true
		end
		
		if zPos >= Settings.ChunkSize then--Back
			local NextChunk = GetChunk(xGridPos,zGridPos + 1)
			if NextChunk then
				local NextGrid = NextChunk.Grid
				if NextGrid then
					if NextGrid[xPos][1][yPos] ~= nil and NextGrid[xPos][1][yPos].BlockName == TargetBlockType then
						--print("Found "..LookingFor.."!")
						return true
					end
				else
					--error("Tried to find a chunk that does not exist yet or has an invalid position!")
					warn("Tried to find a chunk that does not exist yet or has an invalid position!")
				end
			end
		else
			if Grid[xPos][zPos + 1][yPos] ~= nil and Grid[xPos][zPos + 1][yPos].BlockName == TargetBlockType then
				--print("Found "..LookingFor.."!")
				return true
			end
		end
		
		if zPos <= 1 then--Forward
			local NextChunk = GetChunk(xGridPos,zGridPos - 1)
			if NextChunk then
				local NextGrid = NextChunk.Grid
				if NextGrid then
					if NextGrid[xPos][Settings.ChunkSize][yPos] ~= nil and NextGrid[xPos][Settings.ChunkSize][yPos].BlockName == TargetBlockType then
						--print("Found "..LookingFor.."!")
						return true
					end
				else
					--error("Tried to find a chunk that does not exist yet or has an invalid position!")
					warn("Tried to find a chunk that does not exist yet or has an invalid position!")
				end
			end
		else
			if Grid[xPos][zPos - 1][yPos] ~= nil and Grid[xPos][zPos - 1][yPos].BlockName == TargetBlockType then
				--print("Found "..LookingFor.."!")
				return true
			end
		end
		
		if xPos >= Settings.ChunkSize then--Left
			local NextChunk = GetChunk(xGridPos + 1,zGridPos)
			if NextChunk then
				local NextGrid = NextChunk.Grid
				if NextGrid then
					if NextGrid[1][zPos][yPos] ~= nil and NextGrid[1][zPos][yPos].BlockName == TargetBlockType then
						--print("Found "..LookingFor.."!")
						return true
					end
				else
					--error("Tried to find a chunk that does not exist yet or has an invalid position!")
					warn("Tried to find a chunk that does not exist yet or has an invalid position!")
				end
			end
		else
			if Grid[xPos + 1][zPos][yPos] ~= nil and Grid[xPos + 1][zPos][yPos].BlockName == TargetBlockType then
				--print("Found "..LookingFor.."!")
				return true
			end
		end
		
		if xPos <= 1 then--Right
			local NextChunk = GetChunk(xGridPos - 1,zGridPos)
			if NextChunk then
				local NextGrid = NextChunk.Grid
				if NextGrid then
					if NextGrid[Settings.ChunkSize][zPos][yPos] ~= nil and NextGrid[Settings.ChunkSize][zPos][yPos].BlockName == TargetBlockType then
						--print("Found "..LookingFor.."!")
						return true
					end
				else
					--error("Tried to find a chunk that does not exist yet or has an invalid position!")
					warn("Tried to find a chunk that does not exist yet or has an invalid position!")
				end
			end
		else
			if Grid[xPos - 1][zPos][yPos] ~= nil and Grid[xPos - 1][zPos][yPos].BlockName == TargetBlockType then
				--print("Found "..LookingFor.."!")
				return true
			end
		end
	else
		--error("Tried to find a chunk that does not exist yet or has an invalid position!")
		warn("Tried to find a chunk that does not exist yet or has an invalid position!")
	end
	
	return false
end

function IsExposedBlock(xGridPos,zGridPos,xPos,zPos,yPos)
	if FindNeighborBlock(xGridPos,zGridPos,xPos,zPos,yPos,"Air") == true then
		return true
	elseif FindNeighborBlock(xGridPos,zGridPos,xPos,zPos,yPos,"Water") == true then
		return true
	elseif FindNeighborBlock(xGridPos,zGridPos,xPos,zPos,yPos,"Greedy") == true then
		return true
	end
	return false
end

function GenerateChunk(xGridPos,zGridPos)
	local ChunkData = {
		Position = Vector2.new(xGridPos,zGridPos),
		Grid = nil
	}
	
	local Grid = {}

	for index = 1,Settings.ChunkSize do
		table.insert(Grid,{})--X
	end

	for index = 1,#Grid do
		local Xpos = Grid[index]
		for secondindex = 1,Settings.ChunkSize do
			table.insert(Xpos,{})--Z
		end
	end

	for index = 1,#Grid do
		local Xpos = Grid[index]
		for secondindex = 1,Settings.ChunkSize do
			local Zpos = Xpos[secondindex]
			for thirdindex = 1,Settings.WorldMaxHight do
				RunServerCooldown()
				table.insert(Zpos,{
					--Also known as the Ypos but it keeps all the main data
					BlockName = "",
					IsGreedy = false,
					GreedyXsize = 1,
					GreedyYsize = 1,
					GreedyZsize = 1}
				)
			end
		end
	end
	
	----Height Map----
	--Generates the basic heights of the map using various noise values.
	
	--Types of noise:
	--Main noise - Primary noise value that creates the basic hills and valeys that will further be modified later.
	--Sharpness noise - Secondary noise that will amplify or dull the intensity of the hills generated by the main noise based on the value given by this noise.
	--Height noise - A subtle noise value that is much larger in size but it controls the main height of certain areas allowing for high areas and low areas such as oceans and high lands.
	
	for Xpos = 1,#Grid do
		--print("X")
		local Xgrid = Grid[Xpos]
		for Zpos = 1,Settings.ChunkSize do
			--print("Z")
			--game:GetService("RunService").Heartbeat:Wait()
			local Zgrid = Xgrid[Zpos]
			for Ypos = 1,Settings.WorldMaxHight do
				--print("Y")
				RunServerCooldown()
				local Ygrid = Zgrid[Ypos]
				--wait(0.5)
				local perlinX = Xpos / Settings.NoiseScale
				local perlinZ = Zpos / Settings.NoiseScale
				local perlinY = Ypos / Settings.NoiseScale

				local Noise = math.noise(perlinX,worldseed,perlinZ)--Main noise
				Noise = Noise * (2 + math.noise(perlinX * 0.1,worldseed,perlinZ * 0.1))--Sharpness noise
				Noise = Noise + (20 + math.noise(perlinX * 0.05,worldseed,perlinZ * 0.05) * 30)--Height noise
				
				--print("Noise = "..noiseValue)
				
				if Ypos <= Noise then
					Zgrid[Ypos].BlockName = "Noise"
				else
					Zgrid[Ypos].BlockName = "Air"
				end
			end
		end
	end
	----Height Map----
	
	----Perlin Worms----
	print("Generating perlin worms...")
	for Xpos = 1,#Grid do
		--print("X")
		local Xgrid = Grid[Xpos]
		for Zpos = 1,Settings.ChunkSize do
			--print("Z")
			--game:GetService("RunService").Heartbeat:Wait()
			local Zgrid = Xgrid[Zpos]
			for Ypos = 1,Settings.WorldMaxHight do
				--print("Y")
				RunServerCooldown()
				local Ygrid = Zgrid[Ypos]
				--wait(0.5)
				local perlinX = Xpos / Settings.NoiseScale
				local perlinZ = Zpos / Settings.NoiseScale
				local perlinY = Ypos / Settings.NoiseScale

				local noiseValue = AdvancedPerlinNoise.new({perlinX,perlinY,perlinZ,worldseed})

				if noiseValue > 0.5 then
					Zgrid[Ypos].BlockName = "Air"
				end
			end
		end
	end
	print("Perlin worms done.")
	----Perlin Worms----
	
	ChunkData.Grid = Grid
	table.insert(WorldChunks,ChunkData)
	
	----Generation Test----
	print("Final generation...")
	for Xpos = 1,#Grid do
		--print("X")
		local Xgrid = Grid[Xpos]
		for Zpos = 1,Settings.ChunkSize do
			--print("Z")
			--game:GetService("RunService").Heartbeat:Wait()
			local Zgrid = Xgrid[Zpos]
			for Ypos = 1,Settings.WorldMaxHight do
				--print("Y")
				RunServerCooldown()
				local Ygrid = Zgrid[Ypos]
				--wait(0.5)
				if Zgrid[Ypos].BlockName == "Noise" and IsExposedBlock(xGridPos,zGridPos,Xpos,Zpos,Ypos) == true then
					local block = Blocks.Stone:Clone()
					block.Parent = script.Parent.Terrain
					block.Size = Vector3.new(Settings.BlockSizes, Settings.BlockSizes, Settings.BlockSizes)
					block.Position = Vector3.new(Xpos, Ypos, Zpos) * Settings.BlockSizes
					block:SetAttribute("GridPos",Vector3.new(Xpos,Ypos,Zpos))
					--Zgrid[Ypos].BlockName = "Updated"
				end
			end
		end
	end
	print("Final generation done.")
	----Generation Test----
	
	ChunkData.Grid = Grid
	
	--table.insert(WorldChunks,ChunkData)
end
--Functions--

script.Parent.Loaded.Value = false

local findexistingworld = game.Workspace:FindFirstChild("Tfolder")
if findexistingworld ~= nil then
	findexistingworld:Destroy()
end

for _,v in pairs (script.Parent.Terrain:GetChildren()) do
	if v:IsA("BasePart") then
		v:Destroy()
	end
end

for _,v in pairs (script.Parent.Resources:GetChildren()) do
	if v:IsA("Model") then
		v:Destroy()
	end
end

script.Parent.Baseplate.Position = Vector3.new(0, 10, 0)

----Chunks test----
GenerateChunk(0,0)
----Chunks test----

Also, if you have extra tips or criticism of the script feel free to tell me. I even encourage it because I want to know if my script is going in a good direction or if I am unknowingly making a very inefficient script in some shape or form. Or just help me with some good tips for making a good procedurally generated voxel world.

Turns out it was all from the debug prints littered through the script. Most notably the prints that print the entire grid tables. Some tips would be nice still.

1 Like