Creating New Client With C++

(Redirected from CreatingNewClientWithC++)

All VisIt clients use the ViewerProxy class to launch VisIt and communicate with it. The C++ ViewerProxy class was implemented first and VisIt's GUI and CLI use it to communicate with the viewer. The C++ ViewerProxy class offers a lot of flexibility but this means that it is not quite as encapsulated or easy to use as, say, the Java ViewerProxy implementation which came second. However, with a little instruction and additional coding (which could be folded back into ViewerProxy) it is possible to create a new VisIt client using C++ without a lot of effort.

Where to begin

The C++ ViewerProxy class needs a little help to make it easier to set up and start talking to VisIt. The chief complications are plugin loading and synchronization.


Every VisIt client must load plugins in order to set up the communication interface between the client and the viewer. Client plugins also can provide control capabilities such as windows that change plot and operator attributes. When you write a new client, you often need to create a new class of client plugins for plots and operators so your application can control plot and operator settings. However, for many simple programs where you just want to create a simple visualization from C++, you can probably get by with telling VisIt to load the plugins for VisIt's CLI. Of course, this means linking your application with Python, but using the GUI or Viewer plugins for VisIt introduces more VisIt library dependencies. Eventually, VisIt's plugins will be changed so the state object portion of the plugin will migrate into the libI, or common plugin that all C++ plugin managers load. This will enable C++ client developers to use vanilla VisIt plugins with far fewer VisIt library dependencies.

The following class makes launching the viewer and setting up a visualization easy since the class provides the code for loading plugins and also provides a Synchronize() method. The Synchronize method makes the calling code block wait until a reply has been received from the viewer. This is useful for when you want to perform an action that you know will cause a change to the state objects but you can't continue until the new state object values arrive.

#include <ViewerProxy.h>
#include <ViewerMethods.h>
#include <ViewerState.h>

#include <ObserverToCallback.h>
#include <PluginManagerAttributes.h>
#include <SyncAttributes.h>
#include <Connection.h>
#include <PlotPluginManager.h>
#include <OperatorPluginManager.h>
#include <DebugStream.h>

class VisItClient
{
public:
    // Constructor
    VisItClient()
    {
        viewer = 0;
        loadPlugins = 0;
        loadedPlugins = false;
        syncTag = 100;
    }

    // Destructor
    virtual ~VisItClient()
    {
        delete viewer;
        delete loadPlugins;
    }

    // Starts the viewer and executes your Work function
    void Execute(int *argc, char ***argv)
    {
        std::string visitProgram("visit");

        if(viewer != 0)
            return;

        // Let the user override the program we use for VisIt on the command line.
        for(int i = 0; i < *argc; ++i)
        {
            char **argv2 = *argv;
            if(strcmp(argv2[i], "-dir") == 0 && (i+1) < *argc)
            {
                visitProgram = std::string(argv2[i+1]) + "/bin/visit";
                ++i;
            }
        }

        // Create the viewer proxy and launch the viewer.
        viewer = new ViewerProxy;
        viewer->InitializePlugins(PluginManager::Scripting);
        viewer->Create(visitProgram.c_str(), argc, argv);

        // Set up an observer that will call our LoadPlugins method
        // when the plugin manager attributes come from the viewer.
        loadPlugins = new ObserverToCallback(
            viewer->GetViewerState()->GetPluginManagerAttributes(),
            LoadPlugins,
            (void*)this);

        // Wait for synchronization
        Synchronize();

        // Show the viewer windows.
        GetViewerMethods()->ShowAllWindows();

        // Call the user's Work method.
        Work();
    }

    static const int INVALID_PLUGIN_INDEX;

protected:
    // Override this methd to create a program that does something.
    virtual void Work() { };

    typedef struct {int syncTag; bool waitForSync; } sync_data;

    // This method causes the code to block until the viewer has 
    // caught up with the client.
    void Synchronize()
    {
        GetViewerState()->GetSyncAttributes()->SetSyncTag(syncTag);
        GetViewerState()->GetSyncAttributes()->Notify();

        sync_data s;
        s.syncTag = syncTag;
        s.waitForSync = true;
        ObserverToCallback *sync = new ObserverToCallback(
            GetViewerState()->GetSyncAttributes(),
            CheckSync,
            (void*)&s);

        // An event loop
        while(s.waitForSync)
        {
            if(viewer->GetWriteConnection()->NeedsRead(true))
                viewer->ProcessInput();
        }

        delete sync;
        ++syncTag;
    }

