Allegro.cc - Online Community

Allegro.cc Forums » Allegro Development » Possible Solution to Input Thread Stalls

This thread is locked; no one can reply to it. rss feed Print
Possible Solution to Input Thread Stalls
Kris Asick
Member #1,424
July 2001

OS: Windows 98SE
Compiler: MSVC++ 6.0
Allegro Versions Affected: 4.1.0 thru 4.2.1

Awhile back I found that on my computer, and possibly on other computers, starting with Allegro 4.1.0, input would randomly stall for split seconds, causing keyboard keys to stay down, or up, mouse cursors to stop moving, joystick axises to stay at their current values, and then once the split second is up, everything resumes as if nothing went wrong.

During this stall, an Allegro program still runs perfectly. Frames are updated, logic is updated, sound effects play, only the input stalls.

I know it's not just the way I'm coding because since my games became afflicted with this after switching from the 4.0.x builds to 4.2.x, I have also noticed several other games made by other people suffering from this problem, such as Charred Dirt, HeroQuest, and Chickens.

Curiously, the recently released Membrane Massacre is not one of them.

Awhile back, I had theorized after looking through the changelogs and finding the Allegro version this problem started in that the problem was caused by the merging of all the input threads into one and that now the input thread takes too long to process. So I came up with a workaround that works, but not for the reason I thought it did.

All it takes to eliminate this problem is to give time back to the OS. Using rest(1) or Sleep(1) every single frame eliminates the problem, and I discovered about an hour ago that rest() and Sleep() are actually giving the input thread the time it needs to execute during those random times when it needs more than before. This is because if I only call rest(1) or Sleep(1) once every so many frames, and rest(0) or Sleep(0) every other frame, the stalling still occurs but never for longer than the maximum length of time between any two frames with rest(1) or Sleep(1).

IE: If I call rest(0) for 7 frames then rest(1) on every 8th frame, no input stall will last more than 8 frames.

To see this in action, I changed some code in one of my games so that rest(0) was called every frame, except frames where the mouse variables are the exact same as the last frame. In this case, rest(1) is called instead. This code eliminated the input stalling with the mouse, even when the mouse was in constant motion. (But burning CPU time when input is at idle is hardly an ideal solution.)

However, these workarounds don't work simply because the input thread is taking too long to execute, but mostly because the input thread doesn't have a high enough priority!

Originally, the keyboard, mouse and joystick handlers in Allegro ran three separate threads, one of which had a priority of 8 (normal), two of which had a priority of 9 (above normal). To put this in perspective, the timer thread has a priority of 15 (time critical) and most of DirectX has a priority of 24 or higher. (As it is set as a real-time process.)

The new, merged thread, since 4.1.0, still only has a priority of 9! This means that roughly a third of the CPU time originally passed to three input threads is now being used to handle all three inputs and it's just not enough. Especially when other more important threads from other background processes want some of that.

The solution I think would be to either separate the input threads again, or simply make the input thread much higher in priority. (12 or 13 would seem about right. That would put it over most other threads but would still give the timer routines their fair share.)

Unfortunately, I have no multi-threaded programming experience and haven't a clue how to test this myself. If anyone who knows how to do this is willing to give this a try I'm willing to test it and see if it solves the problem.

--- Kris Asick (Gemini)
--- http://www.pixelships.com

--- Kris Asick (Gemini)
--- http://www.pixelships.com

Evert
Member #794
November 2000
avatar

Quote:

This means that roughly a third of the CPU time originally passed to three input threads is now being used to handle all three inputs and it's just not enough.

Ok, that makes sense!

Quote:

The solution I think would be to either separate the input threads again, or simply make the input thread much higher in priority.

I would much prefer the latter.

Quote:

Unfortunately, I have no multi-threaded programming experience and haven't a clue how to test this myself.

Can't you just increase the priority of the thread and recompile Allegro to see if it does what you want?

Kris Asick
Member #1,424
July 2001

Quote:

