Performance advice: what's going on with my gates?

I’ve got these fences in Dungeon Life that I’m opening by setting cframes rather than setting up physics hinges and whatnot because it comes more naturally to me that way, and the result is after a server has been running for a while they become stuttery and slow – this is a moderately bad example, I’ve seen them much worse: https://gyazo.com/08129a615cffe932586d6622fee95509 – which I do not get.
According to the performance stats things are still running fine. Recv is low so I assume that means there should be no problem replicating the gate states to the clients - and even if it was a replication delay problem it wouldn’t explain why they open slower, would it?
Everything else in the game seems to run fine.
There is not much server spam.

Here’s the door script - on my local computer, it takes .00003 seconds to run each frame so I’m mystified why it seems to take over .033 on the roblox server.

while wait() do
-- lots of checks for being in the process of destruction
local startTick = tick()
if not script.Parent then return end
if not door1.PrimaryPart then return end
if not door2.PrimaryPart then return end
if not door1.PrimaryPart.Parent then return end
if not door2.PrimaryPart.Parent then return end

if openValueO.Value == "Close" then
	door1CurrentAngle = MathXL:Lerp( door1CurrentAngle, 0, 0.1 )
	door2CurrentAngle = MathXL:Lerp( door2CurrentAngle, 0, 0.1 )
elseif openValueO.Value == "OpenIn" then
	door1CurrentAngle = MathXL:Lerp( door1CurrentAngle, math.pi / 3, 0.1 )
	door2CurrentAngle = MathXL:Lerp( door2CurrentAngle, -math.pi / 3, 0.1 )
elseif openValueO.Value == "OpenOut" then
	door1CurrentAngle = MathXL:Lerp( door1CurrentAngle, -math.pi / 3, 0.1 )
	door2CurrentAngle = MathXL:Lerp( door2CurrentAngle, math.pi / 3, 0.1 )
else
	DebugXL:Error( "Invalid door state" )
end
	
door1:SetPrimaryPartCFrame( door1StartCFrame * CFrame.fromEulerAnglesXYZ( 0, door1CurrentAngle, 0 ))	
door2:SetPrimaryPartCFrame( door2StartCFrame * CFrame.fromEulerAnglesXYZ( 0, door2CurrentAngle, 0 ))	

if script.Parent:FindFirstChild("Chain1") then
	script.Parent.Chain1.Transparency = math.abs( door1CurrentAngle ) > 0.01 and 1 or 0
end
if script.Parent:FindFirstChild("Chain2") then
	script.Parent.Chain2.Transparency = math.abs( door1CurrentAngle ) > 0.01 and 1 or 0
end
lastTick = startTick

if tick() > outputTick + 60 then
	print( "Fence frame period: "..tick() - lastTick.."; contents: "..tick() - startTick )
	
end
end

Any ideas?

2 Likes

What’s MathXL and DebugXL?

What makes you think that this script is the issue?

Have you tried testing this in it’s own place without any other server scripts running? Nothing in your code here innately screams resource leak. It’s entirely possible that your game is leaking server resources elsewhere.

Even though it’s slow, the opening looks stable (roughly same amount of time between each iteration), so I doubt it’s related to replication. In all likeliness it’s that wait() is not playing nicely. wait() will try to wait for 1/30th of a second, but if the server has a heavy load, it will throttle waits. If the scripts in your server are slowing down as the game goes on longer, this would cause waits to throttle. In general, you should avoid using wait() whenever possible – use events like Heartbeat/Stepped/RenderStepped and their dt parameter.

Unrelated to issue, but:

  • I would not recommend using SetPrimaryPartCFrame as it offsets parts over time
  • I would recommend opening the gates on the client anyway – even without this issue, there will always be latency present when animating the doors. Animating them on the client will allow you to make animations that are as smooth as the player’s framerate
7 Likes

Dammit, you beat me to the punch.

