Updating HumanoidDescription causing memory leak

I have a problem in my game where if you constantly keep adding and removing accessories, it slowly starts to lag out your client.

I have no clue what could be causing this, as I have never had to face memory leaks before, but this is just what someone told me :man_shrugging:

On the client simply clicking a button will fire a RemoteEvent

ItemFrame.Button.Activated:Connect(function()
	UpdateCharacter:FireServer(page.Name)
end)

Then on the server:

local function Update(player, category, item)
	local Character = player.Character
	if not Character then return end
		
	local HumanoidDescription = Character.Humanoid:GetAppliedDescription()
	
	UpdateHumanoidDescription(player, HumanoidDescription, category, item)
	
	Character.Humanoid:ApplyDescription(HumanoidDescription)
end

UpdateCharacter.OnServerEvent:Connect(Update)

Now I believe the problem lies in the UpdateHumanoidDescription function

local function UpdateHumanoidDescription(player, humanoidDescription, category, item)
	local PlayerData = player:FindFirstChild('PlayerData')
	if not PlayerData then return end
	
	local function AccessoryToPlayerData(id, accessoryName)
		local Item = id
			
		local NewValue = Converter.Convertr(Item)
		NewValue.Name = id
		
		NewValue.Parent = PlayerData.Character[accessoryName]
	end
		
	-- Set the PlayerData values
	if category then
		-- Updating character
		local Data = PlayerData.Character:FindFirstChild(category)
		if not Data then return end
		
		if Data:IsA('Folder') then
			-- Accessory
			if Data:FindFirstChild(item) then return end
			
			if #Data:GetChildren() >= 5 then return end
			
			-- EDIT Need to check for VIP, and see if item requires VIP
			
			local NewItem = item
						
			local NewValue = Converter.Convertr(NewItem)
			NewValue.Name = item
			
			NewValue.Parent = Data
		else
			-- Value
			for i, v in pairs(CustomiseData[category]) do
				-- EDIT Need to check for VIP, and see if item requires VIP
				
				local ID -- Whether its the ID (for Shirts, Pants, Faces, etc. or Name for Body Colors
				if category == 'Body Color' then
					ID = v.Name
				else
					ID = i
				end
				
				if item == ID then
					Data.Value = ID
					
					if category == 'Bundle' then
						local Details = AssetService:GetBundleDetailsAsync(ID)
					end
				end
			end
		end
	else
		-- Loading character
		if PlayerData.Character['Roleplay Name'].Value == '' then
			-- No previous data
			PlayerData.Character['Roleplay Name'].Value = player.Name
			
			PlayerData.Character.Face.Value = humanoidDescription.Face
			PlayerData.Character.Shirt.Value = humanoidDescription.Shirt
			PlayerData.Character.Pants.Value = humanoidDescription.Pants
			
			-- Set default accessories
			for _, id in ipairs(humanoidDescription.HatAccessory:split(',')) do
				if id ~= '' then
					AccessoryToPlayerData(id, 'Hats')
				end
			end
						
			for _, id in ipairs(humanoidDescription.HairAccessory:split(',')) do
				if id ~= '' then
					AccessoryToPlayerData(id, 'Hairs')
				end
			end
			
			for _, id in ipairs(humanoidDescription.FaceAccessory:split(',')) do
				if id ~= '' then
					AccessoryToPlayerData(id, 'Face Accessories')
				end
			end
			
			for _, id in ipairs(humanoidDescription.NeckAccessory:split(',')) do
				if id ~= '' then
					AccessoryToPlayerData(id, 'Neck Accessories')
				end
			end
						
			for _, id in ipairs(humanoidDescription.ShouldersAccessory:split(',')) do
				if id ~= '' then
					AccessoryToPlayerData(id, 'Shoulders Accessories')
				end
			end
			
			for _, id in ipairs(humanoidDescription.FrontAccessory:split(',')) do
				if id ~= '' then
					AccessoryToPlayerData(id, 'Front Accessories')
				end
			end
						
			for _, id in ipairs(humanoidDescription.BackAccessory:split(',')) do
				if id ~= '' then
					AccessoryToPlayerData(id, 'Back Accessories')
				end
			end
			
			for _, id in ipairs(humanoidDescription.WaistAccessory:split(',')) do
				if id ~= '' then
					AccessoryToPlayerData(id, 'Waist Accessories')
				end
			end
		end
	end
	
	-- Set HumanoidDescriptions for accessories
	local HatAccessories = {}
	for _, v in pairs(PlayerData.Character.Hats:GetChildren()) do
		table.insert(HatAccessories, v.Name)
	end
	
	humanoidDescription.HatAccessory = table.concat(HatAccessories, ', ')
	
	local HairAccessories = {}
	for _, v in pairs(PlayerData.Character.Hairs:GetChildren()) do
		table.insert(HairAccessories, v.Name)
	end
	
	humanoidDescription.HairAccessory = table.concat(HairAccessories, ', ')
	
	local FaceAccessories = {}
	for _, v in pairs(PlayerData.Character['Face Accessories']:GetChildren()) do
		table.insert(FaceAccessories, v.Name)
	end
	
	humanoidDescription.FaceAccessory = table.concat(FaceAccessories, ', ')
	
	local NeckAccessories = {}
	for _, v in pairs(PlayerData.Character['Neck Accessories']:GetChildren()) do
		table.insert(NeckAccessories, v.Name)
	end
	
	humanoidDescription.NeckAccessory = table.concat(NeckAccessories, ', ')
	
	local ShouldersAccessories = {}
	for _, v in pairs(PlayerData.Character['Shoulders Accessories']:GetChildren()) do
		table.insert(ShouldersAccessories, v.Name)
	end
	
	humanoidDescription.ShouldersAccessory = table.concat(ShouldersAccessories, ', ')
	
	local FrontAccessories = {}
	for _, v in pairs(PlayerData.Character['Front Accessories']:GetChildren()) do
		table.insert(FrontAccessories, v.Name)
	end
	
	humanoidDescription.FrontAccessory = table.concat(FrontAccessories, ', ')
	
	local BackAccessories = {}
	for _, v in pairs(PlayerData.Character['Back Accessories']:GetChildren()) do
		table.insert(BackAccessories, v.Name)
	end
	
	humanoidDescription.BackAccessory = table.concat(BackAccessories, ', ')
	
	local WaistAccessories = {}
	for _, v in pairs(PlayerData.Character['Waist Accessories']:GetChildren()) do
		table.insert(WaistAccessories, v.Name)
	end
	
	humanoidDescription.WaistAccessory = table.concat(WaistAccessories, ', ')
	
	-- Body sizes (need to base sizes on age)
	local Size = AgeSizes[PlayerData.Character.Age.Value]
	
	humanoidDescription.DepthScale = Size
	humanoidDescription.HeadScale = Size
	humanoidDescription.HeightScale = Size
	humanoidDescription.WidthScale = Size

	humanoidDescription.BodyTypeScale = 0
	humanoidDescription.ProportionScale = 0
	
	-- Face/Shirt/Pants
	humanoidDescription.Face = PlayerData.Character.Face.Value
	humanoidDescription.Shirt = PlayerData.Character.Shirt.Value
	humanoidDescription.Pants = PlayerData.Character.Pants.Value
	
	-- BodyColor
	humanoidDescription.HeadColor = BrickColor.new(PlayerData.Character['Body Color'].Value).Color
	humanoidDescription.LeftArmColor = BrickColor.new(PlayerData.Character['Body Color'].Value).Color
	humanoidDescription.LeftLegColor = BrickColor.new(PlayerData.Character['Body Color'].Value).Color
	humanoidDescription.RightArmColor = BrickColor.new(PlayerData.Character['Body Color'].Value).Color
	humanoidDescription.RightLegColor = BrickColor.new(PlayerData.Character['Body Color'].Value).Color
	humanoidDescription.TorsoColor = BrickColor.new(PlayerData.Character['Body Color'].Value).Color
	
	return humanoidDescription
end

It’s a lotta code because I have to convert values into strings to put in the HumanoidDescription for each individual accessory type. But if anyone knows where a certain section could be causing this :grimacing:

1 Like

At first glance, I don’t see any red flags.

Have you tried utilizing the Script Performance tab in Studio to see if there are scripts running at high rates?

No I’ve never used it before :grimacing: Not sure what a ‘good’ memory is either
This is just loaded into the game

Are you sure it’s your HumanoidDescriptions and code that’s causing this then? Seems you believe this is the problem code based off of what someone told you as opposed to diagnosis you performed yourself. If HumanoidDescriptions are actually causing memory leaks and you can credibly source that, that’s something that you need to file a bug report for.

On the other hand: updating the HumanoidDescription so often (in your case, every time a change is made to the character’s appearance) may also be a problem that you need to resolve. Normally I wouldn’t update the appearance that often; only after a few seconds (3-5) when the player isn’t interacting with any part of the Gui would the character get updated. Depending on how you go about character customisation, this might affect previewing abilities (non-immediate updating). Not something that can’t be worked around though.

1 Like

I think I found a better solution to where it was. Adding a removing accessories in quick succession, wasn’t actually ‘destroying’ the values, and thus causing problems. However, now that it does, and removing the accessory does destroy the item, still constantly spamming and removing accessories causes the memory to go up faster than it goes down. I’d imagine if you add a certain amount of accessories, it’d go up, but then removing them all would bring it back down to its original

local function Remove(player, id)
	local Character = player.Character
	if not Character then return end
		
	local PlayerData = player:FindFirstChild('PlayerData')
	if not PlayerData then return end
	
	for _, v in pairs(PlayerData:GetDescendants()) do
		if v.Name == id then -- problem was here
			v:Destroy()
		end
	end
		
	local HumanoidDescription = Character.Humanoid:GetAppliedDescription()
	
	UpdateHumanoidDescription(player, HumanoidDescription)
	
	Character.Humanoid:ApplyDescription(HumanoidDescription)
end
1 Like

cc @only_colbert

Not sure if these help! Sorry I’m new to this whole memory stuff.

Can see, when server starts its at a normal 350ish, but as I add and remove accessories it climbs and at about 450-500 it starts to lag

Place memory is at 473. So I’m guessing something here is causing it? I don’t know what any of the tabs below mean tho.


The LuaHeap and GraphicsTexture are real high. Not sure why GraphucTextures is high tho because it’s a completely low poly game with no textures

EDIT
And having done more tests, it seems that ‘LuaHeap’ is the one that goes up the most. However, I have no clue what that means :confused: GraphicsTexture seems to start at 140 (or whatever) when you join, so I don’t think I can do much about that number

Sorry to bump, but I’d like to follow up on this conversation. I believe OP might’ve been right when they said HumanoidDescriptions could cause memory leaks. I’m also facing a similar issue. Nothing in my game is inherently laggy, and I have thoroughly checked every script that’s being run. The craziest thing happening is a couple of O(n) loops and some cloning. It seems that as the game goes on, and more HumanoidDescriptions are being applied to players, the server becomes increasingly laggy. At its worst, my game’s server ping was hitting ~2000ms, which happened after a couple of rounds.

One test that I did to ensure that HumanoidDescriptions were the cause of server-sided lag was to remove HumanoidDescriptions from my game entirely, and just have players play as some default skin. Any sort of server lag went away immediately, and my game was running smoothly. At max, the server ping hit ~120ms (updated to ~120ms from ~180ms due to other unrelated reasons, and further testing), which happens when my maid does her cleaning.

For anybody curious, all events are disconnected, debris is handled, and other possible memory leaks are taken care of.

EDIT:

local charModel = script.Dummy:Clone() -- reference to some dummy with desired appearance
plr.Character = charModel

This snippet of code (above) is over 100x faster (not satire) than

local desc = script.HumanoidDescription
plr.Character.Humanoid:ApplyDescription(desc)

Stats for nerds:
Above code (setting a player’s character to a model): ~.00174 seconds for 3 players
Bottom code (applying a HumanoidDescription): ~.1756 seconds for 3 players

However, this post isn’t about optimization. This is about the residual lag that comes after using HumanoidDescriptions. The dev hub makes no mention of dropped performance at all, and I only figured this out through experimentation. I am not skilled enough to confidently say that memory leaks are occurring, but I can definitively say that there are performance issues that come with using HumanoidDescriptions.

Please feel free to message me or respond with your findings/thoughts. I would be love to talk more about this.

3 Likes

I have found this as well! I believe it is Humanoid Descriptions that are the root cause here - can you run this code snippet in your game after an hour or two:

local function getNilInstances()
    local InstanceCount = stats().InstanceCount
    local GameInstances = 0

    for _, Instance in ipairs(game:GetDescendants()) do
        GameInstances += 1
    end

    return InstanceCount - GameInstances
end

print("Nil Instances: " .. tostring(getNilInstances()))

We’ve found ours goes well in the hundreds of thousands, and even into the millions of nil referenced instances. Let me know what you find!

2 Likes

Oooh right! You nailed it-- I just set up a test using your code and I can definitely see the number of nil instances is steadily rising. https://gyazo.com/ba457a790aebd9f6c682eb4c8ba19a98

If you’re looking for a substitute, I made another post about this:

I haven’t experienced any problems at all since swapping out HumanoidDescriptions with this method.

1 Like

This is spot on.

Have you filed a bug report for this?

i’ll make a ticket and investigate what’s causing this memory leak issue. Does anyone have a simple rbxl/rbxlx test place file they can give me which i can run to repro the issue?

3 Likes

Hiya,

I’ve put a repro place together for you.

Nil instances should (and does), to my knowledge, count the number of instances in memory. They should be garbage collected when not used. These instances seem to not be garbage collected in Studio, which will obviously increase memory, so that looks like a separate issue and means you can’t test for this memory leak in Studio.

Here is the uncopylocked place, for easier testing: Memory Leak HumanoidDescription test - Roblox

Here is the file, for download: Memory-Leak-HumanoidDescription-test.rbxl (32.7 KB)

I observe permanent memory increases by about 10MB on either of these buttons, which is bizarre. Seems like something is very off on the LoadCharacter front.

1 Like

i gave your place a try. i don’t see memory usage or instance count increases after stepping on the buttons. What are you seeing?

a fix has been put in for the memory leak. That issue should be resolved now

2 Likes