    // This method enters an event loop in case you want to keep
    // VisIt around after your Work() function is done.
    void EventLoop()
    {
        while(true)
        {
            if(viewer->GetWriteConnection()->NeedsRead(true))
                viewer->ProcessInput();
        }
    }

    // Return the viewer methods object, which contains the methods
    // that you can call on the viewer.
    ViewerMethods *GetViewerMethods() { return viewer->GetViewerMethods(); }

    // Return the viewer state object, where you'll find the state
    // objects that you can use to modify the viewer's state.
    ViewerState   *GetViewerState()   { return viewer->GetViewerState(); }

    // Converts a plot plugin name to an index that you can pass to
    // functions such as AddPlot
    int PlotIndex(const std::string &name) const
    {
        return PluginIndex(viewer->GetPlotPluginManager(), name);
    }

    // Converts an operator plugin name to an index that you can pass to
    // functions such as AddOperator.
    int OperatorIndex(const std::string &name) const
    {
        return PluginIndex(viewer->GetOperatorPluginManager(), name);
    }

    // The viewer proxy object that you use to control the viewer.
    ViewerProxy   *viewer;
private:
    // Private callback function for loading plugins
    static void LoadPlugins(Subject *subj, void *data)
    {
        VisItClient *This = (VisItClient *)data;

        // We've received plugin attributes
        if(!This->loadedPlugins)
        {
            This->viewer->LoadPlugins();
            This->loadedPlugins = true;
        }
    }

    // Private callback function for synchronization
    static void CheckSync(Subject *subj, void *data)
    {
        SyncAttributes *s = (SyncAttributes *)subj;
        sync_data *sd = (sync_data*)data;
        if(s->GetSyncTag() == sd->syncTag)
            sd->waitForSync = false;
    }

    // Private helper function for turning plugin name to index.
    int PluginIndex(PluginManager *mgr, const std::string &name) const
    {
        for(int i = 0; i < mgr->GetNEnabledPlugins(); ++i)
        {
            if(mgr->GetPluginName(mgr->GetEnabledID(i)) == name)
                return i;
        }
        return INVALID_PLUGIN_INDEX;
    }

    ObserverToCallback *loadPlugins;
    bool                loadedPlugins;
    int                 syncTag;
};

const int VisItClient::INVALID_PLUGIN_INDEX = -1;

Setting up your visualization

The above class makes calling VisIt's viewer as simple as calling the Execute() method. However, you'll want to create a subclass of VisItClient to override its Work() method so it does something useful. This example subclass opens a database and creates a Pseudocolor plot from it, adds a ThreeSlice operator, and finally sets some plot attributes. Note that the plot and operator plugin attributes cannot be set directly in a C++ client from the client side because your client only has access to the plugin's state object at the base class level. So, for example, you can't access plot attributes through PseudocolorAttributes, you must access them through the base class: AttributeSubject. Use the SetValue methods provided by the base class. The SetValue methods let you set various state object fields by name, providing a way for a generic client that does not implement its own plugins to set plugin attributes.

class PseudocolorVis : public VisItClient
{
public:
    PseudocolorVis() { }
    virtual ~PseudocolorVis() { }
protected:
    virtual void Work()
    {
        // Open a database
        GetViewerMethods()->InvertBackgroundColor();

        //Change the database to point to your own database
        GetViewerMethods()->OpenDatabase("/usr/gapps/visit/data/noise.silo");
        Synchronize();

        // Create a plot and draw it.
        int plotType = PlotIndex("Pseudocolor");
        if(plotType != INVALID_PLUGIN_INDEX)
        {
            debug1 << "Plot type = " << plotType << endl;
            GetViewerMethods()->AddPlot(plotType, "hardyglobal");
      
            int threeSlice = OperatorIndex("ThreeSlice");
            if(threeSlice != INVALID_PLUGIN_INDEX)
                GetViewerMethods()->AddOperator(threeSlice);
            GetViewerMethods()->DrawPlots();
        }
        Synchronize();

        // Save an image.
        GetViewerMethods()->SaveWindow();

        // Set some pseudocolor plot attributes
        AttributeSubject *pcAtts = GetViewerState()->GetPlotAttributes(plotType);
        if(pcAtts != 0)
        {
            pcAtts->SetValue("min", 1.5);
            pcAtts->SetValue("minFlag", true);
            pcAtts->SetValue("max", 4.5);
            pcAtts->SetValue("maxFlag", true);
            pcAtts->SetValue("colorTableName", "calewhite");
            pcAtts->Notify();
            GetViewerMethods()->SetPlotOptions(plotType);
        }

        // Save an image.
        GetViewerMethods()->SaveWindow();

        // Enter an event loop so the program keeps running and we can
        // interact with the viewer.
        EventLoop();
    }
};

