Re: tetris: use monotonic clock for fall timeout

2017-08-12 Thread Scott Cheloha
1 week bump.

--
Scott Cheloha

> On Aug 5, 2017, at 8:25 PM, Scott Cheloha  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 -  1.18
> +++ games/tetris/input.c  6 Aug 2017 01:21:41 -
> @@ -40,89 +40,75 @@
>  */
> 
> #include 
> +
> #include 
> #include 
> +#include 
> #include 
> 
> #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 += 100; \
> - (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(, 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, );
> +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(, NILTZ);
> - TV_SUB(, );
> - TV_SUB(tvp, );/* adjust *tvp by elapsed time */
> + if (limit != NULL) {
> + /* we have input, so subtract the elapsed time from *limit */
> + clock_gettime(CLOCK_MONOTONIC, );
> + timespecsub(, , );
> + timespecsub(limit, , 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())
> - if (rwait() && read(STDIN_FILENO, , 1) != 1)
> + ts.tv_sec = 0;
> + ts.tv_nsec = fallrate;
> + while (TS_POS())
> + if (rwait() && read(STDIN_FILENO, , 1) != 1)
>   break;
> }
> 
> @@ -132,7 +118,7 @@ 

tetris: use monotonic clock for fall timeout

2017-08-05 Thread 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.c27 Aug 2016 02:02:44 -  1.18
+++ games/tetris/input.c6 Aug 2017 01:21:41 -
@@ -40,89 +40,75 @@
  */
 
 #include 
+
 #include 
 #include 
+#include 
 #include 
 
 #include "input.h"
 #include "tetris.h"
 
-/* return true iff the given timeval is positive */
-#defineTV_POS(tv) \
-   ((tv)->tv_sec > 0 || ((tv)->tv_sec == 0 && (tv)->tv_usec > 0))
-
-/* subtract timeval `sub' from `res' */
-#defineTV_SUB(res, sub) \
-   (res)->tv_sec -= (sub)->tv_sec; \
-   (res)->tv_usec -= (sub)->tv_usec; \
-   if ((res)->tv_usec < 0) { \
-   (res)->tv_usec += 100; \
-   (res)->tv_sec--; \
-   }
+/* return true iff the given timespec is positive */
+#defineTS_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];
 
-#defineNILTZ ((struct timezone *)0)
-
-   if (tvp) {
-   (void) gettimeofday(, 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, );
+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(, NILTZ);
-   TV_SUB(, );
-   TV_SUB(tvp, );/* adjust *tvp by elapsed time */
+   if (limit != NULL) {
+   /* we have input, so subtract the elapsed time from *limit */
+   clock_gettime(CLOCK_MONOTONIC, );
+   timespecsub(, , );
+   timespecsub(limit, , 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())
-   if (rwait() && read(STDIN_FILENO, , 1) != 1)
+   ts.tv_sec = 0;
+   ts.tv_nsec = fallrate;
+   while (TS_POS())
+   if (rwait() && read(STDIN_FILENO, , 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().
 */
-