Can't you just increase the priority of the thread and recompile Allegro to see if it does what you want?

I don't know how and can't find the line of code that sets the priority.

EDIT: Granted, I'm sure I could try changing it myself if I knew what to look for.

--- Kris Asick (Gemini)
--- http://www.pixelships.com

--- Kris Asick (Gemini)
--- http://www.pixelships.com

Evert
Member #794
November 2000
avatar

It seems not to be set at all for the input thread. It looks like the input thread is created at or around line 146 of allegro/src/win/winput.c, while the thread priority should be set with SetThreadPriority (eg, SetThreadPriority(input_thread, THREAD_PRIORITY_ABOVE_NORMAL), check MSDN for what options can go there).

EDIT: I just grepped for "thread" and "priority" in allegro/src/win.

Kris Asick
Member #1,424
July 2001

Quote:

It seems not to be set at all for the input thread.

You're right! I found out that the window created by Allegro has a higher priority than the input thread! (The input thread is 8, the window thread is 9.)

For some reason, my request to change the thread priority for the input thread was ignored when I added the line and compiled Allegro with that change... which is odd since I'm not doing it any differently than anywhere else in the Allegro source where SetThreadPriority() is called. shrugs

I'll keep trying.

EDIT: Ok, it does change, but only when in the foreground. (To 11.) Now to test if this solves the problem...

EDIT: I was right the first time. It's not honoring my priority setting requests for some bizarre reason...

EDIT: That line of code you found Evert, the input thread isn't being created there. In fact, it looks as though input is being handled by the dedicated window created by Allegro now, instead of by a separate thread. (IE: That line only gets processed if you attach Allegro to your own window and not have Allegro create it for you.) I'm going to see what happens if I force Allegro to create an input thread even if the dedicated window is active.

EDIT: SOLVED!! (But not without side effects...)

Here is a particular section of winput.c:

1/* _win_input_init: [primary thread]
2 * Initializes the module.
3 */
4void _win_input_init(int need_thread)
5{
6 _win_input_event_id[0] = CreateEvent(NULL, FALSE, FALSE, NULL);
7 _win_input_event_handler[0] = register_pending_event;
8 _win_input_event_id[1] = CreateEvent(NULL, FALSE, FALSE, NULL);
9 _win_input_event_handler[1] = unregister_pending_event;
10 
11 if (need_thread) {
12 input_need_thread = TRUE;
13 _win_input_event_id[2] = CreateEvent(NULL, FALSE, FALSE, NULL);
14 reserved_events = 3;
15 }
16 else {
17 input_need_thread = FALSE;
18 reserved_events = 2;
19 }
20 
21 _win_input_events = reserved_events;
22 
23 ack_event = CreateEvent(NULL, FALSE, FALSE, NULL);
24}

What this code is doing is only creating an input thread if the user opts to attach Allegro to their own window, instead of having Allegro create one for them. (Apparently, the merge of the input threads was not only to merge them into one, but to eliminate the multi-threading nature of it entirely.)

To solve this problem, I changed the code as such to force Allegro to always create the input thread:

1/* _win_input_init: [primary thread]
2 * Initializes the module.
3 */
4void _win_input_init(int need_thread)
5{
6 _win_input_event_id[0] = CreateEvent(NULL, FALSE, FALSE, NULL);
7 _win_input_event_handler[0] = register_pending_event;
8 _win_input_event_id[1] = CreateEvent(NULL, FALSE, FALSE, NULL);
9 _win_input_event_handler[1] = unregister_pending_event;
10 
11 input_need_thread = TRUE;
12 _win_input_event_id[2] = CreateEvent(NULL, FALSE, FALSE, NULL);
13 reserved_events = 3;
14 
15 _win_input_events = reserved_events;
16 
17 ack_event = CreateEvent(NULL, FALSE, FALSE, NULL);
18}

