logo
Main

Forums

Downloads

Unreal-Netiquette

Donate for Oldunreal:
Donate

borderline

Links to our wiki:
Wiki

Walkthrough

Links

Tutorials

Unreal Reference

Usermaps

borderline

Contact us:
Submit News
Page Index Toggle Pages: 1 Send TopicPrint
Normal Topic Any ideas why ClientUpdatePosition is called from PlayerTick? (Read 206 times)
Masterkent
Developer Team
Offline



Posts: 1212
Location: Russia
Joined: Apr 5th, 2013
Gender: Male
Any ideas why ClientUpdatePosition is called from PlayerTick?
Jan 5th, 2019 at 3:44pm
Print Post  
Synchronization of client-side PlayerPawn with the corresponding server-side PlayerPawn is generally done in two steps:

1) ClientAdjustPosition sets PlayerPawn's Location, Velocity, Physics, Base, and state according to the values received from the server:

Code
Select All
function ClientAdjustPosition
(
	float TimeStamp,
	name newState,
	EPhysics newPhysics,
	float NewLocX,
	float NewLocY,
	float NewLocZ,
	float NewVelX,
	float NewVelY,
	float NewVelZ,
	Actor NewBase
)
{
	local Decoration Carried;
	local vector OldLoc, NewLocation;

	if ( CurrentTimeStamp > TimeStamp )
		return;
	CurrentTimeStamp = TimeStamp;

	NewLocation.X = NewLocX;
	NewLocation.Y = NewLocY;
	NewLocation.Z = NewLocZ;
	Velocity.X = NewVelX;
	Velocity.Y = NewVelY;
	Velocity.Z = NewVelZ;

	SetBase(NewBase);
	if ( Mover(NewBase) != None )
		NewLocation += NewBase.Location;

	//log("Client "$Role$" adjust "$self$" stamp "$TimeStamp$" location "$Location$" new location "$NewLocation);
	Carried = CarriedDecoration;
	OldLoc = Location;
	SetLocation(NewLocation);

	if ( Carried != None )
	{
		CarriedDecoration = Carried;
		CarriedDecoration.SetLocation(NewLocation + CarriedDecoration.Location - OldLoc);
		CarriedDecoration.SetPhysics(PHYS_None);
		CarriedDecoration.SetBase(self);
	}
	SetPhysics(newPhysics);
	//FIXME - don't do this state update if client dead???
	ClientAdjustState(newState);

	bUpdatePosition = true;
} 


2) Then PlayerTick calls ClientUpdatePosition to apply all relevant saved moves:

Code
Select All
	event PlayerTick( float DeltaTime )
	{
		if ( bUpdatePosition )
			ClientUpdatePosition();

		PlayerMove(DeltaTime);
	} 


Code
Select All
function ClientUpdatePosition()
{
	local SavedMove CurrentMove;
	local int realbRun, realbDuck;
	local bool bRealJump;

	realbRun = bRun;
	realbDuck = bDuck;
	bRealJump = bPressedJump;
	CurrentMove = SavedMoves;
	while ( CurrentMove != None )
	{
		if ( CurrentMove.TimeStamp <= CurrentTimeStamp )
		{
			SavedMoves = CurrentMove.NextMove;
			CurrentMove.NextMove = FreeMoves;
			FreeMoves = CurrentMove;
			FreeMoves.Clear();
			CurrentMove = SavedMoves;
		}
		else
		{
			if (CurrentMove.ExtraInfoTimeStamp == CurrentMove.TimeStamp) // making sure that the extra info is actual
			{
				SetRotation(CurrentMove.Rotation);
				PlayerDodgeAutonomous(CurrentMove);
			}
			MoveAutonomous(CurrentMove.Delta, CurrentMove.bRun, CurrentMove.bDuck, CurrentMove.bPressedJump, CurrentMove.DodgeMove, CurrentMove.Acceleration, rot(0,0,0));
			CurrentMove = CurrentMove.NextMove;
		}
	}
	bDuck = realbDuck;
	bRun = realbRun;
	bPressedJump = bRealJump;
	bUpdatePosition = false;
	//log("Client adjusted "$self$" stamp "$CurrentTimeStamp$" location "$Location$" dodge "$DodgeDir);
} 


