1

I wanted to create a key counter with seconds. I tried this, but it slows down the program, not just the seconds.

#include <iostream>
#include <Windows.h>
#include <conio.h>
using namespace std;

int x = 1;
int secs = 0;
int clicks = 0;


int main() {
    while (true) {
        if (x == 1) {
            secs++;
            Sleep(1000);
            system("CLS");
            cout << "Clicks: " << clicks << "             Time: " << secs;
            x = 2;
        }
        else if (2 == x) {
            _getch();
            clicks++;
            system("CLS");
            cout << "Clicks: " << clicks << "             Time: " << secs;
            x = 1;
        }
    }
}
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • 3
    what is the desired behaviour? – Raildex Jan 25 '22 at 13:16
  • 2
    Sounds like you're looking for a thread – AndyG Jan 25 '22 at 13:16
  • 4
    During the time that the program is *not* printing a message, *what should happen in between* while waiting? – Karl Knechtel Jan 25 '22 at 13:17
  • try chrono library – Build Succeeded Jan 25 '22 at 13:19
  • Hello, @Raildex .............When any key is pressed it should add 1 to the clicks. Meanwhile, seconds must be active and change every 1000 miliseconds. I tried this, but instead of slow down just the seconds, it slows down the whole program. – thanasisk09 Jan 26 '22 at 14:12
  • @thanasisk09: Please [edit] the question in order to clarify it. This is important information that should be in the question itself. – Andreas Wenzel Jan 26 '22 at 14:22
  • Why don't you simply use [`GetTickCount`](https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount) in order to determine how many seconds have passed? You can call it once at the start of the program and remember this value, and then call it again later and calculate the time difference, in order to determine how many seconds have passed. – Andreas Wenzel Jan 26 '22 at 14:33
  • Hello, @BuildSucceeded ..........I'm a started. I know a little about it, but I can't understand you. Can you please explain it to me? – thanasisk09 Jan 26 '22 at 14:35
  • Hello, @AndreasWenzel ....I have also a problem about this. I used _getch() to make the clicks wait a user input. Doing this I'm make the whole program wait for the user, instead of the seconds. – thanasisk09 Jan 26 '22 at 14:39
  • @thanasisk09: You are probably looking for the function [`WaitForMultipleObjects`](https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitformultipleobjects). That function will allow you to wait on console input and on a timer, at the same time. The function will return as soon as either occurs. However, I am not sure if that function is compatible with `getch`. You may have to use a lower-level API for keyboard input, such as [`ReadConsole`](https://learn.microsoft.com/en-us/windows/console/readconsole). – Andreas Wenzel Jan 26 '22 at 14:46
  • Thanks, @AndreasWenzel . I found my problem. I want to slow down only a line. I'm starter and I have a question. (This is a basic solution to my problem) Can I do something like that: `time++; lineSleep(1000);` – thanasisk09 Jan 26 '22 at 16:06
  • @thanasisk09: On second thought, I believe that [`ReadConsoleInput`](https://learn.microsoft.com/en-us/windows/console/readconsoleinput) would be better than `ReadConsole`, as that function has more similarity to [`getch`](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getch?view=msvc-170). – Andreas Wenzel Jan 26 '22 at 16:07
  • @thanasisk09: What exactly does `lineSleep` do? Did you write that function yourself? – Andreas Wenzel Jan 26 '22 at 16:08
  • @AndreasWenzel Yes. It's just an example to make you understand my thought. My thought is to use a function, like that, but to exist. This function has to slow down just the line, not the _place_ where it is. – thanasisk09 Jan 26 '22 at 16:23
  • @thanasisk09: Would it be acceptable to you to count the number of keystrokes after one second has elapsed, so that it is acceptable that the program freezes until then? Or do you need the keystrokes to be processed immediately? – Andreas Wenzel Jan 26 '22 at 16:34
  • @thanasisk09: If you only want to prevent the function `getch` from blocking, then you can call the function [`kbhit`](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/kbhit?view=msvc-170) to determine whether keyboard input is waiting, and only call the function `getch` if `kbit` tells you that keyboard input is waiting. – Andreas Wenzel Jan 26 '22 at 16:42
  • you can also use alarm and SIGALARM see https://man7.org/linux/man-pages/man2/alarm.2.html – pm100 Jan 26 '22 at 23:45
  • @pm100: Since OP is doing `#include `, I assume that OP is using Microsoft Windows. – Andreas Wenzel Jan 26 '22 at 23:49
  • or since its windows use SetTimer @AndreasWenzel (ty, I missed the include) which does roughly the same thing – pm100 Jan 27 '22 at 00:29
  • @pm100: According to the documentation of the function [`SetTimer`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-settimer), the calling thread requires a functioning message queue to dispatch the message, even if a timer callback function is used. I strongly doubt that OP has such a message queue. Therefore, it would probably be better to use [`SetWaitableTimer`](https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-setwaitabletimer). However, in my answer, I found it unnecessary to create a separate timer. – Andreas Wenzel Jan 27 '22 at 00:57
  • https://stackoverflow.com/questions/20718762/creating-an-interrupt-in-c-for-windows-operating-system?rq=1 – pm100 Jan 27 '22 at 01:22
  • Related: https://stackoverflow.com/questions/19955617/win32-read-from-stdin-with-timeout – Andreas Wenzel Feb 13 '22 at 04:47

1 Answers1

3

You seem to want the Sleep function to not block the entire program while waiting for the timer, but to instead continue processing keyboard input while waiting for the timer. Also, you seem to want the getch function to not block the entire program while waiting for keyboard input, but to instead continue processing timers while waiting for keyboard input. However, this is not the way that these functions work. Both functions will continue blocking forever, until the specific type of event occurs that they are waiting for.

What you instead want is a function that is able to wait on several different types of events simultaneously. The function should block until any one of these events occurs. As soon as such an event occurs, the function should return immediately and specify what type of event occurred, i.e. whether it was a timer event or a keyboard input event. That way, the event can be handled immediately.

Generally, you can use the function WaitForMultipleObjects for this purpose. However, in this case, the function WaitForSingleObject is sufficient, as that function will allow you to specify a timeout interval in addition to the object handle that you want to wait on. That way, you can wait on both a timer and on an object handle for keyboard input.

Unfortunately, according to my tests, using WaitForSingleObject on the input handle of a console will only work reliably when processing console input using low-level Console API functions such as ReadConsoleInput. It seems it cannot reliably predict whether kbhit and getch will work, and it also won't work reliably with ReadConsole.

I believe that this program accomplishes what you want:

#include <Windows.h>
#include <iostream>
#include <cstdio>

int main()
{
    HANDLE hStdin = INVALID_HANDLE_VALUE;
    ULONGLONG ullStartTime;

    //counter of the number of times a keypress was registered
    int num_keypresses = 0;

    //get input handle
    hStdin= GetStdHandle( STD_INPUT_HANDLE );
    if ( hStdin == INVALID_HANDLE_VALUE )
    {
        std::cerr << "error opening input handle!\n" << std::flush;
        exit( EXIT_FAILURE );
    }

    //remember time that program started
    ullStartTime = GetTickCount64();

    for (;;) //infinite loop, equivalent to while(1)
    {
        ULONGLONG ullElapsedTime = GetTickCount64() - ullStartTime;

        int seconds   = (int) ( ullElapsedTime / 1000 );
        int remainder = (int) ( ullElapsedTime % 1000 );

        //round up to next second, if appropriate
        if ( remainder >= 500 )
        {
            seconds++;
            remainder -= 1000;
        }

        //uncomment the folllowing line to clear the screen
        //std::system( "cls" );

        //write data to screen
        std::cout << "Keypresses: " << num_keypresses << "     Time: " << seconds << " seconds\n" << std::flush;

        //wait for either a keypress to occur, or for a timeout to occur
        switch ( WaitForSingleObject( hStdin, 1000 - remainder ) )
        {
            case WAIT_OBJECT_0:
            {
                DWORD dwRead;
                INPUT_RECORD ir;

                //console input event occurred, so read it
                if ( !ReadConsoleInput( hStdin, &ir, 1, &dwRead ) || dwRead != 1 )
                {
                    std::cerr << "error reading input!\n" << std::flush;
                    std::exit( EXIT_FAILURE );
                }

                if ( ir.EventType == KEY_EVENT && ir.Event.KeyEvent.bKeyDown )
                {
                    num_keypresses += ir.Event.KeyEvent.wRepeatCount;
                }
                break;
            }
            case WAIT_TIMEOUT:
            {
                //timeout occurred
                break;
            }
            default:
            {
                std::cerr << "unexpected error!\n" << std::flush;
                std::exit( EXIT_FAILURE );
            }
        }
    }
}

If I wait about 5 seconds before starting to press keys, and then press a few, then the output I get is the following:

Keypresses: 0     Time: 0 seconds
Keypresses: 0     Time: 1 seconds
Keypresses: 0     Time: 2 seconds
Keypresses: 0     Time: 3 seconds
Keypresses: 0     Time: 4 seconds
Keypresses: 0     Time: 5 seconds
Keypresses: 1     Time: 6 seconds
Keypresses: 1     Time: 6 seconds
Keypresses: 2     Time: 7 seconds
Keypresses: 2     Time: 7 seconds
Keypresses: 2     Time: 8 seconds
Keypresses: 3     Time: 9 seconds
Keypresses: 3     Time: 9 seconds
Keypresses: 4     Time: 10 seconds
Keypresses: 4     Time: 10 seconds
Keypresses: 4     Time: 11 seconds
Keypresses: 5     Time: 11 seconds
Keypresses: 5     Time: 11 seconds
Keypresses: 5     Time: 12 seconds
Keypresses: 6     Time: 12 seconds
Keypresses: 6     Time: 13 seconds
Keypresses: 6     Time: 14 seconds
Keypresses: 7     Time: 15 seconds
Keypresses: 7     Time: 15 seconds
Keypresses: 7     Time: 16 seconds
Keypresses: 7     Time: 17 seconds

In your original program, you are using std::system("cls"); to clear the screen, instead of printing a new line every time. In my program, I am not clearing the screen, however, you can uncomment the line std::system("cls"); and it should work. I have already put it in the correct place. Note, however, that using std::system is not recommended. See this page from the official Microsoft documentation for better alternatives.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39