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

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