Have the player leave while Player:LoadCharacter() is being ran on the engine-side
Observe the error LoadCharacter: Failed to apply character appearance, player not in world. The error only occurs some of the time with the repro place file below:
Expected Behavior
I’m not exactly sure if LoadCharacter() spitting an error when a player leaves is expected behavior or not. If it is expected / intended behavior, the error message should be more explicit to convey what exactly triggered the error in the first place. The current error message is very confusing and implicit.
If it isn’t expected, would the player’s character simply be parented to nil while it’s still being built by the engine?
Actual Behavior LoadCharacter() spits an error if the player leaves while it is being executed on the engine side.
Workaround pcall()ing Player:LoadCharacter()
Issue Area: Engine Issue Type: Other Impact: Moderate Frequency: Sometimes Date First Experienced: 2021-10-24 00:10:00 (-04:00)
Update, here is the code we’re currently using. If the player logs out during LoadCharacter() there is no success or failure and nothing after the pcall in the script runs:
if player.Parent == game.Players then
print("Loading character")
local success, message = pcall(function()
player:LoadCharacter()
end)
if success then
print("Loaded character")
else
warn(message)
end
else
print("Player offline, skipping setup")
continue
end
I thought the pcall would let the script continue to run if the function inside of it fails but that does not seem to be the case with player:LoadCharacter().
The issue seems to be entirely with players:LoadCharacter() stopping the thread if a player logs out during it. It also prevents the pcall from returning and anything after it from executing.
Easy to reproduce with either of the following code blocks. Log out and relog a few times until it stops running, which you’ll know because you won’t respawn anymore. Happens within a few attempts. Sometimes the pcall properly returns the error message, but sometimes it completely stops the thread:
while true do
for _,player in ipairs(Players:GetPlayers()) do
player:LoadCharacter()
end
wait()
end
or
while true do
for _,player in ipairs(Players:GetPlayers()) do
local success, message = pcall(function()
player:LoadCharacter()
end)
if not success then
warn(message)
end
end
wait()
end
As a temporary work around I’m running player:LoadCharacter() within a coroutine but I’m hoping this can get resolved soon. Thank you!
This is still a problem. I was able to reproduce it with the following steps:
Open the repro place file (attached below)
Run a test server with 1 connected client
Click on the button in the bottom left of the screen. Observe that the character is reloaded, and all output is displayed in the console.
Click the button again, but close the client window immediately (disconnect the client) before the character can be reloaded. Observe that the last line of output was never displayed by the server. The thread is infinitely yielded / was terminated.
I think the expected behavior here is that LoadCharacter returns a bool, describing whether or not the load was successful. If a player leaves during the operation, it returns false.
-- Respawn player characters at end of each round.
for i,player in ipairs(Players:GetChildren()) do
local statusCodeBoolTF,theErrorOrFirstOfAllReturnsFromTheCall = pcall(function()
task.spawn(
function()
player:LoadCharacter()
end
)
end)
end
This error is happening in my game as well: if people leave right when it’s trying to load their character it will trigger the error “player not in this world” , breaking my player manager script and stopping anyone else from being able to spawn. I may try wrapping player load character in a coroutine to see if it that helps in the meantime, not sure if anyone else had success with that.
Tested the repro in the reply and it’s still an issue. IMO LoadCharacter should throw an error if the player leaves during the process, but any solution is better than silently infinitely yielding.
function PlayerService:LoadCharacter(player: Player, timeout: number?): Model?
local functionThread = coroutine.running()
local asyncThread = task.spawn(function()
local previousCharacter = player.Character
pcall(player.LoadCharacter, player) -- Sometimes yields, sometimes doesn't
task.spawn(functionThread, previousCharacter ~= player.Character and player.Character or player.CharacterAdded:Wait())
end)
task.delay(timeout or 10, function()
if coroutine.status(asyncThread) == "suspended" then
task.spawn(asyncThread)
end
end)
return coroutine.yield(functionThread)
end
This is still an issue. Finally figured out the huge bug in a 2k ccu game I develop for that I’ve been working so hard to try to fix thanks to this post.