Game Development Community

dev|Pro Game Development Curriculum

Network optimization - setMoveMaskIfReallyNeedTo()

by Orion Elenzil · 02/28/2006 (7:00 pm) · 11 comments

edit: !!! do not use this resource as presented here !!! - see post at bottom. a fix is on the way !

If you're interested in this resource, you should definitely read this thread as well.

caveat
- i've since modified this locally so that each server-side player object forces MoveMask at least every 20 frames. i don't have cycles right now to post the change, but it's not too tricky. the motivation for this was because i have custom player-vs-player collision code which will push players around on the client side, and without an occasional re-assert from the server you could push stationary players off cliffs, etc.

player.h

declarations:

after "mMountPending":
Point3F mPosPrev;                ///< Used to determine if we should actually set MoveMask.
   Point3F mRotPrev;                ///< Used to determine if we should actually set MoveMask.

after "void checkMissionArea();"
bool setMoveMaskIfReallyNeedTo(const Point3F& pos, const Point3F& rot);


player.cc

initialize:

in Player::Player(),
after "mVelocity.set(0,0,0);",
mPosPrev.set(0,0,0);
   mRotPrev.set(0,0,0);



the core function:

at the very bottom
bool Player::setMoveMaskIfReallyNeedTo(const Point3F& pos, const Point3F& rot)
{
   F32 dPosThresh = 0.00001f;
   F32 dRotThresh  = mDegToRad(0.01f);

   Point3F dPos;
   Point3F dRot;

   dPos = pos - mPosPrev;
   dRot = rot - mRotPrev;

   // note these are ordered from most to least likely -

   if (mFabs(dRot.z) > dRotThresh) {
      setMaskBits(MoveMask);
      mRotPrev = rot;
      return true;
   }
   if (mFabs(dPos.x) > dPosThresh) {
      setMaskBits(MoveMask);
      mPosPrev = pos;
      return true;
   }
   if (mFabs(dPos.y) > dPosThresh) {
      setMaskBits(MoveMask);
      mPosPrev = pos;
      return true;
   }
   if (mFabs(dPos.z) > dPosThresh) {
      setMaskBits(MoveMask);
      mPosPrev = pos;
      return true;
   }
   if (mFabs(dRot.x) > dRotThresh) {
      setMaskBits(MoveMask);
      mRotPrev = rot;
      return true;
   }
   if (mFabs(dRot.y) > dRotThresh) {
      setMaskBits(MoveMask);
      mRotPrev = rot;
      return true;
   }

   return false;
}



the main culprit:

in updatePos(), change this:
setPosition(start,mRot);
   setMaskBits(MoveMask);
   updateContainer();

to this:
setPosition(start,mRot);
   setMoveMaskIfReallyNeedTo(start, mRot);
   updateContainer();


lesser culprits:

in Player::setTransform(),

change this:
setMaskBits(MoveMask | NoWarpMask);

to this:
setMoveMaskIfReallyNeedTo(pos, rot);
   setMaskBits(NoWarpMask);


last one..

in Player::setVelocity(),

change this:
setMaskBits(MoveMask);

to this:
Point3F pos;
   getTransform().getColumn(3,&pos);
   setMoveMaskIfReallyNeedTo(pos, mRot);


edited 20060228: tightened threshhold.
edited 20060301: added introductory blurb.

#1
03/01/2006 (12:42 am)
Nice change.

Btw if you want to register movement in multiple direction/rotation, do something like this:

bool Player::setMoveMaskIfReallyNeedTo(const Point3F& pos, const Point3F& rot)
{   
	F32 dPosThresh = 0.00001f;   
	F32 dRotThresh  = mDegToRad(0.01f);   
	Point3F dPos;   Point3F dRot;   
	dPos = pos - mPosPrev;   
	dRot = rot - mRotPrev;   
	
	bool moveMask = false;

	if (mFabs(dRot.z) > dRotThresh || mFabs(dPos.x) > dPosThresh || mFabs(dPos.y) > dPosThresh ||
		mFabs(dPos.z) > dPosThresh || mFabs(dRot.x) > dRotThresh || mFabs(dRot.y) > dRotThresh) 
	{      
		setMaskBits(MoveMask);

		moveMask = true;
	}else
		return false; 
       
	
	if (mFabs(dRot.z) > dRotThresh)           
		mRotPrev = rot;
      
	
	if (mFabs(dPos.x) > dPosThresh)           
		mPosPrev = pos; 
       
	
	if (mFabs(dPos.y) > dPosThresh)       
		mPosPrev = pos; 
       
	
	if (mFabs(dPos.z) > dPosThresh)         
		mPosPrev = pos;  
     
	
	if (mFabs(dRot.x) > dRotThresh)           
		mRotPrev = rot;   
   
	
	if (mFabs(dRot.y) > dRotThresh)     
		mRotPrev = rot; 
       
	
	return true;
}
#2
03/01/2006 (5:07 pm)
Hi Westy, thanks!

