Trying to make parts not fall through the map on the server

My game uses chunks to generate terrain, but it does that on the client. This means the server cannot see the terrain and all the parts/models will fall through. I made the code and it works but there is 1 issue, the chunks get destroyed and created repeatedly resulting in massive fps lag. I tried many solutions but they did not help. Here’s my code:

local Chunk = require(game.ReplicatedStorage.Chunk)

local RENDER_DISTANCE = 1
local CHUNKS_LOADED_PER_TICK = 1
local LOAD_CHUNKS_FREQUENCY = 0
local chunks = {}
local chunkCount = 0
local fastLoad = true

local function chunkWait()
	chunkCount = (chunkCount + 1) % CHUNKS_LOADED_PER_TICK
	if chunkCount == 0 and not fastLoad then
		wait()
	end
end

local function doesChunkExist(x, z)
	for index, chunk in pairs(chunks) do
		if chunk.x == x and chunk.z == z then
			return true
		end
	end

	return false
end

local function isChunkOutOfRange(chunk, centerPosX, centerPosZ)
	if math.abs(chunk.x - centerPosX) > RENDER_DISTANCE
		or math.abs(chunk.z - centerPosZ) > RENDER_DISTANCE then
		return true
	end

	return false
end

while true do
	for _, v in pairs(workspace:GetDescendants()) do
		if v:IsA("BasePart") then
			if v.Parent then
				if v.Parent:IsA("Model") then
					if not game.Players:GetPlayerFromCharacter(v.Parent) then
						local centerPosX
						local centerPosZ

						local camPos = v.CFrame.Position

						centerPosX = math.floor(camPos.X / Chunk.WIDTH_SIZE_X)
						centerPosZ = math.floor(camPos.Z / Chunk.WIDTH_SIZE_Z)

						local n = #chunks

						for i = 1, n do
							local chunk = chunks[i]

							if isChunkOutOfRange(chunk, centerPosX, centerPosZ) then
								print("destroyed")
								chunk:Destroy()
								chunkWait()

								chunks[i] = nil
							end
						end

						local j = 0
						for i = 1, n do
							if chunks[i] ~= nil then
								j += 1
								chunks[j] = chunks[i]
							end
						end

						for i = j + 1, n do
							chunks[i] = nil
						end

						for x = centerPosX - RENDER_DISTANCE, centerPosX + RENDER_DISTANCE do
							for z = centerPosZ - RENDER_DISTANCE, centerPosZ + RENDER_DISTANCE do
								if not doesChunkExist(x, z) then
									table.insert(chunks, Chunk.new(x, z))
									print("created")
									chunkWait()
								end
							end
						end
					end
				elseif v.Parent == workspace then
					local centerPosX
					local centerPosZ

					local camPos = v.CFrame.Position

					centerPosX = math.floor(camPos.X / Chunk.WIDTH_SIZE_X)
					centerPosZ = math.floor(camPos.Z / Chunk.WIDTH_SIZE_Z)

					local n = #chunks

					for i = 1, n do
						local chunk = chunks[i]

						if isChunkOutOfRange(chunk, centerPosX, centerPosZ) then
							print("destroyed")
							chunk:Destroy()
							chunkWait()

							chunks[i] = nil
						end
					end

					local j = 0
					for i = 1, n do
						if chunks[i] ~= nil then
							j += 1
							chunks[j] = chunks[i]
						end
					end

					for i = j + 1, n do
						chunks[i] = nil
					end

					for x = centerPosX - RENDER_DISTANCE, centerPosX + RENDER_DISTANCE do
						for z = centerPosZ - RENDER_DISTANCE, centerPosZ + RENDER_DISTANCE do
							if not doesChunkExist(x, z) then
								table.insert(chunks, Chunk.new(x, z))
								print("created")
								chunkWait()
							end
						end
					end
				end
			end
		end
	end
	
	fastLoad = false
	wait(LOAD_CHUNKS_FREQUENCY)
end

I want to ask why we are generating the chunks on the client and not the server just telling the client what to load where

1 Like