Input Responsiveness

Post tutorials on how to do certain tasks within game or engine code here.
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post by r00k »

Does the server send a reply back to the client if the client sends a msg to the server before the next frame? Like a "hangon we havent processed the last frame yet"?

I'm not seeing where it blocks me from sending too many packets based on any response from the server..

Using the above code, if I use sys_ticrate 0.025 for example i send FAR LESS unreliable packets to the server, but using sys_ticrate 0 (which will force the client to CL_SendCmd based on the 1/cl_maxfps) then the client sends a LOT more unreliable packets. I have play tested and the only time I feel input lag is if I set the sys_ticrate on my machine HIGHER than the sys_ticrate that the server is using.

Connecting to the server for only 18 seconds, at ticrate 0.05 i sent 347 unreliable packets. disconnect, cleat net_stats, sys_ticrate 0 connect again, 18 seconds, i send over 3000 packets at 208fps. The server is running at sys_ticrate 0.025.
Last edited by r00k on Tue Apr 26, 2011 11:30 am, edited 1 time in total.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

I've briefly tested this one too and it seems solid. It also nicely alleviates one of my major concerns about setting maxfps too high, which is flooding a server's connection.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Post by Spike »

its possible I was thinking of reliables...
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post by r00k »

you can send the same input data 500 times a second and the server will use only the most recent version.

This triggered my whole perspective. :D
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Post by Spike »

okay, this is bugging me now.
how does NQ do "impulse 8; wait; impulse 2" type stuff if its just spamming commands? :/
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post by r00k »

Are impulse commands sent as reliable?
Are unreliable commands cached until the server does a it's next frame?

The only thing im seeing is that, on the client end, _host_frame is being executed based on the cl_maxfps, which calls CL_SendCmds, which calls, CL_SendMove, which sends a MSG_WriteByte (buf, clc_move);
at the least.
But based on what's in sys_win.c

Code: Select all

	while (time < sys_ticrate.value)
			{				
				Sys_Sleep ();
				newtime = Sys_DoubleTime ();
				time = newtime - oldtime;
			}
waits until the ticrate has transpired before it executes Host_Frame,
which ends up executing SV_RunClients in Host_Frame, where it parses the &cmd from the clients (including impulse commands). So having the client call CL_SendCmds 200fps, while the server reads it 1/sys_ticrate, seems wasteful. I havent fully play tested it out to see if button-presses are getting lost. But logically, if you set your local sys_ticrate to equal (or lower) than the server's you should not lose sent cmds....


Is it possible that the redundancy in client->server messages is intentional to reinforce packet loss?
frag.machine
Posts: 2126
Joined: Sat Nov 25, 2006 1:49 pm

Post by frag.machine »

r00k wrote:Are impulse commands sent as reliable?
Are unreliable commands cached until the server does a it's next frame?
Dunno about the first question, but "unreliable" messages in Quake means the server won't make any effort to ensure they're received by the client, meaning that if it's not received at first or if there's not enough bandwidth to send it at all, it's discarded and gone for good (for example, a rocket explosion sound message can be safely dropped because it won't affect the gameplay).
I know FrikaC made a cgi-bin version of the quakec interpreter once and wrote part of his website in QuakeC :) (LordHavoc)
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Interesting observation.

CL_SendCmd does 2 things in NQ:
  1. Accumulate together input from the mouse, controllers and the keyboard, and
  2. Send this accumulated input to the server.
Throttling CL_SendCmd to the lower framerate does introduce some low-level jerkiness to mouselooking as the frequency of updates to cl.viewangles is based on it, and that is now quantized (to, say, 72 discrete steps). (cl.viewangles comes from the client, not the server, and the screen refresh always uses the current value of it).

To resolve this you should split the function in two, which I've called (for want of anything better) CL_AccumulateCmd and CL_SendCmd.

