You can write your topic however you want, but you need to answer these questions:
-
What do you want to achieve? Keep it simple and clear!
A way to fix this memory leak problem, so that loading and unloading chunks work as intended without causing memory leaks -
What is the issue? Include screenshots / videos if possible!
Memory leaks happend when a client load a new chunk/unload a chunk, the problem is still unknown which part of the code is causing the memory leak
Watch memory leak problem | Streamable -
What solutions have you tried so far? Did you look for solutions on the Developer Hub?
- Connecting a property changed signal event of the voxel’s parent, setting the tables of RenderedVoxels[voxel position] to nil if the parent is nil
Voxel:GetPropertyChangedSignal("Parent"):Connect(function()
if Voxel.Parent == nil then
VoxelManager.RenderedVoxels[`{voxelPosition.X} {voxelPosition.Y} {voxelPosition.Z}`] = nil
end
end)
- Clearing the ChunkData[unloaded chunk x][unloaded chunk z] when unloading chunk
-- ChunkData is a list of blocks in a chunk relative to that chunk
function ChunkRemoved(chunkPos)
ChunkManager.ClearChunk(ChunkData, Vector3.new(chunkPos.X, 0, chunkPos.Z))
end
end
function ChunkManager.ClearChunk(chunkData, chunkPosition: Vector3)
if chunkData[chunkPosition.X] and chunkData[chunkPosition.X][chunkPosition.Z] then
chunkData[chunkPosition.X][chunkPosition.Z] = nil
if next(chunkData[chunkPosition.X]) == nil then
chunkData[chunkPosition.X] = nil
end
end
end
-
Chunk loading is the process of rendering all the voxels/blocks in a chunk trough
Instance.new()
or voxel caching (:Clone()
) and parenting all of the voxels to a part that act as a chunk (lets call this chunk part) - Chunk unloading is the process of destroying the chunk part and its automatically deletes all the children (the voxels) and set RenderedVoxels of that voxel position to nil
- RenderedVoxels is a list of voxel that are rendered and using its position as a key
("{x} {y} {z}")
The Chunk Rendering Code (sorry…)
IM NOT ASKING TO FIX IT!
task.synchronize()
local ChunkFolder = game.Workspace:WaitForChild("Chunks")
local ChunkManager = require(game.ReplicatedStorage.Managers.ChunkManager)
local ChunkRenderer = require(game.ReplicatedStorage.Managers.ChunkManager.ChunkRenderer)
local VoxelManager = require(game.ReplicatedStorage.Managers.VoxelManager)
local BlockDictionary = require(game.ReplicatedStorage.Managers.BlockDictionary)
local ChunkData = require(game.ReplicatedStorage.LocalChunkData)
local ChunkQueue = {}
local isRendering = false
local hb
function doQueue()
if not isRendering and #ChunkQueue > 0 then
isRendering = true
local chunkToRender = ChunkQueue[1]
if chunkToRender then
table.remove(ChunkQueue, 1)
ChunkAdded(chunkToRender)
isRendering = false
end
end
if #ChunkQueue == 0 and hb then hb:Disconnect(); hb = nil end
end
function AddChunkQueue(chunk)
table.insert(ChunkQueue, chunk)
table.sort(ChunkQueue, function(a, b)
local character = game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait()
local humanoidRootPart: Part = character.HumanoidRootPart
return (humanoidRootPart.Position - a.Position).Magnitude < (humanoidRootPart.Position - b.Position).Magnitude
end)
if #ChunkQueue > 0 and not hb then
hb = game["Run Service"].Heartbeat:Connect(doQueue)
end
end
--hb = game["Run Service"].Heartbeat:Connect(doQueue)
function RefreshChunk(chunkPosition: Vector3, allBlocks)
local VoxelToRemove = {}
task.desynchronize()
local chunkData = ChunkManager.GetChunk(ChunkData, chunkPosition)
if chunkData then
for x, column in chunkData do
for y, row in column do
for z, blockId in row do
if not allBlocks and x % 15 == 0 or z % 15 == 0 then
local eligible = true
for _, direction in ChunkRenderer.Directions do
local realBlockPosition = Vector3.new((chunkPosition.X * 16) + tonumber(x), tonumber(y), (chunkPosition.Z * 16) + tonumber(z))
if not ChunkManager.IsExist(ChunkData, realBlockPosition + direction) then
eligible = false
break
end
end
if eligible then
local voxel = VoxelManager.GetVoxel2(ChunkManager.ToBlockPosition(chunkPosition, Vector3.new(x,y,z)))
table.insert(VoxelToRemove, voxel)
end
end
end
end
end
end
task.synchronize()
for _,voxel in VoxelToRemove do
voxel:Destroy()
end
VoxelToRemove = nil
end
function RefreshBlockNeighbor(blockPosition: Vector3, placing)
for _, direction in ChunkRenderer.Directions do
local ChunkPosition, positionInChunk = ChunkManager.ToChunkRelative(blockPosition + direction)
local neighborId = ChunkManager.IsExist(ChunkData, blockPosition + direction)
local neighborVoxel = VoxelManager.GetVoxel(ChunkPosition, positionInChunk)
if neighborId and not neighborVoxel and not placing then
VoxelManager.CreateVoxel(positionInChunk, {ChunkPosition.X, ChunkPosition.Z}, BlockDictionary.GetId(neighborId))
end
if neighborVoxel and placing then
local eligible = true
for _, direction2 in ChunkRenderer.Directions do
if not ChunkManager.IsExist(ChunkData, (blockPosition + direction) + direction2) then
eligible = false
break
end
end
if eligible then
neighborVoxel:Destroy()
end
end
end
end
function ChunkAdded(chunk)
if chunk:IsA("Part") then
local ChunkPosition = chunk.Name:split(" ")
ChunkPosition[1] = tonumber(ChunkPosition[1])
ChunkPosition[2] = tonumber(ChunkPosition[2])
if ChunkData[ChunkPosition[1]] and ChunkData[ChunkPosition[1]][ChunkPosition[2]] then
RenderChunk(ChunkData[ChunkPosition[1]][ChunkPosition[2]], ChunkPosition, chunk)
end
chunk.ChunkRemote.OnClientEvent:Connect(function(positionInChunk: Vector3, id: number)
local blockPosition = ChunkManager.ToBlockPosition(Vector3.new(ChunkPosition[1], 0, ChunkPosition[2]), positionInChunk)
ChunkManager.SetBlock(ChunkData, blockPosition, id)
if id then -- place
VoxelManager.CreateVoxel(positionInChunk, ChunkPosition, BlockDictionary.GetId(tonumber(id)))
RefreshBlockNeighbor(blockPosition, true)
--print(VoxelManager.RenderedVoxels)
else -- destroy
local voxel = VoxelManager.GetVoxel(Vector3.new(ChunkPosition[1], 0, ChunkPosition[2]), positionInChunk)
if voxel then voxel:Destroy() end
RefreshBlockNeighbor(blockPosition)
end
end)
for _, chunkDirection in ChunkRenderer.Direction do
RefreshChunk(Vector3.new(ChunkPosition[1], 0, ChunkPosition[2]) + chunkDirection)
end
end
end
function ToNumberChunkData(chunkData)
local toNumberChunkData = {}
for x, column in chunkData do
for y, row in column do
for z, blockId in row do
if not toNumberChunkData[tonumber(x)] then
toNumberChunkData[tonumber(x)] = {}
end
if not toNumberChunkData[tonumber(x)][tonumber(y)] then
toNumberChunkData[tonumber(x)][tonumber(y)] = {}
end
toNumberChunkData[tonumber(x)][tonumber(y)][tonumber(z)] = tonumber(blockId)
end
end
end
return toNumberChunkData
end
function ChunkRemoved(chunk)
if chunk:IsA("Part") then
local ChunkPosition = chunk.Name:split(" ")
ChunkManager.ClearChunk(ChunkData, Vector3.new(tonumber(ChunkPosition[1]), 0, tonumber(ChunkPosition[2])))
--print("c removed", ChunkData)
end
end
ChunkFolder.ChildAdded:Connect(function(chunk)
local ChunkPosition = chunk.Name:split(" ")
ChunkPosition[1] = tonumber(ChunkPosition[1])
ChunkPosition[2] = tonumber(ChunkPosition[2])
local RequestChunkData = game.ReplicatedStorage.Remotes.RequestChunkData:InvokeServer(ChunkPosition[1], ChunkPosition[2])
RequestChunkData = ToNumberChunkData(RequestChunkData)
-- assign chunk data
if not ChunkData[ChunkPosition[1]] then
ChunkData[ChunkPosition[1]] = {}
end
if not ChunkData[ChunkPosition[1]][ChunkPosition[2]] then
ChunkData[ChunkPosition[1]][ChunkPosition[2]] = {}
end
ChunkData[ChunkPosition[1]][ChunkPosition[2]] = RequestChunkData
AddChunkQueue(chunk)
end)
ChunkFolder.ChildRemoved:Connect(ChunkRemoved)
function RenderChunk(chunkData, chunkPosition, chunkPart: Part)
local count = 0
local ChaceDictionaryBlockId = {}
local ChaceVoxel = {}
task.desynchronize()
local chunkDataToRender = ChunkRenderer.RemoveHiddenBlocks(ChunkData, chunkPosition)
task.synchronize()
for x, column in chunkDataToRender do
for y, row in column do
for z, blockId in row do
local blockIdDict = ChaceDictionaryBlockId[blockId]
if not blockIdDict then
blockIdDict = BlockDictionary.GetId(tonumber(blockId))
ChaceDictionaryBlockId[blockId] = blockIdDict
end
local voxel: Part
if ChaceVoxel[blockIdDict] then
voxel = ChaceVoxel[blockIdDict]:Clone()
local blockPositionReal = Vector3.new((chunkPosition[1] * 16) + x, y, (chunkPosition[2] * 16) + z)
voxel.Name = `{x} {y} {z}`
VoxelManager.RenderedVoxels[`{blockPositionReal.X} {blockPositionReal.Y} {blockPositionReal.Z}`] = voxel
voxel:GetPropertyChangedSignal("Parent"):Connect(function()
if voxel.Parent == nil then
VoxelManager.RenderedVoxels[`{blockPositionReal.X} {blockPositionReal.Y} {blockPositionReal.Z}`] = nil
end
end)
voxel.Position = blockPositionReal * 3
else
voxel = VoxelManager.CreateVoxel(Vector3.new(x,y,z), chunkPosition, blockIdDict, true)
ChaceVoxel[blockIdDict] = voxel:Clone()
end
if voxel and chunkPart then voxel.Parent = chunkPart end
count += 1
if count == 50 then task.wait() count = 0 end
end
end
end
end
game.ReplicatedStorage.Bindables.GetBlock.OnInvoke = function(blockPositon)
return ChunkManager.IsExist(ChunkData, blockPositon)
end
Im sorry for providing a whole messy code
But im only asking in guidance to spot where is the memory leak actually came from?
I have tried to load and unload chunks until the client memory usage is 4gb
Help, answer and guidance to this problem is appreciated