Optimizing Mass Part Spawning for Procedurally Generated Terrain

I’m working on procedurally generating terrain and I’m using remote events for spawning in parts. Each remote event contains chunk coordinates and chunk data.

For lower chunk sizes and lower render distance this all works fine.

But whenever you increase the chunk size and render distance to somewhat high values the game gets super large stutters every time it has to load in chunks.

I’m wondering if there is a better way to spawn in a bunch of parts. The parts don’t need to spawn in all at once, they just need to not freeze the entire game whenever they are loaded in.

EDIT:
I forgot to include the client-side script so here:

game:GetService("ReplicatedStorage"):WaitForChild("SendChunkPacket").OnClientEvent:Connect(function(coords, chunkData)
	local chunkFolder = Instance.new("Folder")
	chunkFolder.Name = coords
	chunkFolder.Parent = workspace
	for i, v in ipairs(chunkData) do
		local grass = part:Clone()
		grass.Position = v
		grass.Parent = chunkFolder
	end
end)
4 Likes

You can’t.

Even if you used meshes, it would still be laggy. The best method is to actually use editable meshes which are more performant.

1 Like

Is there a way for my script to not freeze the game and load the parts over time instead of loading them all at once? I really don’t want to have to do the ID verification thing. also i added my script in the original post.

you can use object cache which reuses parts for much better performance

also why not compute the terrain on the client

I’ll look into object cache. But to answer your question, each chunk is going to be modifiable by players.

You can still have it computed on the client. You can store the chunk data on the server and render it on the client. This is the best way as if you load all on the server it can cause massive performance issues in the near future.

1 Like

As the user happya_x stated, use Part Cache, or reuse old parts rather than destroying them.

Creation and destruction is always more expensive than modification.

That’s just how it is, you can do some optimization by using WordRoot:BulkMoveTo() which reduces how many Changed events go off, but you’re going to have to throttle creating parts (or re-use them like the others have said).

The server never spawns chunks. It only calculates the chunks and sends them to the client.

Unless I’m mistaken I believe that’s what ObjectCache already does.

I’ve never heard of it, but if it does, even better.

This is the link @happya_x posted ObjectCache - a modern, blazing-fast model and part cache!

1 Like

Ah, that makes sense. Thanks for clarifying.

i made a system like this before

i still compute the terrain on the client but the server sends modified terrain data to the clients just one time when it gets modified

1 Like

you can speed up process by like 10% with RawLib
Also you could cache “common part” with most mutual properties and then just do:

Instance.fromExisting()

yes you got me right Instance.fromExisting() NOT :Clone()
Other than that there pretty much nothing to make it more optimized
Also you can try using :PivotTo() + combined with RawLib it would be even faster
Proofs: Can someone verify this benchmark?
Example:

local RawLib = require("./RawLib")
local instance_index = RawLib.instance_index
local instance_newindex = RawLib.instance_newindex
local commonPart = Instance.new("Part")
local PivotTo = instance_index(commonPart,"PivotTo")::(PVInstance,CFrame)->()

local copy = Instance.fromExisting(commonPart)
PivotTo(copy,CFrame.identity)
instance_newindex(copy,"Parent",workspace)

1 Like

I dont belive that you need that
You are not moving a model but instead moves individual parts
Non the less :PivotTo is faster and doesn’t require you to bloatware game further

:PivotTo is million times better bro
Also BulkMoveTo will not really be that good in this setuation

I highly suggest to cache the parts you already created in some table, and when you need to place another part you just get the part from the pool and set position and parent instead of creating a new instance

1 Like

PVInstance:PivotTo() is intended for moving models, and when you call it on parts that don’t have descendant parts, every changed signal still fires (though this might be a bug?) like if you just set CFrame, but it does seem to be slightly faster than setting .CFrame under most situations.

But what consistently beats both? WorldRoot:BulkMoveTo().

image

--!optimize 2, 1000 parts, 50 iterations, unique CFrames each iteration