source: trunk/gui/scripts/visviewer.tcl @ 1536

Last change on this file since 1536 was 1483, checked in by gah, 15 years ago

fix newlines when sending data to pymolproxy

File size: 15.9 KB
Line 
1
2# ----------------------------------------------------------------------
3#  VisViewer -
4#
5#  This class is the base class for the various visualization viewers
6#  that use the nanoserver render farm.
7#
8# ======================================================================
9#  AUTHOR:  Michael McLennan, Purdue University
10#  Copyright (c) 2004-2005  Purdue Research Foundation
11#
12#  See the file "license.terms" for information on usage and
13#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14# ======================================================================
15
16itcl::class ::Rappture::VisViewer {
17    inherit itk::Widget
18
19    itk_option define -sendcommand sendCommand SendCommand ""
20    itk_option define -receivecommand receiveCommand ReceiveCommand ""
21
22    private common _servers         ;# array of visualization server lists
23    set _servers(nanovis) "localhost:2000"
24    set _servers(pymol)   "localhost:2020"
25
26    private variable _sid ""        ;# socket connection to server
27    private common _done            ;# Used to indicate status of send.
28    private variable _buffer        ;# buffer for incoming/outgoing commands
29    private variable _initialized
30    private variable _isOpen 0
31    # Number of milliseconds to wait before idle timeout.
32    # If greater than 0, automatically disconnect from the visualization
33    # server when idle timeout is reached.
34    private variable _idleTimeout 43200000; # 12 hours
35    #private variable _idleTimeout 5000;    # 5 seconds
36    #private variable _idleTimeout 0;       # No timeout
37
38    protected variable _dispatcher ""   ;# dispatcher for !events
39    protected variable _hosts ""    ;# list of hosts for server
40    protected variable _parser ""   ;# interpreter for incoming commands
41    protected variable _image
42
43    constructor { hostlist args } {
44        # defined below
45    }
46    destructor {
47        # defined below
48    }
49    # Used internally only.
50    private method Shuffle { hostlist }
51    private method ReceiveHelper {}
52    private method ServerDown {}
53    private method SendHelper {}
54    private method SendHelper.old {}
55    private method CheckConnection {}
56
57    protected method SendEcho { channel {data ""} }
58    protected method ReceiveEcho { channel {data ""} }
59    protected method Connect { hostlist }
60    protected method Disconnect {}
61    protected method IsConnected {}
62    protected method SendBytes { bytes }
63    protected method ReceiveBytes { nbytes }
64    protected method Flush {}
65    protected method Color2RGB { color }
66    protected method Euler2XYZ { theta phi psi }
67
68    private proc CheckNameList { namelist }  {
69        set pattern {^[a-zA-Z0-9\.]+:[0-9]+(,[a-zA-Z0-9\.]+:[0-9]+)*$}
70        if { ![regexp $pattern $namelist match] } {
71            error "bad visualization server address \"$namelist\": should be host:port,host:port,..."
72        }
73    }
74    public proc GetServerList { tag } {
75        return $_servers($tag)
76    }
77    public proc SetServerList { tag namelist } {
78        CheckNameList $namelist
79        set _servers($tag) $namelist
80    }
81    public proc SetPymolServerList { namelist } {
82        SetServerList "pymol" $namelist
83    }
84    public proc SetNanovisServerList { namelist } {
85        SetServerList "nanovis" $namelist
86    }
87}
88
89itk::usual Panedwindow {
90    keep -background -cursor
91}
92
93# ----------------------------------------------------------------------
94# CONSTRUCTOR
95# ----------------------------------------------------------------------
96itcl::body Rappture::VisViewer::constructor { hostlist args } {
97
98    Rappture::dispatcher _dispatcher
99    $_dispatcher register !serverDown
100    $_dispatcher dispatch $this !serverDown "[itcl::code $this ServerDown]; list"
101    $_dispatcher register !timeout
102    $_dispatcher dispatch $this !timeout "[itcl::code $this Disconnect]; list"
103
104    CheckNameList $hostlist
105    set _hostlist $hostlist
106    set _buffer(in) ""
107    set _buffer(out) ""
108    #
109    # Create a parser to handle incoming requests
110    #
111    set _parser [interp create -safe]
112    foreach cmd [$_parser eval {info commands}] {
113        $_parser hide $cmd
114    }
115
116    #
117    # Set up the widgets in the main body
118    #
119    option add hull.width hull.height
120    pack propagate $itk_component(hull) no
121
122    itk_component add main {
123        Rappture::SidebarFrame $itk_interior.main
124    }
125    pack $itk_component(main) -expand yes -fill both
126    set f [$itk_component(main) component frame]
127
128    itk_component add plotarea {
129        frame $f.plotarea -highlightthickness 0 -background black
130    } {
131        ignore -background
132    }
133    pack $itk_component(plotarea) -fill both -expand yes
134    set _image(plot) [image create photo]
135    itk_component add 3dview {
136        label $itk_component(plotarea).vol -image $_image(plot) \
137            -highlightthickness 0 -borderwidth 0
138    } {
139        usual
140        ignore -highlightthickness -borderwidth  -background
141    }
142    eval itk_initialize $args
143}
144
145#
146# destructor --
147#
148itcl::body Rappture::VisViewer::destructor {} {
149    $_dispatcher cancel !timeout
150    interp delete $_parser
151    array unset _done $this
152}
153
154#
155# Shuffle --
156#
157#   Shuffle the list of server hosts.
158#
159itcl::body Rappture::VisViewer::Shuffle { hostlist } {
160    set hosts [split $hostlist ,]
161    set randomHosts {}
162    set ticks [clock clicks]
163    expr {srand($ticks)}
164    for { set i [llength $hosts] } { $i > 0 } { incr i -1 } {
165        set index [expr {round(rand()*$i - 0.5)}]
166        if { $index == $i } {
167            set index [expr $i - 1]
168        }
169        lappend randomHosts [lindex $hosts $index]
170        set hosts [lreplace $hosts $index $index]
171    }
172    return $randomHosts
173}
174
175#
176# ServerDown --
177#
178#    Used internally to let the user know when the connection to the
179#    visualization server has been lost.  Puts up a tip encouraging the
180#    user to press any control to reconnect.
181#
182itcl::body Rappture::VisViewer::ServerDown {} {
183    if { [info exists itk_component(plotarea)] } {
184        set x [expr {[winfo rootx $itk_component(plotarea)]+10}]
185        set y [expr {[winfo rooty $itk_component(plotarea)]+10}]
186    } else {
187        set x 0; set y 0
188    }
189    Rappture::Tooltip::cue @$x,$y "Lost connection to visualization server.  This happens sometimes when there are too many users and the system runs out of memory.\n\nTo reconnect, reset the view or press any other control.  Your picture should come right back up."
190}
191
192#
193# Connect --
194#
195#    Connect to the visualization server (e.g. nanovis, pymolproxy).
196#    Creates an event callback that is triggered when we are idle
197#    (no I/O with the server) for some specified time. Sends the server
198#    some estimate of the size of our job [soon to be deprecated].
199#    If it's too busy, that server may forward us to another [this
200#    was been turned off in nanoscale].
201#
202itcl::body Rappture::VisViewer::Connect { hostlist } {
203    blt::busy hold $itk_component(hull) -cursor watch
204    update
205
206    # Shuffle the list of servers so as to pick random
207    set servers [Shuffle $hostlist]
208
209    set memorySize 10000
210    # Get the first server
211    foreach {hostname port} [split [lindex $servers 0] :] break
212    set servers [lrange $servers 1 end]
213
214    while {1} {
215        SendEcho <<line "connecting to $hostname:$port..."
216        if { [catch {socket $hostname $port} _sid] != 0 } {
217            if {[llength $servers] == 0} {
218                blt::busy release $itk_component(hull)
219                return 0
220            }
221            # Get the next server
222            foreach {hostname port} [split [lindex $servers 0] :] break
223            set servers [lrange $servers 1 end]
224            continue
225        }
226        fconfigure $_sid -translation binary -encoding binary
227
228        # Send memory requirement to the load balancer
229        puts -nonewline $_sid [binary format I $memorySize]
230        flush $_sid
231
232        # Read back a reconnection order
233        set data [read $_sid 4]
234        if {[binary scan $data cccc b1 b2 b3 b4] != 4} {
235            blt::busy release $itk_component(hull)
236            error "couldn't read redirection request"
237        }
238        set addr [format "%u.%u.%u.%u" \
239            [expr {$b1 & 0xff}] \
240            [expr {$b2 & 0xff}] \
241            [expr {$b3 & 0xff}] \
242            [expr {$b4 & 0xff}]]
243
244        if { [string equal $addr "0.0.0.0"] } {
245            # We're connected. Cancel any pending serverDown events and
246            # release the busy window over the hull.
247            $_dispatcher cancel !serverDown
248            if { $_idleTimeout > 0 } {
249                $_dispatcher event -after $_idleTimeout !timeout
250            }
251            blt::busy release $itk_component(hull)
252            fconfigure $_sid -buffering line
253            fileevent $_sid readable [itcl::code $this ReceiveHelper]
254            return 1
255        }
256        set hostname $addr
257    }
258    #NOTREACHED
259    blt::busy release $itk_component(hull)
260    return 0
261}
262
263
264#
265# Disconnect --
266#
267#    Clients use this method to disconnect from the current rendering
268#    server.  Cancel any pending idle timeout events.
269#
270itcl::body Rappture::VisViewer::Disconnect {} {
271    $_dispatcher cancel !timeout
272    catch {close $_sid}
273    set _sid ""
274    set _buffer(in) ""
275}
276
277#
278# IsConnected --
279#
280#    Indicates if we are currently connected to a server.
281#
282itcl::body Rappture::VisViewer::IsConnected {} {
283    return [expr {"" != $_sid}]
284}
285
286#
287# CheckConection --
288#
289#   This routine is called whenever we're about to send/recieve data on
290#   the socket connection to the visualization server.  If we're connected,
291#   then reset the timeout event.  Otherwise try to reconnect to the
292#   visualization server.
293#
294itcl::body Rappture::VisViewer::CheckConnection {} {
295    if { [IsConnected] } {
296        if { [eof $_sid] } {
297            error "unexpected eof on socket"
298        }
299        $_dispatcher cancel !timeout
300        if { $_idleTimeout > 0 } {
301            $_dispatcher event -after $_idleTimeout !timeout
302        }
303        return 1
304    }
305    # If we aren't connected, assume it's because the connection to the
306    # visualization server broke. Try to open a connection and trigger a
307    # rebuild.
308    $_dispatcher cancel !serverDown
309    set x [expr {[winfo rootx $itk_component(plotarea)]+10}]
310    set y [expr {[winfo rooty $itk_component(plotarea)]+10}]
311    Rappture::Tooltip::cue @$x,$y "Connecting..."
312    set code [catch { Connect } ok]
313    if { $code == 0 && $ok} {
314        $_dispatcher event -idle !rebuild
315        Rappture::Tooltip::cue hide
316    } else {
317        Rappture::Tooltip::cue @$x,$y "Can't connect to visualization server.  This may be a network problem.  Wait a few moments and try resetting the view."
318        return 0
319    }
320    return 1
321}
322
323#
324# Flush --
325#
326#    Flushes the socket.
327#
328itcl::body Rappture::VisViewer::Flush {} {
329    if { [CheckConnection] } {
330        flush $_sid
331    }
332}
333
334
335#
336# SendHelper --
337#
338#   Helper routine called from a file event to send data when the
339#   connection is writable (i.e. not blocked).  Sets a magic
340#   variable _done($this) when we're done.
341#
342itcl::body Rappture::VisViewer::SendHelper {} {
343    if { ![CheckConnection] } {
344        return 0
345    }
346    puts -nonewline $_sid $_buffer(out)
347    flush $_sid
348    set _done($this) 1;     # Success
349}
350
351#
352# SendHelper.old --
353#
354#   Helper routine called from a file event to send data when the
355#   connection is writable (i.e. not blocked).  Sends data in chunks
356#   of 8k (or less).  Sets magic variable _done($this) to indicate
357#   that we're either finished (success) or could not send bytes to
358#   the server (failure).
359#
360itcl::body Rappture::VisViewer::SendHelper.old {} {
361    if { ![CheckConnection] } {
362        return 0
363    }
364    set bytesLeft [string length $_buffer(out)]
365    if { $bytesLeft > 0} {
366        set chunk [string range $_buffer(out) 0 8095]
367        set _buffer(out)  [string range $_buffer(out) 8096 end]
368        incr bytesLeft -8096
369        set code [catch {
370            if { $bytesLeft > 0 } {
371                puts -nonewline $_sid $chunk
372            } else {
373                puts $_sid $chunk
374            }
375        } err]
376        if { $code != 0 } {
377            puts stderr "error sending data to $_sid: $err"
378            Disconnect
379            set _done($this) 0;     # Failure
380        }
381    } else {
382        set _done($this) 1;     # Success
383    }
384}
385
386#
387# SendBytes --
388#
389#   Send a a string to the visualization server.
390#
391itcl::body Rappture::VisViewer::SendBytes { bytes } {
392    SendEcho >>line $bytes
393
394    if { ![CheckConnection] } {
395        return 0
396    }
397    # Even though the data is sent in only 1 "puts", we need to verify that
398    # the server is ready first.  Wait for the socket to become writable
399    # before sending anything.
400    set _done($this) 1
401    set _buffer(out) $bytes
402    fileevent $_sid writable [itcl::code $this SendHelper]
403    tkwait variable ::Rappture::VisViewer::_done($this)
404    set _buffer(out) ""
405    if { [IsConnected] } {
406        # The connection may have closed while we were writing to the server.
407        # This can happen if what we sent the server caused it to barf.
408        fileevent $_sid writable ""
409        flush $_sid
410    }
411    if 0 {
412    if { ![CheckConnection] } {
413        puts stderr "connection is now down"
414        return 0
415    }
416    }
417    return $_done($this)
418}
419
420#
421# ReceiveBytes --
422#
423#    Read some number of bytes from the visualization server.
424#
425itcl::body Rappture::VisViewer::ReceiveBytes { size } {
426    if { ![CheckConnection] } {
427        return 0
428    }
429    set bytes [read $_sid $size]
430    ReceiveEcho <<line "<read $size bytes"
431    return $bytes
432}
433
434#
435# ReceiveHelper --
436#
437#   Helper routine called from a file event when the connection is
438#   readable (i.e. a command response has been sent by the rendering
439#   server.  Reads the incoming command and executes it in a safe
440#   interpreter to handle the action.
441#
442#       Note: This routine currently only handles command responses from
443#         the visualization server.  It doesn't handle non-blocking
444#         reads from the visualization server.
445#
446#       nv>image -bytes 100000      yes
447#       ...following 100000 bytes...    no
448#
449#   Note: All commands from the render server are on one line.
450#         This is because the render server can send anything
451#         as an error message (restricted again to one line).
452#
453itcl::body Rappture::VisViewer::ReceiveHelper {} {
454    if { ![CheckConnection] } {
455        return 0
456    }
457    set n [gets $_sid line]
458
459    if { $n < 0 } {
460        Disconnect
461        return 0
462    }
463    set line [string trim $line]
464    if { $line == "" } {
465        return
466    }
467    if { [string compare -length 3 $line "nv>"] == 0 } {
468        ReceiveEcho <<line $line
469        append _buffer(in) [string range $line 3 end]
470        append _buffer(in) "\n"
471        if {[info complete $_buffer(in)]} {
472            set request $_buffer(in)
473            set _buffer(in) ""
474            if { [catch {$_parser eval $request} err]  != 0 } {
475                global errorInfo
476                puts stderr "err=$err errorInfo=$errorInfo"
477            }
478        }
479    } elseif { [string compare -length 20 $line "NanoVis Server Error:"] == 0} {
480        # this shows errors coming back from the engine
481        ReceiveEcho <<error $line
482        puts stderr "Render Server Error: $line\n"
483    } else {
484        # this shows errors coming back from the engine
485        ReceiveEcho <<error $line
486        puts stderr "Garbled message: $line\n"
487    }
488}
489
490
491#
492# Color2RGB --
493#
494#   Converts a color name to a list of r,g,b values needed for the
495#   engine.  Each r/g/b component is scaled in the # range 0-1.
496#
497itcl::body Rappture::VisViewer::Color2RGB {color} {
498    foreach {r g b} [winfo rgb $itk_component(hull) $color] break
499    set r [expr {$r/65535.0}]
500    set g [expr {$g/65535.0}]
501    set b [expr {$b/65535.0}]
502    return [list $r $g $b]
503}
504
505#
506# Euler2XYZ --
507#
508#   Converts euler angles for the camera placement the to angles of
509#   rotation about the x/y/z axes, used by the engine.  Returns a list:
510#   {xangle, yangle, zangle}.
511#
512itcl::body Rappture::VisViewer::Euler2XYZ {theta phi psi} {
513    set xangle [expr {$theta-90.0}]
514    set yangle [expr {180-$phi}]
515    set zangle $psi
516    return [list $xangle $yangle $zangle]
517}
518
519
520#
521# SendEcho --
522#
523#     Used internally to echo sent data to clients interested in this widget.
524#     If the -sendcommand option is set, then it is invoked in the global scope
525#     with the <channel> and <data> values as arguments.  Otherwise, this does
526#     nothing.
527#
528itcl::body Rappture::VisViewer::SendEcho {channel {data ""}} {
529    #puts stderr ">>($data)"
530    if {[string length $itk_option(-sendcommand)] > 0} {
531        uplevel #0 $itk_option(-sendcommand) [list $channel $data]
532    }
533}
534
535#
536# ReceiveEcho --
537#
538#     Echoes received data to clients interested in this widget.  If the
539#     -receivecommand option is set, then it is # invoked in the global
540#     scope with the <channel> and <data> values # as arguments.  Otherwise,
541#     this does nothing.
542#
543itcl::body Rappture::VisViewer::ReceiveEcho {channel {data ""}} {
544    #puts stderr "<<line $data"
545    if {[string length $itk_option(-receivecommand)] > 0} {
546        uplevel #0 $itk_option(-receivecommand) [list $channel $data]
547    }
548}
Note: See TracBrowser for help on using the repository browser.