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

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

xylegend fixes

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