Enabling this solved a huge memory leak we had on the server relating to animations and signals.
I imagine there are reasons as to why this Destroy call doesn’t replicate to clients but is inconsistent with the rest of Destroy calls on any other Instance. I expect that when an Instance is destroyed on the server this Destroy call replicates to client.
I have a leaderboard, once a player gets parented to nil the leaderboard slot gets destroyed and I still need to disconnect everything manually client side where leaks could also be the case if not handled well.
If Destroying were to be called I wouldn’t need to disconnect Attribute/Property events or any other connections made on its children.
How about making a property that specifies a delay before destroying the character after it’s removed and make it inf for existing experiences and 0 for newly created ones
I’m seeing some very odd behavior relating to memory usage.
To my understanding, this setting is supposed to destroy characters when they’re removed. It also looks like this is supposed to be enabled in live servers.
Given both of these understandings, I should be able to expect identical behavior between destroying characters myself and letting Roblox do it with the new setting; this is not the case from my testing.
In our roleplay game, we parent a custom GUI to the player’s head for showing stats. In diagnosing a memory leak, we noticed that this GUI is not being cleaned up properly; over time, as people respawn, Gui
memory usage will rise indefinitely. This holds true whether PlayerCharacterDestroyBehavior
is enabled or not, and does not happen if we do not clone the GUI into the player’s head.
If I destroy the character myself on CharacterRemoving, it works fine and Gui
never rises out of control.
The ONLY change in the game between these two videos is this snippet of code:
Player.CharacterRemoving:Connect(function(char)
char:Destroy()
end)
PlayerCharacterDestroyBehavior
was set to Enabled in both videos.
It seems like either PlayerCharacterDestroyBehavior
is not working how we’d expect it to or it’s not enabled in live servers. I tried to isolate this in Studio but couldn’t get anything consistent. @WheretIB
hi, a possibly important feedback - even after setting PlayerCharacterDestroyBehavior to true, resetting a character still leaks memory on server side on an empty base plate.
The leaks occur on these areas:
- animation
- physicspart
- instances
My biggest concern is with the animation category - a character’s animator/humanoid can load a bunch of animations with high memory load, and then die a bunch of times. Multiply those two together and you can see how fast the memory leak adds up.
This bug has been reported here months ago but hadn’t been addressed, but I thought you might want to know about it as it is highly relevant to this new PlayerCharacterDestroyBehavior feature.
Automatic character destroy is performed only for the server.
This is because many servers run for days and see many more players and characters than a client will experience over the time of a shorter session.
Having said that, extending auto-call to characters on clients might be a good idea.
Yeah, I see absolutely no downsides to it. Let us know what your team decides.
For our game in particular, some people tend to stay in-game for upwards of 6 hours roleplaying. For these dedicated people, their game crashing mid-RP because of a mem leak is less than ideal.
The expected behavior at least to me was that it’d act the same as Destroy (which does propagate to clients)
This is a much-needed change, but it does break some behavior. I can think of several games that use corpses that despawn much later than the player respawns, and I believe the most seamless way to achieve the effect is to use method in the following pseudocode:
local Debris = game:GetService("Debris")
local Players = game:GetService("Players")
local player = players.Andy_Wirus --Server-sided
local character = player.Character
player.Character = nil
character.Parent = workspace
Debris:AddItem(character, 300)
task.wait(Players.RespawnTime)
player:LoadCharacter()
This code needs to reparent the character to work, because changing the Player.Character
reparents the old character to nil
with the old behavior, and calls Destroy
on it with the new behavior. Cloning the old character causes visible replication and physics lag, which isn’t present with the reparenting. I believe this is because the reparenting happens in one replication cycle, so the reparenting doesn’t actually take place on the client-side at all, and only the Character
property of the Player
changes. The change also breaks what I believe to be the only clean way to achieve the effect seen in Roblox’s body swap potion, which I have written psuedocode for below:
local Players = game:GetService("Players")
local player1 = Players.Player1
local player2 = Players.Player2
local character1 = player1.Character
local character2 = player2.Character
player1.Character = character2
player2.Character = character1
character1.Parent = workspace
character2.Parent = workspace
There needs to be a bypass in the function that calls Destroy
on the character to keep these scripts functional, and I believe that if it’s a use case interesting enough for Roblox to make a R$400 gear based on it, it’s not something that deserves to be affected by this change. I thought about requesting a new function to unlink the character from the Player
without destroying it, but I don’t think such a niche function would land very well with the team. Instead, I’d like to propose that characters with nil
Parent
s do not have Destroy
called on them when Player.Character
is changed. This way, I only have to add one line of code per character to keep the scripts functional:
local Debris = game:GetService("Debris")
local Players = game:GetService("Players")
local player = players.Andy_Wirus --Server-sided
local character = player.Character
character.Parent = nil --the new line of code
player.Character = nil
character.Parent = workspace
Debris:AddItem(character, 300)
task.wait(Players.RespawnTime)
player:LoadCharacter()
I do not believe this is a loophole that will cause any side effects in pre-existing games, and I hope this is a use case you will find as important as I do, @WheretIB.
so disabling this option will not remove player and character left game
and this option will be locked in enabled and no longer possible to disable, right?
That was the original plan, but we are now considering only enabling it for Player instance and keep Character destruction a responsibility of the developer.
I fully agree with the new plan.
I am aiming to have AI take over the gameplay when a player leaves but their character remains in the game until either the character dies or the player rejoins. However, if the character is automatically destroyed, I will need to copy the character from just before destroy back into the workspace. This process may not be very seamless.
Is it intentional for the destroy callback to run before the playerremoving event in deferred signal behavior?
Not really a big issue but the signal is called Player “Removing”
Would appreciate having character destruction be a permanent option that developers can switch on or off in StarterPlayer or Players for example. Less work for us as developers.
How deferred events behave is unrelated to this feature.
This is a really useful change, but how did it take you guys nearly 20 years to catch this? Players/Characters have been an engine feature since around 2004-2005, and the scripting API since 2006, so surely this issue would’ve existed then as well.
Why not add a character-removing function on the client of every player instead of using a remote? It seems easier to call a single character:Destroy()
on the client when character-removing triggers.
Also, should I call character:Destroy()
on the client before or after calling character:Destroy()
on the server? Do I need to set character = nil
on either the client or server, or is character:Destroy()
sufficient?
Thanks for this clarification as I was wondering why the instances on the client never detect being destroyed. I also assumed this character destroy behavior would actually call the :Destroy() which replicates to the client, but we love inconsistency so this is not the case.
As I’m using a custom character loading module, these changes is inconvenient to me in so many ways since the character is removed when a player disconnects, thus I can’t notify the clients before character is streamed out. This was possible with SignalBehavior Immediate (but that’s getting straight up removed in the future as well, yay!) because CharacterRemoving actually happened before the character was removed, but now with Deferred it’s basically CharacterRemoved.
I would appreciate if the engine devs actually worked together on these features including SignalBehavior Deferred, especially since they’re pushing these changes on all experiences.
With PlayerCharacterDestroyBehavior Enabled, SignalBehavior Deferred, and the Destroy() replication not applying in this case, it’s really annoying and unintuitive to detect on other clients whether the character was removed from the workspace due to something like StreamingEnabled’s stream out or due to a general disconnection.
Roblox is slowly pushing these features on all experiences and not considering how many hoops you have to jump through for them to work together.
I needed to vent this out here so thanks for reading and sorry.
A bit late but do you still agree with your post?
Hi
I’m working on a ragdoll system that places a ‘body’ at the location the player died.
To achieve this currently, I’m using the character model itself (no clone). I dereference it as the player’s character by setting player.Character = nil
.
This works with this feature disabled. However enabling the toggle causes the ragdoll body to be removed at the player.Character = nil
.
Should I use a clone instead?
But more importantly: should I bother? I.e. is this behavior going to be forced in the future?
Thanks
We are planning to change the default behavior of this property to ‘Enabled’ soon with a separate announcement on the forum.
You will still have the ability to set it to ‘Disabled’ for a limited (but pretty long) amount of time, so we suggest resolving any issues you might have using cloning if necessary.