Support! I think this gets away of a useful pattern I was thinking of with coroutine.close()
Whenever you make a system that yields inside a event handler, on the player’s team changing many people don’t account for “async race conditions”
For example. player joins admin team, and they are given the description of a random player using a yielding function like game.GetHumanoidDescriptionFromUserId(). People on the normal team are just given their own outfit ID.
Now what happens if the player changes teams to admin and then quickly to normal before the network request, game.GetHumanoidDescriptionFromUserId() is completed? Then once that request is completed they will be given the admin random morph even though their team isn’t admin currently and is normal!
Example of what I mean:
local NormalDescription = Instance.new("HumanoidDescription")
local NormalTeam = Instance.new("Team", game:GetService('Teams'))
NormalTeam.TeamColor = BrickColor.new(255,0,0)
NormalTeam.Name = "Normal"
NormalTeam.AutoAssignable = false
local AdminTeam = Instance.new("Team", game:GetService('Teams'))
AdminTeam.Name = "Admin"
AdminTeam.TeamColor = BrickColor.new(255,255,0)
AdminTeam.AutoAssignable = false
game.Players.PlayerAdded:Connect(function(Player: Player)
Player:GetPropertyChangedSignal("Team"):Connect(function()
local Character = Player.Character or Player.CharacterAdded:Wait()
if Player.Team ==AdminTeam then
local AdminMorph = game.Players:GetHumanoidDescriptionFromUserId(3783643606)
Character.Humanoid:ApplyDescription(AdminMorph)
else
Character.Humanoid:ApplyDescription(NormalDescription) -- internally figures out and uses latest description passed so no need to worry about race conditions
end
end)
task.delay(5,function()
print "START"
Player.Team = AdminTeam
Player.Team = NormalTeam -- after the network request from the GetHumanoidDescription from the admin team is completed. it will make the player have the admin morph even though their team is normal
end)
end)
This is tryable in studio it will team you a random player’s humanoid description which only should happen when the team is admin, even though your team is NormalTeam. This is due the race conditions I was talking about
Ofcourse, you can pre-emptively cache any requests for these use cases, but it’s not always possible. An other way is having a guard clause after a yielding function to see if the current player.Team matches with the team the player had when you called this function.
I don’t feel like it’s the most cleanest way. This is the pattern I was thinking of.
local IdentityTable = {}
local function UseLatestCall(IdentityArg, Functor) -- The pattern which makes this possible easily
local MyFunction = nil
MyFunction = function(...)
local SelectedArg = select(1, ...)
local Args = {...}
if IdentityTable[MyFunction][SelectedArg] then
coroutine.close(IdentityTable[MyFunction][SelectedArg])
IdentityTable[MyFunction][SelectedArg] = nil
print "Cancelled thread"
end
IdentityTable[MyFunction][SelectedArg] = coroutine.create(function()
print(IdentityTable[MyFunction][SelectedArg])
Functor(unpack(Args))
IdentityTable[MyFunction][SelectedArg] = nil
end)
task.spawn(IdentityTable[MyFunction][SelectedArg])
end
IdentityTable[MyFunction] = {}
return MyFunction
end
local NormalDescription = Instance.new("HumanoidDescription")
local Update = UseLatestCall(1, function(Player)
local function Guard()
if Player.Character == nil or Player.Chracter.Parent == nil then return true end
end if Guard() then return end
if Player.Team == game.Teams.Admin then
local DressPlayerAsRoblox = game.Players:GetHumanoidDescriptionFromUserId(1)
if Guard() then return end
Player.Character.Humanoid:ApplyDescription(DressPlayerAsRoblox)
print "Your team is admin"
else
Player.Character.Humanoid:ApplyDescription(NormalDescription)
end
end)
local NormalDescription = Instance.new("HumanoidDescription")
game.Players.PlayerAdded:Connect(function(Player: Player)
Player:GetPropertyChangedSignal("Team"):Connect(function()
Update(Player)
end)
Player.CharacterAdded:Connect(function()
Update(Player)
end)
task.delay(5,function()
print "START"
Player.Team = game.Teams.Admin
Player.Team = game.Teams.Normal
end)
end)
Ofcourse there are still guard clauses in here, but they are only to check if the character still exists since humanoid:applyDescription() will not work if the character is parented to nil.
Not to check if a new request was called and cancel the old one, that gets dirty quickly for reusability. This code does work, but it errors cannot resume dead coroutine like in this post’s case.