[racket-dev] [plt] Push #29062: master branch updated

From: Matthias Felleisen (matthias at ccs.neu.edu)
Date: Sat Jul 19 19:20:31 EDT 2014

Thanks for the extensive internal documentation. -- Matthias



On Jul 19, 2014, at 7:07 PM, gcooper at racket-lang.org wrote:

> gcooper has updated `master' from 45306397cc to 2881b60536.
>  http://git.racket-lang.org/plt/45306397cc..2881b60536
> 
> =====[ One Commit ]=====================================================
> Directory summary:
> 100.0% pkgs/frtime/
> 
> ~~~~~~~~~~
> 
> 2881b60 Gregory Cooper <ghcooper at gmail.com> 2014-07-19 16:06
> :
> | Rewrite the delay-by primitive so it's easier to understand.
> |
> | Also, add comments that attempt to explain how it's intended to work.
> :
>  M pkgs/frtime/lang-ext.rkt | 139 ++++++++++++++++++++++++++++++++------------
> 
> =====[ Overall Diff ]===================================================
> 
> pkgs/frtime/lang-ext.rkt
> ~~~~~~~~~~~~~~~~~~~~~~~~
> --- OLD/pkgs/frtime/lang-ext.rkt
> +++ NEW/pkgs/frtime/lang-ext.rkt
> @@ -3,6 +3,7 @@
>                      (only-in racket/list first second last-pair empty empty?))
>          (only-in racket/list first second cons? empty empty? rest last-pair)
>          (only-in racket/function identity)
> +         data/queue
>          (only-in frtime/core/frp super-lift undefined undefined? behavior? do-in-manager-after do-in-manager proc->signal set-signal-thunk! register unregister 
>                   signal? signal-depth signal:switching? signal-value value-now signal:compound? signal:compound-content signal:switching-current signal:switching-trigger 
>                   set-cell! snap? iq-enqueue value-now/no-copy event-receiver event-set? proc->signal:switching set-signal-producers! set-signal-depth! safe-signal-depth 
> @@ -403,46 +404,110 @@
>     (set-signal-value! ret ((signal-thunk ret)))
>     ret))
> 
> -; XXX general efficiency fix for delay
> -; signal[a] signal[num] -> signal[a]
> -(define (delay-by beh ms-b)
> -  (letrec ([last (mcons (cons (if (zero? (value-now ms-b))
> -                                  (value-now/no-copy beh)
> -                                  undefined)
> -                              (current-inexact-milliseconds))
> -                        empty)]
> -           [head last]          
> -           [consumer #f]
> -           [producer (proc->signal
> -                      (lambda ()
> -                        (let* ([now (and (signal? consumer) (current-inexact-milliseconds))]
> -                               [ms (value-now ms-b)])
> -                          (let loop ()
> -                            (if (or (empty? (mcdr head))
> -                                    (< now (+ ms (cdr (mcar (mcdr head))))))
> -                              (let ([val (car (mcar head))])
> -                                (if (event-set? val)
> -                                  (make-events-now (event-set-events val))
> -                                  val))
> -                              (begin
> -                                (set! head (mcdr head))
> -                                (loop)))))))])
> +;; signal[a] num -> signal[a]
> +;;
> +;; Returns a signal whose value at (approximately) time (+ t |delay-millis|) is a (deep) snapshot
> +;; of the value of |sig| at time t, for all times t from now on. For earlier times, the value of the
> +;; returned signal is undefined.
> +;;
> +;; Assumptions: (current-inexact-milliseconds) is monotonically non-decreasing; |delay-millis| is
> +;; positive and finite.
> +(define (delay-by sig delay-millis)
> +  ;; Implementation strategy:
> +  ;;
> +  ;; Maintain a queue of pairs (snapshot . timestamp) of the observed signal going back in
> +  ;; time for at least |delay-millis|. Start with (undefined . -inf.0) and (current-value . now), so
> +  ;; there should always be at least one item (value . timestamp) in the queue such that
> +  ;; (>= now (+ timestamp delay-millis)).
> +  ;;
> +  ;; |consumer| runs whenever |sig| changes and adds an item with the observed value and current
> +  ;;     time to the queue; schedules |producer| to run at |delay-millis| in the future, by which
> +  ;;     time it should be ready to take on that observed value.
> +  ;;
> +  ;; |producer| has no dependencies recorded in the dataflow graph and only runs when scheduled
> +  ;;     by the consumer. (This is what allows delay-by to break cycles.) It traverses the queue
> +  ;;     looking for the latest observation (value . timestamp) such that
> +  ;;     (>= now (+ timestamp delay-millis)), and takes on the observed value. |producer| is the
> +  ;;     value returned by this procedure, so it stays alive as long as anything cares about its
> +  ;;     value.
> +  (let* ([queue (make-queue)]
> +         
> +         ;; finish : (a . num) a -> a
> +         ;; Puts |queue-item| back on the front of the queue and returns |val|, updating the
> +         ;; occurrence timestamp if |val| represents an event set.
> +         ;; TODO(gcooper): We could avoid this if data/queue supported a "peek" operation.
> +         [finish! (lambda (queue-item val)
> +                    (enqueue-front! queue queue-item)
> +                    (if (event-set? val)
> +                        (make-events-now (event-set-events val))
> +                        val))]
> +         [now-millis (current-inexact-milliseconds)]
> +          
> +         [_ (begin
> +              ;; Add initial observations to the queue.
> +              (enqueue! queue (cons undefined -inf.0))
> +              (enqueue! queue (cons (deep-value-now sig empty) now-millis)))]
> +         
> +         ;; |consumer|'s thunk needs |producer| to be in scope so it can schedule it, and
> +         ;; |producer|'s thunk needs |consumer| to be in scope so it can keep it alive. To set up
> +         ;; this cycle, we first create |consumer| with a dummy thunk (void), then define
> +         ;; |producer|, and finally update |consumer|'s thunk to what we want it to be.
> +         [consumer (proc->signal void sig)]
> +         [producer (proc->signal
> +                    (lambda ()
> +                      (let ([now-millis (current-inexact-milliseconds)])
> +                        ;; There's no way to "peek" at the next item in the queue, so we have to
> +                        ;; dequeue it, check whether we're ready for it, and if not, stick it back
> +                        ;; on the front...
> +                        (let loop ([front (dequeue! queue)])
> +                          ;; |val| is our current candidate value; we'll use it if there's no later
> +                          ;; observation that's at least |delay-millis| old.
> +                          (let* ([val (car front)])
> +                            (if (queue-empty? queue)
> +                                ;; There are no later observations to consider, so use the current
> +                                ;; one.
> +                                (finish! front val)
> +                                ;; Look at the next item in the queue to see if we're ready for it.
> +                                ;; If so, recur. Otherwise, put it back on the front of the queue
> +                                ;; and use the previous value.
> +                                (let* ([next (dequeue! queue)]
> +                                       [timestamp-millis (cdr next)])
> +                                  ;; Kludge: since there's nothing that would otherwise keep
> +                                  ;; |consumer| alive, we retain a reference to it here, and we
> +                                  ;; trick the runtime into not optimizing it away by calling a
> +                                  ;; predicate and using the result in a conditional expression. If
> +                                  ;; the compiler ever gets smart enough to determine that the
> +                                  ;; outcome is provably always true, and therefore that it can
> +                                  ;; optimize away this code, we'll have to come up with a
> +                                  ;; different strategy (e.g., adding a special field to the signal
> +                                  ;; structure).
> +                                  (if (and (signal? consumer)
> +                                           (< now-millis (+ timestamp-millis delay-millis)))
> +                                      ;; We're not ready for the next value yet, so push it back
> +                                      ;; and proceed with the previous value.
> +                                      (begin
> +                                        (enqueue-front! queue next)
> +                                        (finish! front val))
> +                                      (loop next)))))))))])
>     (begin
> -      (set! consumer (proc->signal
> -                      (lambda ()
> -                        (let* ([now (current-inexact-milliseconds)]
> -                               [new (deep-value-now beh empty)]
> -                               [ms (value-now ms-b)])
> -                          (when (not (equal? new (car (mcar last))))
> -                            (set-mcdr! last (mcons (cons new now)
> -                                                   empty))
> -                            (set! last (mcdr last))
> -                            (schedule-alarm (+ now ms) producer))))
> -                      beh ms-b))
> +      (set-signal-thunk!
> +       consumer
> +       (lambda ()
> +         (let* ([now-millis (current-inexact-milliseconds)]
> +                [new-value (deep-value-now sig empty)])
> +           ;; Record the current observation and schedule |producer| to run when it's time to take
> +           ;; on this value.
> +           (enqueue! queue (cons new-value now-millis))
> +           (schedule-alarm (+ now-millis delay-millis) producer))))
> +      
> +      ;; Make sure producer is scheduled to run as soon as there's a value ready for it.
> +      (schedule-alarm (+ now-millis delay-millis) producer)
>       producer)))
> 
> -(define (inf-delay beh)
> -  (delay-by beh 0))
> +;; signal[a] -> signal[a]
> +;; Delays |sig| by the smallest possible amount of time.
> +(define (inf-delay sig)
> +  (delay-by sig 0))
> 
> ; XXX fix to take arbitrary monotonically increasing number
> ; (instead of milliseconds)



Posted on the dev mailing list.