Input Responsiveness

Post tutorials on how to do certain tasks within game or engine code here.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Input Responsiveness

Post by mh »

Traditionally in Quake clients to get better input responsiveness you'd increase the value of your maxfps cvar to something that works well for you. This is all fine in multiplayer games, but in single player it causes havoc as physics now also run at the increased framerate, which creates all kinds of weird glitches.

There is however a quick and dirty way to get better input responsiveness without having to increase your maxfps cvar.

Just open your host.c file, find the _Host_Frame function, and find these lines:

Code: Select all

// decide the simulation time
	if (!Host_FilterTime (time))
		return;			// don't run too fast, or packets will flood out
		
// get new key events
	Sys_SendKeyEvents ();

// allow mice or other external controllers to add commands
	IN_Commands ();
Now change them to this:

Code: Select all

// get new key events
	Sys_SendKeyEvents ();

// allow mice or other external controllers to add commands
	IN_Commands ();

// decide the simulation time
	if (!Host_FilterTime (time))
		return;			// don't run too fast, or packets will flood out
So what's happening here is that we're recording input events as they actually happen rather than every time Quake runs a frame, so Quake's frame timer is no longer out of step with your mouse, keyboard and/or joystick polling rates.
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 »

Any risk of buffer overflow as im holding +forward down 90% of the time?

