Vortex Studio SDK - Adding User Controls

Introduction

A common task among developers that use the Vortex Studio SDK is the development of a user interface to control their application and thus the simulation. An example of such an interface can be seen in the Vortex Studio Player application.
Some of its main features are:

  • Load content

  • Start/stop/pause the simulation

  • Shows info, warnings, and error messages generated by the application.

This section of the guide will show you how you can develop equivalent functionality inside your own application using the Vortex Studio SDK.

Adding an Operator Console

There are basically two ways to allow an external user control over a Vortex application. Insert our user controls inside the application or make a separate application that communicates with Vortex through an external channel.
Here we will focus on the former method, which is both simpler and gives more direct access to Vortex through your user interface.

When you launch the Vortex Studio Player, you might have the impression that there is two distinct applications: the simulation application with its blue background and the user control application containing all widgets.
Actually, those two windows are part of the same Vortex application. The user control portion of the application is simply a plugin (see Vortex Studio SDK - Customizing Vortex - About Plugins) containing extensions (see Vortex Studio SDK - Customizing Vortex - Extensions) which handle the user interface and its logic. As with any extension, they must be handled by a module. Each extension will also typically implement an interface specific to your user interface. That interface can be checked against on each extension received by your module's onExtensionAdded method.

Most of Vortex Studio's graphical applications uses the Qt GUI toolkit to develop native C++ graphical application. This is in no way a restriction on which technology you may use for your own application. Since you will be making your own module to handle your UI extensions, you can use whatever UI technology is most appropriate for the task at hand.

Going back to the example of the Vortex Studio Player, each of its pages are class deriving from VxSim::IExtension and also VxSim::UI::IQtPage. The VxQtModule which handles extensions implementing the VxSim::UI::IQtPage is added to the application setup document (.vxc). All UI extensions are also added to the setup document. When launching the application, the module will be automatically added to the application and it will manage all extensions it accepts in its onExtensionAdded method. It is also at that moment, when the extension is added to the module that you would signal the extension to start the creation of its user control elements.

To ensure that operations done by your user interface do not negatively impact the performance of your simulation, be sure to separate both process into different threads of execution. In other words, when your extension gets added to your module in its onExtensionAdded method do not start creating widgets in the simulation thread. Instead signal your extension in another thread that it can now create its content and handle UI related events.

For example, the IQtPage interface offers the createPage virtual method that is implemented by each specific extension and will be called from the UI thread.

Here is a small code example that demonstrates in C++ what was explained above using a custom IUIPage tagging interface which our module will use to distinguish extensions it is interested in managing.

#include <VxSim/IExtension.h> #include <VxSim/ISimulatorModule.h> #include <VxSim/VxSmartInterface.h> #include "IUIPage.h" // Tagging interface for our module class IUIPage { // Pure virtual function to implement in each derived class. virtual void createPage() = 0; }; class ContentBrowser : public VxSim::IExtension, public IUIPage { public: // Methods overloaded from VxSim::IExtension virtual ~ContentBrowser(); ContentBrowser(VxSim::VxExtension *proxy); ... // Method overloaded from IUIPage virtual void createPage() { // Create widgets in UI thread. ... } ... void setApplication(VxSim::VxApplication* application) { mApplication = application; } private: VxSim::VxApplication* mApplication; }; class UIModule : public VxSim::ISimulatorModule { ... virtual void onExtensionAdded(VxSim::VxExtension* extension) { VxSim::VxSmartInterface<IUIPage> uiPage = extension; if (uiPage.valid()) { _addManagedExtension(extension); uiPage->setApplication(getApplication()); uiPage->createPage(); } } ... };

The following sections will detail common functionalities that developers typically want to expose in their Vortex application.

Loading Content

