Programmers Guide
Wingman Framework Programmers Guide
Version 0.6.0
Date Updated : February 5th, 2016
Documentation in progress, not complete.
Table of contents
1. Introduction
This guide describes the Wingman Framework, and how to use Wingman facilities in the development of XPlane plugins. This guide covers the following topics.
- XPlane Interface
- Framework Basics
- Build System
- Debugging
- Framework Usage
1.1. Purpose
The purpose of the Wingman Framework is to provide an easy to use API to develop plugins in an Object Orientated Programming,OOP, langauge.
1.2. Audience
This document is targeted to programmers who are writing XPlane plugins using the Wingman System, WMS. This system includes the myplugin template, Clamshell-VCCE, build system and the framework libraries.
1.3. Design Goals
The design goals for the framework is limit the overhead from the parent XPLM API while giving the programmer an Object Orientated Programming,OOP, design. Also to aid the developer to create XPlane plugins faster and for all targets XPlane supports.
1.4. Variants
Each release of Wingman includes a debug and production versions of the linkable Framework Library for each target. Targets include
- Windows 32bit
- Windows 64bit
- Linux 32bit
- Linux 64bit
The debug variant of Wingman's framework library supports the output of debug messages while the production variant suppresses them. Some complex framework classes have debug level messages to aid the programmer with debugging and provides a class method to enable the output. Also, when Wingman's build system with debug enabled then symbols are not stripped from the plugins binaries.
1.5. Framework Components
The below list of components and it's header files can be viewed from Wingman's framework include folder.
ex. "XPlane/Resources/Wingman/include/wingman/framework"
Component | Description |
---|---|
base | Base object and container list class. |
common | Common defines, enums, struct etc... |
data | Data and container list objects for XPLM Datarefs |
exception | Custom exception classes |
gui | 2D graphical user interface |
graphics | 3D drawing interface |
interface | Interface classes. (aka pure virtual classes) |
io | Input/Output classes for file and other communications. |
location | 2D and 3D coordinate system |
message | Internal communication system. |
system | External events and system processing |
templates | Template classes |
utilities | Commonly used classes and functions. |
view | 3D View classes. |
1.6. References and Resources
2. XPlane Interface
The interface from Wingman to XPlane is via the XPlane Plugin Manager, XPLM. This XPLM supports XPlane 10.20 and newer for 32bit and 64bit platforms. XPlane 9 is not supported by Wingman. Interaction between XPLM and plugins is event, or callback, driven.
2.1. Plugin SDK
The XPlane Plugin SDK includes the XPLM 32bit and 64bit libraries and header files which provide a lower level C API to interface to the XPLM. The XPLM API is very well written, however it is written for procedural programming which can take more infrastructure to build a complex plugin.
2.2. Context
Context is what process or thread the plugin is running from. Preemptive vs cooperative and which one is the plugin running under. When designing your plugin you should design your tasking model under cooperative mythology. This means any events, callbacks, from XPLM the callback routine must run to completion. Any attempt to hold the context will lower XPlane's performance.
3. Framework Basics
3.1. Directory layout
Extracting the Wingman zip/tar.gz file revelas the below contents.
folder/files | Description |
---|---|
3rdParty | Any 3rdparty or contribution components. |
include | Framework include files. |
lib | Pre-built binaries of Wingman Framework for Windows and Linux in 32bit or 64bit and debug versions of those libraries. |
sample | Template project, source files located in there, called "myplugin" |
XPSDK | Pre-built low level XPLM libraries |
License.txt | License information for Wingman & location of where to find the EULA is kept in there. |
ReleaseNotes.txt | Information about the release. |
rules.mk | Make rules, required by Wingman's build system. |
version.txt | Version information. Need to know what version is installed, check this file. |
3.2. Main
Every application requires a "main", somewhere where the application code starts, and under Wingman this is the class "PluginApplication". This class has an interface with 4 methods that handle the start, stop and communication.
Interface | Description |
---|---|
Initialize() | Method to initialize or create components |
Start() | Callback, after Initialize(), to start process events. |
Stop() | Called by XPLM when to stop your plugin. |
Destroy() | Called just before the plugin is unloaded from memory. |
3.3. Header files
The Wingman Framework library consists of many header files with each one containing classes or functions. However only two header files are needed for plugin development.
- wingman.h
- xpsdk.h
The "wingman.h" includes all header files within the Wingman Framework Library. Just add this header file and all Wingman classes, typedefs, enumeration etc... will be included. This way any updates to the Wingman Framework header file locations and names will not impact you, just recompile and you are done.
The "xpsdk.h" includes all XPLM SDK header files as well as defines to compile against the XPLM SDK. You may need to include this file if you need to access the lower level XPLM API.
3.4. Namespace
The Wingman library is built under the namespace "wingman". This is to separate Wingman classes and functions from other libraries that may use the same names. Wingman definitions must be prefixed with "wingman::" otherwise the compiler will raise an error of unknown type.
For example: To declare an object of type XPWindow and instantiate
wingman::XPWindow *window = new wingman::XPWindow("winMain","Main Window");
However adding a "using namespace wingman" can eliminate the need to use the "wingman::" prefix.
#ifndef MYPLUGIN_H_ #define MYPLUGIN_H_ #include "wingman.h" using namespace wingman;
For example: Add to the plugins header file.
#include "myplugin.h" void Myplugin::Initialize() { XPWindow *window = new XPWindow("winMain","Main Window"); }
The use of XPWindow no longer needs to be prefixed with "wingman::".
3.5. Short Paths
Short paths is a method to specify file paths within XPlane folders. This method allows plugins to define a file path that will work for all operating systems.
Short paths are used internally by Wingman. For example:
- wingman.ini - To specify log and debug files.
- xpcimage class - For loading PNG files.
- configuration class - When creating your own INI configuration files.
Any methods with a string parameter named shortpath is expecting a shortpath.
Syntax: SHORTPATH | ["folders"]/filename"
The format of a short path is the SHORTPATH followed by a '|' and then directory names and finally a file. Directory and file names can be separated with either a backslash, '\', or forward slash, '/'.
SHORTPATH | Description |
---|---|
PLUGIN | The plugins root/home directory. |
FAT | Plugins executable directory. Under 64/ or 32/ |
EXEC | XPlane's executable directory. |
PREF | XPlane's preference directory. |
USER | User defined. If you use then provide the full path as expected under the operating system. Caution, this will limit which operating system this plugin can load/save files. |
3.6. Defines
Defines available when compiling.
define | Description |
---|---|
OS_BITS | Gives build type for the operating system, example 32 or 64 bits. |
PLUGIN_VERSION | Version string of the plugin version. This is the same as what is provided on the make command line make VERSION=x.y.z |
DEBUG_ON | If defined then the build has debug enabled. make DEBUG=ON |
OS | The Operating System from the make commandline , value will either be WIN32, WIN64, LINUX or MAC |
OS_WINDOWS | Will be defined if build is for a MS Windows Operating system |
OS_LINUX | Will be defined if build is for a Linux Operating system |
OS_MAC | Will be defined if build is for a Apple Mac Operating system |
3.7. Events
Events are callbacks and callbacks are events, under the hood these are just function pointers. Where they differ is how they are used. A callback is called by a function/method to do some work and provide a result. An event is when something has happened in the system that requires attention or notification. Wingman utilizes a synchronous event model for internal events and external events. Internal events are events generated by the Wingman framework and external events are events that sent from outside of Wingmans framework. Since these events are synchronous they must all run to completion.
All Wingman classes that support events have a method to set a handler, only one event handler can be set for each object instance. Every set event handler method has an event wrapper where a reference to the instance of the class that hosts the handler is provided along with a reference of the class method that handles the event.
Example:
/* Setup the callback handler for when this timer expires */
priTimer->SetTimerEventHandler(TIMER_EVENT(&myplugin::timer_cb, myinstance))
TIMER_EVENT is the event wrapper.
&myplugin::timer_cb - Is the class and method name that will handle the event
myinstance - Actual instance of myplugin class that will
To clear an event handler simply pass in NULL to the handler parameter.
example:
priTimer->SetTimerEventHandler(NULL);
3.8. The wingman.ini
The wingman.ini file is a set of configuration parameters used by the Wingman framework.
When the plugin is initialized, Wingman will search for a wingman.ini file within the plugin's root directory and load it. If not found then one is created with default values.
Out of date wingman.ini files are upgraded automatically without reverting back to default values.
All file paths are in short path format. See section Short Paths.
Configuration Item | Group | Description |
---|---|---|
version | VERSION | The version of the wingman.ini file. Do not change. |
verbose | DEBUG | Verbosity level to use for debug output. Only required if linked with the debug framework libraries. |
output | DEBUG | Where to output the debug messages, XPlane's log.txt or plugins log file. |
output_file | DEBUG | If output set to "FILE" then specify the file name of the plugins log file. |
output | LOG | Where to output log messages, XPlane's log.txt or plugins log file. |
output_file | LOG | If output set to "FILE" then specify the file name of the plugins log file. |
4. Build System
The Make System builds, cleans and packages Wingman plugins.
The system consists of the plugin's Makefile and a rules.mk file which is located in the Wingman folder.
Wingman folder includes a plugin template project, "examples/myplugin" to start developing and building using the Wingman build system.
4.1. Files and Directory Structure
The plugin template project includes the below folders and files.
File/Folder | Re-distributable | Description |
---|---|---|
src/ | YES |
Folder that contains source and header files |
Doc/ | YES |
Folder to hold any documentation. This folder is re-distributable via build.conf |
Resources/ | YES |
Folder to hold any loadable files by the plugin . This folder is re-distributable via build.conf |
scripts/ | YES |
Scripts required during the build process. |
build.conf | YES |
Editable text file to configure how the plugin is built and distributed. |
ChangeLog.txt | YES |
Text file that includes a list of changes for each of the plugins releases. |
README.txt | YES |
A message to anyone using the plugin. |
ReleaseNotes.txt | YES |
A message to users of the plugin for the specific release |
LICENSE.txt | YES |
Text file that includes the software license of your plugin. |
Makefile | YES |
File read by Make that builds, cleans and packages the plugin. |
4.2. Configuration
The plugin folder includes a file called "build.conf" which configures the plugins project info. This file is used by both the Makefile and package scripts. The build.conf contains the information on what to build but not how to build. The how to build is done in the rules.mk file which is included in the Wingman Framework folder.
Configuration Item | default value | Description |
---|---|---|
PLUGIN_NAME | "myplugin" | Set the name of your plugin project. This is used by the package creator to name the packages. |
BUILD_TYPE | PLUGIN | Plugin build type - Do not change |
INCLUDE_RESOURCES | yes | Include Resource Folder when creating a package |
INCLUDE_DOCS | yes | Include Doc Folder when creating a package |
INCLUDE_SOURCE | no | Include Source Code Folder when creating a package |
INCLUDE_BUILD | no | Include Build System when creating a package |
INCLUDE_LICENSE | yes | Include LICENSE.txt when creating a package |
INCLUDE_README | yes | Include README.txt when creating a package |
INCLUDE_CHANGELOG | yes | Include ChangeLog.txt when creating a package |
INCLUDE_RELEASENOTES | yes | Include ReleaseNotes.txt when creating a package |
INCLUDE_WM_INI | no | Include Wingman INI when creating a package |
INCLUDE_ALL_INI | no | Include All INI files (*.ini) in the main folder when creating a package |
INCLUDE_ALL_CONF | no | Include All conf files (*.conf) in the main folder when creating a package |
CREATE_ZIP_PKG | yes | Create a zip distribution package |
CREATE_TARGZ_PKG | yes | Creates a tar.gz distribution package |
4.3. Adding Source Files
Adding source files is done by adding in your *.cpp and *.h/*.hpp into the src/ directory. The build system scans this directory for cpp files to compile.
4.4. Targets
There are three targets in the Wingman Make System
- Building
- Cleaning
- Packaging
4.4.1. Building
Building is compiling and linking a plugin. There are three parameters that can be set on the make command line.
Syntax: make OS={OSTYPE} [DEBUG=ON] [VERSION={x.y.z}]
Example: make OS=WIN64 DEBUG=ON VERSION=1.0.0
Parameter | Options | Mandatory | Description |
---|---|---|---|
OS | WIN64, WIN32, LIN32, LIN64 | YES | Sets the target for the build. |
DEBUG | ON | NO | Links with the debug version of the framework and adds the DEBUG_ON define when compiling. |
VERSION | x.y.z | NO | Version format x= major, y=minor, z=build# - However any format can be used. |
4.4.1. Cleaning
There are two targets related to cleaning; clean and distclean.
command | OBJs | XPL | Package | Description |
---|---|---|---|---|
clean | YES |
NO |
NO |
Cleans up only object files. |
distclean | YES |
YES |
YES |
Cleans up everything. |
Note: Make clean is required before building another OS target. If object files exist when building for another OS target the linker will raise an error of invalid format.
Example 1: make clean.
Example 2: make distclean
4.4.2. Packaging for distribution
After the plugin is built for one or more XPlane targets, calling "make package" will create a ZIP file and/or a tar.gz (tarball) file depending on the configuration set in build.conf.
Syntax: make package <version=x.y.z> </version=x.y.z>
The package will contain plugin binaries but can contain other files depending on the configuration set in build.conf.
5. Debugging
At this time, debugging plugins can be accomplished with output messages to either the XPlane log.txt file, to the plugins log file or WingmanOSS console by using the DEBUG_x output streams. See section Debug Output Streams for more info.
To enable debugging, the plugin must be built with the DEBUG=ON parameter when calling Make.
Example 'make DEBUG=ON OS=WIN64'
Including the DEBUG=ON parameter includes symbol names into the plugin binary and links against the debug versions of the Wingman libraries. Also the debug verbosity level must be set to the highest level of output needed, this can be done in either the plugins wingman.ini file or from the WingmanOSS console.
6. Framework Usage
6.1. Introduction
This section describes Wingman classes and how to use them in detail.
6.2. System
6.2.1. Output streams
Two output streams are supported, Logging and Debug messages.
6.2.1.1. Message Termination
Whether using LOG, SYSLOG or DEBUG streams, all messages that are to be outputed require a line end termination with WM_ENDL.
LOG_INFO << "3D Panel loaded" << WM_ENDL; DEBUG_1 << "Line 1 was executed" << WM_ENDL;
The WM_ENDL macro is required only when you want to write the message to the output file.
If required, output can be delayed until full message can be created across multiple lines of code.
LOG_INFO << "Loading 3D Panel: "; if (success) LOG_INFO << " Success" << WM_ENDL; else LOG_INFO << " Failed" << WM_ENDL;
Will give the output depending on the value of "success".
0:0:0 [INFO](myplugin.applet) Loading 3D Panel: Success or 0:0:0 [INFO](myplugin.applet) Loading 3D Panel: Failed
6.2.1.2. Logging
There are two types of logging classes, LOG and SYSLOG. Each type has several associated iostream class.
LOG classes by default write to XPlane's log file, however the log file and path can be change in the plugins wingman.ini file. These LOG classes should be used as this can help separate the plugins log messages from Xplane or other plugins.
class | Description |
---|---|
LOG_INFO | Informative message. Example would be something has loaded, a certain event was received etc... |
LOG_WARN | Warning the user of an event but system is still usable. |
LOG_ERROR | An error has occurred but system is still running but some features will not work. |
LOG_FATAL | A fatal error where the plugin needs to be shut down. |
SYSLOG classes are always written to XPlane's log file and should be used only when necessary to always write the message to XPlanes log file. This would be more beneficial if what you have logged would help diagnose issues within XPlane.
class | Description |
---|---|
SYSLOG_INFO | Informative message. Example would be something has loaded, a certain event was received etc... |
SYSLOG_WARN | Warning the user of an event but system is still usable. |
SYSLOG_ERROR | An error has occurred but system is still running but some features will not work. |
SYSLOG_FATAL | A fatal error where the plugin needs to be shut down. |
Example:
LOG_INFO << "Loading 3D Panel: " << WM_ENDL; SYSLOG_INFO << "Always goes to Xplanes log.txt file " << WM_ENDL; LOG_FATAL << "Fatal error" << WM_ENDL;
Format of a log message
{time stamp}{log type} {plugin.name} message
0:0:0 [INFO](myplugin.applet) Example of INFO 0:0:0 [WARNING](myplugin.applet) Example of WARNING 0:0:0 [ERROR](myplugin.applet) Exmaple of ERROR 0:0:0 [FATAL](myplugin.applet) Example of FATAL
6.2.1.3. Debug
Debug messages by default are written in to XPlane's log file. However this can be changed so debug messages are written to the plugins own debug file or in the plugins own log file. There are a maximum of 4 verbosity levels. See section wingman.ini
Debug messages are only enabled when the plugin is linked against the debug version of the wingman library.
Debug verbosity level can be changed in the plugins wingman.ini file or using WingmanOSS. The difference between these two is if changing the level in wingman.ini then the plugin will need to be restarted for the change to take effect. However if using WingmanOSS via the console then the verbosity level can be changed while the plugin is running.
class | Description |
---|---|
DEBUG_1 | Level 1 verbosity |
DEBUG_2 | Level 2 verbosity |
DEBUG_3 | Level 3 verbosity |
DEBUG_4 | Level 4 verbosity |
Format of a log message
{time stamp}{DEBUG_x} {plugin.name} message
0:0:0 [DEBUG_1](wingman.myplugin) My debug level 1 message 0:0:0 [DEBUG_2](wingman.myplugin) My debug level 2 message 0:0:0 [DEBUG_3](wingman.myplugin) My debug level 3 message 0:0:0 [DEBUG_4](wingman.myplugin) My debug level 4 message
When the plugin is not linked against the debug version of Wingman Framework Library then all calls to DEBUG_x classes are redirected to NULL streams.
When setting the verbosity level, a value of 0, zero, disables all output from the DEBUG_x class streams.
6.2.2. Timers
Wingman supports Event Timers, which are timers that expire will send a event to registered handlers.
6.2.2.1. Event Timers
Event timers are supported using XPlanes FlightLoop. Wingman's event timers are based on a precision timer algorithm.
There are two types of event timers: Continuous and OneShot.
Continuous timers, when started, will continue to run and send the expire event until the timer is instructed to be stopped.
Oneshot timers, shen started, will only give one expiration event and must be restarted if another event is required.
The precision timer framework supports two models of operations; Precise and Estimate.
At this time only the Precise model is implemented which provides the highest accuracy but uses the most amount of CPU cycles.
At this time, the Estimate model is not been implemented but will give the lowest accuracy and lower amount of CPU cycles.
No matter what the timer model is set to, the accuracy depends on what XPlane is doing. If the simulator is slow, either due to CPU limitation or low FPS then the timer accuracy will only be as good as when XPlane can call Wingmans timer backend. But no matter which model is selected, Wingman will call your event callback at the most precise time it can.
The below code snippets comes from the WingmanDemo1 source code, under the TimerDialog.cpp.
This will execute the timer_cb method every second using the TimerContinuous class.
void myplugin::timer_cb(PrecisionTimer &timer, float delta, float timestamp) { LOG_INFO << "Continuous Timer expired" << WM_ENLD; } /* A PrecisionTimer can be either a TimerContinuous or TimerOneShot objects */ PrecisionTimer *priTimer; /* Create new Continuous timer object and set the default timeout to 1000ms */ priTimer = new TimerContinuous("tmrEvent", 1000); /* Setup the callback handler for when this timer expires */ priTimer->SetTimerEventHandler(TIMER_EVENT(&myplugin::timer_cb, this)); /* Set the interval to 1 second or 1000ms priTimer->SetInterval(1000); /* Adding the timer object to the Timer subsystem will start the timer */ Timer::Add(priTimer);
This will execute the timer_cb only once after 1 second using the TimerOneShot class.
void myplugin::timer_cb(PrecisionTimer &timer, float delta, float timestamp) { LOG_INFO << "OneShot Timer expired" << WM_ENLD; } /* A PrecisionTimer can be instantiated as either a TimerContinuous or TimerOneShot objects */ PrecisionTimer *priTimer; /* Create new OneShot timer object and set the default timeout to 1000ms */ priTimer = new TimerOneShot("tmrEvent", 1000); /* Setup the callback handler for when this timer expires */ priTimer->SetTimerEventHandler(TIMER_EVENT(&myplugin::timer_cb, this)); /* Set the interval to 1 second or 1000ms priTimer->SetInterval(1000); /* Adding the timer object to the Timer subsystem will start the timer */ Timer::Add(priTimer);
6.2.3. Process Events
Process Events is the base class that handles all external events, for example events sent from XPlane.
6.2.3.1. Interface
ProcessEvents class ueses the IProcess and IControl interfaces. IProcess is a single method called by to process when the process event is called, this method takes no parameters and returns no vlaue. IControl provides the interface to start, stop and reset the object.
The ProcessEvent manages a list of DataObjects and has properties to control direct reads and writes to that list.
CheckWriteProtect() method is used as a flag to skip over all data writes. This is useful to save CPU cycles when there are no data objects that need to be written.
6.2.3.2. XPLM Flight Loop
This class handles the basic XPLM flight loop, default is called for every frame unless a value is specified in the SetInterval method.
SetInterval takes a value that is divisble by 1000, 1 second. If you want the next flight loop event to be called every 2 seconds then it will be "SetInterval(2000)". To get called every frame pass in a value of -1.
Graphic showing DirectReads, Flight Loop Event, DirectWrites for DataRefs.
The FlightLoops default process method calls DirectRead on all DataRef objects assigned to the DataObject manager before calling the flight loop event. When the flight loop event returns, the process method takes care of any DirectWrites for DataRef objects that are not set with write protect on.
To begin receiving FlightLoop events, Instantciate a FlightLoop object, set the FlightLoopEventHandler, set the interval then finally call the Start() method.
Within the flight loop event handler, SetInterval() can be called to change when the next interval dynamically. Also, within the flight loop event handler, Stop() can be called to stop receiving events. Start() may be called to start receiving events again.
7. Appendix
6.3. Abbreviations
Abbreviation | Description |
---|---|
API | Application Programming Interface |
OOP | Object Oriented Programming |
SDK | Software Development Kit |
WMS | Wingman System |
XPLM | X-Plane Plugin Manager |