Wait +forward gets handled in CL_SendCmd, hmmso does the mouse, and the keyboard input :(

Ugh, now i've made a mess ...

edit:
well i tried to no avail, but i did notice something odd, with proquake's synthetic lag...

Code: Select all

	// decide the simulation time
	if (!Host_FilterTime(time))
	{
		if (!sv.active && (cl.movemessages > 2))
		{
			if (pq_lag.value)//R00k if not +ping then dont send the lagged move!
				CL_SendLagMove();// JPG - if we're not doing a frame, still check for lagged moves to send
		}
		return;			// don't run too fast, or packets will flood out
	}
On short frames ProQuake still sends out unreliable messages when filling up the lag buffer, even when you arent pinged up which is 99.9999% of the time. Hmm :|
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

I'm interested in anything that reduces how physics, fps and input are synchronized in NQ. Eager to hear back player response on this kind of feature.
r00k wrote:On short frames ProQuake still sends out unreliable messages when filling up the lag buffer, even when you arent pinged up which is 99.9999% of the time. Hmm :|
Interesting. That doesn't sound efficient. But maybe it isn't bad? Those messages are mighty small aren't they? Still, sounds like a crapton of them would be going out unnecessarily.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Mouse and keyboard events just record movement or state in a small collection of variables rather than filling up buffers (there are no buffers involved) so it's safe. One point however I didn't mention is that this won't work with DirectInput which fills the usercmd_t for sending to the server at the time that client input is sent to the server. This part obviously needs to still be framerate-throttled.

For DirectInput it's probably preferable to move the buffer reading from IN_MouseMove to IN_Commands (the same way as the joystick is done in other words).

This will get you the mouse wheel in DirectInput by the way, in case anyone doesn't have it yet:

Code: Select all

case DIMOFS_Z:
	// because od.dwData is a DWORD we need to cast to int to get negative values
	if ((int) od.dwData > 0)
	{
		Key_Event (K_MWHEELUP, true);
		Key_Event (K_MWHEELUP, false);
	}
	else if ((int) od.dwData < 0)
	{
		Key_Event (K_MWHEELDOWN, true);
		Key_Event (K_MWHEELDOWN, false);
	}
	break;
I've always been wary of that PQ synthetic lag thing too. It may be only small buffers, but net communication is subject to the same rules as memory allocation, GPUs and hard disks: lots of small events are orders of magnitude slower than a few large events, even if the total size is the same. The fact that it's never totally clear what JPG was thinking or why he was doing certain things is not good either (there's a good tip about how to write comments correctly in there).
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
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

mh wrote:I've always been wary of that PQ synthetic lag thing too. It may be only small buffers, but net communication is subject to the same rules as memory allocation, GPUs and hard disks: lots of small events are orders of magnitude slower than a few large events, even if the total size is the same. The fact that it's never totally clear what JPG was thinking or why he was doing certain things is not good either (there's a good tip about how to write comments correctly in there).
JPG got a PHD at MIT. I'm glad he made the admittedly very "creative" improvements to ProQuake in the short period of time he had to do so. Many are very unelegant, hard to maintain, but they were all backwards compatible. Still, I'd like to remove all the hacks ... but that would be a network protocol change.

And eventually I believe that will happen for a number of reasons.

1. Smaller demo files (sheesh!)
2. Server side multiview recording
3. True compressed downloading
4. Some sort of DP7 like predictive capability where needed.

5. 64 player support .... now why? Well ... I want multiplayer to have a mega hub server where groups of players can walk to, say, the CTF bar and press a button when the group is assembled and ready to play and kabang! --- those clients are forwarded to an appropriate CTF server.

But the one I really want ... peer to peer coop and a supporting backend to support that.

Maybe my goals for 2nd half 2012 ;)

I believe open source leads to unlimited community building and expression and invention ... and my thoughts are that if you just build enough infrastructure and user-friendliness you can cause a chain reaction. :D

I mean if you look at all the cumulative works the greater Q1 community has created ... it is mindblowing. FitzQuake, DP, FTE, your stuff, QER, Quark, Quake Reforged, Quaddicted, Quake Injector, QuakeC tools, mapping tools ... hordes of engine and QuakeC dev and texture projects and on and on ...
Last edited by Baker on Wed Mar 23, 2011 1:27 pm, edited 1 time in total.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Post by Spike »

NQ makes no real difference in regard to times things are sent to the server. you can send the same input data 500 times a second and the server will use only the most recent version.

synthetic lag suffers from granularity, dedicated QW servers still fail in this regard also (due to too high select timeouts).
You can fix that clientside using threads I suppose, but if you're not on a dual core cpu, it can be even worse (unless you ensure its done periodically in the main thread too, in which case it can only help).

client-side synthetic lag can be seen as a cheat, especially if it allows the user to change it mid-game (when noones looking at the pings on the scoreboard).
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Spike wrote:NQ makes no real difference in regard to times things are sent to the server. you can send the same input data 500 times a second and the server will use only the most recent version.
Filed away in brain.
client-side synthetic lag can be seen as a cheat, especially if it allows the user to change it mid-game (when noones looking at the pings on the scoreboard).
Well, the truth is at least in NQ that pinging up really does not have the same effect that you would think.

You don't get lagged input from the server ... if you have real 25 ping, you are seeing the world faster than the guy with 150 ping. Delaying the client output to the server does not really level the playing field.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Baker wrote:
Spike wrote:NQ makes no real difference in regard to times things are sent to the server. you can send the same input data 500 times a second and the server will use only the most recent version.
Filed away in brain.
Quite true.

The point though is that with input there are two different steps to consider. (1) when the data is read from the input device, and (2) when the data is sent to the server. Step (2) is what Spike is talking about here, but step (1) is what I was talking about in my original post, and is important and overlooked.

Consider mouse input. Consider a mouse that samples 40 times per second. Consider a Quake client that runs at 72 FPS.

Your mouse sampling points are going to be at 25ms, 50ms, 75ms, 100ms, etc. Your frame runs are going to be at 13.88ms, 27.77ms, 41.66ms, etc.

So your mouse sampling is out of sync with your engine frames. The first frame run you're not even going to have a sample, on the second you'll have one (which will be very recent), the third you'll have the same sample as the second, and so on. Basically the input data that gets sent to the server is not representative of the actual client state at the time it's sent.

Over the course of one eighth of a second it all averages out, but on a smaller scale it's jerky as hell.

So the theory is that instead of recording events on the same time scale as a frame in the engine, you record them as they happen instead and that jerkiness just disappears.
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 »

mh wrote:So the theory is that instead of recording events on the same time scale as a frame in the engine, you record them as they happen instead and that jerkiness just disappears.
Window messages are synced only with calls to GetMessage. This means that rawinput and regular windows events cannot be updated more often than the app calls GetMessage, so cannot be updated more than once per frame anyway. The mouse cursor or DirectInput normally use explicit polling to update the movement.
Thus regardless of what you do with the data once you've received it, the limiting factor is the frequency of polling.

You're still better off using a separate thread on a dual-core machine to collect+send input data. And if you can somehow gauge the time when the server will next process your packet, you could potentially get another sample in before it would even process the data.

Regarding JPG's lagged moves, CL_SendLagMove probably sources movements from a ringbuffer, thus while it checks for pending movements each time its called between frames, it'll only actually send each movement packet no more than once, and hopefully it'll generate those movement packets at the normal place, just adding them to the ring instead of sending.
Presumably.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Baker wrote:JPG got a PHD at MIT. I'm glad he made the admittedly very "creative" improvements to ProQuake in the short period of time he had to do so. Many are very unelegant, hard to maintain, but they were all backwards compatible. Still, I'd like to remove all the hacks ... but that would be a network protocol change.
My primary problem with the original PQ code is neatly summarised here: http://mhquake.blogspot.com/2011/03/dir ... art-2.html

I don't mind hacks as long as they can be figured out by someone else (or even by the person who originally wrote them 6 months further down the line). This kind of stuff drives me batshit however.
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
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

mh wrote:For DirectInput it's probably preferable to move the buffer reading from IN_MouseMove to IN_Commands (the same way as the joystick is done in other words).
I did notice that the input scheme is quite inconsistent. If you aren't playing a game, there is no pathway to capture mousewheel events (either at all or with DirectInput -- I can't remember at the moment which was the case) ... so you can't scroll the console which is quite stupid.

[I added a check for the mousewheel even if the client was not playing. Although that isn't quite the right way to do it. I probably did it that way due to a lack of time, instead of going all-in and addressing the actual problem.]
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Hmmm; I forgot about scrolling the console. That's one I need to fix myself.

I'm actually slightly weird in that I love working on the input code. There's just something about it that feels good to me.
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 »

I was just thinking about the wheelscroll in console the other day.
I was working on some code and used the wheel to scroll, later i was trying the same in the console, immediately thinking ack wtf, oh duh!


I'm testing this atm... as doing what MH says in the 1st post ignores all input and im stuck at the main menu having to alt-tab to exit...

Code: Select all

	// decide the simulation time
	if (!Host_FilterTime(time))
	{
		if (!sv.active && (cl.movemessages > 2))
		{
			//R00k testing, read client inputs as they happen, process when server wants them...
			if ((realtime - oldrealtime) > (1 / 500))
			{		
				usercmd_t cmd;

				// get new key events
				Sys_SendKeyEvents ();
				// allow joystick or other external controllers to add commands
				IN_Commands ();
				CL_BaseMove (&cmd);		
				// allow mice or other external controllers to add to the move
				IN_Move (&cmd);
			}

			if (pq_lag.value)//R00k if not +ping then dont send the lagged move!
				CL_SendLagMove();// JPG - if we're not doing a frame, still check for lagged moves to send
		}
		return;			// don't run too fast, or packets will flood out
	}
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post by r00k »

After some thought...

Most modern quake clients since after 2001 have added some sort of
maxfps cvar, ie cl_maxfps. As for basic Quake (not quakeworld etc), the host_filtertime was limiting the server frames to 72fps.

Code: Select all

	if (!cls.timedemo && realtime - oldrealtime < 1.0/72.0)
		return false;		// framerate is too high
Replacing the 72 with a maxfps cvar passed a TRUE back to host_frame which resulted in drawing the screen updates at the higher FPS.
All GREAT, sort of...
One problem though, is that as input is read from the client, it's then spammed to the server unnecessarily, as the server only reads the clients' messages based on the ticrate, usually once every 20-40 frames per second. So, if cl_maxfps is set to 200, the screen is being redrawn at 200fps, client input is also being read locally at 200fps, client is sending out cmd packets to the server at 200fps, but the server is only reading at 20fps. And since the server controls the advancement of the frames, logically nothing is changing while the client is drawing 10x more frames.
I added this lil bit of code, which does the opposite of what ezQuake/ZQuake's cl_independant_physics does.

Code: Select all

	// if running the server remotely, send intentions now after
	// the incoming messages have been read
	if (!sv.active)
	{		
		if (realtime > cmdframetime)//R00k: 99.9% of Quake servers dont run with a ticrate lower than 0.0138 anyways.
		{
			CL_SendCmd ();//R00k
			cmdframetime = realtime + (sys_ticrate.value);
		}
		else
		{
			usercmd_t cmd;
			CL_BaseMove (&cmd);	// Add Keyboard cmds
			IN_Move (&cmd);		// allow mice or other external controllers to add to the move
		}
	}
as cl_independant_physics in QW is what cl_maxfps in netQuake already does. This above code will still gather the most recent client input (based on cl_maxfps), but only send out packets based on the client's sys_ticrate. Saving network bandwidth.
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Post by Spike »

except that an NQ client sends packets only in response to a server packet. If the server stops sending packets for any reason, the client also stops sending packets.
So that code just means it won't send out replies as soon as it can, but will add an extra delay.
Post Reply