Game Development Community

T3D 1.2 - Player doesn't call advanceTime for controlled vehicle

by Henry Todd · in Torque 3D Professional · 06/05/2012 (2:54 pm) · 4 replies

This one's the cause of the wheels-won't-spin bug you may or may not have seen when a WheeledVehicle is controlled by a Player (not directly by a client). It happens less often in 1.2, but you can still make it happen depending on how you mount your player.

When a Player controls an object, the object calls setProcessTick(false) which means it will no longer call functions like processTick, advanceTime, etc. on its own. It becomes the Player's responsibility to handle calling these functions at the appropriate times.

The issue is that Player never calls advanceTime for the object it's mounted to. For WheeledVehicles this means that sounds and suspension/wheel animations are never updated. The fix is pretty simple:

void Player::advanceTime(F32 dt)
{
   if (mControlObject) // new line
      mControlObject->advanceTime(dt); // new line

   // Client side animations
   Parent::advanceTime(dt);
   updateActionThread();
   updateAnimation(dt);
   updateSplash();
   updateFroth(dt);
   updateWaterSounds(dt);
   ...

The only question I'm left with is why the WheeledVehicles ever animate their wheels. With this line absent I can't see how advanceTime could ever get called while a Player is driving. Makes me wonder if there's some situation where these calls could be getting doubled up (if the controlling Player is calling them and the object starts doing its own processTick calls for some reason on top of it).

#1
06/06/2012 (1:35 am)
This was supposedly fixed, here and here.

I've not seen this happening in 1.2
#2
06/06/2012 (9:25 am)
@Henry
Are you seeing this in a stock 1.2?
#3
06/06/2012 (10:38 am)
It won't happen using the normal mounting procedures, but you can still get it to happen by mounting a player that doesn't have a client attached to it (for example, if you're in editor camera while you mount your player) and then attaching a client to that player.

Just tried it with the stock FPS Example and it still happened. Create a vehicle, then Alt-C to your editor camera and mount the player manually (vehicleID.mountObject(playerID, 0)). When you Alt-C back to the player the wheels won't be animating.

How does advanceTime get called right now for objects controlled by Players? The wheels also start spinning properly once you fire the Cheetah's mounted gun, but I can't figure out how that's getting advanceTime going (which is why I wasn't certain if the fix I posted would result in double calls).

*edit: I can see now that my original assumption was wrong, I guess I read something incorrectly. ITickable::advanceTime skips processTick and interpolateTick if the object is set not tickable, but supposedly always calls advanceTime.

**editX2: I think I found it. I forgot where I got the idea that advanceTime didn't get called on Player-controlled objects:

void SceneObject::setProcessTick( bool t )
{
   if ( t == mProcessTick )
      return;

   if ( mProcessTick )
   {
      plUnlink();
      mProcessTick = false;
   }
   else
   {
      // Just to be sure...
      plUnlink();

      getProcessList()->addObject( this );

      mProcessTick = true;  
   }   
}

So this code removes the object entirely from the process list (plUnlink()) when you setProcessTick(false), which happens when Player::setControlObject is called. Added some debug output and figured out why advanceTime only stops when the client attaches to the Player... this function:
-Gets called on server when Player is mounted to the Vehicle.
-Gets called on client when client attaches to Player.
This is a matter of networking. The client doesn't need to know the Player is controlling the Vehicle unless that client is attached to the Player.

Now the question becomes why it works at all. Based on this code the Vehicle should never get advanceTick calls until the Player releases control. Or until something else adds the Vehicle back to the processList, which apparently happens somehow and involves image state updates.
#4
06/06/2012 (12:43 pm)
(Getting too long)
I can't really decide how to fix this properly. Seems strange that setProcessTick actually removes the object from the process list (neither ProcessObject nor iTickable's versions of this function do that) but I'm sure there was a reason for this. Not that the reason is really particularly important if under normal mounting conditions the Vehicle's just getting added back to the list with mProcessTick set to false anyway.

So if it can be determined that there's no harm I'd just not have SceneObject::setProcessTick remove the object from the process list, which fixes the whole issue.

*edit: Apparently I'm still wrong about how the ticking works, because objects get their calls from StdClientProcessList::advanceTime and I have no idea what ITickable::advanceTime actually is for. It doesn't really matter since StdClientProcessList::advanceTime is functionally identical (ticks get checked for isTicking, advanceTime doesn't). The problem remains that setProcessTick removes the object from the process list (which stops calls to advanceTime) until some unknown bit of code manages to re-add it.

I probably should have started with proper debugging, but I just found this out:
Mounting a Vehicle normally (stock debug build with breaks in SceneObject::setProcessTick, ServerProcessList::addObject and ClientProcessList::addObject, mounting through collision) the following sequence of events occurs:
-SceneObject::setProcessTick(false) is called on the server.
-A Player::readPacketData event triggers setProcessTick(false) on the client
-neither ClientProcessList::addObject or ServerProcessList::addObject ever get called.
-The Vehicle remains on the client process list only.

Obviously it's getting re-added somewhere. plUnlink is a pretty basic remove-from-list function with no conditions. I'm not familiar enough with all this low level code to know if there's another way to get an object onto the client process list, but I'll keep hunting.