[ltk-user] *wish* global

Thomas F. Burdick tfb at ocf.berkeley.edu
Sun Jun 24 10:41:41 UTC 2007


Hi Shawn,

Thanks for the clear example code; it's a lot easier to support users
who are clear about what they're trying to do :-)

On 6/24/07, Shawn Betts <sabetts at vcn.bc.ca> wrote:
> Hi Peter,
>
> Sorry for the delay. I'll try to illustrate what I'm doing and my
> problem without quoting a bunch of irrelevant source code:
>
> (defun do-rad-things ()
>   (ltk:do-msg "activity from network!"))
>
> (defun start-network ()
>   (let ((socket-stream (....))
>         (wish ltk:*wish*))
>     (sb-ext:add-fd-handler socket-stream
>                            (lambda ()
>                              (let ((ltk:*wish* wish))
>                                'do-rad-things)))))
>
> (defun start ()
>   (ltk:with-ltk (:serve-event t)
>     (build-our-gui)
>     (start-network)))
>
>
> When tk sends output to ltk, the ltk handler is called and
> ltk:*wish* is bound to the poper wish structure. But when
> do-rad-things is called, ltk:*wish* is no longer bound to the
> proper wish structure so I have to do a little let dance to keep
> it in order.

Okay, I see what your problem is.  The reason that *wish* is handled
the way it is in the serve-event backend is that you might have more
than one active wish process, so there is no "the" tk connection to
make global.  If you're perfectly happy with having just one gui
running, or just one primary gui at least, you can skip with-ltk and
use the lower-level functions: start-wish and mainloop.  So your start
function would become:

 (defun start ()
   (start-wish)
   (mainloop :serve-event t)
   (build-our-gui)
   (start-network))

This will start a serve-event wish connection on the global binding of
*wish*.  This is what I do for playing around with ltk at the sbcl
repl.

However, a word of warning: serve-event ltk is not fully safe wrt
reentrant serve-event.  It's not likely to be a problem for you, but
here's the situation where it could bite you: if you're trying to send
the wish process more data than there is room for in its buffer,
instead of blocking your sbcl calls back into serve-event, gets some
network activity, wish processes the previous input and clears its
buffer, your network event handler tries to send more data to wish and
it ends out in the middle of the data for the previous command.

This is highly unlikely, and if you're just doing widget stuff (as
opposed to, say, sending images across the pipe) I don't think it can
come up.  But if you see Ltk tripping over itself under high network
load, this would be the reason.

We could, can, and probably will fix this.  Up to now we haven't had
any known serve-event users besides ourselves, so it's been a low
priority (higher priority is merging the little bug fixes and
enhancements developed at work).  So holler if this bothers you.

Again, we have a work-around.  Rather than push changes out to wish,
you can have it pull them.  The ltk:after function lets you schedule
idle events.  So you could have your network code push events onto a
global queue and have the GUI check for updates every 1 sec.

Putting all that together, here's how I'd rewrite your example:

(defvar *gui-updates* ())

(defun process-updates ()
 (let ((updates (nreverse *gui-updates*)))
   (setf *gui-updates* nil)
   (mapc #'funcall *gui-updates*)
   (after 1000 #'process-updates)))

(defun do-rad-things ()
 (labels ((aux ()
            (ltk:do-msg "activity from network!")))
   (push #'aux *gui-updates*)))

(defun start-network ()
 (let ((socket-stream (....)))
   (sb-ext:add-fd-handler socket-stream
                          (lambda ()
                            (do-rad-things)))))

(defun start ()
 (start-wish)
 (mainloop :serve-event t)
 (build-our-gui)
 (start-network)
 (process-updates))

> I propose that instead of a with-ltk macro, an ltk:root class is
> created which holds the connection to the subprocess and
> represents the default root window wish produces when you start
> it. It would probably be most convenient if all other widgets
> also carried a pointer to the wish subprocess or the root class.
>
> So then the above might look like this:
>
> (defun do-rad-things (wish)
>   (ltk:do-msg wish "activity from network!"))
>
> (defun start-network (wish)
>   (let ((socket-stream (....))
>     (sb-ext:add-fd-handler socket-stream
>                            (lambda ()
>                              (do-rad-things wish)))))
>
> (defun start ()
>   (let ((wish (make-instance 'ltk:root)))
>     (build-our-gui wish)
>     (start-network wish)))
>
> What do you think?

This actually looks more cumbersome to me than what we have right now.
 But I like the idea of having widgets know about their wish
connection; then *wish* is only relevant to the few truly global
things you can do: make a new toplevel, do-msg, etc.



More information about the ltk-user mailing list