Hi,

In tetris(6) we use gettimeofday(2) to determine (roughly) how
long we polled for user input.  This then gets subtracted from
the time remaining until we drop the block another row.

This should be computed with a monotonic clock instead, lest
bullshit like clock drift rob you of that crucial tenth of a
second and cost you your new high score.

Moving to a monotonic clock implies using nanoseconds instead of
microseconds, but this actually turns out to be kind of nice
because ppoll(2) accepts a timespec structure, so then we can
delete a few things we were using to jam a timeval into poll(2).

I've playtested a bit and it doesn't ~feel~ any different.  If
anything the game *should* feel less choppy under certain
conditions, though I can't really prove that.

Feedback?

--
Scott Cheloha

Index: games/tetris/input.c
===================================================================
RCS file: /cvs/src/games/tetris/input.c,v
retrieving revision 1.18
diff -u -p -r1.18 input.c
--- games/tetris/input.c        27 Aug 2016 02:02:44 -0000      1.18
+++ games/tetris/input.c        6 Aug 2017 01:21:41 -0000
@@ -40,89 +40,75 @@
  */
 
 #include <sys/time.h>
+
 #include <errno.h>
 #include <poll.h>
+#include <time.h>
 #include <unistd.h>
 
 #include "input.h"
 #include "tetris.h"
 
-/* return true iff the given timeval is positive */
-#define        TV_POS(tv) \
-       ((tv)->tv_sec > 0 || ((tv)->tv_sec == 0 && (tv)->tv_usec > 0))
-
-/* subtract timeval `sub' from `res' */
-#define        TV_SUB(res, sub) \
-       (res)->tv_sec -= (sub)->tv_sec; \
-       (res)->tv_usec -= (sub)->tv_usec; \
-       if ((res)->tv_usec < 0) { \
-               (res)->tv_usec += 1000000; \
-               (res)->tv_sec--; \
-       }
+/* return true iff the given timespec is positive */
+#define        TS_POS(ts) \
+       ((ts)->tv_sec > 0 || ((ts)->tv_sec == 0 && (ts)->tv_nsec > 0))
 
 /*
- * Do a `read wait': poll for reading from stdin, with timeout *tvp.
- * On return, modify *tvp to reflect the amount of time spent waiting.
+ * Do a `read wait': poll for reading from stdin, with timeout *limit.
+ * On return, subtract the time spent waiting from *limit.
  * It will be positive only if input appeared before the time ran out;
  * otherwise it will be zero or perhaps negative.
  *
- * If tvp is nil, wait forever, but return if poll is interrupted.
+ * If limit is NULL, wait forever, but return if poll is interrupted.
  *
- * Return 0 => no input, 1 => can read() from stdin
+ * Return 0 => no input, 1 => can read() from stdin, -1 => interrupted
  */
 int
