Below is my example for "calling a function after updateable delay": the idea is that,
- when you first press a key, a timer starts, and when three seconds have expired, the function to delay runs
- if thereafter you press a key again during the delay, the delay should reset itself again back to 3 seconds
So, if I do the very first press of a key and start the delay, then after 1 second press a key again, then after 2 seconds press a key again, then after 1 second press a key again, and then stop pressing keys - from this point the code should still wait 3 seconds before running the function to delay (so in this example, a total of 1+2+1+3 = 7 seconds would have expired from the very first press of a key).
Unfortunately, the code does not work as intended:
// compile on Linux with:
// g++ test.cpp -o test.exe -pthread
#include <iostream>
#include <chrono>
#include <thread>
#include <functional>
#include <sys/ioctl.h> // FIONREAD
#include <sys/select.h>
#include <termios.h>
#include <stropts.h>
std::chrono::time_point<std::chrono::steady_clock> tnow;
std::chrono::time_point<std::chrono::steady_clock> tend;
unsigned int interval_ms = 3000;
bool do_stop = false;
bool timer_started = false;
int _kbhit() { // https://www.flipcode.com/archives/_kbhit_for_Linux.shtml
static const int STDIN = 0;
static bool initialized = false;
if (! initialized) {
// Use termios to turn off line buffering
termios term;
tcgetattr(STDIN, &term);
term.c_lflag &= ~ICANON;
tcsetattr(STDIN, TCSANOW, &term);
setbuf(stdin, NULL);
initialized = true;
}
int bytesWaiting;
ioctl(STDIN, FIONREAD, &bytesWaiting);
return bytesWaiting;
}
void update_timing() {
std::cout << "updating timer" << std::endl;
tnow = std::chrono::steady_clock::now();
tend = tnow + std::chrono::milliseconds(interval_ms);
}
void timer_start(std::function<void(void)> func) // inspired by https://stackoverflow.com/a/43373364/6197439
{
std::cout << "starting timer" << std::endl;
timer_started = true;
tnow = std::chrono::steady_clock::now();
tend = tnow + std::chrono::milliseconds(interval_ms);
std::thread([func]()
{
std::this_thread::sleep_until(tend);
func();
}).detach();
}
void the_function_to_delay(void)
{
std::cout << "The function (to delay) has ran!" << std::endl;
do_stop = true;
}
int main()
{
std::cout << "Press any key to start timed function; press it again to update the timing" << std::endl;
int kbhit_ret = 0;
char kbchar;
while( not(do_stop) )
{
kbhit_ret = _kbhit();
if (kbhit_ret) {
scanf( "%c", &kbchar ) ;
std::cout << "kbhit " << kbhit_ret << " " << kbchar << std::endl;
if (not(timer_started)) {
timer_start( the_function_to_delay );
} else {
update_timing();
}
}
std::cin.clear();
std::this_thread::sleep_for(std::chrono::milliseconds(1)); //usleep(1000);
}
}
... in the sense that it always runs the function to delay 3 seconds after the very first keypress, regardless of if there are other key presses in-between.
It kinda makes sense, if std::this_thread::sleep_until(tend); basically "cached" the tend time the very first time it is called.
But if that is the case, how could I get the behavior I want (updateable delay) in C++?
OK, after some reading, there are in essence two things that prevent the approach in the OP code to work, even if it sort of appears semantically correct:
thread_runner.~thread();, but this terminates/aborts all threads, including the main programtendvariable is not viable either; looking at thesleep_untilcode from https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-api-4.5/a01060_source.html#l00265 : ... it basically calculates the delta__atime - _Clock::now()and uses it directly in a call tosleep_for.Which is to say, we cannot just update timestamps (that is,
std::chrono::time_point) and then rely on a single call tosleep_untilto obtain the desired behavior.Instead, we can do our own "waiting loop" where we sleep for a rather small quantum of time (since the desired delay is 3 seconds, 1 ms sleep is acceptable), and then check if the conditions for the equivalent to
sleep_untilare met, before proceeding to call the delayed function; this short sleep loop will make it fast to wait for a thread to finish, when using.join().So when a keypress should "update" the timer, we simply set variables and then wait for the delayed caller thread to exit; and then, we can simply restart the timer waiting thread again.
So here is the updated code:
... and with a behavior like this: