source: branches/blt4/gui/scripts/visviewer.tcl @ 1695

Last change on this file since 1695 was 1650, checked in by gah, 15 years ago
File size: 15.8 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 picture]
135    $_image(plot) blank black
136    eval itk_initialize $args
137}
138
139#
140# destructor --
141#
142itcl::body Rappture::VisViewer::destructor {} {
143    $_dispatcher cancel !timeout
144    interp delete $_parser
145    array unset _done $this
146}
147
148#
149# Shuffle --
150#
151#   Shuffle the list of server hosts.
152#
153itcl::body Rappture::VisViewer::Shuffle { hostlist } {
154    set hosts [split $hostlist ,]
155    set randomHosts {}
156    set ticks [clock clicks]
157    expr {srand($ticks)}
158    for { set i [llength $hosts] } { $i > 0 } { incr i -1 } {
159        set index [expr {round(rand()*$i - 0.5)}]
160        if { $index == $i } {
161            set index [expr $i - 1]
162        }
163        lappend randomHosts [lindex $hosts $index]
164        set hosts [lreplace $hosts $index $index]
165    }
166    return $randomHosts
167}
168
169#
170# ServerDown --
171#
172#    Used internally to let the user know when the connection to the
173#    visualization server has been lost.  Puts up a tip encouraging the
174#    user to press any control to reconnect.
175#
176itcl::body Rappture::VisViewer::ServerDown {} {
177    if { [info exists itk_component(plotarea)] } {
178        set x [expr {[winfo rootx $itk_component(plotarea)]+10}]
179        set y [expr {[winfo rooty $itk_component(plotarea)]+10}]
180    } else {
181        set x 0; set y 0
182    }
183    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."
184}
185
186#
187# Connect --
188#
189#    Connect to the visualization server (e.g. nanovis, pymolproxy).
190#    Creates an event callback that is triggered when we are idle
191#    (no I/O with the server) for some specified time. Sends the server
192#    some estimate of the size of our job [soon to be deprecated].
193#    If it's too busy, that server may forward us to another [this
194#    was been turned off in nanoscale].
195#
196itcl::body Rappture::VisViewer::Connect { hostlist } {
197    blt::busy hold $itk_component(hull) -cursor watch
198    # Can't call update because of all the pending stuff going on
199    #update
200
201    # Shuffle the list of servers so as to pick random
202    set servers [Shuffle $hostlist]
203
204    set memorySize 10000
205    # Get the first server
206    foreach {hostname port} [split [lindex $servers 0] :] break
207    set servers [lrange $servers 1 end]
208
209    while {1} {
210        SendEcho <<line "connecting to $hostname:$port..."
211        if { [catch {socket $hostname $port} _sid] != 0 } {
212            if {[llength $servers] == 0} {
213                blt::busy release $itk_component(hull)
214                return 0
215            }
216            # Get the next server
217            foreach {hostname port} [split [lindex $servers 0] :] break
218            set servers [lrange $servers 1 end]
219            continue
220        }
221        fconfigure $_sid -translation binary -encoding binary
222
223        # Send memory requirement to the load balancer
224        puts -nonewline $_sid [binary format I $memorySize]
225        flush $_sid
226
227        # Read back a reconnection order
228        set data [read $_sid 4]
229        if {[binary scan $data cccc b1 b2 b3 b4] != 4} {
230            blt::busy release $itk_component(hull)
231            error "couldn't read redirection request"
232        }
233        set addr [format "%u.%u.%u.%u" \
234            [expr {$b1 & 0xff}] \
235            [expr {$b2 & 0xff}] \
236            [expr {$b3 & 0xff}] \
237            [expr {$b4 & 0xff}]]
238
239        if { [string equal $addr "0.0.0.0"] } {
240            # We're connected. Cancel any pending serverDown events and
241            # release the busy window over the hull.
242            $_dispatcher cancel !serverDown
243            if { $_idleTimeout > 0 } {
244                $_dispatcher event -after $_idleTimeout !timeout
245            }
246            blt::busy release $itk_component(hull)
247            fconfigure $_sid -buffering line
248            fileevent $_sid readable [itcl::code $this ReceiveHelper]
249            return 1
250        }
251        set hostname $addr
252    }
253    #NOTREACHED
254    blt::busy release $itk_component(hull)
255    return 0
256}
257
258
259#
260# Disconnect --
261#
262#    Clients use this method to disconnect from the current rendering
263#    server.  Cancel any pending idle timeout events.
264#
265itcl::body Rappture::VisViewer::Disconnect {} {
266    $_dispatcher cancel !timeout
267    catch {close $_sid}
268    set _sid ""
269    set _buffer(in) ""
270}
271
272#
273# IsConnected --
274#
275#    Indicates if we are currently connected to a server.
276#
277itcl::body Rappture::VisViewer::IsConnected {} {
278    return [expr {"" != $_sid}]
279}
280
281#
282# CheckConection --
283#
284#   This routine is called whenever we're about to send/recieve data on
285#   the socket connection to the visualization server.  If we're connected,
286#   then reset the timeout event.  Otherwise try to reconnect to the
287#   visualization server.
288#
289itcl::body Rappture::VisViewer::CheckConnection {} {
290    if { [IsConnected] } {
291        if { [eof $_sid] } {
292            error "unexpected eof on socket"
293        }
294        $_dispatcher cancel !timeout
295        if { $_idleTimeout > 0 } {
296            $_dispatcher event -after $_idleTimeout !timeout
297        }
298        return 1
299    }
300    # If we aren't connected, assume it's because the connection to the
301    # visualization server broke. Try to open a connection and trigger a
302    # rebuild.
303    $_dispatcher cancel !serverDown
304    set x [expr {[winfo rootx $itk_component(plotarea)]+10}]
305    set y [expr {[winfo rooty $itk_component(plotarea)]+10}]
306    Rappture::Tooltip::cue @$x,$y "Connecting..."
307    set code [catch { Connect } ok]
308    if { $code == 0 && $ok} {
309        $_dispatcher event -idle !rebuild
310        Rappture::Tooltip::cue hide
311    } else {
312        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."
313        return 0
314    }
315    return 1
316}
317
318#
319# Flush --
320#
321#    Flushes the socket.
322#
323itcl::body Rappture::VisViewer::Flush {} {
324    if { [CheckConnection] } {
325        flush $_sid
326    }
327}
328
329
330#
331# SendHelper --
332#
333#   Helper routine called from a file event to send data when the
334#   connection is writable (i.e. not blocked).  Sets a magic
335#   variable _done($this) when we're done.
336#
337itcl::body Rappture::VisViewer::SendHelper {} {
338    if { ![CheckConnection] } {
339        return 0
340    }
341    puts -nonewline $_sid $_buffer(out)
342    flush $_sid
343    set _done($this) 1;     # Success
344}
345
346#
347# SendHelper.old --
348#
349#   Helper routine called from a file event to send data when the
350#   connection is writable (i.e. not blocked).  Sends data in chunks
351#   of 8k (or less).  Sets magic variable _done($this) to indicate
352#   that we're either finished (success) or could not send bytes to
353#   the server (failure).
354#
355itcl::body Rappture::VisViewer::SendHelper.old {} {
356    if { ![CheckConnection] } {
357        return 0
358    }
359    set bytesLeft [string length $_buffer(out)]
360    if { $bytesLeft > 0} {
361        set chunk [string range $_buffer(out) 0 8095]
362        set _buffer(out)  [string range $_buffer(out) 8096 end]
363        incr bytesLeft -8096
364        set code [catch {
365            if { $bytesLeft > 0 } {
366                puts -nonewline $_sid $chunk
367            } else {
368                puts $_sid $chunk
369            }
370        } err]
371        if { $code != 0 } {
372            puts stderr "error sending data to $_sid: $err"
373            Disconnect
374            set _done($this) 0;     # Failure
375        }
376    } else {
377        set _done($this) 1;     # Success
378    }
379}
380
381#
382# SendBytes --
383#
384#   Send a a string to the visualization server.
385#
386itcl::body Rappture::VisViewer::SendBytes { bytes } {
387    SendEcho >>line $bytes
388    if { ![CheckConnection] } {
389        return 0
390    }
391    # Even though the data is sent in only 1 "puts", we need to verify that
392    # the server is ready first.  Wait for the socket to become writable
393    # before sending anything.
394    set _done($this) 1
395    set _buffer(out) $bytes
396    fileevent $_sid writable [itcl::code $this SendHelper]
397    tkwait variable ::Rappture::VisViewer::_done($this)
398    set _buffer(out) ""
399    if { [IsConnected] } {
400        # The connection may have closed while we were writing to the server.
401        # This can happen if what we sent the server caused it to barf.
402        fileevent $_sid writable ""
403        flush $_sid
404    }
405    if 0 {
406    if { ![CheckConnection] } {
407        puts stderr "connection is now down"
408        return 0
409    }
410    }
411    return $_done($this)
412}
413
414#
415# ReceiveBytes --
416#
417#    Read some number of bytes from the visualization server.
418#
419itcl::body Rappture::VisViewer::ReceiveBytes { size } {
420    if { ![CheckConnection] } {
421        return 0
422    }
423    set bytes [read $_sid $size]
424    ReceiveEcho <<line "<read $size bytes"
425    return $bytes
426}
427
428#
429# ReceiveHelper --
430#
431#   Helper routine called from a file event when the connection is
432#   readable (i.e. a command response has been sent by the rendering
433#   server.  Reads the incoming command and executes it in a safe
434#   interpreter to handle the action.
435#
436#       Note: This routine currently only handles command responses from
437#         the visualization server.  It doesn't handle non-blocking
438#         reads from the visualization server.
439#
440#       nv>image -bytes 100000      yes
441#       ...following 100000 bytes...    no
442#
443#   Note: All commands from the render server are on one line.
444#         This is because the render server can send anything
445#         as an error message (restricted again to one line).
446#
447itcl::body Rappture::VisViewer::ReceiveHelper {} {
448    if { ![CheckConnection] } {
449        return 0
450    }
451    set n [gets $_sid line]
452
453    if { $n < 0 } {
454        Disconnect
455        return 0
456    }
457    set line [string trim $line]
458    if { $line == "" } {
459        return
460    }
461    if { [string compare -length 3 $line "nv>"] == 0 } {
462        ReceiveEcho <<line $line
463        append _buffer(in) [string range $line 3 end]
464        append _buffer(in) "\n"
465        if {[info complete $_buffer(in)]} {
466            set request $_buffer(in)
467            set _buffer(in) ""
468            if { [catch {$_parser eval $request} err]  != 0 } {
469                global errorInfo
470                puts stderr "err=$err errorInfo=$errorInfo"
471            }
472        }
473    } elseif { [string compare -length 20 $line "NanoVis Server Error:"] == 0} {
474        # this shows errors coming back from the engine
475        ReceiveEcho <<error $line
476        puts stderr "Render Server Error: $line\n"
477    } else {
478        # this shows errors coming back from the engine
479        ReceiveEcho <<error $line
480        puts stderr "Garbled message: $line\n"
481    }
482}
483
484
485#
486# Color2RGB --
487#
488#   Converts a color name to a list of r,g,b values needed for the
489#   engine.  Each r/g/b component is scaled in the # range 0-1.
490#
491itcl::body Rappture::VisViewer::Color2RGB {color} {
492    foreach {r g b} [winfo rgb $itk_component(hull) $color] break
493    set r [expr {$r/65535.0}]
494    set g [expr {$g/65535.0}]
495    set b [expr {$b/65535.0}]
496    return [list $r $g $b]
497}
498
499#
500# Euler2XYZ --
501#
502#   Converts euler angles for the camera placement the to angles of
503#   rotation about the x/y/z axes, used by the engine.  Returns a list:
504#   {xangle, yangle, zangle}.
505#
506itcl::body Rappture::VisViewer::Euler2XYZ {theta phi psi} {
507    set xangle [expr {$theta-90.0}]
508    set yangle [expr {180-$phi}]
509    set zangle $psi
510    return [list $xangle $yangle $zangle]
511}
512
513
514#
515# SendEcho --
516#
517#     Used internally to echo sent data to clients interested in this widget.
518#     If the -sendcommand option is set, then it is invoked in the global scope
519#     with the <channel> and <data> values as arguments.  Otherwise, this does
520#     nothing.
521#
522itcl::body Rappture::VisViewer::SendEcho {channel {data ""}} {
523    #puts stderr ">>($data)"
524    if {[string length $itk_option(-sendcommand)] > 0} {
525        uplevel #0 $itk_option(-sendcommand) [list $channel $data]
526    }
527}
528
529#
530# ReceiveEcho --
531#
532#     Echoes received data to clients interested in this widget.  If the
533#     -receivecommand option is set, then it is # invoked in the global
534#     scope with the <channel> and <data> values # as arguments.  Otherwise,
535#     this does nothing.
536#
537itcl::body Rappture::VisViewer::ReceiveEcho {channel {data ""}} {
538    #puts stderr "<<line $data"
539    if {[string length $itk_option(-receivecommand)] > 0} {
540        uplevel #0 $itk_option(-receivecommand) [list $channel $data]
541    }
542}
Note: See TracBrowser for help on using the repository browser.