Writing your main function

All C++ programs need a main() function as an entry point into the program. The VisIt libraries that are used require a little initialization before they are used. The following example program defines a main function that initializes the required VisIt libraries and instantiates the PseudocolorVis class from above, which launches VisIt's viewer and sets up a visualization.

Note that we're using VisIt's existing scripting plugins so you don't have to create your own plugins for all of VisIt's plots and operators.

#include <VisItInit.h>
#include <VisItException.h>
#include <PlotPluginManager.h>
#include <OperatorPluginManager.h>

int
main(int argc, char *argv[])
{
    // Step 1: Initialize error logging.
    VisItInit::Initialize(argc, argv, 0, 1, false);
    VisItInit::SetComponentName("proxyexample");

    // Step 2: Initialize the plugin managers. (use scripting plugins for now)
    // Following are not need anymore. It is handled by viewer proxy
    // PlotPluginManager::Initialize(PlotPluginManager::Scripting);
    // OperatorPluginManager::Initialize(OperatorPluginManager::Scripting);

    // Step 3: Create the object and enter its Execute method.
    PseudocolorVis vis;
    TRY
    {
        vis.Execute(&argc, &argv);
    }
    CATCH(VisItException)
    {
    }
    ENDTRY

    // Step 4: Finalize to close error logging, etc.
    VisItInit::Finalize();
}

Building the client

Here are some variables that you can modify and add to the Makefile that you use to build your client. When the below examples indicate /path/to/visit/version/platform you would substitute a path such as: /usr/local/apps/visit/2.1.0/linux-intel.

CPPFLAGS -I/path/to/visit/version/platform/include/visit
LDFLAGS -L/path/to/visit/version/platform/lib
LIBS -lviewerproxy -lviewerrpc -lvisitcommon -lpython2.6

You can also use the CMakeLists.txt file below and make the necessary modifications to compile your new client.

CMAKE_MINIMUM_REQUIRED(VERSION 2.6.0 FATAL_ERROR)

PROJECT(newclient)

SET(VISIT_INSTALL_DIR "/data/store/research_tools/visit/current/linux-x86_64")

SET(VISIT_INCLUDE_DIR ${VISIT_INSTALL_DIR}/include)

INCLUDE(${VISIT_INCLUDE_DIR}/PluginVsInstall.cmake)

INCLUDE_DIRECTORIES	(${VISIT_COMMON_INCLUDES}
                        ${VISIT_INCLUDE_DIR}/visit/avt/DBAtts/SIL
                        ${VISIT_INCLUDE_DIR}/visit/avt/DBAtts/MetaData
                        ${VISIT_INCLUDE_DIR}/visit/viewer/main
                        ${VISIT_INCLUDE_DIR}/visit/viewer/proxy
                        ${VISIT_INCLUDE_DIR}/visit/viewer/rpc)

LINK_DIRECTORIES(${VISIT_INSTALL_DIR}/lib)

SET(NEW_CLIENT_SRCS newclient.cpp)

ADD_EXECUTABLE(newclient ${NEW_CLIENT_SRCS})

TARGET_LINK_LIBRARIES(newclient viewerproxy viewerrpc visitcommon python2.6)

If you are running on Linux, you can add -Wl,-rpath,/path/to/visit/version/platform/lib to the LDFLAGS Makefile variable to avoid having to specify LD_LIBRARY_PATH at runtime.

Running the client

Your program needs to know where to find the VisIt libraries at runtime. You can use -rpath on Linux when building your client to let it know where to find VisIt's libraries. You can instead set the LD_LIBRARY_PATH environment variable (DYLD_LIBRARY_PATH on MacOS X) to tell the dynamic loader that loads your program where to find the VisIt libraries. In addition, VisIt requires the VISITPLUGINDIR environment variable to be set up so plugins can be loaded. Here is an example command line for running your new VisIt client:

env LD_LIBRARY_PATH=/path/to/visit/version/platform/lib VISITPLUGINDIR=//path/to/visit/version/platform/plugins newclient