--!native --!optimize 2 local ChunkBuilder = {} local Chunk = require("./Chunk") local BlockManager = require("./BlockManager") local util = require("../Util") local objects = script.Parent.Parent.Parent.Objects local RunService = game:GetService("RunService") local ChunkBorderFolder = game:GetService("Workspace"):FindFirstChildOfClass("Terrain") local DEBUG_GHOST = true local NEIGHBOR_OFFSETS = { {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1} } -- TODO: Move to Chunk type BlockData = { id: number, state: { [typeof("")]: string | boolean | number } } local function placeBorder(a,b,c) local pos = util.ChunkPosToCFrame(Vector3.new(a,b,c),Vector3.new(1,1,1)).Position - Vector3.new(2,2,2) local d = objects.ChunkLoading:Clone() d:PivotTo(CFrame.new(pos)) d.Parent = ChunkBorderFolder return d end local function Swait(l) for i = 1,l do RunService.Stepped:Wait() end end local function propogateNeighboringBlockChanges(cx, cy, cz, x, y, z) --warn("propogateNeighboringBlockChanges",cx,cy,cz,x,y,z) -- updates block in another chunk local c = Chunk.AllChunks[`{cx},{cy},{cz}`] if not c then return end local d = c.data[`{x},{y},{z}`] if not d then return end if c:IsBlockRenderable(x, y, z) then if c.instance:FindFirstChild(`{x},{y},{z}`) then return end task.synchronize() local block = BlockManager:GetBlockRotated(d.id, util.RotationStringToNormalId(d.state["r"] or "f"), d.state) block.Name = `{x},{y},{z}` block:PivotTo(util.ChunkPosToCFrame(c.pos, Vector3.new(x, y, z))) block.Parent = c.instance task.desynchronize() else local existing = c.instance:FindFirstChild(`{x},{y},{z}`) if existing then task.synchronize() existing:Destroy() task.desynchronize() end end end function ChunkBuilder:BuildChunk(c: typeof(Chunk.new(0,0,0)),parent: Instance?) if c.loaded then return c.instance end local blocks = c.data local newcache = {} :: {[typeof("")]: BlockData} local finished = false local ch = Instance.new("Folder") ch.Parent = parent ch.Name = `{c.pos.X},{c.pos.Y},{c.pos.Z}` local conn = c.UpdateBlockBindableL.Event:Connect(function(x: number, y: number, z: number, d: BlockData) task.desynchronize() if finished == false then newcache[`{x},{y},{z}`] = d return end task.synchronize() for _, o in pairs(NEIGHBOR_OFFSETS) do --warn("propogate",o[1],o[2],o[3]) -- Adjust for chunk boundaries local b = {x = x + o[1], y = y + o[2], z = z + o[3]} local ch = {x = c.pos.X, y = c.pos.Y, z = c.pos.Z} if b.x < 1 then ch.x = c.pos.X - 1 b.x = 8 end if b.x > 8 then ch.x = c.pos.X + 1 b.x = 1 end if b.y < 1 then ch.y = c.pos.Y - 1 b.y = 8 end if b.y > 8 then ch.y = c.pos.Y + 1 b.y = 1 end if b.z < 1 then ch.z = c.pos.Z - 1 b.z = 8 end if b.z > 8 then ch.z = c.pos.Z + 1 b.z = 1 end propogateNeighboringBlockChanges(ch.x, ch.y, ch.z, b.x, b.y, b.z) --BlockManager:GetBlock(ch.x) end local blockName = `{x},{y},{z}` local existing = ch:FindFirstChild(blockName) if d == 0 then if c.delayedRemoval and c.delayedRemoval[blockName] then c.delayedRemoval[blockName] = nil if existing then task.defer(function() task.synchronize() RunService.RenderStepped:Wait() if existing.Parent then existing:Destroy() end task.desynchronize() end) elseif DEBUG_GHOST then print("[CHUNKBUILDER][GHOST] Delayed remove missing instance", c.pos, blockName) end return end if existing then task.synchronize() existing:Destroy() task.desynchronize() elseif DEBUG_GHOST then print("[CHUNKBUILDER][GHOST] Remove missing instance", c.pos, blockName) end return end if not c:IsBlockRenderable(x, y, z) then if existing then task.synchronize() existing:Destroy() task.desynchronize() end return end if existing then task.synchronize() existing:Destroy() task.desynchronize() end if not d then return end if d.id == 0 then return end local N = util.RotationStringToNormalId(d.state["r"] or "f") task.synchronize() local block = BlockManager:GetBlockRotated(d.id, N, d.state) block.Name = blockName block:PivotTo(util.ChunkPosToCFrame(c.pos, Vector3.new(x, y, z))) block.Parent = ch task.desynchronize() end) c.unloadChunkHook = function() conn:Disconnect() blocks = nil c = nil end task.defer(function() local p = 0 task.synchronize() local hb = false for a,b in pairs(blocks) do hb = true end local border = Instance.new("Part") if hb == true then border:Destroy() border = placeBorder(c.pos.X, c.pos.Y, c.pos.Z) end for a,b in pairs(blocks) do task.desynchronize() local coords = util.BlockPosStringToCoords(a) if not c:IsBlockRenderable(coords.X, coords.Y, coords.Z) then if ch:FindFirstChild(a) then task.synchronize() ch:FindFirstChild(a):Destroy() task.desynchronize() end continue end task.desynchronize() local N = util.RotationStringToNormalId(b.state["r"] or "f") task.synchronize() local block = BlockManager:GetBlockRotated(b.id, N, b.state) if ch:FindFirstChild(a) then ch:FindFirstChild(a):Destroy() end block.Name = a block:PivotTo(util.ChunkPosToCFrame(c.pos, coords)) block.Parent = ch p += 1 if p == 15 then p = 0 Swait(1) end end finished = true task.synchronize() border:Destroy() task.desynchronize() task.defer(function() task.synchronize() for key, data in pairs(newcache) do local coords = util.BlockPosStringToCoords(key) for _, o in pairs(NEIGHBOR_OFFSETS) do -- chunks are 8x8x8 local nb = {x = coords.X + o[1], y = coords.Y + o[2], z = coords.Z + o[3]} local chCoords = {x = c.pos.X, y = c.pos.Y, z = c.pos.Z} if nb.x == 0 then chCoords.x = c.pos.X - 1 nb.x = 8 end if nb.x == 9 then chCoords.x = c.pos.X + 1 nb.x = 1 end if nb.y == 0 then chCoords.y = c.pos.Y - 1 nb.y = 8 end if nb.y == 9 then chCoords.y = c.pos.Y + 1 nb.y = 1 end if nb.z == 0 then chCoords.z = c.pos.Z - 1 nb.z = 8 end if nb.z == 9 then chCoords.z = c.pos.Z + 1 nb.z = 1 end propogateNeighboringBlockChanges(chCoords.x, chCoords.y, chCoords.z, nb.x, nb.y, nb.z) end local existing = ch:FindFirstChild(key) if data == 0 or (data and data.id == 0) then if existing then existing:Destroy() end continue end if not c:IsBlockRenderable(coords.X, coords.Y, coords.Z) then if existing then existing:Destroy() end continue end if existing then existing:Destroy() end if not data then continue end local N = util.RotationStringToNormalId(data.state["r"] or "f") local block = BlockManager:GetBlockRotated(data.id, N, data.state) block.Name = key block:PivotTo(util.ChunkPosToCFrame(c.pos, coords)) block.Parent = ch end newcache = nil blocks = nil end) task.desynchronize() end) c.loaded = true return ch end return ChunkBuilder