Thread: How should I prevent a keyboard event from firing too much?

  1. #1
    Registered User HelpfulPerson's Avatar
    Join Date
    Jun 2013
    Location
    Over the rainbow
    Posts
    288

    How should I prevent a keyboard event from firing too much?

    Code:
    #include <stdio.h>
    #include <stdlib.h>
    
    
    #include <process.h>
    #define WIN_32_LEAN_AND_MEAN
    #include <windows.h>
    #include <strsafe.h>
    
    
    #define MAX_THREADS 6
    
    /* Key-input macro and usage credit goes to : C pong program ( need help smoothing animations ) */
    #define VKEY_IS_PRESSED(vk)    ( GetAsyncKeyState(vk) & 0x8000 )
    
    
    enum v_key
    {
        up_arrow = VK_UP,
        down_arrow = VK_DOWN,
        right_arrow = VK_RIGHT,
        left_arrow = VK_LEFT,
        clearscreen = VK_BACK,
        escape = VK_ESCAPE
    };
    
    
    typedef struct shared_game_data
    {
        DWORD value;
    } shared_game_data;
    
    
    void * data = NULL;
    HANDLE h_threads[MAX_THREADS] = {0};
    
    
    HANDLE up_arrow_event = 0;
    HANDLE down_arrow_event = 0;
    HANDLE right_arrow_event = 0;
    HANDLE left_arrow_event = 0;
    HANDLE clear_screen_event = 0;
    HANDLE escape_event = 0;
    
    
    CRITICAL_SECTION critical_section;
    
    
    /* Credit to Microsoft : http://msdn.microsoft.com/en-us/libr...(v=vs.85).aspx */
    
    
    void cls( HANDLE hConsole )
    {
       COORD coordScreen = { 0, 0 };    // home for the cursor
       DWORD cCharsWritten;
       CONSOLE_SCREEN_BUFFER_INFO csbi;
       DWORD dwConSize;
    
    
    // Get the number of character cells in the current buffer.
    
    
       if( !GetConsoleScreenBufferInfo( hConsole, &csbi ))
       {
          return;
       }
    
    
       dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
    
    
       // Fill the entire screen with blanks.
    
    
       if( !FillConsoleOutputCharacter( hConsole,        // Handle to console screen buffer
                                        (TCHAR) ' ',     // Character to write to the buffer
                                        dwConSize,       // Number of cells to write
                                        coordScreen,     // Coordinates of first cell
                                        &cCharsWritten ))// Receive number of characters written
       {
          return;
       }
    
    
       // Get the current text attribute.
    
    
       if( !GetConsoleScreenBufferInfo( hConsole, &csbi ))
       {
          return;
       }
    
    
       // Set the buffer's attributes accordingly.
    
    
       if( !FillConsoleOutputAttribute( hConsole,         // Handle to console screen buffer
                                        csbi.wAttributes, // Character attributes to use
                                        dwConSize,        // Number of cells to set attribute
                                        coordScreen,      // Coordinates of first cell
                                        &cCharsWritten )) // Receive number of characters written
       {
          return;
       }
    
    
       // Put the cursor at its home coordinates.
    
    
       SetConsoleCursorPosition( hConsole, coordScreen );
    }
    
    
    /* Credit to Microsoft : http://msdn.microsoft.com/en-us/libr...(v=vs.85).aspx */
    
    
    void ErrorExit(LPTSTR lpszFunction)
    {
        LPVOID lp_msg_buf;
        LPVOID lp_display_buf;
        DWORD dw = GetLastError();
    
    
        FormatMessage
        (
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lp_msg_buf,
        0,
        NULL
        );
    
    
        lp_display_buf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, (lstrlen((LPCTSTR)lp_msg_buf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR));
    
    
        StringCchPrintf((LPTSTR)lp_display_buf, LocalSize(lp_display_buf) / sizeof(TCHAR), TEXT("%s failed with error %d: %s"), lpszFunction, dw, lp_msg_buf);
        MessageBox(NULL, (LPCTSTR)lp_display_buf, TEXT("Error"), MB_OK);
    
    
        LocalFree(lp_msg_buf);
        LocalFree(lp_display_buf);
        ExitProcess(dw);
    }
    
    
    void close_events( )
    {
        CloseHandle(up_arrow_event);
        CloseHandle(down_arrow_event);
        CloseHandle(right_arrow_event);
        CloseHandle(left_arrow_event);
        CloseHandle(escape_event);
    
    
        return;
    }
    
    
    void close_threads( )
    {
        unsigned index;
    
    
        for (index = 0; index < MAX_THREADS; index++)
        {
            CloseHandle(h_threads[index]);
        }
    
    
        return;
    }
    
    
    unsigned __stdcall event_loop( void * m_sleep )
    {
        const DWORD msleep = *(DWORD *)m_sleep;
    
    
        for (;;)
        {
            if ( VKEY_IS_PRESSED(escape) )
            {
                if ( !SetEvent(escape_event) )
                {
                    close_events();
                    ErrorExit(TEXT("SetEvent-Escape"));
                }
    
    
                return 0;
            }
    
    
            if ( VKEY_IS_PRESSED(up_arrow) )
            {
                if ( !SetEvent(up_arrow_event) )
                {
                    close_events();
                    ErrorExit(TEXT("SetEvent-UpArrow"));
                }
            }
    
    
            if ( VKEY_IS_PRESSED(down_arrow) )
            {
                if ( !SetEvent(down_arrow_event) )
                {
                    close_events();
                    ErrorExit(TEXT("SetEvent-DownArrow"));
                }
            }
    
    
            if ( VKEY_IS_PRESSED(right_arrow) )
            {
                if ( !SetEvent(right_arrow_event) )
                {
                    close_events();
                    ErrorExit(TEXT("SetEvent-RightArrow"));
                }
            }
    
    
            if ( VKEY_IS_PRESSED(left_arrow) )
            {
                if ( !SetEvent(left_arrow_event) )
                {
                    close_events();
                    ErrorExit(TEXT("SetEvent-LeftArrow"));
                }
            }
    
    
            if ( VKEY_IS_PRESSED(clearscreen) )
            {
                if ( !SetEvent(clear_screen_event) )
                {
                    close_events();
                    ErrorExit(TEXT("SetEvent-ClearScreen"));
                }
            }
    
    
            Sleep(msleep);
        }
    
    
        return 1;
    }
    
    
    unsigned __stdcall left_arrow_handler( void * parameter )
    {
        DWORD return_value = 0;
        HANDLE objects[2] = {left_arrow_event, escape_event};
        shared_game_data * my_data = (shared_game_data *)parameter;
    
    
        for ( ; ; )
        {
    
    
            return_value = WaitForMultipleObjects(2, objects, FALSE, INFINITE);
    
    
            switch ( return_value )
            {
                case WAIT_OBJECT_0 :
                    EnterCriticalSection(&critical_section);
    
    
                    printf("Left-arrow-handler, I.D. : %d\n", (int)GetCurrentThreadId());
                    my_data->value -= 10;
                    printf("Value : %d\n", (int)my_data->value);
    
    
                    LeaveCriticalSection(&critical_section);
    
    
                    ResetEvent(objects[0]);
    
    
                    break;
                case WAIT_OBJECT_0 + 1 :
                    return 0;
            }
        }
    
    
        return 0;
    }
    
    
    unsigned __stdcall right_arrow_handler( void * parameter )
    {
        DWORD return_value = 0;
        HANDLE objects[2] = {right_arrow_event, escape_event};
        shared_game_data * my_data = (shared_game_data *)parameter;
    
    
        for ( ; ; )
        {
    
    
            return_value = WaitForMultipleObjects(2, objects, FALSE, INFINITE);
    
    
            switch ( return_value )
            {
                case WAIT_OBJECT_0 :
                    EnterCriticalSection(&critical_section);
    
    
                    printf("Right-arrow-handler, I.D. : %d\n", (int)GetCurrentThreadId());
                    my_data->value += 10;
                    printf("Value : %d\n", (int)my_data->value);
    
    
                    LeaveCriticalSection(&critical_section);
    
    
                    ResetEvent(objects[0]);
    
    
                    break;
                case WAIT_OBJECT_0 + 1 :
                    return 0;
            }
        }
    
    
        return 0;
    }
    
    
    unsigned __stdcall down_arrow_handler( void * parameter )
    {
        DWORD return_value = 0;
        HANDLE objects[2] = {down_arrow_event, escape_event};
        shared_game_data * my_data = (shared_game_data *)parameter;
    
    
        for ( ; ; )
        {
    
    
            return_value = WaitForMultipleObjects(2, objects, FALSE, INFINITE);
    
    
            switch ( return_value )
            {
                case WAIT_OBJECT_0 :
                    EnterCriticalSection(&critical_section);
    
    
                    printf("Down-arrow-handler, I.D. : %d\n", (int)GetCurrentThreadId());
                    my_data->value /= 10;
                    printf("Value : %d\n", (int)my_data->value);
    
    
                    LeaveCriticalSection(&critical_section);
    
    
                    ResetEvent(objects[0]);
    
    
                    break;
                case WAIT_OBJECT_0 + 1 :
                    return 0;
            }
        }
    
    
        return 0;
    }
    
    
    unsigned __stdcall up_arrow_handler( void * parameter )
    {
        DWORD return_value = 0;
        HANDLE objects[2] = {up_arrow_event, escape_event};
        shared_game_data * my_data = (shared_game_data *)parameter;
    
    
        for ( ; ; )
        {
    
    
            return_value = WaitForMultipleObjects(2, objects, FALSE, INFINITE);
    
    
            switch ( return_value )
            {
                case WAIT_OBJECT_0 :
                    EnterCriticalSection(&critical_section);
    
    
                    printf("Up-arrow-handler, I.D. : %d\n", (int)GetCurrentThreadId());
                    my_data->value *= 10;
                    printf("Value : %d\n", (int)my_data->value);
    
    
                    LeaveCriticalSection(&critical_section);
    
    
                    ResetEvent(objects[0]);
    
    
                    break;
                case WAIT_OBJECT_0 + 1 :
                    return 0;
            }
        }
    
    
        return 0;
    }
    
    
    unsigned __stdcall clear_screen_handler( void * parameter )
    {
        UNREFERENCED_PARAMETER(parameter);
    
    
        HANDLE h_stdout = NULL;
        h_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
        DWORD return_value = 0;
        HANDLE objects[2] = {clear_screen_event, escape_event};
    
    
        for ( ; ; )
        {
    
    
            return_value = WaitForMultipleObjects(2, objects, FALSE, INFINITE);
    
    
            switch ( return_value )
            {
                case WAIT_OBJECT_0 :
    
    
                    cls(h_stdout);
    
    
                    ResetEvent(objects[0]);
    
    
                    break;
                case WAIT_OBJECT_0 + 1 :
                    return 0;
            }
        }
    
    
        return 0;
    }
    
    
    void init_events ( )
    {
        /* Create our keyboard events */
    
    
        up_arrow_event = CreateEvent( NULL, TRUE, FALSE, TEXT("UpArrowEvent") );
    
    
        if (!up_arrow_event)
            ErrorExit(TEXT("CreateEvent 1"));
    
    
        down_arrow_event = CreateEvent( NULL, TRUE, FALSE, TEXT("DownArrowEvent") );
    
    
        if (!down_arrow_event)
            ErrorExit(TEXT("CreateEvent 2"));
    
    
        right_arrow_event = CreateEvent( NULL, TRUE, FALSE, TEXT("RightArrowEvent") );
    
    
        if (!right_arrow_event)
            ErrorExit(TEXT("CreateEvent 3"));
    
    
        left_arrow_event = CreateEvent( NULL, TRUE, FALSE, TEXT("LeftArrowEvent") );
    
    
        if (!left_arrow_event)
            ErrorExit(TEXT("CreateEvent 4"));
    
    
        escape_event = CreateEvent( NULL, TRUE, FALSE, TEXT("EscapeEvent") );
    
    
        if (!escape_event)
            ErrorExit(TEXT("CreateEvent 5"));
    
    
        clear_screen_event = CreateEvent( NULL, TRUE, FALSE, TEXT("ClearScrenEvent") );
    
    
        if(!clear_screen_event)
            ErrorExit(TEXT("CreateEvent 6"));
    
    
        return;
    }
    
    
    inline shared_game_data * create_shared_game_data_node( shared_game_data * data )
    {
        data = malloc(sizeof(shared_game_data));
    
    
        if (!data)
        {
            perror("Malloc");
            ExitProcess(1);
        }
    
    
        return data;
    
    
    }
    
    
    inline shared_game_data * inigame_data_node( shared_game_data * data, DWORD value )
    {
        data->value = value;
    
    
        return data;
    }
    
    
    inline void create_threads( void * data, unsigned * thread_id )
    {
        DWORD msleep = 50;
        void * sleep = &msleep;
    
    
        h_threads[0] = (HANDLE)_beginthreadex(NULL, 0, &event_loop, sleep, 0, &thread_id[0] );
        h_threads[1] = (HANDLE)_beginthreadex(NULL, 0, &left_arrow_handler, data, 0, &thread_id[1] );
        h_threads[2] = (HANDLE)_beginthreadex(NULL, 0, &right_arrow_handler, data, 0, &thread_id[2] );
        h_threads[3] = (HANDLE)_beginthreadex(NULL, 0, &up_arrow_handler, data, 0, &thread_id[3] );
        h_threads[4] = (HANDLE)_beginthreadex(NULL, 0, &down_arrow_handler, data, 0, &thread_id[4] );
        h_threads[5] = (HANDLE)_beginthreadex(NULL, 0, &clear_screen_handler, data, 0, &thread_id[5] );
    
    
        return;
    }
    
    
    void cleanup( )
    {
        close_threads();
        DeleteCriticalSection(&critical_section);
        close_events();
        free(data);
    
    
        return;
    }
    
    
    int main( )
    {
        if (atexit(cleanup))
        {
            perror("Atexit");
            ExitProcess(1);
        }
    
    
        unsigned thread_id[MAX_THREADS] = {0};
    
    
        if (!InitializeCriticalSectionAndSpinCount(&critical_section, 0x0000050))
            ErrorExit(TEXT("InitialCriticalSectionAndSpinCount"));
    
    
        init_events();
        shared_game_data * game_data = create_shared_game_data_node( data );
        game_data = inigame_data_node( game_data, 2 );
        data = game_data;
        create_threads( data, thread_id );
        WaitForMultipleObjects(MAX_THREADS, h_threads, TRUE, INFINITE);
    
    
        return 0;
    }
    The code I made above basically makes 6 threads that handle and coordinate keyboard events, and edit a value in shared memory according to the key pressed. It works, but it's super sensitive to keyboard input, and fires the event 2-3 times if I'm not careful. Is there any good way to make it not fire the event as much?

    Edit : I forgot to mention that the escape key exits, and backspace clears the screen. Also, the structure is called game_data because I will eventually make this into a game that uses arrow keys.
    Last edited by HelpfulPerson; 08-18-2013 at 03:12 PM.
    "Some people think they can outsmart me, maybe. Maybe. I've yet to meet one that can outsmart bullet" - Meet the Heavy, Team Fortress 2

  2. #2
    Registered User
    Join Date
    Mar 2010
    Posts
    583
    Your event loop is running very very quickly, so a single keypress can trigger GetAsyncKeyState() several times round the loop. Probably many hundreds of times if you didn't already have a sleep() in there.

    What behaviour do you want? If you hold a key down right now, you'll see a letter, then a pause, then repeats. That's good behaviour for typing on a forum, probably not good behaviour for a game. I expect all you'll want to do is increase the Sleep() time. Play with it until it's right.

    GetAsyncKeyState doesn't give you very much to work with, so I think sending the event less will have to be dealt with in your code. The MSDN doc says "If the most significant bit is set, the key is down, and if the least significant bit is set, the key was pressed after the previous call to GetAsyncKeyState. However, you should not rely on this last behavior; for more information, see the Remarks." -- GetAsyncKeyState function (Windows)

    This would allow you to detect if the key has been pressed or if it's being held down. The page goes on to say that if another application executes GetAsyncKeyState, it can pick up "your" recently pressed bit. So you can't use that, but you can achieve the same effect:

    Code:
        // outside the loop
        bool up_arrow_pressed = false;
        
        ....
                
            if ( VKEY_IS_PRESSED(up_arrow) )
            {
                if (!up_arrow_pressed)
                {
                    if ( !SetEvent(up_arrow_event) )
                    {
                        close_events();
                        ErrorExit(TEXT("SetEvent-UpArrow"));
                    }
                    up_arrow_pressed = true;
                }
            }
            else
            {
                up_arrow_pressed = false;
            }
    If you wanted to have something like the way a keyboard behaves in this text box, there are two factors you'd usually configure: the time to wait before beginning repeating, and the rate at which to repeat at.

    Rather than having to deal with both of these, it makes sense so have your Sleep() value set to a repeat rate that you're happy with. Then you just need to delay the start of the loop. Say you want the loop to run 10 times with the key pressed down before beginning repeating. You could do:

    Code:
    #define LOOPS_BEFORE_REPEAT 10
    
        // outside the loop
        unsigned up_loops = 0;
        
        .....
        
            if ( VKEY_IS_PRESSED(up_arrow) )
            {
                if (up_loops >= LOOPS_BEFORE_REPEAT)
                {
                    if ( !SetEvent(up_arrow_event) )
                    {
                        close_events();
                        ErrorExit(TEXT("SetEvent-UpArrow"));
                    }
                }
                else
                {
                    up_loops++;
                }
            }
            else
            {
                up_loops = 0;
            }
    The reason I put the increment of up_loops in an else clause rather than incrementing it unconditionally is make sure up_loops doesn't overflow.

    I don't have a working windows machine, so I haven't actually tested or even compiled any of that. Hopefully you see the idea though.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Keyboard Event Listener
    By Matt Hintzke in forum C Programming
    Replies: 7
    Last Post: 12-29-2011, 09:11 AM
  2. Replies: 2
    Last Post: 06-06-2011, 08:23 AM
  3. Simulating keyboard event on Windows xp
    By GOBLIN-85 in forum Windows Programming
    Replies: 5
    Last Post: 08-17-2009, 08:27 AM
  4. How to Get Keyboard event????
    By Shidlingayya in forum C++ Programming
    Replies: 9
    Last Post: 12-18-2007, 01:14 PM
  5. keyboard event
    By SuperNewbie in forum Windows Programming
    Replies: 4
    Last Post: 06-10-2002, 06:51 PM