New Player and Character Destroy Behavior

I would suspect that yes it would because of those asynchronous calls you have in it. It looks like when the player leaves, the server has one shot of doing the work and anything that delays (task wait, async, etc.) will be ignored and the player object deleted if I’m reading the technical info correctly here. :face_with_raised_eyebrow:

14 Likes

Wow, didn’t even know this was a problem. I always thought that they were :Destroy()'d. Good to know I have TONS of memory leaks that need fixing.

19 Likes

What’s funny about this is just yesterday I noticed that the parent of the character gets set to nil, so I made a system to manually destroy them. lol

Nice update, hope this will fix memory leaks!

13 Likes

Does this change apply to the Player:LoadCharacter method? My game has a custom character loader and I currently destroy the old character manually.

14 Likes

It’s so nice to see that work is finally going into correcting this stone age issue and that the behaviour will become default. There’s virtually no reason at all for players and characters not to get destroyed when they’re no longer in use and this missing contract caused a lot of obscure and hidden bugs with developer code if not applying heavy workarounds.

I’ll be enabling this immediately and experimenting across all my projects upon their next updates. Players and characters are some of the most memory-hogging features in my experiences and I would not be surprised to learn that some of my memory consumption over time is related to both.

16 Likes

THANK YOU. As someone who used to experience server crashes, this is great news.

Although, is there any news about the memory leak that occurs when models (or in my case, vehicles) are destroyed (even when there are no variables holding the object)? Here’s a post about it:

19 Likes

I will be using custom characters a lot as well, along with Player.CharacterAutoLoads disabled.

In this case it seems the workflow shouldn’t differ from what you’re doing right now, as the new feature will clean up the old character if it still exists when the new one is added, as visible in the code below.

CharacterAdded always fires before Destorying.

The new feature is pretty neat, and is compatible with manual removal of the character.

Code (try resetting)
local Players = game:GetService("Players")
Players.CharacterAutoLoads = false

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		local humanoid = character:WaitForChild("Humanoid")
		
		humanoid.Died:Once(function()
			task.wait(3)
			player:LoadCharacter()
		end)
		
		character.Destroying:Once(function()
			print("Character destroying")
		end)
		
		print("Character added")
	end)
	
	player:LoadCharacter()
end)
18 Likes

Wow, honestly, this is a real announcement, unlike the other one. :smirk:

Also, this feature is super cool tbh.

11 Likes

Yooo, update that fixes long present memory leak?

That’s a W in my book.
I didn’t even know characters were just parented to nil, there was a memory leak I didn’t even know existed.

13 Likes

Love this update. Now I can delete all my player and character handling on remove!

12 Likes

Does this mean if a character dies I don’t have to update the character variable in scripts?

10 Likes

Connecting to Players.PlayerAdded and Players.PlayerRemoving does not necessarily create a memory leak, because the connection is only related to the Players service. If you maintain references to the Player argument that gets passed by those events however, that’s where you could see a memory leak.

To give some examples:

Fixed by this change (because the CharacterAdded connection would have maintained the reference under current behavior)

game:GetService("Players").PlayerAdded:Connect(function(player: Player)
    player.CharacterAdded:Connect(function()
        print("New character!")
    end)
end)

Not fixed by this change (because the instance reference exists indefinitely in the AllPlayers table)

local AllPlayers = {}
game:GetService("Players").PlayerAdded:Connect(function(player: Player)
    AllPlayers[player] = true
end)

The above example, manually patched

local AllPlayers = {}
game:GetService("Players").PlayerAdded:Connect(function(player: Player)
    AllPlayers[player] = true
end)
game:GetService("Players").PlayerRemoving:Connect(function(player: Player)
    AllPlayers[player] = nil
end)

As for a reduction in server memory usage, you will a change to the rate at which memory increases if you had a memory leak patched by this. It will likely not have an impact on baseline memory usage.

12 Likes

If it is no longer possible to reparent characters on CharacterRemoving please add an alternative such as a character property to avoid being destroyed immediately on player death/leave.

This would be useful in cases where a death animation is played on a character and would be much simpler to play it on the existing character rather than having to clone it.

7 Likes

They should have called it :SweepUnderRug() because that’s what it is really doing.

9 Likes

You could probably work with this: New Player and Character Destroy Behavior - #12 by WheretIB

7 Likes

Well this is surprising, I kinda thought the new behavior was how it worked the whole time so this is a neat history lesson for me. Glad to see Roblox continuing to improve their core tech stack.

7 Likes

This is a great update and I have already been doing this in my game for a long time with a custom script. Will events like Players.PlayerRemoving() and Player.CharacterRemoving() be affected by this? Will the :Destroy() function happen after these events have run?

EDIT: This is the answer!

  • When a player leaves the experience, the Destroy method is called on the Player Instance. This task is deferred and occurs after bound event connections are invoked.
6 Likes

I had no idea that Players and Characters were not destroyed.
I’m sure I had leaks because of that assumption.

Thank you for finally fixing that hidden issue.

8 Likes
-- Broken by PlayerCharacterDestroyBehavior = Enabled

Players.PlayerRemoving:Connect(function(player)
    task.wait(1) -- Because we are deferring, this code will not work
    local points = player.leaderstats.Points.Value
    print(`player.Name left with ${points} points!`)
end)

-- Still works, even with PlayerCharacterDestroyBehavior = Enabled
Players.PlayerRemoving:Connect(function(player)
    local points = player.leaderstats.Points.Value
    print(`player.Name left with ${points} points!`)
end)

I got a concern, does the “task.wait()” still breaks even so it is not at the start of the function.
Below is a summary of how I use “task.wait()”

game:GetService("Players").PlayerAdded:Connect(function(Player)
	Player.CharacterAdded:Connect(function(Character)

      local StatsInfo = Player:WaitForChild("StatsInfo") 
		local PlayerLevel = StatsInfo:WaitForChild("PlayerLevel")
		local PlayerClothes = StatsInfo:WaitForChild("PlayerClothes")
      local FakeServerhop = game.ServerStorage.FakeServerhop


---About 40 ~ 50 lines down of info for Player
 

task.wait(1) --- I add this to let the info load, while the player respawns.
-- for I need the player to spawn to give them their clothes.


---About 40 ~ 50 lines down to Add Player Info to Gui, etc.

    Character:WaitForChild("Humanoid").Died:Connect(function()
         FakeServerhop:Fire(Player, 'PlayerClothesRenew')
       end)

		


   end)
end)

EDIT:

This is used every time the player dies. (And a few corrections)

6 Likes

Wow, I never knew this was even a thing, even after looking into oddities with removing players’ characters years ago (there was some change that made simply setting a player’s character to a different humanoid model cause a respawn… I never fully figured it out, but my current assumption is a change to the order objects are loaded into the player character model, and when removed, it would remove either the head or torso before the humanoid object, causing the Died event to fire?)

Any chance of getting a setting to control that? Manually controlling respawns fixed it, but I’d rather not have to do that in the first place.

3 Likes