From 924472f5d6d6a10bb7ed66540446cb49370a5098 Mon Sep 17 00:00:00 2001 From: Matthew Kilgore Date: Mon, 9 Jan 2023 03:33:23 -0500 Subject: [PATCH 1/3] Fix timers not firing at the start of the program Timer's were not firing at the right time if they were started shortly after the program started, instead they would fire at twice the interval time (and then work correctly after that). The issue was a mistaken assumption about `time_now`, with the idea that if `last_time == 0` then `time_now` will be large enough such that the interval check will pass. This is wrong because in most cases `time_now` starts at zero at program start, so when `last_time == 0` it will take one full interval of the timer before `time_now` is large enough for the interval check to pass (at which point the timer is initialized and runs normally). This simply refactors the timer logic so that `last_time == 0` is checked first, rather than if the interval has expired. This doesn't change how the normal logic works, but ensures that the value of `time_now` does not matter for initializing a timer. Fixes: #273 --- internal/c/qbx.cpp | 50 +++++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/internal/c/qbx.cpp b/internal/c/qbx.cpp index 643231fd5..6ae40d802 100755 --- a/internal/c/qbx.cpp +++ b/internal/c/qbx.cpp @@ -2079,8 +2079,7 @@ void sub_timer(int32 i, int32 option, int32 passed) { ontimer[i].active = 0; if (ontimer[i].state == 1) ontimer[i].state = 0; // retract event if not in progress - ontimer[i].last_time = - 0; // when ON is next used, event will be triggered immediately + ontimer[i].last_time = 0; // when ON is next used, the timer will start over return; } if (option == 3) { // STOP @@ -2114,34 +2113,25 @@ void TIMERTHREAD(void *unused) { if (!ontimerthread_lock) { // mutex time_now = ((double)GetTicks()) * 0.001; for (i = 0; i < ontimer_nextfree; i++) { - if (ontimer[i].allocated) { - if (ontimer[i].id) { - if (ontimer[i].active) { - if (!ontimer[i].state) { - if (time_now - ontimer[i].last_time > - ontimer[i].seconds) { - if (!ontimer[i].last_time) { - ontimer[i].last_time = time_now; - } else { - // keep measured time for accurate - // number of calls overall - ontimer[i].last_time += - ontimer[i].seconds; - // if difference between actual time and - // measured time is beyond 'seconds' set - // measured to actual - if (fabs(time_now - - ontimer[i].last_time) >= - ontimer[i].seconds) - ontimer[i].last_time = time_now; - ontimer[i].state = 1; - qbevent = 1; - } - } // time check - } // state==0 - } // active - } // id - } // allocated + if (ontimer[i].allocated && ontimer[i].id && ontimer[i].active && !ontimer[i].state) { + if (!ontimer[i].last_time) { + ontimer[i].last_time = time_now; + } else if (time_now - ontimer[i].last_time > ontimer[i].seconds) { + // keep measured time for accurate + // number of calls overall + ontimer[i].last_time += ontimer[i].seconds; + + // if difference between actual time and + // measured time is beyond 'seconds' set + // measured to actual + if (fabs(time_now - + ontimer[i].last_time) >= + ontimer[i].seconds) + ontimer[i].last_time = time_now; + ontimer[i].state = 1; + qbevent = 1; + } // time check + } if (ontimerthread_lock == 1) goto quick_lock; } // i From f995f38e38d04a0b9057a3812441e6f23708b4de Mon Sep 17 00:00:00 2001 From: Matthew Kilgore Date: Sat, 14 Jan 2023 23:49:23 -0500 Subject: [PATCH 2/3] Using Sleep with Console programs does not trigger timers The command Sleep is supposed to allow timers to trigger while the program is sleeping on the delay. This is achieved in QB64 by having commands that do delays manually call evnt() to trigger events if they come up (of which timers are one). Sleep has a custom implementation for console programs on Windows which doesn't do this, so I redid the logic so that it calls evnt() at regular intervals while waiting for input. Additionally, due to now calling evnt() we also need to check if we should exit sleep early due to an evnt() firing. Fixes: #294 --- internal/c/libqb.cpp | 65 ++++++++++++------- .../compile_tests/timer/sleep_ends_early.bas | 13 ++++ .../timer/sleep_ends_early.output | 1 + 3 files changed, 55 insertions(+), 24 deletions(-) create mode 100644 tests/compile_tests/timer/sleep_ends_early.bas create mode 100644 tests/compile_tests/timer/sleep_ends_early.output diff --git a/internal/c/libqb.cpp b/internal/c/libqb.cpp index 9f258003e..40089b21b 100644 --- a/internal/c/libqb.cpp +++ b/internal/c/libqb.cpp @@ -20093,36 +20093,53 @@ void sub_sleep(int32 seconds, int32 passed) { DWORD dwRet; HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); FlushConsoleInputBuffer(hStdin); - if (passed) { - do { + + do { + uint32_t sleepms = 10; + + // Check if ms is less than 10, if so sleep for that many ms to + // try to make the sleep a bit more accurate + if (passed && ms < 10 && ms > 0) + sleepms = ms; + + // Only sleep for a max of 10ms intervals so that we can pump the evnt() handler + dwRet = WaitForSingleObject(hStdin, sleepms); // this should provide our pause + + if (dwRet == WAIT_OBJECT_0) { // this says the console had input + junk = func__getconsoleinput(); + if (junk == 1) { // this is a valid keyboard event. Let's exit SLEEP in the console. + Sleep(100); // Give the user time to remove their finger from the key, before clearing the buffer. + FlushConsoleInputBuffer( + hStdin); // flush the keyboard buffer after, so we don't leave stray events to be processed (such as key up events). + return; + } else { // we had an input event such as the mouse. Ignore it and clear the buffer so we don't keep responding to mouse inputs + FlushConsoleInputBuffer(hStdin); + } + } + + // Handle periodic events (timers and some other things) + evnt(0); + + // evnt() may trigger us to end sleep early + if (sleep_break) + return; + if (stop_program) + return; + + // We only update the ms count if we were provided a max number of + // seconds to sleep + if (passed) { now = GetTicks(); if (now < prev) return; // value looped? elapsed = now - prev; // elapsed time since prev ms = ms - elapsed; prev = now; - dwRet = WaitForSingleObject(hStdin, ms); // this should provide our pause - if (dwRet == WAIT_TIMEOUT) - return; // and if we timeout without any input, we exit early. - if (dwRet == WAIT_OBJECT_0) { // this says the console had input - junk = func__getconsoleinput(); - if (junk == 1) { // this is a valid keyboard event. Let's exit SLEEP in the console. - Sleep(100); // Give the user time to remove their finger from the key, before clearing the buffer. - FlushConsoleInputBuffer( - hStdin); // flush the keyboard buffer after, so we don't leave stray events to be processed (such as key up events). - return; - } else { // we had an input event such as the mouse. Ignore it and clear the buffer so we don't keep responding to mouse inputs - FlushConsoleInputBuffer(hStdin); - } - } - } while (ms > 0); // as long as our timer hasn't expired, we continue to run the loop and countdown the time remaining - return; // if we get here, something odd happened. We should expire automatically with the WAIT_TIMEOUT event before this occurs. - } - do { // ignore all console input unless it's a keydown event - junk = func__getconsoleinput(); - } while (junk != 1); // only when junk = 1 do we have a keyboard event - Sleep(100); // Give the user time to remove their finger from the key, before clearing the buffer. - FlushConsoleInputBuffer(hStdin); // and flush the keyboard buffer after, so we don't leave stray events to be processed. + } + + } while (!passed || ms > 0); // We loop if we weren't provided a number of seconds to sleep, or if we reached the timeout + + // If we got here, then we timed out return; } #endif diff --git a/tests/compile_tests/timer/sleep_ends_early.bas b/tests/compile_tests/timer/sleep_ends_early.bas new file mode 100644 index 000000000..97183a9e1 --- /dev/null +++ b/tests/compile_tests/timer/sleep_ends_early.bas @@ -0,0 +1,13 @@ +$Console:Only + +On Timer(2) GoSub timerhand +Timer On + +' The timer triggering should end sleep early, so it only triggers once +Sleep 10 + +System + +timerhand: +Print "Timer!" +return diff --git a/tests/compile_tests/timer/sleep_ends_early.output b/tests/compile_tests/timer/sleep_ends_early.output new file mode 100644 index 000000000..5d4a3dfd7 --- /dev/null +++ b/tests/compile_tests/timer/sleep_ends_early.output @@ -0,0 +1 @@ +Timer! From d4ed3716811c4a65703fde46c5b8dc70ca71c95b Mon Sep 17 00:00:00 2001 From: Matthew Kilgore Date: Sat, 14 Jan 2023 23:54:33 -0500 Subject: [PATCH 3/3] Stopped timers don't trigger on TIMER ON If a timer expires while stopped, it should trigger when TIMER ON is run. Instead, on QB64 it triggers randomly after the TIMER ON happens. The basic issue is that `qbevent` needs to be set to trigger the timer, but TIMER ON doesn't do that. The regular timer logic that does that already set it when the timer expired while sleeping, so it won't set it again. The simplest solution is to just alway set qbevent = 1 when TIMER ON is done. It's slightly less efficent but doesn't hurt to set it even when there are no timers that expired. Fixes: #293 --- internal/c/qbx.cpp | 3 +++ .../compile_tests/timer/timer_delayed_run.bas | 15 +++++++++++++++ .../timer/timer_delayed_run.output | 2 ++ tests/compile_tests/timer/timer_first_run.bas | 12 ++++++++++++ .../compile_tests/timer/timer_first_run.output | 2 ++ tests/compile_tests/timer/timer_stop.bas | 18 ++++++++++++++++++ tests/compile_tests/timer/timer_stop.output | 1 + 7 files changed, 53 insertions(+) create mode 100644 tests/compile_tests/timer/timer_delayed_run.bas create mode 100644 tests/compile_tests/timer/timer_delayed_run.output create mode 100644 tests/compile_tests/timer/timer_first_run.bas create mode 100644 tests/compile_tests/timer/timer_first_run.output create mode 100644 tests/compile_tests/timer/timer_stop.bas create mode 100644 tests/compile_tests/timer/timer_stop.output diff --git a/internal/c/qbx.cpp b/internal/c/qbx.cpp index 6ae40d802..ba2991a5a 100755 --- a/internal/c/qbx.cpp +++ b/internal/c/qbx.cpp @@ -2073,6 +2073,9 @@ void sub_timer(int32 i, int32 option, int32 passed) { // ref: uint8 active;//0=OFF, 1=ON, 2=STOP if (option == 1) { // ON ontimer[i].active = 1; + + // This is necessary so that if a timer triggered while stopped we will run it now. + qbevent = 1; return; } if (option == 2) { // OFF diff --git a/tests/compile_tests/timer/timer_delayed_run.bas b/tests/compile_tests/timer/timer_delayed_run.bas new file mode 100644 index 000000000..5b3eb0ddb --- /dev/null +++ b/tests/compile_tests/timer/timer_delayed_run.bas @@ -0,0 +1,15 @@ +$Console:Only + +On Timer(2) GoSub timerhand + +' This first delay should not matter +_Delay 3 +Timer On + +' The timer should be triggered twice +_Delay 5 +System + +timerhand: +Print "Timer!" +return diff --git a/tests/compile_tests/timer/timer_delayed_run.output b/tests/compile_tests/timer/timer_delayed_run.output new file mode 100644 index 000000000..9b9060b4c --- /dev/null +++ b/tests/compile_tests/timer/timer_delayed_run.output @@ -0,0 +1,2 @@ +Timer! +Timer! diff --git a/tests/compile_tests/timer/timer_first_run.bas b/tests/compile_tests/timer/timer_first_run.bas new file mode 100644 index 000000000..ddc2f66ad --- /dev/null +++ b/tests/compile_tests/timer/timer_first_run.bas @@ -0,0 +1,12 @@ +$Console:Only + +On Timer(2) GoSub timerhand +Timer On + +' Timer should be triggered twice +_Delay 5 +System + +timerhand: +Print "Timer!" +return diff --git a/tests/compile_tests/timer/timer_first_run.output b/tests/compile_tests/timer/timer_first_run.output new file mode 100644 index 000000000..9b9060b4c --- /dev/null +++ b/tests/compile_tests/timer/timer_first_run.output @@ -0,0 +1,2 @@ +Timer! +Timer! diff --git a/tests/compile_tests/timer/timer_stop.bas b/tests/compile_tests/timer/timer_stop.bas new file mode 100644 index 000000000..fab87d629 --- /dev/null +++ b/tests/compile_tests/timer/timer_stop.bas @@ -0,0 +1,18 @@ +$Console:Only + +On Timer(2) GoSub timerhand +Timer On +Timer Stop + +' Timer will not trigger when stopped +Sleep 3 + +' Timer should trigger immediately when started as two seconds have elapsed +' while it was stopped +Timer On +Timer Off 'Shouldn't matter, timer triggers as soon as Timer On runs +System + +timerhand: +Print "Timer!" +return diff --git a/tests/compile_tests/timer/timer_stop.output b/tests/compile_tests/timer/timer_stop.output new file mode 100644 index 000000000..5d4a3dfd7 --- /dev/null +++ b/tests/compile_tests/timer/timer_stop.output @@ -0,0 +1 @@ +Timer!