Game Development Community

Vehicle::updatePos - Physics clock too fast

by Mr. Kristen Overmyer · in Torque Game Engine · 01/05/2005 (7:21 am) · 51 replies

Comparing the physics time (accumulated dt's in Vehicle::updatePos) against the game time (roughly speaking as represented by the accumulated dt's in Vehicle::advanceTime), the physics time runs about 1.8 times faster.

If I understand the intent correctly, the vehicle physics should only be advanced in the server context and Vehicle::processTick should only be called once for each tick in ProcessList::advanceServerTime. However, Vehicle::processTick is also getting called in the client context and since Vehicle::updatePos makes no test on server or client context for doing rigid body integration, the physics time is advanced additional times. Consequently, for example, for two ticks, Vehicle::updatePos may be called 8 (4 calls per tick in Vehicle::processTick) to 16 times depending on the number of client ticks. These are usually the same as server ticks or one less, resulting in an almost doubling of the physics time.

I am presently attempting to introduce my own physics into the game platform. The most natural place for single body unconstrained physics seems to be in Vehicle::updatePos. But as described above, the physics clock appears both too fast and irregular.

Thanks for any help.

Kristen.

P.S. Your engine appears to be much more powerful than Conitec's 3D Game Studio. Keep up the good work! It's greatly appreciated.
Page«First 1 2 3 Next»
#41
01/21/2005 (10:01 am)
@Scott
Re:
I did finaly remove all the calls to interpolateTick() except the last one in advanceClientTime() on line 184.
Removing the two calls in gameConnection.cc seemed to cause no issues. Removing the first call in advanceClientTime() on line 135 however, made for very un-smooth movement of my control object. Removing the block of code from lines 166 to 177 (including the other processTick() call) seems to have fixed that. Allowing me to remove the first interpolateTick() call. Now moves are only processed on the tick. I don't think this will cause any problems however, since all moves in the list will still be processed, it's just a matter of when.

Does this still seem to be working, and did you see a performance improvement? As a developer of a single user torque project, I'm eager to try anything I can to improve performance. If this still seems to be working for you, I'll try implementing the same thing in my project.
#42
01/21/2005 (10:58 am)
@Steve

I didn't notice the changes Scott mentioned when he first mentioned them, but they seem like trouble to me. The reason the interpolateTick(0) calls are there is that some objects assume the back delta gets set to zero before a tick, so those objects will not tick properly if interpolateTick(0) isn't called first. That's why you get jerky movement when the first call is removed from advanceClientTime. It is probably the case, that removing the code between 166-177 fixes this because that code is executed under the condition where the back delta was previously very non-zero (i.e., in the other case, the object is ticking, so assuming you have a high frame rate, the last back delta was probably almost 0 anyway).