CL_AccumulateCmd now looks after (you guessed it) accumulating input from meeces/etc. If we're not connected we clear the usercmd_t, otherwise we add current movement values to the usercmd_t. This runs every frame, irrespective of whether or not Host_FilterTime says we're running a frame. It needs a frametime param as several functions it calls depend on the frametime, so pass it the same time value that you pass to Host_Frame, and modify the functions it calls to take this time value as a param and use it instead of using host_frametime. (The right thing to do is nuke every use of host_frametime, mod all the functions that use it to take a frametime param, and make them independent of each other, but that's way outside the scope of this discussion).

CL_SendCmd does the actual send to the server. If we're not connected we just clear the usercmd_t, otherwise we send it and then clear it. This one can run at the throttled rate, and it sends the accumulated commands in the same usercmd_t.

If you've got host_timescale and/or host_framerate (the former is preferred) you can test this and compare the difference between the two. Dial your framerate down to 10% of what it was, and spin around doing some mouselooking.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post by r00k »

Ya in the last month after some testing, i did notice certain "input lag" while online, that I blamed the CL_SendCmd throttle on :P

I had tested this out

Code: Select all

	// decide the simulation time
	if (!Host_FilterTime(time))
	{
		if (!sv.active && (cl.movemessages > 2))
		{
			usercmd_t	cmd;
			
			if (cls.state != ca_connected)
				return;
			if (cls.signon == SIGNONS)
			{		
				CL_BaseMove (&cmd);		
				IN_Move (&cmd);
			}
		}
		return;			// don't run too fast, or packets will flood out
	}	
But figured all I'm doing is updating the usercmd_t 100 times a second, though when the server says "okay show your cards", it reads the mouse AGAIN in CL_SENDCMD thus overwriting the update.
Unless somewhere there's a buffer to interpolate all this input... (future expansion for prediction?).
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

It looks a little like this:

Code: Select all

void CL_ClearCmd (usercmd_t *cmd)
{
	cmd->forwardmove = 0;
	cmd->sidemove = 0;
	cmd->upmove = 0;
}


void CL_BaseMove (usercmd_t *cmd, double frametime)
{
	if (cls.signon != SIGNONS)
		return;

	CL_AdjustAngles (frametime);

	// just accumulating to cmd causes differences in acceleration so instead we accumulate to 
	// a new usercmd_t, adjust that for frametime, then add the adjusted move to the original move
	usercmd_t basecmd;

	CL_ClearCmd (&basecmd);

	// fixme - adjust these speeds for frametime? (done)
	if (in_strafe.state & 1)
	{
		basecmd.sidemove += cl_sidespeed.value * CL_KeyState (&in_right);
		basecmd.sidemove -= cl_sidespeed.value * CL_KeyState (&in_left);
	}

	basecmd.sidemove += cl_sidespeed.value * CL_KeyState (&in_moveright);
	basecmd.sidemove -= cl_sidespeed.value * CL_KeyState (&in_moveleft);

	basecmd.upmove += cl_upspeed.value * CL_KeyState (&in_up);
	basecmd.upmove -= cl_upspeed.value * CL_KeyState (&in_down);

	if (!(in_klook.state & 1))
	{
		basecmd.forwardmove += cl_forwardspeed.value * CL_KeyState (&in_forward);
		basecmd.forwardmove -= cl_backspeed.value * CL_KeyState (&in_back);
	}

	// adjust for speed key
	if (in_speed.state & 1)
	{
		basecmd.forwardmove *= cl_movespeedkey.value;
		basecmd.sidemove *= cl_movespeedkey.value;
		basecmd.upmove *= cl_movespeedkey.value;
	}

	// rebalance movement to 72 FPS (fixme - make this user-configurable)
	float moveadjust = frametime * 72.0;

	cmd->forwardmove += basecmd.forwardmove * moveadjust;
	cmd->sidemove += basecmd.sidemove * moveadjust;
	cmd->upmove += basecmd.upmove * moveadjust;
}


usercmd_t cl_usercommand;


void CL_AccumulateCmd (float frametime)
{
	if (cls.state != ca_connected)
	{
		CL_ClearCmd (&cl_usercommand);
		return;
	}

	if (cls.signon == SIGNONS)
	{
		// get basic movement from keyboard
		CL_BaseMove (&cl_usercommand, frametime);

		// allow mice or other external controllers to add to the move
		IN_MouseMove (&cl_usercommand, frametime);
		IN_JoyMove (&cl_usercommand, frametime);

		return;
	}

	CL_ClearCmd (&cl_usercommand);
}


void CL_SendCmd (void)
{
	if (cls.state != ca_connected)
	{
		CL_ClearCmd (&cl_usercommand);
		return;
	}

	if (cls.signon == SIGNONS)
	{
		// send the unreliable message
		CL_SendMove (&cl_usercommand);
	}

	// clear the command after sending (or if we're not sending)
	CL_ClearCmd (&cl_usercommand);

	if (cls.demoplayback)
	{
		SZ_Clear (&cls.message);
		return;
	}

	// check any commands which were queued by the client
	// done before the message size check so that it won't prevent it from happening
	CL_CheckQueuedCommands ();

	// send the reliable message
	if (!cls.message.cursize)
		return;		// no message at all

	if (!NET_CanSendMessage (cls.netcon))
	{
		Con_DPrintf ("CL_WriteToServer: can't send\n");
		return;
	}

	if (NET_SendMessage (cls.netcon, &cls.message) == -1)
		Host_Error ("CL_WriteToServer: lost server connection");

	SZ_Clear (&cls.message);
}


void IN_MouseMove (usercmd_t *cmd, float movetime)
{
	if (ActiveApp && !Minimized)
	{
		// if the mouse isn't active or there was no movement accumulated we don't run this
		if (!in_mouseacquired) return;

		float mx = in_mousestate.currpos.x * sensitivity.value;
		float my = in_mousestate.currpos.y * sensitivity.value;

		usercmd_t basecmd;
		float moveadjust = movetime * 72.0f;

		CL_ClearCmd (&basecmd);

		if (in_mousestate.currpos.x || in_mousestate.currpos.y)
		{
			mouselooking = freelook.integer || (in_mlook.state & 1);

			if ((in_strafe.state & 1) || (lookstrafe.value && mouselooking))
				basecmd.sidemove += m_side.value * mx;
			else cl.viewangles[YAW] -= m_yaw.value * mx;

			if (mouselooking && !(in_strafe.state & 1))
			{
				cl.viewangles[PITCH] += m_pitch.value * my;
				CL_BoundViewPitch (cl.viewangles);
			}
			else
			{
				if ((in_strafe.state & 1) && noclip_anglehack)
					basecmd.upmove -= m_forward.value * my;
				else basecmd.forwardmove -= m_forward.value * my;
			}
		}

		cmd->forwardmove += basecmd.forwardmove * moveadjust;
		cmd->sidemove += basecmd.sidemove * moveadjust;
		cmd->upmove += basecmd.upmove * moveadjust;

		// reset the accumulated positions
		in_mousestate.currpos.x = 0;
		in_mousestate.currpos.y = 0;
	}
}


void Host_Frame (double time)
{
	static double time1 = 0;
	static double time2 = 0;
	static double time3 = 0;
	int pass1, pass2, pass3;

	// something bad happened, or the server disconnected
	if (setjmp (host_abortserver)) return;

	// keep the random time dependent
	// (unless we're in a demo - see srand call in cl_demo - in which case we keep random effects consistent across playbacks)
	if (!cls.demoplayback) rand ();

	// always accumulate commands even if we're not running a frame
	CL_AccumulateCmd (time);

	// decide if we're going to run a frame
	if (!Host_FilterTime (time)) return;

	Cbuf_Execute ();
	...
	...
	...
I've included IN_MouseMove just to demonstrate that you also need to rebalance the movement there too (and for the Joystick too, but I've left that code out to keep things short).
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Post Reply