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

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