State objects

VisIt is comprised of a set of separate component programs that communicate over sockets. Since each of these components must communicate, a common protocol is needed to simplify the passage of messages over the socket. VisIt uses state objects, which are essentially C++ structs that contain fields needed to set plot, operator, or other attributes. State objects are sent over the socket in between VisIt components and on either side, appropriate action is taken when they are received. Each state object knows how to serialize itself onto VisIt's sockets and can be reconstituted on the other side of the communication once the entire state object has been read from the socket.

The socket in between different VisIt components can be thought of as a conduit or pipe for a specific set of state objects. Both sides of the "pipe" will have instances of the state objects that may pass over the socket. The instances of the state objects are registered with an object called Xfer, which is responsible for processing the incoming data from the socket, putting the contents into the appropriate state object, and calling Notify() on the state object to notify any observers that it may have. This notification is how VisIt's GUI windows are told to update themselves when new state comes to the GUI from the viewer.

StateObjectTransmission.png

In the above diagram, an observer of a state object, which for argument's sake is a GUI window, changes some fields on the state object and calls its Notify() method. The Notify() on the state object causes the Update method on the Xfer observer to be called and the state object gets serialized into binary and written to the socket destined for the viewer. It is worth noting that serialization is binary and in the type representation and endianness of the destination machine. Furthermore, partial transmission of state objects is supported so VisIt only needs to send the parts that changed when a state object is modified. Once the data comes over the socket on the viewer, the Xfer object there reads the state object, fills up its local state object with the new data values and calls Notify() to update any observers in the viewer.

As was mentioned before, the composition of the set of state objects that makes up the communication layer between components differs, depending on the component. For example, the GUI/viewer interface may consist of state objects A,B,C,... while the GUI/mdserver interface consists of state objects X,Y,Z. The set of state objects that are possessed by a client and the program that it represents is defined in the proxy class for the client. For example, the state objects that can be exchanged between the GUI and viewer are exposed through the ViewerProxy class, which is a local C++ object that allows the user to direct the viewer process.

What state objects contain

State objects can contain simple C++ built-in types such as: bool char, unsigned char, int, long, float, double, and std::string as well as arrays or std::vector<> containers of those simple types. In addition, state objects can contain other state objects, so more complex nested structures can be created.

Editing a state object

All of the state objects in VisIt are code-generated using VisIt's XML tools such as xml2atts. Each state object has a corresponding XML file that contains a description of its member fields and their types. The XML file is used for a variety of purposes, including code generation of the C++, Java, and Python implementations of the state object. So, in order to edit a state object, you must edit the XML file and then regenerate the code from the modified XML file.

State objects can be modified by hand but VisIt provides a tool called xmledit that simplifies the creation and editing of state objects. You can use xmledit to add new fields to state objects and then save out the modified XML file. From there, you would regenerate the code using xml2atts, xml2python, and xml2java.

Most of the source code for VisIt's state objects is located in the src/common/state directory. State objects for plot and operator plugins are colocated with the plugin sources. Metadata state objects are located in the src/avt/DBAtts/MetaData directory.

Steps in regenerating a state object:

  1. Edit the state object's XML file using XMLEdit
  2. Regenerate the state object's C++ code using: xml2atts -clobber Foo.xml
  3. Regenerate the state object's Python binding if it has one: xml2python -clobber Foo.xml; mv PyFoo* ../../visitpy/visitpy
  4. Regenerate the state object's Java binding: xml2java -clobber Foo.xml; mv Foo*.java ../../java

Adding a new state object to the GUI/viewer

The set of state objects used to define the GUI/viewer interface is determined in the ViewerState class in the src/viewer/rpc directory. However, the source code for most of those state objects resides in the src/common/state directory. When adding a new state object to the GUI/viewer interface, you should first examine whether you can accomplish what you want by adding new fields to existing state objects since that is simpler. If you do need to add a new state object, first create its XML description using xmledit and save the XML file to the src/common/state directory. Once you have saved the XML description and generated the code, generate the code for the state object by running:

# Generate the code
cd src/common/state
xml2atts -clobber MyNewStateObject.xml
xml2java -clobber MyNewStateObject.xml
xml2python -clobber MyNewStateObject.xml
# Move Python and Java code into the right directories
mv Py*.{C,h} ../../visitpy/visitpy
mv *.java ../../java
# Add the new code to the SVN repository
svn add MyNewStateObject*.{C,h,xml}
svn add ../../java/MyNewStateObject.java
svn add ../../visitpy/visitpy/PyMyNewStateObject.{C,h}
  1. Edit src/common/CMakeList.txt to ensure that state/MyNewStateObject.C will be compiled and built into the visitcommon library.
  2. Edit src/java/CMakeLists.txt to ensure that java/MyNewStateObject.java will be compiled into the VisIt Java library.
  3. Edit src/visitpy/CMakeLists.txt to ensure that visitpy/visitpy/PyMyNewStateObject.C will be compiled into the CLI.

Adding to ViewerState

Now that you've added a new state object to the visitcommon shared library, which is used in various places in VisIt, you can proceed with adding the state object to the GUI/viewer interface. The next step is to add the state object to the ViewerState class in src/viewer/rpc. The ViewerState class contains the set of state objects that can be sent between the viewer and its clients through the ViewerProxy class. The viewer and the ViewerProxy both use the ViewerState class to set up the state objects that can be exchanged. In order to add the new state object to the ViewerState, append the state object to the VIEWER_OBJECT_CREATION macro in ViewerState.h. The VIEWER_OBJECT_CREATION macro is used so you must only add your state object in one location instead of adding it to Xfer yourself and then adding Set/Get methods to access the state object. You'll need to add a line that looks like this, taking care to ensure that the previous line ends with a trailing backslash:

  VIEWER_REGISTER_OBJECT(MyNewStateObject, MyNewStateObject, true)

The last step is to add the include file for your new state object inside ViewerState.C.

Add a viewer behavior

Now that you've done these steps, you've added a new state object to the interface between the GUI and the viewer. This means that the ViewerProxy's ViewerState object will contain an instance of your state object that can be sent from the viewer and that you can add observer windows to display the contents of the new state object. Of course, this is just the client side. Even though you've added a new state object to the GUI/viewer interface, there can be additional work in the viewer, depending on what you need to do. For example, certain state objects must change their values depending on which plots or which windows are active. This requires code in the viewer to populate the state object and Notify() to send it to the clients.

Make similar changes to Java

The Java client does not use macros in the source code because macros are not supported in Java. This means that in order to add the new state object to the Java client, you'll need to create an instance of the object explicitly in ViewerState and add Set/Get methods. Note that when you add the new state object to ViewerState, it *must* be added in the same order as you added the state object in the ViewerState C++ class. The order of state objects in ViewerState determines, to a degree, the composition of the VisIt protocol. Also, don't forget to add the new state object to the Makefile.in and to the SVN repository.