However, even though this solves the problem, it may have side effects I'm not aware of since it's ignoring the state of need_thread, which would be set elsewhere. There is also one side effect I am aware of: The thread isn't properly shut down when Allegro does since Allegro wasn't trying to create it, causing a page fault when Allegro is exited.

Still, it works! Clearing the side effects should be as simple as going through and changing any code that depends on the state of the input thread to assume that the thread is always present.

...should I do that or is that better left to the devs?

EDIT: I've tested as much as I can by supplementing the Allegro DLL created with that change into my programs and other people's programs and I can't find anything wrong with the new code... short of the invalid page fault when Allegro exits. I can't figure out why the invalid page fault is happening at all. It's definitely related to the closing of the input thread, but I don't understand why...

----------------------------------------------------------------------------

EDIT: Ok, after hours of testing, here's the situation as best as I can figure:

  • Prior to 4.1.0, input methods had their own threads. From 4.1.0 and onward, all input was handled either in the thread for the dedicated Allegro window, or in a single thread when the dedicated window is bypassed.


  • For the window thread: by mixing the input event handling with the windows message handling of the dedicated window, conflicts occur which can delay the updating of the input system, causing keyboard, mouse and joystick processes to appear to stall for a split second.


  • Manually forcing the input thread active solves the problem, but introduces an invalid page fault when Allegro exits. The cause of which is probably related to having both the input thread and window thread processing the same event array at a time, or to the potential for the window thread to call the event handler for array index 2, which in the input thread normally causes it to break out and exit.


  • Manually forcing the input thread active and eliminating the input handler in the dedicated window thread solves the problem as well, but introduces a hoard of new ones since many of the things the window thread was now handling, isn't handled by the input thread. (However, the invalid page fault also disappears.)


  • Conclusion: There are two threads for handling the keyboard, mouse and joystick in Allegro 4.1.0 and later: The input thread and the window thread. The solution is to remove the window thread's ability to process input and to have the input thread handle all input. Unfortunately, the solution is simpler to say than to do and I'm stumped as to how to accomplish it, but, there we are. I now leave it in the hands of someone more experienced with Allegro. faints from overworking

--- Kris Asick (Gemini)
--- http://www.pixelships.com

--- Kris Asick (Gemini)
--- http://www.pixelships.com

Evert
Member #794
November 2000
avatar

Quote:

...should I do that or is that better left to the devs?

It's better left to someone who uses Windows. I guess that means you, sorry. ;)

Kris Asick
Member #1,424
July 2001

In creating my own input library I ran into the same stuttering problem that's apparent in Allegro. Since my input system is about twenty times simpler than the Allegro one I was able to trace the cause of the input stuttering down... and it isn't anything like what I thought it was...

In my testing program for my input system, I decided to check to see if my I/O thread was being stalled or if the input simply wasn't going through. So what I did was I created a counter in my input thread that would continually count up and then my program would display this counter on-screen so I could monitor it whenever a stutter occurred.

Now, my first thought was that if the stuttering occurs, the number will stop, then continue. And that is what appeared to be happening at first... until I realized something peculiar was happening with the number.

It's like this: Every loop of my I/O thread, the counter increases by 1. There would be roughly 100 increases a second while the I/O was idle and about 200 increases a second while pressing keys or moving the mouse.

But when a stall occurred, the loop would increase by several thousand in a split second!

