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

Last change on this file since 925 was 925, checked in by gah, 16 years ago

Fix: changed _CheckServerList to proc from method

File size: 13.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) ""
24    set _servers(pymol)   ""
25
26    protected variable _dispatcher ""   ;# dispatcher for !events
27    protected variable _hosts ""        ;# list of hosts for server
28    protected variable _sid ""          ;# socket connection to server
29    protected variable _parser ""       ;# interpreter for incoming commands
30    protected variable _image
31    private common _done                ;# Used to indicate status of send.
32    private variable _buffer            ;# buffer for incoming/outgoing commands
33
34    constructor { hostlist args } {
35        # defined below
36    }
37    destructor {
38        # defined below
39    }
40    # Used internally only.
41    private method _Shuffle { hostlist }
42    private method _ReceiveHelper {}
43    private method _ServerDown {}
44    private method _SendHelper {}
45
46    protected method SendEcho { channel {data ""} }
47    protected method ReceiveEcho { channel {data ""} }
48    protected method Connect { hostlist }
49    protected method Disconnect {}
50    protected method IsConnected {}
51    protected method SendBytes { bytes }
52    protected method ReceiveBytes { nbytes }
53    protected method Flush {}
54    protected method Color2RGB { color }
55    protected method Euler2XYZ { theta phi psi }
56
57    private proc _CheckNameList { namelist }  {
58        set pattern {^[a-zA-Z0-9\.]+:[0-9]+(,[a-zA-Z0-9\.]+:[0-9]+)*$}
59        if { ![regexp $pattern $namelist match] } {
60            error "bad visualization server address \"$namelist\": should be host:port,host:port,..."
61        }
62    }
63    public proc GetServerList { tag } {
64        return $_servers($tag)
65    }
66    public proc SetServerList { tag namelist } {
67        _CheckNameList $namelist
68        set _servers($tag) $namelist
69    }
70    public proc SetPymolServerList { namelist } {
71        SetServerList "pymol" $namelist
72    }
73    public proc SetNanovisServerList { namelist } {
74        SetServerList "nanovis" $namelist
75    }
76}
77
78# ----------------------------------------------------------------------
79# CONSTRUCTOR
80# ----------------------------------------------------------------------
81itcl::body Rappture::VisViewer::constructor { hostlist args } {
82
83    Rappture::dispatcher _dispatcher
84    $_dispatcher register !serverDown
85    $_dispatcher dispatch $this !serverDown "[itcl::code $this _ServerDown]; list"
86
87    _CheckNameList $hostlist
88    set _hostlist $hostlist
89    set _buffer(in) ""
90    set _buffer(out) ""
91    #
92    # Create a parser to handle incoming requests
93    #
94    set _parser [interp create -safe]
95    foreach cmd [$_parser eval {info commands}] {
96        $_parser hide $cmd
97    }
98
99    #
100    # Set up the widgets in the main body
101    #
102    option add hull.width hull.height
103    pack propagate $itk_component(hull) no
104
105    itk_component add controls {
106        frame $itk_interior.cntls
107    } {
108        usual
109        rename -background -controlbackground controlBackground Background
110    }
111    pack $itk_component(controls) -side right -fill y
112
113    #
114    # RENDERING AREA
115    #
116    itk_component add area {
117        frame $itk_interior.area
118    }
119    pack $itk_component(area) -expand yes -fill both
120
121    set _image(plot) [image create photo]
122    itk_component add 3dview {
123        label $itk_component(area).vol -image $_image(plot) \
124            -highlightthickness 0 -width 1 -height 1
125    } {
126        usual
127        ignore -highlightthickness
128    }
129    pack $itk_component(3dview) -expand yes -fill both
130
131    eval itk_initialize $args
132}
133
134#
135# destructor --
136#
137itcl::body Rappture::VisViewer::destructor {} {
138    interp delete $_parser
139    array unset _done $this
140}
141
142#
143# _Shuffle -- 
144#
145#       Shuffle the list of server hosts.
146#
147itcl::body Rappture::VisViewer::_Shuffle { hostlist } {
148    set hosts [split $hostlist ,]
149    set random_hosts {}
150    for { set i [llength $hosts] } { $i > 0 } { incr i -1 } {
151        set index [expr {round(rand()*$i - 0.5)}]
152        if { $index == $i } {
153            set index [expr $i - 1]
154        }
155        lappend random_hosts [lindex $hosts $index]
156        set hosts [lreplace $hosts $index $index]
157    }
158    return $random_hosts
159}
160
161#
162# _ServerDown --
163#
164#    Used internally to let the user know when the connection to the
165#    visualization server has been lost.  Puts up a tip encouraging the
166#    user to press any control to reconnect.
167#
168itcl::body Rappture::VisViewer::_ServerDown {} {
169    if { [info exists itk_component(area)] } {
170        set x [expr {[winfo rootx $itk_component(area)]+10}]
171        set y [expr {[winfo rooty $itk_component(area)]+10}]
172    } else {
173        set x 0; set y 0
174    }
175    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."
176}
177
178#
179# Connect --
180#
181#    Connect to the visualization server (e.g. nanovis, pymolproxy).  Send
182#    the server some estimate of the size of our job.  If it's too busy, that
183#    server may forward us to another. 
184#
185itcl::body Rappture::VisViewer::Connect { hostlist } {
186    blt::busy hold $itk_component(hull) -cursor watch
187    update
188
189    # Shuffle the list of servers so as to pick random
190    set servers [_Shuffle $hostlist]
191
192    set memorySize 10000
193    # Get the first server
194    foreach {hostname port} [split [lindex $servers 0] :] break
195    set servers [lrange $servers 1 end]
196
197    while {1} {
198        SendEcho <<line "connecting to $hostname:$port..."
199        if { [catch {socket $hostname $port} _sid] != 0 } {
200            if {[llength $servers] == 0} {
201                blt::busy release $itk_component(hull)
202                return 0
203            }
204            # Get the next server
205            foreach {hostname port} [split [lindex $servers 0] :] break
206            set servers [lrange $servers 1 end]
207            continue
208        }
209        fconfigure $_sid -translation binary -encoding binary
210
211        # send memory requirement to the load balancer
212        puts -nonewline $_sid [binary format I $memorySize]
213        flush $_sid
214       
215        # read back a reconnection order
216        set data [read $_sid 4]
217        if {[binary scan $data cccc b1 b2 b3 b4] != 4} {
218            blt::busy release $itk_component(hull)
219            error "couldn't read redirection request"
220        }
221        set addr [format "%u.%u.%u.%u" \
222            [expr {$b1 & 0xff}] \
223            [expr {$b2 & 0xff}] \
224            [expr {$b3 & 0xff}] \
225            [expr {$b4 & 0xff}]]
226
227        if { [string equal $addr "0.0.0.0"] } {
228            # We're connected. Cancel any pending serverDown events and
229            # release the busy window over the hull.
230            $_dispatcher cancel !serverDown
231            blt::busy release $itk_component(hull)
232            fconfigure $_sid -buffering line
233            fileevent $_sid readable [itcl::code $this _ReceiveHelper]
234            return 1
235        }
236        set hostname $addr
237    }
238    #NOTREACHED
239    blt::busy release $itk_component(hull)
240    return 0
241}
242
243
244#
245# Disconnect --
246#
247#    Clients use this method to disconnect from the current rendering
248#    server.
249#
250itcl::body Rappture::VisViewer::Disconnect {} {
251    if { [IsConnected] } {
252        catch {close $_sid} err
253        set _sid ""
254        $_dispatcher event -after 750 !serverDown
255    }
256    set _buffer(in) ""                 
257}
258
259#
260# IsConnected --
261#
262#    Indicates if we are currently connected to a server.
263#
264itcl::body Rappture::VisViewer::IsConnected {} {
265    return [expr {"" != $_sid}]
266}
267
268#
269# _SendHelper --
270#
271#       Helper routine called from a file event to send data when the
272#       connection is writable (i.e. not blocked).  Sends data in chunks
273#       of 8k (or less).  Sets magic variable _done($this) to indicate
274#       that we're either finished (success) or could not send bytes to
275#       the server (failure).
276#
277itcl::body Rappture::VisViewer::_SendHelper {} {
278    set bytesLeft [string length $_buffer(out)]
279    if { $bytesLeft > 0} {
280        set chunk [string range $_buffer(out) 0 8095]
281        set _buffer(out)  [string range $_buffer(out) 8096 end]
282        incr bytesLeft -8096
283        set code [catch {
284            if { $bytesLeft > 0 } {
285                puts -nonewline $_sid $chunk
286            } else {
287                puts $_sid $chunk
288            }           
289        } err]
290        if { $code != 0 } {
291            puts stderr "error sending data to $_sid: $err"
292            Disconnect
293            set _done($this) 0;         # Failure
294        }
295    } else {
296        set _done($this) 1;             # Success
297    }
298}
299
300#
301# SendBytes --
302#
303#       Send a a string to the visualization server. 
304#
305itcl::body Rappture::VisViewer::SendBytes { bytes } {
306    SendEcho >>line $bytes
307
308    if { ![IsConnected]} {
309        # If we aren't connected, assume it's because the connection to the
310        # visualization server broke. Try to open a connection and trigger a
311        # rebuild.
312        $_dispatcher cancel !serverDown
313        set x [expr {[winfo rootx $itk_component(area)]+10}]
314        set y [expr {[winfo rooty $itk_component(area)]+10}]
315        Rappture::Tooltip::cue @$x,$y "Connecting..."
316        set code [catch { Connect } ok]
317        if { $code == 0 && $ok} {
318            $_dispatcher event -idle !rebuild
319            Rappture::Tooltip::cue hide
320        } else {
321            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."
322        }
323        return
324    }
325    set _done($this) 1
326    set _buffer(out) $bytes
327    fileevent $_sid writable [itcl::code $this _SendHelper]
328    tkwait variable ::Rappture::VisViewer::_done($this)
329    fileevent $_sid writable ""
330    flush $_sid
331    set _buffer(out) ""
332    return $_done($this)
333}
334
335#
336# ReceiveBytes --
337#
338#    Read some number of bytes from the visualization server.
339#
340itcl::body Rappture::VisViewer::ReceiveBytes { size } {
341    if { [eof $_sid] } {
342        error "unexpected eof on socket"
343    }
344    set bytes [read $_sid $size]
345    ReceiveEcho <<line "<read $size bytes"
346    return $bytes
347}
348
349#
350# _ReceiveHelper --
351#
352#       Helper routine called from a file event when the connection is
353#       readable (i.e. a command response has been sent by the rendering
354#       server.  Reads the incoming command and executes it in a safe
355#       interpreter to handle the action.
356#
357#       Note: This routine currently only handles command responses from
358#             the visualization server.  It doesn't handle non-blocking
359#             reads from the visualization server.
360#
361#           nv>image -bytes 100000              yes
362#           ...following 100000 bytes...        no             
363#
364itcl::body Rappture::VisViewer::_ReceiveHelper {} {
365    if { [IsConnected] } {
366        if { [eof $_sid] } {
367            error "_receive: unexpected eof on socket"
368        }
369        if { [gets $_sid line] < 0 } {
370            Disconnect
371        } elseif {[string equal [string range $line 0 2] "nv>"]} {
372            ReceiveEcho <<line $line
373            append _buffer(in) [string range $line 3 end]
374            append _buffer(in) "\n"
375            if {[info complete $_buffer(in)]} {
376                set request $_buffer(in)
377                set _buffer(in) ""
378                $_parser eval $request
379            }
380        } else {
381            # this shows errors coming back from the engine
382            ReceiveEcho <<error $line
383            puts stderr $line
384        }
385    }
386}
387
388
389#
390# Flush --
391#
392#    Flushes the socket.
393#
394itcl::body Rappture::VisViewer::Flush {} {
395    if { [IsConnected] } {
396        flush $_sid
397    }
398}
399
400
401#
402# Color2RGB --
403#
404#       Converts a color name to a list of r,g,b values needed for the
405#       engine.  Each r/g/b component is scaled in the # range 0-1. 
406#
407itcl::body Rappture::VisViewer::Color2RGB {color} {
408    foreach {r g b} [winfo rgb $itk_component(hull) $color] break
409    set r [expr {$r/65535.0}]
410    set g [expr {$g/65535.0}]
411    set b [expr {$b/65535.0}]
412    return [list $r $g $b]
413}
414
415#
416# Euler2XYZ --
417#
418#       Converts euler angles for the camera placement the to angles of
419#       rotation about the x/y/z axes, used by the engine.  Returns a list:
420#       {xangle, yangle, zangle}. 
421#
422itcl::body Rappture::VisViewer::Euler2XYZ {theta phi psi} {
423    set xangle [expr {$theta-90.0}]
424    set yangle [expr {180-$phi}]
425    set zangle $psi
426    return [list $xangle $yangle $zangle]
427}
428
429
430#
431# SendEcho --
432#
433#     Used internally to echo sent data to clients interested in this widget.
434#     If the -sendcommand option is set, then it is invoked in the global scope
435#     with the <channel> and <data> values as arguments.  Otherwise, this does
436#     nothing.
437#
438itcl::body Rappture::VisViewer::SendEcho {channel {data ""}} {
439    #puts stderr ">>line $data"
440    if {[string length $itk_option(-sendcommand)] > 0} {
441        uplevel #0 $itk_option(-sendcommand) [list $channel $data]
442    }
443}
444
445#
446# ReceiveEcho --
447#
448#     Echoes received data tzo clients interested in this widget.  If the
449#     -receivecommand option is set, then it is # invoked in the global
450#     scope with the <channel> and <data> values # as arguments.  Otherwise,
451#     this does nothing.
452#
453itcl::body Rappture::VisViewer::ReceiveEcho {channel {data ""}} {
454    #puts stderr "<<line $data"
455    if {[string length $itk_option(-receivecommand)] > 0} {
456        uplevel #0 $itk_option(-receivecommand) [list $channel $data]
457    }
458}
Note: See TracBrowser for help on using the repository browser.