He’s right though. Don’t use SPCF. Try WeldConstraints clientside and tween the cframe on a reference part. Roblox has interpolation for physics updates, but not for rapid CFrame changes. It’ll look bad.

1 Like

My own math and debug libraries

You can also use an animation to animate the door, so long as the door is unanchored and is jointed to an anchored part with a Motor6D and not a Weld (which I don’t think even work with animations?). This is generally standard practice on other platforms like Unity, so it may be preferable to manually setting CFrame, and takes care of replication / local playback automatically for you.

2 Likes

Because it’s the only thing noticeably degrading. Yes, I’ve tested locally and it’s fine. How can I tell if my server is leaking resources (more resources than usual, I mean, as far as I know the sound-related memory leak has not been fixed yet.)

If you’re concerned about resource distribution, your method is wasteful. You’re sending multiple CFrame updates per part vs just firing a remote at your clients to move the door. Even if you’re dead set on SPCF, do it clientside.

What constitutes a heavy load and how can I tell if that’s happening? All I think I’m seeing is this one symptom.

(Like any engine sure, you can’t keep setting the matrix relative to itself but since I save the cframe once at the beginning and my changes are always multiples of that it stays stable.)
(I don’t care of there’s 100 msec of latency on door opening, it feels fine.)

That explains the stuttering though not the slowness. Use the tween service to tween the cframes or would tweening manually frame-by-frame the way I’m doing here work with weldconstraints - do they replicate every frame?

There are a number of possible issues here with the waits. Have you considered using TweenService? It would greatly simplify your code and it would likely be much smoother.

Edit: You can even just tween your CFrames.

1 Like

You can use the properties/methods of Stats to log server resource usage over time, or check the developer console while you’re in a server that’s slowed down. You’re not going to really be interested in memory usage, but script activity (specifically HeartbeatTimeMs).

For the CFrame of the primary part, sure, but not between the components and the primary part.

2 Likes

I generally avoid tweens when some state comes into play - when different agents are telling a curtain to open and/or close and using fire-and-forget tweens to do it the results become too unpredictable for me so I usually prefer polling like above, but if the tweens replicate and cframes don’t that would probably be worth switching for.

Tweening would probably not work in this case because setting the CFrame of each part is not synchronized with setting the CFrame of the other parts.

2 Likes

My recv is in green though, even with many fences in a level. So isn’t that a premature optimization?

I thought WeldConstraints update relative positions when a member of the weld changes CFrame?

So - this is from a server where the problem is particularly bad - is Heartbeat the same as HeartbeatTimeMs? Looks like everything is 60hz. Does that mean it’s probably not due to resource leaks?

We’d have to look at the developer console source to find out, but even if it isn’t, it doesn’t look like anything is particularly bad in that tab. Since there are no obvious offenders, it’s going to come down to just basic debugging to figure out what the cause is. First, replace the wait with a stepped event and see if that fixes it. If not, replace the SetPrimaryPartCFrame and set the CFrame of each part manually. If even that doesn’t do it, then make sure the script is only running once and add another property into the mix like transparency to see if it really is replication. That will more than likely identify the culprit, making this is a much easier discussion.

2 Likes

For reference while you’re refactoring this, the way that I prefer to do things like animated gates and doors is:

  • rig the door model with Motor6Ds (or Welds) to a “hinge part” at either end of the door, making it one rigid body, and allowing it to be rotated about that part easily. The hinge part should be anchored, and the rest of the model should be unanchored.
  • connect a function to the touched event of the door (or however else you want it to open) which fires a message to all clients telling them to animate that door (I like to use TweenService to rotate the hinge part)
  • lastly, you can also do simple client-side prediction by having the client that triggers the door animate it right away - however, that client now needs to ignore the message that will soon come from the server telling it to animate again. I recommend passing the player that triggered on the server to all clients, and then having the client ignore it if that player matches their local player

minor correction: don’t use Welds for this