-rwait(struct timeval *tvp)
+rwait(struct timespec *limit)
 {
-       int     timo = INFTIM;
-       struct timeval starttv, endtv;
+       struct timespec start, end, elapsed;
        struct pollfd pfd[1];
 
-#define        NILTZ ((struct timezone *)0)
-
-       if (tvp) {
-               (void) gettimeofday(&starttv, NILTZ);
-               endtv = *tvp;
-               timo = endtv.tv_sec * 1000 + endtv.tv_usec / 1000;
-       }
-again:
        pfd[0].fd = STDIN_FILENO;
        pfd[0].events = POLLIN;
-       switch (poll(pfd, 1, timo)) {
+
+       if (limit != NULL)
+               clock_gettime(CLOCK_MONOTONIC, &start);
+again:
+       switch (ppoll(pfd, 1, limit, NULL)) {
        case -1:
-               if (tvp == 0)
+               if (limit == NULL)
                        return (-1);
                if (errno == EINTR)
                        goto again;
                stop("poll failed, help");
-
        case 0: /* timed out */
-               tvp->tv_sec = 0;
-               tvp->tv_usec = 0;
+               timespecclear(limit);
                return (0);
        }
-       if (tvp) {
-               /* since there is input, we may not have timed out */
-               (void) gettimeofday(&endtv, NILTZ);
-               TV_SUB(&endtv, &starttv);
-               TV_SUB(tvp, &endtv);    /* adjust *tvp by elapsed time */
+       if (limit != NULL) {
+               /* we have input, so subtract the elapsed time from *limit */
+               clock_gettime(CLOCK_MONOTONIC, &end);
+               timespecsub(&end, &start, &elapsed);
+               timespecsub(limit, &elapsed, limit);
        }
        return (1);
 }
 
 /*
- * `sleep' for the current turn time (using poll).
- * Eat any input that might be available.
+ * `sleep' for the current turn time and eat any
+ * input that becomes available.
  */
 void
 tsleep(void)
 {
-       struct timeval tv;
+       struct timespec ts;
        char c;
 
-       tv.tv_sec = 0;
-       tv.tv_usec = fallrate;
-       while (TV_POS(&tv))
-               if (rwait(&tv) && read(STDIN_FILENO, &c, 1) != 1)
+       ts.tv_sec = 0;
+       ts.tv_nsec = fallrate;
+       while (TS_POS(&ts))
+               if (rwait(&ts) && read(STDIN_FILENO, &c, 1) != 1)
                        break;
 }
 
@@ -132,7 +118,7 @@ tsleep(void)
 int
 tgetchar(void)
 {
-       static struct timeval timeleft;
+       static struct timespec timeleft;
        char c;
 
        /*
@@ -144,10 +130,10 @@ tgetchar(void)
         *
         * Most of the hard work is done by rwait().
         */
-       if (!TV_POS(&timeleft)) {
+       if (!TS_POS(&timeleft)) {
                faster();       /* go faster */
                timeleft.tv_sec = 0;
-               timeleft.tv_usec = fallrate;
+               timeleft.tv_nsec = fallrate;
        }
        if (!rwait(&timeleft))
                return (-1);
Index: games/tetris/input.h
===================================================================
RCS file: /cvs/src/games/tetris/input.h,v
retrieving revision 1.5
diff -u -p -r1.5 input.h
--- games/tetris/input.h        3 Jun 2003 03:01:41 -0000       1.5
+++ games/tetris/input.h        6 Aug 2017 01:21:41 -0000
@@ -35,6 +35,6 @@
  *     @(#)input.h     8.1 (Berkeley) 5/31/93
  */
 
-int    rwait(struct timeval *);
+int    rwait(struct timespec *);
 int    tgetchar(void);
 void   tsleep(void);
Index: games/tetris/tetris.c
===================================================================
RCS file: /cvs/src/games/tetris/tetris.c,v
retrieving revision 1.31
diff -u -p -r1.31 tetris.c
--- games/tetris/tetris.c       10 Jun 2016 13:07:07 -0000      1.31
+++ games/tetris/tetris.c       6 Aug 2017 01:21:41 -0000
@@ -197,7 +197,7 @@ main(int argc, char *argv[])
        if (argc)
                usage();
 
-       fallrate = 1000000 / level;
+       fallrate = 1000000000L / level;
 
        for (i = 0; i <= 5; i++) {
                for (j = i+1; j <= 5; j++) {
@@ -280,7 +280,7 @@ main(int argc, char *argv[])
                                scr_msg(key_msg, 0);
                                scr_msg(msg, 1);
                                (void) fflush(stdout);
-                       } while (rwait((struct timeval *)NULL) == -1);
+                       } while (rwait(NULL) == -1);
                        scr_msg(msg, 0);
                        scr_msg(key_msg, 1);
                        place(curshape, pos, 0);
Index: games/tetris/tetris.h
===================================================================
RCS file: /cvs/src/games/tetris/tetris.h,v
retrieving revision 1.11
diff -u -p -r1.11 tetris.h
--- games/tetris/tetris.h       20 Nov 2015 07:40:23 -0000      1.11
+++ games/tetris/tetris.h       6 Aug 2017 01:21:41 -0000
@@ -135,15 +135,15 @@ extern const struct shape *nextshape;
 /*
  * Shapes fall at a rate faster than once per second.
  *
- * The initial rate is determined by dividing 1 million microseconds
- * by the game `level'.  (This is at most 1 million, or one second.)
- * Each time the fall-rate is used, it is decreased a little bit,
+ * The initial rate is determined by dividing 1 billion nanoseconds
+ * by the game `level'.  (This is at most 1 billion, or one second.)
+ * Each time the fallrate is used, it is decreased a little bit,
  * depending on its current value, via the `faster' macro below.
  * The value eventually reaches a limit, and things stop going faster,
  * but by then the game is utterly impossible.
  */
-extern long    fallrate;       /* less than 1 million; smaller => faster */
-#define        faster() (fallrate -= fallrate / 3000)
+extern long    fallrate;       /* less than 1 billion; smaller => faster */
+#define        faster() (fallrate -= fallrate / 3000000)
 
 /*
  * Game level must be between 1 and 9.  This controls the initial fall rate

Reply via email to