I’ve been trying to implement a part-based terrain system for my game, where i generate terrain in chunks and vertical slices using greedy meshing to combine them into the least parts possible, and having the terrain modifiable with a shovel which adds a block (3x3x3, each chunk is 11x1x11 blocks)
The issue I’m having is generating terrain is very laggy and I don’t know how to optimize it to run faster, I have read about using parallel luau to split loading but I don’t know how to implement this properly. I currently use a lot of task.spawn() but I want the terrain generation to happen faster without lag rather than just without lag
I tried splitting the terrain generator into 4-8 actors but this didn’t help much, and then I tried making the code that reads/writes to the table I store the chunk data on to work on actors but it broke everything
I currently store my terrain as one table with a key representing the chunk position and a 16 byte buffer (makes serialization more straightforward for saving) to represent whether each x,z position in the chunk has a tile or not, then I use a greedy mesher to determine when to make a part. Whenever the terrain is modified I call a function which determines the size of the new terrain part
This is AddTilesAt, the one that handles the terrain table content
local function AddTilesAt(position: Vector3, size: Vector3, value: number, dont_regenerate: boolean?)
local size: Vector3 = size or Vector3.one
size = Vector3.new(
math.round(math.max(size.X, 1)),
math.round(math.max(size.Y, 1)),
math.round(math.max(size.Z, 1))
)
local time_ = os.clock()
local affectedchunks = {}
for ax=position.X,position.X + size.X - 1 do
for az=position.Z,position.Z + size.Z - 1 do
for ay=position.Y,position.Y + size.Y - 1 do
local xp, yp, zp = ax % 11, ay, az % 11
local _chunk = Vector3.new(
math.floor(ax/11),
math.floor(ay),
math.floor(az/11)
)
local chunk = string.format("%i,%i,%i",
_chunk.X,
_chunk.Y,
_chunk.Z
)
if tiles[chunk] == nil then tiles[chunk] = buffer.create(16) end
buffer.writebits(tiles[chunk], xp*11+zp, 1, value)
affectedchunks[chunk] = _chunk
end
end
end
if not dont_regenerate then
task.spawn(function()
for _,v in pairs(affectedchunks) do
generatechunk(v)
task.wait()
end
end)
end
return affectedchunks
end
This is generatechunk, which generates each chunk (11x1x11) based off of the contents of the table
Actor:BindToMessageParallel("Generate", function(data: buffer?, position: Vector3)
if not data then return end
task.synchronize()
local pos = string.format("%i,%i,%i", position.X, position.Y, position.Z)
task.spawn(function()
for _,v in pairs(CollectionService:GetTagged("Chunk"..pos)) do
v:Destroy()
end
end)
task.desynchronize()
local partdataresult = {}
local lvx, lvz = 0, 0
while lvx ~= 11 do
if lvz >= 11 then lvx += 1; lvz = 0 end
if lvx >= 11 then break end
if buffer.readbits(data, lvx * 11, 11) == 0 then lvx += 1; lvz = 0; continue end
local lptr = lvx * 11 + lvz
local type_ = buffer.readbits(data, lptr, 1)
local npz = lvz
for p=lvz,11 do
npz = p
if p == 11 then break end
if buffer.readbits(data, lvx*11 + npz, 1) ~= type_ then break end
end
npz = npz - 1
local npx = lvx
buffer.writebits(data, lvx*11 + lvz, npz-lvz+1, 0)
if type_ == 1 then
local function cango()
if (npx+1)*11 + (npz-lvz) >= 121 then return false end
return buffer.readbits(data, (npx+1) * 11 + lvz, npz-lvz+1) == (math.pow(2, npz+1)-1)
end
while cango() do
npx += 1
end
for i=lvx+1, npx do
buffer.writebits(data, i*11 + lvz, npz-lvz+1, 0)
end
--local npart = Instance.new("Part")
local npart = {}
npart.Size = Vector3.new(npx-lvx+1, 1, npz-lvz+1) * TileSize
npart.ChunkStart = string.format(
"%i,%i,%i",
position.X*11+lvx-1,
position.Y,
position.Z*11+lvz-1
)
npart.ChunkSize = string.format(
"%i,%i",
npx-lvx+1,
npz-lvz+1
)
npart.Position = (Vector3.new(
position.X*11+lvx,
position.Y+0.5,
position.Z*11+lvz
) * TileSize) + npart.Size/2 - Vector3.new(TileSize.X/2, TileSize.Y, TileSize.Z/2)
npart.ChunkPosition = position
table.insert(partdataresult, npart)
end
lvz += 1
end
data = nil
task.synchronize()
for _,v in ipairs(partdataresult) do
local npart = Instance.new("Part")
npart.Size = v.Size
npart.Position = v.Position
npart.Anchored = true
npart.Name = v.ChunkPosition.Y
npart:SetAttribute("Size", v.ChunkSize)
npart:SetAttribute("Start", v.ChunkStart)
npart:AddTag(string.format("Chunk%i,%i,%i", v.ChunkPosition.X, v.ChunkPosition.Y, v.ChunkPosition.Z))
npart.Parent = workspace.TerrainBlocks
npart:MakeJoints()
ServerStorage.Events.SetUpTerrain:Fire(npart, position.Y)
RunService.PreSimulation:Wait()
end
end)
