Collaborative VisAD applications.
VisAD applications can run in any of three networked modes:
standalone, server or client.
Standalone mode is the ordinary non-networked application mode.
Server mode is almost identical to standalone mode, with a few
exceptions. Once the data setup is complete, the application
listens for (and responds to) clients in the background and
otherwise operates in exactly the same manner as a standalone
application.
In client mode, the application connects to the server application
and has it send the information necessary to construct copies of any
(or all) of the server's Displays. Once that is done, the server
and client remain connected and forward any significant changes to
each other, so that the Display(s) remain synchronized.
A client's Display information comes from the server. All data for
the copied Display comes from the server as well, so if the server
application is terminated, the client's Display will no longer work.
Refactoring an application for collaboration
A typical VisAD application initializes some data objects
(Displays, ScalarMaps, RealTypes, FlatFields, etc),
associates them with some user interface elements (a JFrame
containing the Display component, some widgets from
visad.util, possibly a locally developed widget
or two), packs everything in a window and pops it up on
the user's screen.
To add collaboration to a VisAD application like this (or to build a
simple collaborative application), the initialization code should
be separated into
one or more data setup methods and one or more user interface setup
methods, and called in that order (data setup then UI setup).
This isn't an absolute necessity, but it simplifies things for
code-reuse purposes, because the client needs to build its own copy
of the user interface widgets (so it needs to run the UI setup
method(s)) but it will be using the server's data (and thus does not
need to run the data setup method(s).)
Also make sure that user interface setup methods don't depend upon
the data initialization methods setting class-wide variables.
Instead, user interface methods should fetch the Controls they need
via the DisplayImpl getControl() methods, any ScalarMaps via
the getMapVector() method, and any ConstantMaps via the
getConstantMapVector() method.
Making an application collaborative
After you have the standalone application working, make the
following changes:
-
Choose a "service name", which is the name used to look up the
server's information. I typically use the application's class
name:
String serviceName = getClass().getName();
but any string will do, provided it is not used by multiple
applications (or multiple instances of the same application)
on a single machine. If you want to run multiple instances
of an application on a single machine, you'll need to have
the user specify the service name somehow.
-
Optionally add some way to start the application up in
standalone, server and client modes. This is often done with a
special argument in the String argument list passed to the
application's main() method, but it could also be indicated
through a property or even via a UI widget.
-
For standalone/server mode, call the data setup method to create
the Display(s) and any data references.
-
For server mode:
-
start up a server using:
RemoteServerImpl server = ClientServer.startServer(serviceName);
-
tell the server about the DisplayImpl(s) by calling:
server.addDisplay(new RemoteDisplayImpl(display));
once for each DisplayImpl in your application.
-
if you have any data references which are needed by user
interface widgets (a Slider, for example) but which have not
been added to the server's Display, tell the server about those
by calling:
server.addDataReference(new RemoteDataReferenceImpl(ref));
-
For client mode:
-
open a connection to the server using:
RemoteServer client = ClientServer.connectToServer(hostName, serviceName);
where serviceName is the name
chosen above.
-
fetch local copies of the remote Display objects using:
LocalDisplay[] dpys = ClientServer.getClientDisplays(client);
or, to fetch individual Display objects:
LocalDisplay dpy = ClientServer.getClientDisplay(client, 0);
The getClientDisplays() method is preferred for fetching more
than one display because using the second method for multiple
displays tends to slow down client initialization, because it
causes multiple trips across the network, slowing down program
initialization.
-
fetch local copies of the remote DataReference objects using:
DataReference ref = (DataReference )client.getDataReference(0);
(where 0 is replaced by the number of the DataReference to be
fetched. The references are numbered in the order they were
added to the server.)
-
For the initial debugging, you may want to disable the user
interface widget setup on the client end until you've got the
basic collaborative application working.
-
For all modes, call the user interface setup method
This is greatly simplified if you use the
DisplayImpl.getWidgetPanel() method, which automatically
builds controls for the Display's ScalarMaps.
-
Verify that all collaborative user interface widgets are based on
either Controls or ScalarMaps (this is already true
of all the widgets in the visad.util package). This means that,
as well as
changing some aspect of the display due to user input (via the
itemStateChanged() method), a collaborative widget must also
change its visible state based on Control or ScalarMap changes
(as a ControlListener via the controlChanged() method, or as a
ScalarMapListener via the mapChanged() method.)
For instance, a standalone version of a widget to turn the VisAD
box on and off might be implemented as a JCheckBox widget with an
ItemListener which calls display.getDisplayRenderer().setBoxOn()
with the appropriate value (true or false) A collaborative
version needs to also listen to the RendererControl (which holds
the state of several DisplayRenderer values) and reflect box
visibility changes in RendererControl by changing the state of the
UI widget (adding a checkmark when the box is turned on, removing
a checkmark when the box is turned off.)
For more details, you can read
a walkthrough of the conversion process.
Testing your collaborative application
Once you've converted your application, verify that it still runs in
standalone mode.
After confirming that standalone mode continues to work, try running
the application as a server. This shouldn't break anything, since
you've simply adding a few steps which should have no effect on the
operation of your application.
When you have your application running as a server, try starting
another copy of your application as a client. This is the point
where a number of problems can be revealed:
-
If you are trying to create a user interface widget using a Control
or ScalarMap saved during the data setup phase, the widget will
throw a NullPointerException since the saved variable will not
have been initialized because the client did not (and should not
have) run the data setup.
-
If a client Display change (via a UI widget) results in the
same change happening to the server's Display but the server's
UI widget is left unchanged, it is likely that the widget is not
responding properly to Control/ScalarMap changes.
See "Making a VisAD widget collaborative"
for details on how to fix this.
Some things to consider
To simplify things, keep the following points in mind
when designing a VisAD application:
-
Any shared data references or Controls should be initialized during
the data setup phase, not as a side effect of the setup code for some
widget in the user interface setup phase.
If a shared VisAD object is initialized by a widget you have
written, it will be *re*initialized when the widget is created by
each client. This will be sent to the server, causing the
current settings to be overwritten and then broadcast to all the
other clients currently connected to the server.
NOTE: All widgets in visad.util should properly
handle shared VisAD objects, so you don't need to worry about them
in a collaborative application.
-
All shared user interface widgets should be initialized from
Controls or ScalarMaps which have been fetched from the Display,
rather than from a variable which had been initialized during the
data setup phase. Since the client does not go through the data
setup phase, it won't have a copy of any Controls or ScalarMaps
saved during that phase.
-
All shared user interface widgets (i.e. user interface widgets
which will be implemented on both the server and the client)
should be connected to Display-based Controls or ScalarMaps. All
client-server coordination currently happens via Displays, and if
a Control or ScalarMap isn't attached to a Display, changes to it
will not be forwarded to the server.
-
Most of the above only applies to simple collaborative
applications. It is possible to build an application which is a
client using a remote Display and acting as a server for a local
Display or ScalarMap, or for an application to switch between
standalone and client/server modes. You're only limited by
your imagination (and the memory/bandwidth of your workstation :-)
If you have any questions join the VisAD mailing list (see the
VisAD web page
for instructions) and then
send your questions
Last modified: Tue Jul 11 14:59:42 CDT 2000