ClientUpdatePosition is called a tick later since the last call to ClientAdjustPosition. Probably, it was implemented this way on purpose, but I can't figure out why. I don't see any theoretical reasons to do so. Any ideas why ClientAdjustPosition shouldn't call ClientUpdatePosition directly without delays?

I tried to insert a call to ClientUpdatePosition in the end of ClientAdjustPosition and couldn't notice any issues with this approach so far. This change is needed to fix some bug related to crouching: PlayerWalking.AnimEnd doesn't do its work well when it's called between ClientAdjustPosition and ClientUpdatePosition, because the player's Physics has outdated value at that moment.
  
Back to top
 
IP Logged
 
.:..:
Board Moderator
Developer Team
*****
Offline



Posts: 1451
Location: Finland
Joined: Aug 16th, 2005
Gender: Male
Re: Any ideas why ClientUpdatePosition is called from PlayerTick?
Reply #1 - Jan 5th, 2019 at 6:13pm
Print Post  
No idea, I couldn't find a reason for that.
Only possible idea is that network events are called before Level.TimeSeconds is updated to current frame time.
  

Shivaxi wrote on Jul 25th, 2013 at 12:50pm:
...and now im stuck trying to fix everything you broke for the next 227 release xD Tongue

(ಠ_ಠ)
Back to top
ICQYIM  
IP Logged
 
Masterkent
Developer Team
Offline



Posts: 1212
Location: Russia
Joined: Apr 5th, 2013
Gender: Male
Re: Any ideas why ClientUpdatePosition is called from PlayerTick?
Reply #2 - Jan 5th, 2019 at 6:53pm
Print Post  
.:..: wrote on Jan 5th, 2019 at 6:13pm:
Only possible idea is that network events are called before Level.TimeSeconds is updated to current frame time.

ClientUpdatePosition does not use network events nor Level.TimeSeconds (at least, in UScript code). It applies accumulated moves stored in the chain of SavedMove actors starting from the initial timestamp. The initial timestamp is taken from parameter TimeStamp of function ClientAdjustPosition (its value is stored in CurrentTimeStamp).

The only idea that comes to mind is that AutonomousPhysics might not work well when executed inside a replicated function call due to its internal limitations, but my tests don't reveal any real issues with such a usage.
  
Back to top
 
IP Logged
 
han
Global Moderator
Unreal Rendering Guru
Developer Team
*****
Offline


Oldunreal member

Posts: 553
Location: Germany
Joined: Dec 10th, 2014
Gender: Male
Re: Any ideas why ClientUpdatePosition is called from PlayerTick?
Reply #3 - Jan 6th, 2019 at 12:48pm
Print Post  
Quote:
ClientUpdatePosition is called a tick later since the last call to ClientAdjustPosition.


Technically input is handled at the end of each Tick, and immediatly at the start of the new Tick network and Actors are ticked.

So ClientUpdatePosition should be called the very same Tick after all RPCs are received. Or is there something is miss?

  

HX on Mod DB. Revision on Steam.
Back to top
 
IP Logged
 
Masterkent
Developer Team
Offline



Posts: 1212
Location: Russia
Joined: Apr 5th, 2013
Gender: Male
Re: Any ideas why ClientUpdatePosition is called from PlayerTick?
Reply #4 - Jan 6th, 2019 at 3:38pm
Print Post  
han wrote on Jan 6th, 2019 at 12:48pm:
So ClientUpdatePosition should be called the very same Tick after all RPCs are received.

According to my logs, Level.TimeSeconds has different values in ClientAdjustPosition and ClientUpdatePosition, so I presumed that these functions are called in two different ticks. Maybe my guess is wrong and the given functions are technically called in the same tick, but ClientAdjustPosition is called before updating Level.TimeSeconds and ClientUpdatePosition is called after updating Level.TimeSeconds.

What really matters here is that ClientAdjustPosition and ClientUpdatePosition do not constitute a single transaction; there is at least one event (AnimEnd) that can be called between these functions, albeit it's not a part of the synchronization process.