The big problem, though, is that the code between 166-177 is needed for clients to catch up on moves when the server updates the client but the last move the server processed is different than the last one the client processed (remember, server sends client an update and the number of the last move, the client synchs with the update and then replays all moves it has acquired since then...that's what is happening in this code). So you might see smooth movement on a local connection or even a low ping connection, but as the connection gets worse you'll start to see issues.

Hope that makes sense...
#43
01/21/2005 (11:23 am)
@clark

I'm dedicated single user. No network issues at all. Does this change make sense in this case?
#44
01/21/2005 (11:41 am)
@Steve

Not 100% sure. It might be, but then the code that was removed still might be crucial in some situations (perhaps a momentary slowdown due to cpu loss would require that code to get re-synched up). I'd have to study it more to be sure. One approach might be to instead go into the game classes you are using (e.g., vehicle) and make sure they don't really need the interpolate tick call. To do that, you'd need to make sure that none of the processTick code depends on the back delta value or any variable that was set using a back delta value. Once you do this, you should be able to remove all the interpolateTick calls except the one that is used to set up all the objects for rendering. This would lower cpu load for both single player games and multiplayer.

This might be a tall order to accomplish, but at least you could contribute it back to GG and the community would be grateful.

Of course, if you are only worried about single player, the more extreme optimization would be to get rid of the client/server architecture for your game completely. Before you go this route, or the route above for that matter, be aware of this: in all the profiles I've done of ThinkTanks, I've never seen the client+server processing to exceed around 10% together*. So you are talking about a lot of work for a small optimization (which is why nobody has actually done it yet). So my advice would be to hold off on this change till you are farther along on your game and you can do some profiles to test to see where your bottlenecks are. My bet is you will have many bigger fish to fry than redundant client/server processing, or extra interpolate ticks, for that matter.

*Of course, we use custom vehicle code in order to reduce the cpu load of the vehicle processing. I know that the stock vehicles can have much higher cpu loads when colliding with complex geometry. We deal with that by performing much simpler but totally adequate collisions, allowing 10-20 vehicles on our servers without noticable cpu loads.
#45
01/21/2005 (11:54 am)
@ Clark

Thanks for the info. I think I'll hold off on this for the moment. Your 10% stat is quite interesting.
#46
01/21/2005 (2:24 pm)
@ Clark

RE: The big problem, though, is that the code between 166-177 is needed for clients to catch up on moves when the server updates the client but the last move the server processed is different than the last one the client processed...

I thought about that, but I don't think it's really needed. It looks to me like the client will still catch up on any extra moves on the next client tick (in advanceObjects).

RE: That's why you get jerky movement when the first call is removed from advanceClientTime. It is probably the case, that removing the code between 166-177 fixes this because that code is executed under the condition where the back delta was previously very non-zero...

Yeah, I'm pretty sure that's the case. That's why I don't like the idea of calling processTicks when it's not actually time for a tick. Seems to me like a great way to screw up the interpolation. I'd prefer the client objects wait for their next tick to catch up on overdue moves. Doesn't matter how many processTicks they need to call to do it, so long as they're all called at Tick Time.

Removing 166-177 and allowing advanceObjects to handle the moves I think will take care of that.

---

I currently have no calls to interpolateTick(0). Only interpolateTick(dt). And yes, dt is rarely exactly 0.

I just tested the demo with these changes. The Vehicle in the racing demo seemed fine with it. The Player in the FPS demo on the other hand, seemed to have some issues. Movement was ok, but the pitching the camera up/down was jumpy. Putting back the call to interpolateTick(0) on the tick helped.

---

@ Steve

So far these changes have worked fine with my own version of the Vehicle class. They appear not to work so well with the default Player calss however. I don't see any immediate problems with the default Vehicle class, but I've done very little testing there. Bottom line, I cannot recommend anyone make these changes at this point, unless they're familiar with the workings of the interpolateTick and processTick functions of the classes they're using... And are willing to do a little tweaking/debugging.

As for performance... I cannot say for certain. I haven't tested these changes with more than 2 objects in play. But frankly, I don't expect these changes to have any real measurable impact on performance, even with many objects in play.

---

If you're (re)writing a Player or Vehicle class, it may be worth considering when and why the tick functions are called, but I wouldn't expect this to be a quick-fix framerate-booster.
#47
01/21/2005 (3:39 pm)
@Steve

I see what you mean about the move getting handled eventually in advanceObjects...which begs the question of why it was done this way in the first place. Perhaps it's that the sooner new information is folded into the old information the smoother the result will be (packUpdate/readPacket deal with the fact that the info might not be coming on a tick boundary). But, in any case, I think you are right that these changes are safe (except to the extent that the game object erroneously uses back delta during a tick update, as appears to happen on the player).

Good stuff.
#48
01/21/2005 (3:47 pm)
@Clark
I think you meant @scott.

@Scott
I may spend some time trying to figure out this code, then. Do you have any idea why the player behaves differently from the vehicle?
#49
01/21/2005 (9:04 pm)
I think I might know what's going on. I think the problem is mHead, a Point3F used to store the player head rotation.

Vehicles have only position and rotation. However, they store multiple versions of their position and rotation. They have their mObjToWorld transforms as well as mRigid. Both hold current positional and rotational information that is updated per-tick. They also have mDelta, which contains positional and rotational information for the current tick and the last tick. The mDelta values are used by interpolateTick to construct the vehicle's mRenderObjToWorld transform. mRenderObjToWorld is computed on a per-frame basis (I think) by interpolateTick. mRenderObjToWorld is used only for client side functions demanding smooth positional information. mRenderObjToWorld is never used to determine values for any of the per-tick variables. This clean separation of tick physics and interpolation data means that Vehicles don't care what their interpolation state is when they process a tick.

The Player class has similar positional and rotational data. However, the Player class adds mHead to the mix. mHead, whether by design or by accident, blurs the line between per-frame interpolation and per-tick physics. It's set by interpolation between delta.head and delta.headVec in interpolateTick. It's then used in updateMove (called by processTick). It's sent from server to client in write/readPacketData. mHead appears to be having an identity crisis. It's not sure whether it's a per-tick or interpolated variable. I'm pretty sure that's why Player objects need to interpolateTick(0) before processing a tick.

Quick fix: add a call to interpolateTick(0) in Player::processTick() before calling updateMove(). I personally think this is more correct then calling interpolateTick(0) in ProcessList::advanceClientTime(). Not all GameBase objects need this call to interpolateTick. If Player does, it should be Player which calls it. I've tested this, and it seems to work fine. (I just added it on line 1140.)

Note on the Quick fix: It occurs to me that this is a bit of a waste of time for the Player class when it has to run multiple processTicks to catch up on missed Moves. Calling interpolateTick(0) from advanceClientTime() does have the advantage of being more efficient here, since it can interpolateTick once and then processTick multiple times. OTOH it wastes time for non-Player classes which don't need the interpolateTick(0) call. What's best? Dunno.

Better solution: mHead could be broken into mHead and mRenderHead (like mObjToWorld). mHead would be processed per-tick and used in updateMove() and write/readPacketData. mRenderHead should be calculated in interpolateTick and used in the client-side render functions such as getRenderEyeTransform() and getRenderMuzzleTransform().

Unless: mHead is this way by design. A reason eludes me at the moment, but maybe I'm jumping the gun here. If anyone knows why it needs to be this way, please enlighten me. Would I break something by splitting mHead like that?
#50
04/27/2006 (1:28 am)
Any changes about this in TGE 1.4?
#51
04/27/2006 (11:39 am)
Sorry. Not able to help here. Have yet to run 1.4.

However, I believe my original description of the problem to be invalid. I have since brushed up on the Torque client/server model and am now running fine.

Kristen.
Page«First 1 2 3 Next»