Hello everybody! I’m working on some back-end systems for a game, and after creating a morphing (server) & animation (client) backend, I have 2 frames that take ~500ms each.
I have been staring at microprofiler dumps and doing research for awhile trying to figure out what the root cause of it is.
Both of the frames look like this (on different service workers between the frames)
Advanced Stuff
Both frames contain:
- Thread (FG)
- TS::Reschedule
- Write Marshalled (NotOrderDependant; Game)
- TS::JobStep
- Write Marshalled
The first frame used Service Worker D and the second frame used Service Worker A
Here is a list of every class inside the character:
{
["Animator"] = 1,
["Bone"] = 57,
["Folder"] = 1,
["Humanoid"] = 1,
["MeshPart"] = 4,
["Motor6D"] = 5,
["Part"] = 2,
["SurfaceAppearance"] = 3
}
Old Hypothesis (DISPROVEN)
After doing some research, it seems part of the issue is the network serialization (write marshaling), but nothing looks too out of the ordinary as I’m not sending large data models over the network (the base roblox replication is the biggest thing to worry about)
After doing some research, the engine call to Player.Character changing is causing the issue. Root cause is still unknown.
I am using the Packet Networking Library for my client-server communication.
Due to the unreleased nature of the project, I cannot share full codebases or full models, but I can share snippets and information about them.
Assume that any and all code is properly referenced and working.
MorphController:DoMorph()
function MorphController:DoMorph(playerToMorph:Player, newChr:string)
print(`[MorphController] Attempting to morph {playerToMorph.Name} to {newChr}`)
local data = self.Util:GetCharacterData(newChr).RigInfo
if data == nil then warn('data nil') return end
local oldCharacter = playerToMorph.Character
if not oldCharacter then return end
local oldRoot = oldCharacter:FindFirstChild("HumanoidRootPart") or oldCharacter:FindFirstChild("RootPart")
debug.profilebegin("Clone Morph")
local morph = self.Util:FindFirstDescendant(game:GetService('ServerStorage').Characters, newChr):FindFirstChild("Morph"):Clone()
debug.profileend()
if morph == nil or oldRoot == nil then warn('cant morph, something is nil (oldroot, morph, chrname)', oldRoot, morph, newChr) return end
local newHumanoid = morph:FindFirstChildOfClass("Humanoid")
if newHumanoid == nil then warn('new hum not found') return end
local newRoot = morph:FindFirstChild("HumanoidRootPart") or morph:FindFirstChild("RootPart")
if newRoot == nil then warn('new root not found') return end
playerToMorph:SetAttribute("CurrentCharacter", newChr) -- for animations
morph.Name = playerToMorph.Name
newHumanoid.HipHeight = data.HipHeight
newRoot.Anchored = false
morph:PivotTo(oldRoot.CFrame + (data.SpawnOffset or Vector3.zero))
debug.profilebegin("Set Char")
playerToMorph.Character = morph
debug.profileend()
debug.profilebegin("Parent Morph")
morph.Parent = workspace
debug.profileend()
-- this is all set up as documented in the Packet docs
self.UpdatedCharacter:FireClient(playerToMorph)
self.FixCameraPacket:FireClient(playerToMorph, data.CameraOffset)
self.ChangeAnimData:FireClient(playerToMorph)
end
AnimationController:UpdateAnimations()
function AnimationController:UpdateAnimationData()
local currentCharacter = self.Player:GetAttribute("CurrentCharacter")
if currentCharacter == nil then return end
self.CharacterData = self.Util:GetCharacterData(currentCharacter)
if self.CharacterData == nil then return end
print("[AnimationController] Updating Animation Data")
for name, id in pairs(self.CharacterData.Animations) do
if id == nil or id == "" then continue end
local list = typeof(id) == 'table' and id or {id}
for i, id in ipairs(list) do
local track = Instance.new("Animation")
track.AnimationId = string.match(id, 'rbxassetid://') and id or "rbxassetid://"..id
self.AnimationTracks[typeof(id) == 'table' and name..i or name] = track
end
end
self:ChangeState(self.State) -- redo animations after morph
end
IMPORTANT NOTES:
- My
debug.profilebegin()calls are NOT lining up with any of the long frames. self.Util:FindFirstDescendant()is NOT causing long frame times (finishing in ~200μ)- The model being used for this only has 323 total descendants and has a mid-poly tri-count.
- Remember, huge data models are NOT being sent over the network.
If you need any more information that might help, please don’t be afraid to ask!