So the stuttering has nothing to do with the thread not working and everything to do with the thread eating a giant load of CPU time at random moments! (Which doesn't make sense. My I/O thread calls Sleep(0) every iteration.)

And I presume this is also what's happening in Allegro.

Adding Sleep(1) into the I/O loop solves the problem, but on certain systems that's going to cause the input to update at a poor rate due to the granularity of the Sleep() statement.

So the solution is to find some way to detect when the I/O thread starts executing more than once per CPU timeslice and to call Sleep(1) in that moment to prevent the stutter from happening. That's what I'm going to try to do with my input system and that's what needs to happen in Allegro in order to prevent these stutters.

--- Kris Asick (Gemini)
--- http://www.pixelships.com

--- Kris Asick (Gemini)
--- http://www.pixelships.com

BAF
Member #2,981
December 2002
avatar

I've never noticed any input stuttering. Is it an issue with your Windows or drivers or something?

Kris Asick
Member #1,424
July 2001

I doubt it, since it only happens with Allegro-based programs on my system. (And as I've said above, it's not limited to my own programs, and as I also said above, this problem didn't start until Allegro 4.1.0. Until then, there was no stuttering.)

Granted, I'm on Windows 98 SE, and am using a computer far more powerful than Windows 98 was ever expected to handle. (2 Ghz, 512 MB RAM, GeForce FX5200 video card...)

Anyways, with my input engine I've solved the problem, but my solution relies on the way I've built it and on the way I access the I/O data retrieved, but the solution was essentially as I said: Find a way to detect when the I/O thread hogs the CPU time and call Sleep(1) to force the thread to give up its time so that it doesn't get processed a few thousand times in rapid succession.

I suppose there might be some combination of hardware and software drivers I have that's making this happen, but if it can happen to me it could happen to anyone, and as I work with shareware, compatability is of paramount importance to me.

EDIT: A side note I should mention: When building the I/O thread for my input system I discovered that changing its thread priority was a really bad idea. If it's higher than the Allegro window thread, the framerate dies. If it's lower, the input drifts as it only gets updated a few times a second.

--- Kris Asick (Gemini)
--- http://www.pixelships.com

--- Kris Asick (Gemini)
--- http://www.pixelships.com

BAF
Member #2,981
December 2002
avatar

Offtopic, but why are you running Windows 98 on that box? You could be running 2k or XP, which is tons more stable.

Kris Asick
Member #1,424
July 2001

And in the process eliminate the ability to run half of my software collection.

My plan is to skip 2K/XP and go straight to Vista once I have the finances to invest into an entirely new system.

--- Kris Asick (Gemini)
--- http://www.pixelships.com

--- Kris Asick (Gemini)
--- http://www.pixelships.com

BAF
Member #2,981
December 2002
avatar

Quote:

And in the process eliminate the ability to run half of my software collection.

What do you have that won't run on 2k or XP? DOS games and apps will run fine in DOSBOX.

Kris Asick
Member #1,424
July 2001

Quote:

What do you have that won't run on 2k or XP? DOS games and apps will run fine in DOSBOX.

I'd rather wait to have a second computer above the 120Mhz mark all the same. ;) (My other four computers are P120, P100, 486 DX2/66 and 286 SX/8.)

--- Kris Asick (Gemini)
--- http://www.pixelships.com

--- Kris Asick (Gemini)
--- http://www.pixelships.com

BAF
Member #2,981
December 2002
avatar

Oh yeah, I forgot that dosbox won't run well on XP at 120 MHz. :P

Kris Asick
Member #1,424
July 2001

I don't think XP runs at 120 MHz! ;D (I don't think it runs with 24 MB of RAM either, which is what my old 120 MHz system has. If my 2 GHz system ever goes down again, that's my next most powerful system... I could really use a 400 or 533 MHz system to help bridge the processing gap. Then I wouldn't care about loading XP on my main system.)

--- Kris Asick (Gemini)
--- http://www.pixelships.com

--- Kris Asick (Gemini)
--- http://www.pixelships.com

BAF
Member #2,981
December 2002
avatar

Oh, you're concerned about having a backup computer? What does it matter if your backup computer runs XP, it's not like you can just throw your XP hdd in your spare computer and have it work, unless the hardware is like identical.

[edit]
Knock on wood, the only hardware I've ever had die was an old 540 MB hdd, a NIC in my Linux server, and a NIC in my Linux router. And the one in the router died slowly, it was a puzzling week while I was trying to find the cause of my 50-75%+ packet loss and slow-as-hell laggy net access.

Matthew Leverton
Supreme Loser
January 1999
avatar

Let's keep it on topic here.

Go to: