Simulation User Interface

When VisIt connects interactively to a running simulation, the Simulation window in VisIt's GUI displays information about the connected simulation. The window also presents a set of buttons that enable the user initiate simple simulation commands that are processed by the Libsim simulation control callback function. The number of commands is currently limited so only 5 custom commands can be shown in the Simulation window (in VisIt 2.9.2 and earlier). Simulations can provide custom user interfaces to provide better control over the simulation.

  • The user interface capabilities of Libsim would benefit from further development. That is, they are still somewhat experimental.
  • Uintah has a nice simulation dashboard based on the Libsim user interface.

Commands

Custom simulation commands can be exposed by way of the simulation's metadata. The commands are used to set the labels for the command buttons in the Simulation window in VisIt's GUI. When the command buttons are clicked, VisIt sends the simulation a message to execute those commands and the simulation handles the command via the control command callback, registered via VisItSetCommandCallback().

SimulationWindow.png

Exposing a Command

Commands are exposed by adding new command objects to the simulation metadata.

const char *cmd_names[] = {"halt", "step", "run", "update", "toggleupdates"};

visit_handle
SimGetMetaData(void *cbdata)
{
    visit_handle md = VISIT_INVALID_HANDLE;
    simulation_data *sim = (simulation_data *)cbdata;

    /* Create metadata. */
    if(VisIt_SimulationMetaData_alloc(&md) == VISIT_OKAY)
    {
        /* MESH AND VARIABLE CODE OMMITTED */

        /* Add some custom commands. */
        for(size_t i = 0; i < sizeof(cmd_names)/sizeof(const char *); ++i)
        {
            visit_handle cmd = VISIT_INVALID_HANDLE;
            if(VisIt_CommandMetaData_alloc(&cmd) == VISIT_OKAY)
            {
                VisIt_CommandMetaData_setName(cmd, cmd_names[i]);
                VisIt_SimulationMetaData_addGenericCommand(md, cmd);
            }
        }
    }

    return md;
}

Handling a Command

Simulation commands are handled via the control callback function registered via the VisItSetCommandCallback() function. All generic simulation commands are routed to this callback function for handling. The control callback can be installed once the Libsim runtime library has been loaded after VisIt connects to the simulation. When registering the control callback function it is helpful to pass a pointer to some simulation state so that the simulation state can be accessed from the control callback function. The commands can be set up to do whatever the implementor wants; it is often helpful to provide commands to step, halt, and run the simulation.

void ControlCommandCallback(const char *cmd, const char *args, void *cbdata)
{
    simulation_data *sim = (simulation_data *)cbdata;

    if(strcmp(cmd, "halt") == 0)
        sim->runMode = SIM_STOPPED;
    else if(strcmp(cmd, "step") == 0)
        simulate_one_timestep(sim);
    else if(strcmp(cmd, "run") == 0)
        sim->runMode = SIM_RUNNING;
    else if(strcmp(cmd, "update") == 0)
    {
        /* When we get "update", send new metadata and make VisIt update the plots it may have with new simulation data. */
        VisItTimeStepChanged();
        VisItUpdatePlots();
    }
    else if(strcmp(cmd, "toggleupdates") == 0)
    {
        sim->autoupdate = !sim->autoupdate;
        if(sim->autoupdate)
        {
            VisItTimeStepChanged();
            VisItUpdatePlots();
        }
    }
    else
    {
        fprintf(stderr, "cmd=%s, args=%s\n", cmd, args);
    }
}

/* Register the control callback function*/
VisItSetCommandCallback(ControlCommandCallback, (void*)sim);

Custom User Interface

VisIt relies on the Qt GUI library. Qt is a portable C++ library that enables VisIt to create a user interface across various platforms. A user interface is built up of Qt widgets (user interface controls) that that emit specific signals in response to user interaction. The Qt widgets' signals are hooked up to slot functions that implement a specific behavior. Thus, if the user clicks on a button, a specific slot function will be invoked and cause the program to do something. Libsim's user interface support follows the pattern established by Qt where a GUI is defined and then the various widgets that comprise it are connected to slot functions that cause the simulation to undertake some action.

MandelbrotUserInterface.png
Mandelbrot Custom UI

Creating the User Interface

A custom user interface is created using Qt Designer, which is a Qt design utility that creates windows based on Qt widgets. The designs can be saved to its "*.ui" file format, which is an XML-based description of the widgets and their properties that can be used to create the window dynamically. Qt Designer's GUI contains a few windows that you will need to use in order to create your user interface:

  1. Widget Box
  2. Object Inspector
  3. Property Editor

QtDesigner.png

Qt Designer will provide a blank form window onto which new Qt widgets can be placed. To add a new widget, click on the desired widget type in the Widget Box window and then place the new widget into the form window. Once the new widget appears in the form window, the Property Inspector can be used to set properties that influence the widget's look and feel, as well as the contents of menus, lists, etc. It is important to give the new widget a memorable name if it will be used to trigger actions for the simulation. The simulation will reference the Qt widget by this name when connecting the widget to action functions or when updating the widget in response to the simulation setting the widget's values. The Object Inspector panel provides an overview of the Qt widgets that have been added to the form and how they relate to one another.

  • Give the widgets that will be associated with simulation actions a good name using the property editor.
  • Qt Download

Supporting the User Interface in the Simulation

The user interface ui file must be copied to the ~/.visit/ui directory on the computer that will run the VisIt client. When the file is present there and the simulation is associated with that user interface file then the Simulation window in VisIt will provide a Custom ... button that will cause VisIt to read that UI file and create a window for the Simulation's custom user interface.

To associated a user interface file with the simulation, supply the name of the UI file as the 5th argument to VisItInitializeSocketAndDumpSimFile().

VisItInitializeSocketAndDumpSimFile(
    "mandelbrot",
    "Demonstrates creating the Mandelbrot set on an AMR mesh",
    "/path/to/where/sim/was/started",
    NULL,
    "mandelbrot.ui", //<---- here
    SimulationFilename());

Registering UI Actions

UI actions can be handled by individual C callback functions. These callback functions are the equivalent of a Qt slot function that gets attached to a Qt widget. The difference here is that the actions for the simulation's user interface are executed within the simulation as opposed to the VisIt GUI. The functions are named with the "VisItUI" prefix followed by the name of the Qt signal for which they are valid. The name of the signal must be a signal that is valid for the Qt widget with which the action function is associated. For instance, a QPushButton object named "STEP" will have a "clicked" signal since it is a button. In that case, it is appropriate to associate the "STEP" widget with an action callback function using the VisItUI_clicked() function. All widgets are identified by the name given to them when designing the UI file.

Libsim functions for connecting a widget's signal with a user action callback function:

int VisItUI_clicked(const char *name, void (*cb)(void*), void *cbdata2);
int VisItUI_stateChanged(const char *name, void (*cb)(int,void*), void *cbdata2);
int VisItUI_valueChanged(const char *name, void (*cb)(int,void*), void *cbdata2);

The following table lists the supported Qt widgets, their signals, and which Libsim functions apply to each widget type.

Qt Widget Qt Signals Libsim Function
QCheckBox stateChanged VisItUI_stateChanged
QPushButton clicked VisItUI_clicked
QSpinBox valueChanged VisItUI_valueChanged

Example usage:

/* Register some ui actions */
VisItUI_clicked("STEP", ui_step_clicked, sim);
VisItUI_clicked("HALT", ui_halt_clicked, sim);
VisItUI_clicked("RUN", ui_run_clicked, sim);
VisItUI_clicked("RESET", ui_reset_clicked, sim);
VisItUI_valueChanged("LEVELS", ui_levels_changed, sim);
VisItUI_valueChanged("REFINEMENTRATIO", ui_ratio_changed, sim);
VisItUI_stateChanged("SAVEIMAGES", ui_saveimages_changed, sim);
VisItUI_stateChanged("UPDATEPLOTS", ui_updateplots_changed, sim);

/* Function that gets called when the "STEP" button is clicked. */
void
ui_step_clicked(void *cbdata)
{
    simulation_data *sim = (simulation_data *)cbdata;
    /* Advance to the next time step. */
    simulate_one_timestep(sim);
}

Updating the UI

The simulation UI's job is to gather input from the user and to display some information about the simulation. In order to show information from the simulation, a different set of functions must be called. Libsim provides the VisItUI_setValue set of functions so the simulation can pass integer and string values to the named widget.

int VisItUI_setValueI(const char *name, int value, int enabled);
int VisItUI_setValueS(const char *name, const char *value, int enabled);

Example usage:

/* Update some UI elements */
VisItUI_setValueI("LEVELS", sim->max_levels, 1);
VisItUI_setValueI("REFINEMENTRATIO", sim->refinement_ratio, 1);
VisItUI_setValueI("SAVEIMAGES", sim->savingFiles, 1);
VisItUI_setValueI("UPDATEPLOTS", sim->autoupdate?1:0, 1);