What do you want to achieve?
My goal is to create an infinite scrolling map that creates the illusion of movement on a train.
What is the issue?
Performance is struggling to keep up with moving these large chunks every frame and I’m trying to figure out how to reduce frame times as much as possible.
What solutions have you tried so far?
I’ve tried switching from :PivotTo() to :BulkMoveTo() and has given me negligible increase.
Frame times average 20.43ms, usually moving each chunk costs about 3-4ms and theres 3 chunks.
Chunks can vary in the content that they contain, some have forests, others are tunnels and some are just grass and tracks.
--... this is a snippet of the code I am using, most parts of this script are removed as they do not relate to the issue (probably)
RunService.RenderStepped:Connect(function(dt)
worldOffset += speed * dt
if worldOffset >= chunkLength then
worldOffset -= chunkLength
local first = table.remove(chunks, 1)
table.insert(chunks, first)
task.defer(function()
debug.profilebegin("[CHUNK] Iterate Chunk")
iterateChunk(first)
debug.profileend()
end)
end
debug.profilebegin("[CHUNK] Chunk RenderFrame")
for i, chunk in ipairs(chunks) do
local x = originX + (i - 1) * chunkLength - worldOffset
local targetCF = CFrame.new(x, baseY, baseZ)
local pivot = chunk:GetPivot()
local parts = {}
local cframes = {}
for _, descendant in ipairs(chunk:GetDescendants()) do
if descendant:IsA("BasePart") then
table.insert(parts, descendant)
-- preserve relative offset from pivot
local relative = pivot:ToObjectSpace(descendant.CFrame)
table.insert(cframes, targetCF * relative)
end
end
workspace:BulkMoveTo(parts, cframes, Enum.BulkMoveMode.FireCFrameChanged)
end
debug.profileend()
end)
Why not just :PivotTo(targetCF) on the entire model? It looks like you are trying to move all the parts to a new place preserving the original offsets which is the same as just moving the model
If you have to do it that way then you should put all of the basepart descendants in a table that maps chunk to array of basepart when a chunk is made instead of recalculating them each frame, and now the loop inside of renderstepped can iterate through a clean array of parts without of checking :IsA or going through unecessary stuff
Unfortunately multi-threading does not allow for the movement of objects as BulkMoveTo nor PivotTo is parallel safe.
Originally, that was what I was doing, and BulkMoveTo was my attempt to try and save on frame times (ended up contributing negligibly saving around ~0.5ms). I have attached that code for reference.
for i, chunk in ipairs(chunks) do
local x = originX + (i - 1) * chunkLength - worldOffset
chunk:PivotTo(CFrame.new(
x,
baseY,
baseZ
))
end
As for now, I will mark xnlogical’s post as the solution as I do not believe there is anything more that can be done to optimize the code as is without changing the way my game works.
It should be noted that you’re doing chunk:GetDescendants() in your original loop, per chunk, every time you move them. The overhead of that is likely contributing to how slow this loop is; Instance:GetDescendants() does not cache, so it has to create a new array containing every instance within that chunk every single time.
I am aware this is solved, but have you considered turning your chunks into MeshParts? You can’t exactly move massive assemblies chock full of parts every frame and expect it to run very well, so the next best solution is to simply move less.
I’m unsure what your chunks are supposed to look like (or what happens in them), but you can likely cut down on how many instances you’re moving at once by exporting your chunk into gLTF or OBJ, merging whichever parts have the same material and color into the same mesh (e.g. some terrain or perhaps a lot of trees), then re-exporting it back to Roblox.
Technically, you’d be moving way less parts per frame, since they’d all be meshed into a few parts (kind of like unions, I suppose). While it’s unfortunately a little gross, you can’t really improve performance in any other way