- I've since realized some problems w/ this resource, please read the new blurb at the top !

- I'm not sure i totally understand what you mean by "register movement in multiple direction/rotation"..

- You might want to restructure the logic in your code there to avoid all those duplicate mFabs()'s and >'s.
#3
03/08/2006 (2:30 am)
#4
03/08/2006 (9:08 am)
I haven't used vehicle players at all,
I have a suspicion that it might not be as significant w/ vehicles, since i presume they're almost always in motion.
#5
04/24/2006 (9:31 am)
is this needed for 1.4?
#6
04/24/2006 (9:57 am)
Hey Juha -
this is definitely in the "if you don't know whether or not you need this, you don't need it" category - torque's networking is basically pretty efficient.

that said, 1.4 does call updateMoveMask() every tick for player objects the same way 1.3 does.
#7
04/25/2006 (1:29 am)
This is for all those people who wants to create MMO games ;)

13 bytes from 100 000 connected users every tick (32ms)...
97Kb every tick, with ~31 ticks in a second...

That is 3mb per second the server is saving.

THen again... all 100 000 users need to stand still :-P
#8
04/28/2006 (4:16 am)
I did read all the comments and the resource again and I think I finally got it :P
This is indeed needed for my project (all things are that helps to save some bandwitdh)
#9
06/08/2006 (9:04 am)
hey all -
yesterday i realized that this resource has a kinda serious flaw, which is that when a player *stops* moving, that fact is not transmited prompty to the clients, who then over-shoot the client-side ghost position & rotation, and you end up with characters "popping" back when they stop moving.

i've got a fix, but i'm not at the right computer today so i can't post it just now.

basically,
in addition to testing the first derivative of position & rotation for being > some threshhold, you need to also test the second derivative.

not so terrible.

so, a new and substantially cleaned-up version of setMoveMaskIfReallyNeedTo() is coming soon.

sorry if this has muddled up anyone's networked play!
#10
07/24/2006 (4:24 pm)
Just curious if there's to be an update anytime soon.

Thanks!
#11
07/24/2006 (4:48 pm)
Hi! Sorry!

hokay, improvements to setMoveMaskIfReallyNeedTo(), as per above.

player.h
additions in bold:
Point3F mPosPrev;                ///< Used to determine if we should actually set MoveMask.
   Point3F mRotPrev;                ///< Used to determine if we should actually set MoveMask.[b]
   Point3F mdPosPrev;               ///< Second-order derivative. Also used for conditional moveMask.
   Point3F mdRotPrev;               ///< Second-order derivative. Also used for conditional moveMask.[/b]


player.cc
in the constructor, additions in bold.
mPosPrev .set(0,0,0);
   mRotPrev .set(0,0,0);[b]
   mdPosPrev.set(0,0,0);
   mdRotPrev.set(0,0,0);[/b]


and a new version of player::setMoveMaskIfReallyNeedTo():
bool Player::setMoveMaskIfReallyNeedTo(const Point3F& pos, const Point3F& rot)
{
   static U32 sMaskCountDown = 20;

   moveMaskForceCountdown--;

   Point3F  dPos;
   Point3F  dRot;
   Point3F ddPos;
   Point3F ddRot;

   dPos  =  pos -  mPosPrev;
   dRot  =  rot -  mRotPrev;

   if (moveMaskForceCountdown <= 0) {
      setMaskBits(MoveMask);
      moveMaskForceCountdown = sMaskCountDown;
   }
   else {
      F32 posThresh = 0.00001f;
      F32 rotThresh  = mDegToRad(0.1f);

      ddPos = dPos - mdPosPrev;
      ddRot = dRot - mdRotPrev;

      // note these are ordered from most to least likely -

      if ((mFabs(ddRot.z) > rotThresh || mFabs(dRot.z) > rotThresh) ||
          (mFabs(ddPos.x) > posThresh || mFabs(dPos.x) > posThresh) ||
          (mFabs(ddPos.y) > posThresh || mFabs(dPos.y) > posThresh) ||
          (mFabs(ddPos.z) > posThresh || mFabs(dPos.z) > posThresh) ||
          (mFabs(ddRot.x) > rotThresh || mFabs(dRot.x) > rotThresh)) {
         setMaskBits(MoveMask);
         moveMaskForceCountdown = sMaskCountDown;
      }
   }

   mPosPrev  =  pos;
   mRotPrev  =  rot;
   mdPosPrev = dPos;
   mdRotPrev = dRot;

   return false;
}

note there's some looseness with using the same threshhold for the first & second-order derivatives, but it seems to work well.

also this version of the routine is cleaner.

furthermore, i'm using a static counter, sMaskCountDown, which should probably be a per-instance member variable.

hopefully this works for folks and causes less grief than it saves bandwidth !