tetris: use monotonic clock for fall timeout

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

tetris: use monotonic clock for fall timeout

Scott Cheloha
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 | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: tetris: use monotonic clock for fall timeout

Scott Cheloha
1 week bump.

--
Scott Cheloha

> On Aug 5, 2017, at 8:25 PM, Scott Cheloha <[hidden email]> wrote:
>
> 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

Loading...