Code
Select All
	function AnimEnd()
	{
		local name MyAnimGroup;

		bAnimTransition = false;
		if ( Physics == PHYS_Spider )
		{
			if ( VSize(Velocity)<10 )
				PlayDuck();
			else PlayCrawling();
		}
		else if (Physics == PHYS_Walking)
		{
			if (bIsCrouching)
			{
				if ( !bIsTurning && ((Velocity.X * Velocity.X + Velocity.Y * Velocity.Y) < 1000) )
					PlayDuck();
				else
					PlayCrawling();
			}
			else
			{
				MyAnimGroup = GetAnimGroup(AnimSequence);
				if ((Velocity.X * Velocity.X + Velocity.Y * Velocity.Y) < 1000)
				{
					if ( MyAnimGroup == 'Waiting' )
						PlayWaiting();
					else
					{
						bAnimTransition = true;
						TweenToWaiting(0.2);
					}
				}
				else if (bIsWalking)
				{
					if ( (MyAnimGroup == 'Waiting') || (MyAnimGroup == 'Landing') || (MyAnimGroup == 'Gesture') || (MyAnimGroup == 'TakeHit')  )
					{
						TweenToWalking(0.1);
						bAnimTransition = true;
					}
					else
						PlayWalking();
				}
				else
				{
					if ( (MyAnimGroup == 'Waiting') || (MyAnimGroup == 'Landing') || (MyAnimGroup == 'Gesture') || (MyAnimGroup == 'TakeHit')  )
					{
						bAnimTransition = true;
						TweenToRunning(0.1);
					}
					else
						PlayRunning();
				}
			}
		}
		else if (bIsReducedCrouch)
			PlayDuck();
		else
			PlayInAir();
	} 


Right after returning from ClientAdjustPosition, the value of Physics corresponds to some situation in the past, but ClientUpdatePosition cannot properly reproduce all animations since CurrentTimeStamp (the initial timestamp for synchronization in the past) up to the current moment, because AutonomousPhysics doesn't update animations. Any call to PlayAnim/LoopAnim within ClientUpdatePosition will run an animation only after returning from ClientUpdatePosition. Consequently, such calls generally lead to badly looking interrupted/doubled animations:
https://www.youtube.com/watch?v=H4k1UV-8_I0

The doubled landing and the doubled standing up showed in this video happen due to calls to ClientUpdatePosition which tries to replay animations as a part of the synchronization process. The first person view is also affected, because some animation-related functions alter BaseEyeHeight which determines the target camera height.

I see only one viable way to reduce the appearance of such glitches to the minimum: calls to PlayDuck, PlayLanded, TweenToWaiting, etc should be allowed only when bUpdatePosition is false or when such calls take place inside BeginState of any PlayerPawn's state.

Although playing animations in BeginState leads to similar glitches when the state is first entered due to normal client-side evaluations and then reentered by means of ClientAdjustPosition, I don't see any way to set proper player animations (relevant for the given state) in case if the state was entered only by means of ClientAdjustPosition. So, here I have to choose between the two evils: doubled animations or mismatching between the animation and the new state.

After adding the restrictions for PlayDuck, PlayLanded, TweenToWaiting, etc as described above, I still have an issue with AnimEnd operating on outdated Physics; this is why I want to perform the synchronization ClientAdjustPosition + ClientUpdatePosition as a single transaction.
  
Back to top
 
IP Logged
 
Masterkent
Developer Team
Offline



Posts: 1212
Location: Russia
Joined: Apr 5th, 2013
Gender: Male
Re: Any ideas why ClientUpdatePosition is called from PlayerTick?
Reply #5 - Jan 8th, 2019 at 1:09pm
Print Post  
I've got an idea: maybe devs from Epic wanted to optimize evaluations in case if ClientAdjustPosition is executed two or more times during the same tick. Then ClientUpdatePosition is applied only to the most recent set of player properties after all calls to ClientAdjustPosition.

Calling ClientUpdatePosition directly from ClientAdjustPosition implies that all changes made by ClientUpdatePosition may be immediately discarded by a subsequent evaluation of ClientAdjustPosition (hence ClientUpdatePosition may waste CPU resources).

