gui/gui-lib/mred/private/wx/common/queue.rkt
2015-01-06 19:54:52 -07:00

681 lines
27 KiB
Racket

#lang racket/base
(require ffi/unsafe
racket/draw/private/utils
ffi/unsafe/atomic
racket/class
racket/port
"rbtree.rkt"
"../../lock.rkt"
"handlers.rkt"
"once.rkt"
"keep-forever.rkt")
(provide
(protect-out queue-evt
set-check-queue!
set-queue-wakeup!
add-event-boundary-callback!
add-event-boundary-sometimes-callback!
remove-event-boundary-callback!
pre-event-sync
boundary-tasks-ready-evt
sometimes-delay-msec
set-platform-queue-sync!
eventspace?
current-eventspace
queue-event
queue-refresh-event
yield
yield/no-sync
yield-refresh
eventspace-event-evt
(rename-out [make-new-eventspace make-eventspace])
event-dispatch-handler
eventspace-shutdown?
main-eventspace?
eventspace-handler-thread
eventspace-event-evt
eventspace-wait-cursor-count
eventspace-extra-table
eventspace-adjust-external-modal!
queue-callback
middle-queue-key
make-timer-callback
add-timer-callback
remove-timer-callback
register-frame-shown
get-top-level-windows
other-modal?
queue-quit-event
queue-prefs-event
queue-about-event
queue-file-event
queue-start-empty-event
begin-busy-cursor
end-busy-cursor
is-busy?)
scheme_register_process_global)
;; ------------------------------------------------------------
;; Create a Scheme evt that is ready when a queue is nonempty
(define _Scheme_Type _short)
(define-mz scheme_make_type (_fun _string -> _Scheme_Type))
(define event-queue-type (scheme_make_type "event-queue"))
(define-mz scheme_add_evt (_fun _Scheme_Type
(_fun #:atomic? #t _scheme -> _int)
(_fun #:atomic? #t _scheme _gcpointer -> _void)
_pointer
_int
-> _void))
(define (do-check-queue) #f)
(define (do-queue-wakeup fds) #f)
(define (check-queue o)
(if (do-check-queue) 1 0))
(define (queue-wakeup o fds)
(do-queue-wakeup fds))
(scheme_add_evt event-queue-type check-queue queue-wakeup #f 0)
(keep-forever check-queue)
(keep-forever queue-wakeup)
(define queue-evt (let ([p (malloc 16)]
[p2 (malloc 'nonatomic _pointer)])
(memset p 0 16)
(ptr-set! p _Scheme_Type event-queue-type)
(ptr-set! p2 _pointer p)
(ptr-ref p2 _scheme)))
(define (set-check-queue! check)
(set! do-check-queue check))
(define (set-queue-wakeup! wake)
(set! do-queue-wakeup wake))
;; ------------------------------------------------------------
;; Pre-event sync
(define boundary-ht (make-hasheq))
(define sometimes-boundary-ht (make-hasheq))
(define tasks-ready? #f)
(define task-ready-sema (make-semaphore))
(define boundary-tasks-ready-evt (semaphore-peek-evt task-ready-sema))
(define (alert-tasks-ready)
(let ([ready? (or (positive? (hash-count boundary-ht))
(positive? (hash-count sometimes-boundary-ht)))])
(unless (eq? ready? tasks-ready?)
(set! tasks-ready? ready?)
(if ready?
(semaphore-post task-ready-sema)
(semaphore-wait task-ready-sema)))))
(define (add-event-boundary-callback! v proc)
(atomically
(hash-set! boundary-ht v proc)
(alert-tasks-ready)))
(define (add-event-boundary-sometimes-callback! v proc)
(atomically
(when (zero? (hash-count sometimes-boundary-ht))
(set! last-time (current-inexact-milliseconds)))
(hash-set! sometimes-boundary-ht v proc)
(alert-tasks-ready)))
(define (remove-event-boundary-callback! v)
(atomically
(hash-remove! boundary-ht v)
(hash-remove! sometimes-boundary-ht v)
(alert-tasks-ready)))
(define last-time -inf.0)
(define sometimes-delay-msec 100)
;; Call this function only in atomic mode:
(define (pre-event-sync force?)
(let ([now (current-inexact-milliseconds)])
(when (or (now . > . (+ last-time sometimes-delay-msec))
force?)
(set! last-time now)
(hash-for-each sometimes-boundary-ht
(lambda (v p) (hash-remove! sometimes-boundary-ht v) (p v)))))
(hash-for-each boundary-ht (lambda (v p) (hash-remove! boundary-ht v) (p v)))
(alert-tasks-ready))
;; ------------------------------------------------------------
;; Eventspaces
(define-struct eventspace (handler-thread
queue-proc
frames-hash
done-evt
[shutdown? #:mutable]
done-sema
[wait-cursor-count #:mutable]
extra-table
[external-modal #:mutable])
#:property prop:evt (lambda (v)
(wrap-evt (eventspace-done-evt v)
(lambda (_) v))))
(define-struct timed (alarm-evt msecs val [id #:mutable]))
(define (make-timer-callback msecs thunk)
(make-timed (alarm-evt msecs)
msecs
thunk
0))
(define (timed-compare a b)
(if (eq? a b)
0
(let ([am (timed-msecs a)]
[bm (timed-msecs b)])
(cond
[(= am bm) (if ((timed-id a) . < . (timed-id b))
-1
1)]
[(< am bm) -1]
[else 1]))))
;; This table refers to handle threads of eventspaces
;; that have an open window, etc., so that the eventspace
;; isn't GCed
(define active-eventspaces (make-hasheq))
(define current-cb-box (make-parameter #f))
(define-mz scheme_add_managed (_fun _racket ; custodian
_racket ; object
(_fun #:atomic? #t #:keep (lambda (v) (set-box! (current-cb-box) v))
_racket _racket -> _void)
_racket ; data
_int ; strong?
-> _gcpointer))
(define (shutdown-eventspace! e ignored)
;; atomic mode
(unless (eventspace-shutdown? e)
(set-eventspace-shutdown?! e #t)
(semaphore-post (eventspace-done-sema e))
(for ([f (in-list (get-top-level-windows e))])
(send f destroy))
(hash-remove! active-eventspaces (eventspace-handler-thread e))))
(define platform-queue-sync void)
(define (set-platform-queue-sync! proc)
(set! platform-queue-sync proc))
(define (make-eventspace* th)
(let ([done-sema (make-semaphore 1)]
[done-set? #t]
[frames (make-hasheq)])
(let ([e
(make-eventspace th
(let ([count 0])
(let ([lo (mcons #f #f)]
[refresh (mcons #f #f)]
[med (mcons #f #f)]
[hi (mcons #f #f)]
[timer (box '())]
[timer-counter 0]
[newly-posted-sema (make-semaphore)])
(let* ([check-done
(lambda ()
(if (or (positive? count)
(positive? (hash-count frames))
(not (null? (unbox timer))))
(when done-set?
(hash-set! active-eventspaces th #t)
(set! done-set? #f)
(semaphore-try-wait? done-sema))
(unless done-set?
(hash-remove! active-eventspaces th)
(set! done-set? #t)
(semaphore-post done-sema))))]
[enqueue (lambda (v q)
(set! count (add1 count))
(check-done)
(let ([p (mcons v #f)])
(if (mcdr q)
(set-mcdr! (mcdr q) p)
(set-mcar! q p))
(set-mcdr! q p)))]
[first (lambda (q peek?)
(and (mcar q)
(if peek?
always-evt
(wrap-evt
always-evt
(lambda (_)
(start-atomic)
(set! count (sub1 count))
(check-done)
(let ([result (mcar (mcar q))])
(set-mcar! q (mcdr (mcar q)))
(unless (mcar q)
(set-mcdr! q #f))
(end-atomic)
result))))))]
[remove-timer
(lambda (v timer)
(set-box! timer (rbtree-remove
timed-compare
v
(unbox timer)))
(check-done))]
[timer-first-ready
(lambda (timer peek?)
(let ([rb (unbox timer)])
(and (not (null? rb))
(let* ([v (rbtree-min (unbox timer))]
[evt (timed-alarm-evt v)])
(and (sync/timeout 0 evt)
;; It's ready
(if peek?
always-evt
(wrap-evt
always-evt
(lambda (_)
(start-atomic)
(remove-timer v timer)
(end-atomic)
(timed-val v)))))))))]
[timer-first-wait
(lambda (timer peek?)
(let ([rb (unbox timer)])
(and (not (null? rb))
(wrap-evt
(timed-alarm-evt (rbtree-min (unbox timer)))
(lambda (_) #f)))))]
[make-event-choice
(lambda (peek? sync?)
(choice-evt
(wrap-evt (semaphore-peek-evt newly-posted-sema)
(lambda (_) #f))
(or (first hi peek?)
(timer-first-ready timer peek?)
(first refresh peek?)
(first med peek?)
(and (not peek?)
sync?
;; before going with low-priority events,
;; make sure we're sync'ed up with the
;; GUI platform's event queue:
(platform-queue-sync)
(first med peek?))
(first lo peek?)
(timer-first-wait timer peek?)
;; nothing else ready...
never-evt)))])
(case-lambda
[(v)
;; Enqueue
(start-atomic)
(let ([val (cdr v)])
(case (car v)
[(lo) (enqueue val lo)]
[(refresh) (enqueue val refresh)]
[(med) (enqueue val med)]
[(hi) (enqueue val hi)]
[(timer-add)
(set! timer-counter (add1 timer-counter))
(set-timed-id! val timer-counter)
(set-box! timer
(rbtree-insert
timed-compare
val
(unbox timer)))
(check-done)]
[(timer-remove) (remove-timer val timer)]
[(frame-add) (hash-set! frames val #t) (check-done)]
[(frame-remove) (hash-remove! frames val) (check-done)]))
(semaphore-post newly-posted-sema)
(set! newly-posted-sema (make-semaphore))
(check-done)
(end-atomic)]
[()
;; Dequeue as evt
(start-atomic)
(begin0
(make-event-choice #f #t)
(end-atomic))]
[(only-refresh? peek? sync?)
(start-atomic)
(begin0
(cond
[only-refresh?
;; Dequeue only refresh event
(or (first refresh peek?) never-evt)]
[else
(make-event-choice peek? sync?)])
(end-atomic))]))))
frames
(semaphore-peek-evt done-sema)
#f
done-sema
0
(make-hash)
0)]
[cb-box (box #f)])
(parameterize ([current-cb-box cb-box])
(scheme_add_managed (current-custodian)
e
shutdown-eventspace!
cb-box ; retain callback until it's called
0))
e)))
(define main-eventspace (make-eventspace* (current-thread)))
(define current-eventspace (make-parameter main-eventspace))
;; So we can get from a thread to the eventspace that
;; it handles (independent of the `current-eventspace'
;; parameter):
(define handler-thread-of (make-thread-cell #f))
(thread-cell-set! handler-thread-of main-eventspace)
(define make-new-eventspace
(let ([make-eventspace
(lambda ()
(define pause (make-semaphore))
(define break-paramz (current-break-parameterization))
(define es
(make-eventspace*
(parameterize-break
#f ; disable breaks until we're in the yield loop
(thread
(lambda ()
(sync pause) ; wait until `es' has a value
(thread-cell-set! handler-thread-of es)
(current-eventspace es)
(let loop ()
(call-with-continuation-prompt
(lambda ()
;; re-enable breaks (if they are supposed to be enabled):
(call-with-break-parameterization
break-paramz
(lambda ()
;; yield; any abort (including a break exception)
;; will get caught and the loop will yield again
(yield (make-semaphore))))))
(loop)))))))
(semaphore-post pause) ; `es' has a value
es)])
make-eventspace))
(define (queue-event eventspace thunk [level 'med])
((eventspace-queue-proc eventspace) (cons level thunk)))
(define (queue-refresh-event eventspace thunk)
((eventspace-queue-proc eventspace) (cons 'refresh thunk)))
(define dispatch-event-prompt (make-continuation-prompt-tag))
(define dispatch-event-key (gensym))
(define (really-dispatch-event e)
(let ([b (continuation-mark-set-first
#f
dispatch-event-key
#f
dispatch-event-prompt)])
(unless b
(error 'default-event-dispatch-handler
"not in an event-dispatch context"))
(let ([thunk (atomically
(begin0
(unbox b)
(set-box! b #f)))])
(unless thunk
(error 'default-event-dispatch-handler
"event in current context was already dispatched"))
(thunk))))
(define event-dispatch-handler (make-parameter really-dispatch-event))
(define event-logger (make-logger 'gui-event (current-logger)))
;; start? : boolean -- indicates if this is a start of an event being handled or not
;; msec : start time if start? is #t, delta from start to end if start? is #f
;; name : (or/c #f symbol?)
(struct gui-event (start end name) #:prefab)
(define (handle-event thunk e)
(call-with-continuation-prompt ; to delimit continuations
(lambda ()
(call-with-continuation-prompt ; to delimit search for dispatch-event-key
(lambda ()
;; communicate the thunk to `really-dispatch-event':
(define before (current-inexact-milliseconds))
(when (log-level? event-logger 'debug)
(log-message event-logger 'debug
(format "starting to handle an event from ~a" (object-name thunk))
(gui-event before #f (object-name thunk))))
(let ([b (box thunk)])
;; use the event-dispatch handler:
(with-continuation-mark dispatch-event-key b
((event-dispatch-handler) e))
;; if the event-dispatch handler doesn't chain
;; to the original one, then do so now:
(when (unbox b)
(set-box! b #f)
(thunk)))
(define after (current-inexact-milliseconds))
(when (log-level? event-logger 'debug)
(log-message event-logger 'debug
(format "handled an event: ~a msec"
(- after before))
(gui-event before after (object-name thunk)))))
dispatch-event-prompt))))
(define yield
(case-lambda
[()
(let ([e (current-eventspace)])
(if (eq? (current-thread) (eventspace-handler-thread e))
(let ([v (sync/timeout 0 ((eventspace-queue-proc e)))])
(if v
(begin (handle-event v e) #t)
#f))
#f))]
[(evt)
(unless (or (evt? evt)
(eq? evt 'wait))
(raise-type-error 'yield "evt or 'wait" evt))
(let* ([e (current-eventspace)]
[handler? (eq? (current-thread) (eventspace-handler-thread e))])
(cond
[(and (eq? evt 'wait)
(not handler?))
#t]
[else
(define (wait-now)
(if handler?
(sync (if (eq? evt 'wait)
(wrap-evt e (lambda (_) #t))
evt)
(handle-evt ((eventspace-queue-proc e))
(lambda (v)
(when v (handle-event v e))
(yield evt))))
(sync evt)))
(if (evt? evt)
;; `yield' is supposed to return immediately if the
;; event is already ready:
(sync/timeout wait-now evt)
(wait-now))]))]))
(define (yield/no-sync)
(let ([e (current-eventspace)])
(when (eq? (current-thread) (eventspace-handler-thread e))
(let ([v (sync/timeout 0 ((eventspace-queue-proc e) #f #f #f))])
(if v
(begin (handle-event v e) #t)
#f)))))
(define yield-refresh
(lambda ()
(let ([e (current-eventspace)])
(and (eq? (current-thread) (eventspace-handler-thread e))
(let loop ([result #f])
(let ([v (sync/timeout 0 ((eventspace-queue-proc e) #t #f #t))])
(if v
(begin
(handle-event v e)
(loop #t))
result)))))))
(define (eventspace-event-evt [e (current-eventspace)])
(unless (eventspace? e)
(raise-type-error 'eventspace-event-evt "eventspace" e))
(wrap-evt ((eventspace-queue-proc e) #f #t #t)
(lambda (_) e)))
(define (main-eventspace? e)
(eq? e main-eventspace))
(define (queue-callback thunk [high? #t])
(let ([es (current-eventspace)])
(when (eventspace-shutdown? es)
(error 'queue-callback "eventspace is shutdown: ~e" es))
(queue-event es thunk (cond
[(not high?) 'lo]
[(eq? high? middle-queue-key) 'med]
[else 'hi]))))
(define middle-queue-key (gensym 'middle))
(define (add-timer-callback cb es)
;; in atomic mode
(queue-event es cb 'timer-add))
(define (remove-timer-callback cb es)
;; in atomic mode
(unless (eventspace-shutdown? es)
(queue-event es cb 'timer-remove)))
(define (register-frame-shown f on?)
(queue-event (send f get-eventspace) f (if on?
'frame-add
'frame-remove)))
(define (get-top-level-windows [e (current-eventspace)])
;; called in event-pump thread
(hash-map (eventspace-frames-hash e)
(lambda (k v) k)))
(define (other-modal? win [e #f] [ignore-win #f])
;; called in atomic mode in eventspace's thread
(and
;; deliver mouse-motion events even if a modal window
;; is open
(or (not e)
(not (or (send e leaving?)
(send e entering?)
(send e moving?))))
;; for any other kind of mouse or key event, deliver only
;; if no model dialog is open
(let ([es (send win get-eventspace)])
(or (positive? (eventspace-external-modal es))
(let loop ([frames (get-top-level-windows es)])
(and (pair? frames)
(let ([status (if (eq? ignore-win (car frames))
#f
(send (car frames) frame-relative-dialog-status win))])
(case status
[(#f) (loop (cdr frames))]
[(same) (loop (cdr frames))]
[(other) #t]))))))))
(define (eventspace-adjust-external-modal! es amt)
(atomically
(set-eventspace-external-modal!
es
(+ (eventspace-external-modal es) amt))))
(define (queue-quit-event)
;; called in event-pump thread
(queue-event main-eventspace (application-quit-handler) 'med))
(define (queue-prefs-event)
;; called in event-pump thread
(queue-event main-eventspace (application-pref-handler) 'med))
(define (queue-about-event)
;; called in event-pump thread
(queue-event main-eventspace (application-about-handler) 'med))
(define (queue-file-event file)
;; called in event-pump thread
(queue-event main-eventspace (lambda ()
((application-file-handler) file))
'med))
(define (queue-start-empty-event)
;; called in event-pump thread
(queue-event main-eventspace (application-start-empty-handler)
'med))
(define (begin-busy-cursor)
(let ([e (current-eventspace)])
(atomically
(set-eventspace-wait-cursor-count!
e
(add1 (eventspace-wait-cursor-count e)))
(when (= (eventspace-wait-cursor-count e) 1)
(for ([e (in-list (get-top-level-windows))])
(send e set-wait-cursor-mode #t))))))
(define (end-busy-cursor)
(let ([e (current-eventspace)])
(atomically
(set-eventspace-wait-cursor-count!
e
(sub1 (eventspace-wait-cursor-count e)))
(when (zero? (eventspace-wait-cursor-count e))
(for ([e (in-list (get-top-level-windows))])
(send e set-wait-cursor-mode #f))))))
(define (is-busy?) (positive? (eventspace-wait-cursor-count (current-eventspace))))
;; ----------------------------------------
;; Before exiting, wait until frames are closed, etc.:
(executable-yield-handler
(let ([old-eyh (executable-yield-handler)])
(lambda (v)
(yield main-eventspace)
(old-eyh v))))
;; When using a REPL in a thread that has an eventspace,
;; yield to events when the port would block.
(current-get-interaction-input-port
(let ([orig (current-get-interaction-input-port)])
(lambda ()
(let ([e (thread-cell-ref handler-thread-of)])
(if e
(let ([filter (lambda (v)
(cond
[(eq? v 0) (yield) 0]
[(evt? v)
(parameterize ([current-eventspace e])
(yield))
(choice-evt v
(wrap-evt (eventspace-event-evt e)
(lambda (_) 0)))]
[else v]))])
(filter-read-input-port
(orig)
(lambda (str v)
(filter v))
(lambda (s skip evt v)
(filter v))))
(orig))))))