As described in Integrating the Application, you can use the class VxSim::VxSimulationFileManager of your VxSim::VxApplication to load content on all network nodes. Sometimes this is enough but there are also other cases where the class VxSim::VxSimulationFileManagerFacade is more appropriate. The class VxSimulationFileManagerFacade has the added advantage of not requiring direct access to the VxApplication and of being thread safe. You can call the loadObject() method from your UI thread and it will dispatch an event to the VxApplication in the simulation thread to load your object. Additionally you can register instances of VxSimulationFileManagerFacade::Listener to the VxSimulationFileManagerFacade to be notified about the state of your load/unload request.

Available callbacks are:

  • onRequestedObjectLoaded / onRequestedObjectUnloaded

  • onObjectAboutToLoad / onObjectAboutToUnload

  • onObjectLoaded / onObjectUnloaded

These callbacks give you all the flexibility needed to adjust your user interface based on the state of the content.

Start, Pause, Stop

As explained in Integrating the Application, there are three application modes. You initially load content in Editing mode and when you are ready to start the simulation, you switch to Simulating mode. During the simulation you can easily pause, resume or even make it run for just one simulation update (i.e. simulating step by step).

The class VxSim::VxApplication contains many methods to perform each of these tasks. The methods which will trigger a change in the state of the application will dispatch an event which will perform the actual task on the simulation thread. They can thus safely be called from the UI thread.
Here is an excerpt from the VxApplication's class header. Some comments have been shortened, for the full documentation please refer to the SDK's doxygen documentation or the file VxApplication.h.

// Changes the application mode asynchronously. The application mode will change when possible. // Note that the current update will stay at the same mode. bool setApplicationMode(eApplicationMode mode); // Determines whether the paused flag is set on the simulation. bool isPaused(); // Negation of isPaused() when the mode is VxSim::kModeSimulating. bool isSimulationRunning(); // Pauses the simulation. // It will take effect at the next application update. void pause(bool pause = true); // Resumes the simulation. // It will take effect at the next application update. void resume(); // Makes the simulation do one step and then pause. void stepOnce();

Going back to our example ContentBrowser class, here's how it may expose some of these functionalities.

void ContentBrowser::simulate() { mApplication->setApplicationMode(VxSim::kModeSimulating); mApplication->resume(); } void ContentBrowser::pause() { mApplication->pause(); } void ContentBrowser::step() { mApplication->setApplicationMode(VxSim::kModeSimulating); mApplication->stepOnce(); } void ContentBrowser::stop() { mApplication->setApplicationMode(VxSim::kModeEditing); }

Logging

Logging information for users is a task that many applications must perform. Vortex Studio isn't different and there is a wide range of informative messages that are logged by Vortex. These messages can easily be re-routed to your application and you can also use Vortex's logging system to add your own messages. All those functionalities are exposed in C++ through the functions in the VxMessage.h header file.

You can log messages to Vortex using a set of increasing levels based on the severity of the message you wish to convey. The levels are represented in the following enum.

By default, there is no way to recover from a fatal error. It will halt your application. An error throws an exception which allows for a softer termination or even a recovery depending on the circumstances. If you implement your error message handler (see below) be sure to follow the guidelines listed in the eLogLevel enum as this is what Vortex expects.

You can use the function LogSetLevel() to set the minimum level of messages you are interested in receiving. For example, if you call LogSetLevel(kWarn), you will only receive warnings, errors, and fatal errors; info, debug and trace messages will be silently dropped.

To register your own message handler instead of the default one, use the function LogSetHandler() which takes a callback function of type void (*LogHandler)(eLogLevel level, const char *const format, va_list ap) as its sole argument. Your handler will be called for each new message from that point on with the message's log level, its format string and a variadic list of arguments for the string. The LogSetHandler()function returns the previous handler in case you would want to put it back at some point.

To log your own messages, simply use the appropriate function for the log level of the message (LogFatal(), LogError(), LogWarning(), etc...). All of those functions use a format string with a variadic argument list, exactly like the C printf() function.

If you want to log to a file, use the setLogFile() function with the path to the file as a string. To disable file logging, simply pass and empty string to the function.