On the other hand, on modern machines, ClientUpdatePosition is evaluated really fast: my laptop handles 120 saved moves in about 1 - 2 ms (tested with frame rate of ~400 FPS, time intervals were measured with AppSeconds), so a possible overhead could become noticeable only in case of a big number of calls to ClientUpdatePosition or receiving ClientAdjustPosition with too big delays. However, such things don't happen unless there are huge lags which probably would cause much more troubles than the overhead caused by redundant evaluations of ClientUpdatePosition.

In order to reduce possible overhead, we can also call ClientUpdatePosition from ClientAdjustPosition only when the time interval between sending movement info to the server (by calling ServerMove) and handling the corresponding response from the server in ClientAdjustPosition is less than 0.5 seconds:

Code
Select All
function ClientAdjustPosition
(
	float TimeStamp,
	name newState,
	EPhysics newPhysics,
	float NewLocX,
	float NewLocY,
	float NewLocZ,
	float NewVelX,
	float NewVelY,
	float NewVelZ,
	Actor NewBase
)
{
	local Decoration Carried;
	local vector OldLoc, NewLocation;
	local SavedMove FirstMove;

	if ( CurrentTimeStamp > TimeStamp )
		return;
	CurrentTimeStamp = TimeStamp;

	FirstMove = GetFirstSavedMoveAfter(TimeStamp);
	ClientAdjustMovement(FirstMove);

	NewLocation.X = NewLocX;
	NewLocation.Y = NewLocY;
	NewLocation.Z = NewLocZ;
	Velocity.X = NewVelX;
	Velocity.Y = NewVelY;
	Velocity.Z = NewVelZ;

	SetBase(NewBase);
	if ( Mover(NewBase) != None )
		NewLocation += NewBase.Location;

	//log("Client "$Role$" adjust "$self$" stamp "$TimeStamp$" location "$Location$" new location "$NewLocation);
	Carried = CarriedDecoration;
	OldLoc = Location;
	SetLocation(NewLocation);

	if ( Carried != None )
	{
		CarriedDecoration = Carried;
		CarriedDecoration.SetLocation(NewLocation + CarriedDecoration.Location - OldLoc);
		CarriedDecoration.SetPhysics(PHYS_None);
		CarriedDecoration.SetBase(self);
	}
	SetPhysics(newPhysics);
	//FIXME - don't do this state update if client dead???
	ClientAdjustState(newState, FirstMove);

	bUpdatePosition = true;
-	ClientUpdatePosition();
+	if (Level.TimeSeconds - TimeStamp < 0.5 * Level.TimeDilation)
+		ClientUpdatePosition();
} 


If (Level.TimeSeconds - TimeStamp < 0.5 * Level.TimeDilation) is false, then ClientUpdatePosition is called either in PlayerTick (if there are no subsequent evaluations of ClientAdjustPosition where (Level.TimeSeconds - TimeStamp < 0.5 * Level.TimeDilation) is true) or in a further evaluation of ClientAdjustPosition (where such a condition holds).

I think, this should be a good compromise between correctness and performance.
  
Back to top
 
IP Logged
 
.:..:
Board Moderator
Developer Team
*****
Offline



Posts: 1451
Location: Finland
Joined: Aug 16th, 2005
Gender: Male
Re: Any ideas why ClientUpdatePosition is called from PlayerTick?
Reply #6 - Jan 8th, 2019 at 9:31pm
Print Post  
Masterkent wrote on Jan 6th, 2019 at 3:38pm:
According to my logs, Level.TimeSeconds has different values in ClientAdjustPosition and ClientUpdatePosition, so I presumed that these functions are called in two different ticks. Maybe my guess is wrong and the given functions are technically called in the same tick, but ClientAdjustPosition is called before updating Level.TimeSeconds and ClientUpdatePosition is called after updating Level.TimeSeconds.

Actually what I meant is, engine processes input network packets on start of the engine tick even before Level.TimeSeconds is being updated.
After that all actors get their Tick events called, then audio gets updated and last frame is being rendered.
  

Shivaxi wrote on Jul 25th, 2013 at 12:50pm:
...and now im stuck trying to fix everything you broke for the next 227 release xD Tongue

(ಠ_ಠ)
Back to top
ICQYIM  
IP Logged
 
Page Index Toggle Pages: 1
Send TopicPrint
Bookmarks: del.icio.us Digg Facebook Google Google+ Linked in reddit StumbleUpon Twitter Yahoo