Avoiding unplayable lag while generating large amounts of parts

Hello,

I am working on a project which is essentially a mining game. The goal is to make a system where I can generate a volume of blocks at any size necessary, while having each one be mine-able.

On the smaller side, I’m usually working with 15x15x300 sizes, so even a smaller generation involves ~60000 parts to be generated and regenerated during the lifetime of any particular server. I handle all of the part coordinates/properties/mine-makeup prior to physical generation via tables, so the process of designing the mine works smoothly.
The issue I am running into is that whenever I need to generate parts (When the server starts it usually loads quickly, which is good, but if I need to clear out the mine and regenerate it there is always an issue) I encounter over 20 seconds of lag, sometimes much more depending on the size of the mine. During this period of lag the player is completely unable to talk in chat, respawn, or interact with other things such as tools.

While troubleshooting this problem I’ve tried various methods to lessen the severity of the lag, or even fix it all together if possible. While I’ve had small success with some methods, nothing I’ve tried has made this a playable experience.

The part of the process where I believe is causing this lag is when I need to clone objects and move them in to the workspace. Because I need the mines to be accessible by every player, I am doing this on the server side. I’ve tried making all the parts invisible and without collisions, but this does not help the lag whatsoever. The most recent version of my generation function looks like this:

function GenerationService:generate()
	
        --One big issue I've had with this process is running into the Exhausted Allowed Execution Time
        --Error. This happens whenever I need to move the objects to the workspace, so to mitigate this I
        --am breaking the volume into separate partitions, and then loading them individually when I need to
        --move the mine to the workspace.
	local vol = self.length*self.width*self.depth
	local partitions = math.floor(vol/50000)
	self.cache = {}
	for index = 1, partitions + 1, 1  do
		table.insert(self.cache, Instance.new("Folder"))
	end

	--CFrame variables
	local lv = self.Origin.CFrame.LookVector
	local rv = self.Origin.CFrame.RightVector
	local uv = self.Origin.CFrame.UpVector

	
	local partition_index = 1
	for index, object in pairs(self.Mine) do
                --Every 50,000 blocks, I make a new partition
		if index % 50001 == 0 then
			partition_index += 1
		end
		local base = self.Library:FindFirstChild(object.bType)
		local new_object = base:Clone()
		new_object:SetPrimaryPartCFrame(self.Origin.CFrame + (lv * object.y * 6) - (rv * object.x * 6) - (uv * object.z * 6))
		new_object.Parent = self.cache[partition_index]
	end
	
	
	for index, object in pairs(self.cache) do
		object.Parent = self.Parent
                --Despite making the separate partitions... If I don't yield here I still run into
                --the execution time error.
		task.wait()
	end
	
	self.Origin:Destroy()
	print("Passed Destroyed")
end

The exhausted execution time error has been a big problem working through this process. Sometimes I want to work with hundreds of thousands of parts, at these scales the script will crash because of this error. I’ve tried working in yields every 100,000 parts or so, but this causes the mine to generate at an incredibly, unreasonably slow pace, during which the game still experiences the same amount of unplayable lag.

I’ve tested this process out with and without streaming enabled. While it helps slightly (1-2 seconds improvement), it does not affect the lag and is still unplayable even if the player is very far away.

I’ve thought of incorporating coroutines, but I am not exactly sure if they would even help with this issue or how to implement them. Perhaps I can use them when I need to parent the partition folders, avoiding the yield entirely?

This is what the script calling the generation functions looks like:

local new_mine = Mine.new(mine_origin, grid_allocation, BT)

print("Starting")
new_mine:define(15, 15, 300, "Stone")
new_mine:addLayer(50, 99, "2nd Layer")
new_mine:addLayer(100, 150, "3rd Layer")
new_mine:addLayer(0, 299, "Iron Layer")
new_mine:setLayer("2nd Layer", "HardStone")
new_mine:setLayer("3rd Layer", "Bedrock")
new_mine:populateLayer("Iron Layer", "IronOre", .05)
new_mine:generate()
print("Loaded")

And here is the function I call when I try to regenerate the mine:

local function resetMine()
	print("Resetting")
        --Message is a BillboardGui that says that the mine is resetting
	message.Enabled = true
	wait(0.5)
	
    --:clear() wipes the table that holds all the mine information
    --and calls :Destroy() on the partitions holding the objects
	new_mine:clear()
	new_mine:define(15, 15, 300, "Stone")
	new_mine:addLayer(50, 99, "2nd Layer")
	new_mine:addLayer(100, 150, "3rd Layer")
	new_mine:addLayer(0, 299, "Iron Layer")
	new_mine:setLayer("2nd Layer", "HardStone")
	new_mine:setLayer("3rd Layer", "Bedrock")
	new_mine:populateLayer("Iron Layer", "IronOre", .05)
	new_mine:generate()
	
	wait(0.5)
	message.Enabled = false
end

The generation functions are in a module script and I am calling them with a script in the workspace.
Like previously mentioned, from all my testing the functions other than generate work completely fine and cause no lag, so I only included the generate function in this post, but if there is reason to believe that they could be affecting the script then I can post them too.

I’m sorry for the longer post,
Any help at this point would be greatly appreciated.

You should only generate parts when it is physically possible to interact with them, as in when they are visible. This would mean only generating the top layer and then generating new blocks as they are exposed. Barring that, you can limit how many blocks are generated per tick by yielding generation after some number.

So, whenever a player would mine a block, I would generate the parts that would be exposed? Is this method reliable enough to support very quick block breaking while remaining seamless?

Also, I’ve tried yielding more often during the generation and it does not solve the lag problem, it only makes the generation unreasonably slow (several minutes) and the player experiences the same style of lag as before, where they can’t interact with anything.

As long as your “radius” of parts that are generated around the part interacted with is large enough, it should be seamless for players with a good enough internet connection.

Berezaa’s azure mines would generate an un-mineable part over blocks that would start to generate so that players would not fall into the void. You should also make a system like this.

Sounds good. I’ll try implementing this whenever I have a chance and then return to this post with the results.

Got around to working on it, this was the best solution for us! Thanks a lot.

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