X-Developer Cross-platform development in C++, Flight Simulation

23Jan/11Off

C++ bei Chaosradio Express

Note to my english readers: This is a link to a german podcast.

Endlich bin ich dazu gekommen, mir die vielversprechende Folge über C++ anzuhören. Chaosradio Express steht seit etwa einem Jahr bei mir ganz oben auf der Liste der abzugrasenden Informationsquellen. Ich habe aufgehört zu zählen, wie viele Stunden ich schon damit verbracht habe, mir Folgen dieses hochinformativen Podcasts reinzuziehen. Jede einzelne war eine Erleuchtung.

So auch diese (schon recht alte) Folge über das "Programmieruniversum" C++. C++ ist mehr als eine Sprache, es ist eine Art zu Denken. Das wird hier sehr gut herausgearbeitet, vorallem der Kontrast zwischen old-school Programmierer (Pavel, Gast) und Scriptsprachen-Jungspund (Tim, Moderator) sorgt für eine gute Spannung. Abzug in der B-Note gibt es für Pavel's Diktus - er spricht sehr langsam mit vielen "Füllwörtern". Im Zweifel einen Mediaplayer verwenden, wo man mit 1,3facher Geschwindigkeit abspielen kann. Dann ist es perfekt.

http://chaosradio.ccc.de/cre063.html

Wer einen Einblick in das C++ Universum braucht, kommt voll auf seine Kosten. Anhören! Sofort!

  • del.icio.us
  • Digg
  • email
  • Facebook
  • Google Buzz
  • Identi.ca
  • MisterWong
  • MySpace
  • Orkut
  • Print
  • Reddit
  • RSS
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
Tagged as: , , No Comments
23Jan/11Off

Lean Assist 1.4 update is out

I updated LeanAssist to include even more functions of the original JPI EDM-800 instrument. The latest update includes all modes brought to you by installing GPS tie-in and fuel flow sensor tie-in. So this is the top-notch installation you can have on your aircraft.

To learn more about the real thing, I strongly suggest reading the original manual, which is available online from JPI : http://www.jpitech.com/pilotguide.php

As always, the download of the latest version is on the x-plane.org download manager: http://forums.x-plane.org/index.php?app=downloads&showfile=12206

  • del.icio.us
  • Digg
  • email
  • Facebook
  • Google Buzz
  • Identi.ca
  • MisterWong
  • MySpace
  • Orkut
  • Print
  • Reddit
  • RSS
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
20Dec/10Off

LeanAssist 1.2 is out

The 1.2 1.3 version of LeanAssist features a completely new UI which resembles the Engine Digital Management System 800 by JP Instruments.

  • Lean Find Modes accurately modeled for both normal (rich-of-peak) and lean-of-peak leaning.
  • Various other display mode accurately modeled.
  • "Cheat" mode for automatic leaning refined from version 1.1.

Watch the introduction video on youtube:

Download from the X-Plane.org download manager.

Click the Flattr Button if you like it.

  • del.icio.us
  • Digg
  • email
  • Facebook
  • Google Buzz
  • Identi.ca
  • MisterWong
  • MySpace
  • Orkut
  • Print
  • Reddit
  • RSS
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
12Dec/10Off

LeanAssist, a plugin for flying recips in X-Plane

Did you ever wonder how to lean your engine properly? How to get the maximum power out of it, when in climb? Or howto squeeze the last NM of range out of your fuel on board?

LeanAssist is a small plugin that helps you with leaning the engine. While you lean, it detects peak EGT and can then guide you to either best power or best economy mixture.

LeanAssist works with all airplanes having a fuel-injected, normally aspirated piston engine. As opposed to real-life,  in X-Plane all engines have perfect mixture distribution, so you can all run them lean-of-peak.

To install LeanAssist, simply unzip to your X-Plane main folder. To finish installation, you have to assign a key command or a joystick button in X-Plane. This is explained in the video.

To use lean assist, press the assigned key or joystick button and start leaning the engine slowly. A small screen overlay will appear, reading "looking for peak". Lean slowly until the indication changes to "Peak detected".

  • For best power mixture, enrich again. The indication will display the ROP EGT delta. At 70°F ROP, the display will signal "best power". Stop enriching. The indication will disappear after a few seconds.
  • For best economy mixture, lean further. The indication will display the LOP EGT delta. At 50°F LOP, the display will signal "best economy". Stop leaning. The indication will disappear after a few seconds.

Also explained in the video:

If you want to learn more about leaning and what happens inside your engine when you adjust your mixture, read this excellent article:  http://www.avweb.com/news/pelican/182084-1.html

Download at X-Plane.org:

On Windows, please make sure you have the latest Visual C++ 2010 redistributable package installed. Download it here: http://www.microsoft.com/downloads/en/details.aspx?familyid=A7B7A05E-6DE6-4D3A-A423-37BF0912DB84&displaylang=en

  • del.icio.us
  • Digg
  • email
  • Facebook
  • Google Buzz
  • Identi.ca
  • MisterWong
  • MySpace
  • Orkut
  • Print
  • Reddit
  • RSS
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
24Nov/10Off

Byte datarefs and refcon-pointers to heap data

Designing a cockpit in Plane-Maker you probably came across the "byte datarefs" that allow you to show text on generic instruments, like the generic text LED readout.

In a discussion on the .org forum a plugin programmer was wondering how to publish a byte dataref himself from an X-Plane plugin and promptly fell prey to "wild pointers" and memory corruption, because strings in C are not as easy as Strings in other programming languages like Java.

So here is the bullet-proof solution for publishing string data refs from a plugin:

1. The basics:
How do you tell X-Plane that you serve a string?

XPLM_API XPLMDataRef          XPLMRegisterDataAccessor(
                                   const char *         inDataName,
                                   XPLMDataTypeID       inDataType,
                                   int                  inIsWritable,
                                   XPLMGetDatai_f       inReadInt,
                                   XPLMSetDatai_f       inWriteInt,
                                   XPLMGetDataf_f       inReadFloat,
                                   XPLMSetDataf_f       inWriteFloat,
                                   XPLMGetDatad_f       inReadDouble,
                                   XPLMSetDatad_f       inWriteDouble,
                                   XPLMGetDatavi_f      inReadIntArray,
                                   XPLMSetDatavi_f      inWriteIntArray,
                                   XPLMGetDatavf_f      inReadFloatArray,
                                   XPLMSetDatavf_f      inWriteFloatArray,
                                   XPLMGetDatab_f       inReadData,
                                   XPLMSetDatab_f       inWriteData,
                                   void *               inReadRefcon,
                                   void *               inWriteRefcon);

This monster is the function for registering what the X-Plane SDK calls "owned" datarefs. "Owned" as in "my plugin owns this data. If it's not there, the data won't be available". But also "owned" as in "X-Plane crashed by memory corruption when reading array data. 0wned!"

The important parameters of the function are

XPLMGetDatab_f   inReadData, XPLMSetDatab_f   inWriteData

representing the function pointers that will be invoked when some instance tries to read from or write to your dataref.

So everytime X-Plane wants to update, say, your generic LED readout, it calls the funtion you registered as the inReadData function.

Let's have a look at the signature:

typedef long (* XPLMGetDatab_f)(
                                   void *               inRefcon,
                                   void *               outValue,    /* Can be NULL */
                                   int                  inOffset,
                                   long                 inMaxLength);

As we all know, calling the string read with outValue==NULL should return the number of characters in the string. Otherwise, the content of the string starting at inOffset to the end or until reaching inMaxLength shall be copied to outValue.

2. Strings in C are pointers !
The crucial point here is that strings in C are not values like and int or a float: A string in C is a pointer to some characters sitting in the memory, the last of them being 0x0 to indicate that the string ends. If this memory goes out of scope, the pointer will still be there, pointing to some random garbage in your memory.

This is the error most newbies trying to implement a byte-dataref fall prey to.
The most obvious solution of course would be to allocate the string on the heap and store the pointer in a global variable.

This approach has a downside: Raw string handling in C is s a pain in the neck.

3. Using std::string instead of char*
The string from the C++ standard library is great: You don't have to worry about pointers, scope, manual memory management, resizing, reallocations, ...
The std::string can be converted to a c-string and vice-versa. Let's implement the read function using a global C++ std::string:

#include  <cstring> // for strcpy
#include  <string>  // for std::string
 
std::string global_string = "hello world";
 
long readFuncStr(void* inRefCon, void* outValue, int inOffset, long inMaxLength)
{
    // we were asked to return the length of the string
    if (outValue == NULL)
        return global_string.length();
    // we have to copy the characters from offset to maxlength into outValue
    // c_str() is the conversion function from std::string to const char*
    strcpy(static_cast<char* >(outValue), global_string.substr(inOffset,inMaxLength).c_str());
    return inMaxLength;
}

now we don't need to worry about pointers to invalid data anymore.
The write funtion is about just as easy:

void writeFuncStr(void* inRefCon, void* inValue, int inOffset, long inMaxLength)
{
    char* str = static_cast<char* >(inValue);
    // replace global string with required substring of inValue
    // note that the std::string c'tor is overloaded to take a char*
    global_string = std::string(str).substr(inOffset, inMaxLength);
}

Okay, now suppose we have to two strings in our plugin. What do we do now? Create two sets of read- and write-functions? No, of course not.

First, lets register the global strings as refcon-pointers to our functions. Looks like this:

long readFuncStr(void* inRefCon, void* outValue, int inOffset, long inMaxLength)
{
    std::string* p_data = static_cast<std::string* >(inRefCon);
    if (outValue == NULL)
        return p_data->length() + 1;
    strcpy(static_cast<char* >(outValue), p_data->substr(inOffset,inMaxLength).c_str());
    return inMaxLength;
}
 
void writeFuncStr(void* inRefCon, void* inValue, int inOffset, long inMaxLength)
{
    std::string* p_data = static_cast<std::string* >(inRefCon);
    char* str = static_cast<char* >(inValue);
    *p_data = std::string(str).substr(inOffset,inMaxLength);
}

We now register our read and write function and assign the pointers accordingly:

XPLMDataRef my_data_ref = XPLMRegisterDataAccessor(
    "my/plugin/data/textdata" , // dataref
    xplmType_Data,  // data type
    1,   // writeable
    NULL, NULL, // no int functions
    NULL, NULL, // no float functions
    NULL, NULL, // no double functions
    NULL, NULL, // no int-array functions
    NULL, NULL, // no float-array functions
    &readFuncStr, // our read function
    &writeFuncStr, // our write function
    &global_string, // refcon-pointer for read function
    &global_string  // refcon-pointer for write function
);

4. Using a class to manage the strings
As said before, globals are evil. One possible solution is to store the string itself in a class, that calls XPLMRegisterDataAccessor() in its c'tor and XPLMUnregisterDataAccessor() in its d'tor. This way it is assured that the string you use lives exactly as long as the dataref is available to X-Plane.
The readFunc and writeFunc then become static functions in this class, and the refcon-pointer is the this-pointer of your instance.

long readFuncStr(void* inRefCon, void* outValue, int inOffset, long inMaxlength)
{
    OwnedData* p_owned_data = static_cast<OwnedData* >(inRefCon);
    long length = p_owned_data->value().length();
    if (outValue == NULL)
        return length;
    long maxlen = std::min(inMaxLength,length);
    strcpy(static_cast<char* >(outValue), p_owned_data->value().substr(inOffset, maxlen).c_str());
    return inMaxLength;
}
 
void writeFuncStr(void* inRefCon, void* inValue, int inOffset, long inMaxLength)
{
    OwnedData* p_owned_data = static_cast<OwnedData* >(inRefCon);
    char* str = static_cast<char* >(inValue);
    p_owned_data->setValue(std::string(str).substr(inOffset,inMaxLength));
}
 
class OwnedData{
public:
 
    OwnedData(const std::string& identifier):
        m_data_ref_identifier(identifier),
        m_data_ref(0),
        m_value(std::string(""))
    {
        m_data_ref = XPLMRegisterDataAccessor( m_data_ref_identifier.c_str(), xplmType_Data, 1,
                                                 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                                                 NULL, NULL, readFuncStr, writeFuncStr, this, this );
    }
 
    ~OwnedData()
    {
        if (m_data_ref)
        {
            XPLMUnregisterDataAccessor(m_data_ref);
            m_data_ref = 0;
        }
    }
 
    std::string value() const { return m_value; }
 
    void setValue(const std::string&amp; val) { m_value = val; }
 
private:
        friend long readFuncStr(void* inRefCon, void* outValue, int inOffset, long inMaxLength);
        friend void writeFuncStr(void* inRefCon, void* inValue, int inOffset, long inLength);
 
    XPLMDataRef m_data_ref;
    std::string m_value;
};

As always: beware of bugs in the above code :)

  • del.icio.us
  • Digg
  • email
  • Facebook
  • Google Buzz
  • Identi.ca
  • MisterWong
  • MySpace
  • Orkut
  • Print
  • Reddit
  • RSS
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
13Nov/10Off

Fat plugins, aircraft plugins, and how to install them

Looking at various postings in X-Plane forums I came to the conclusion that the two new concepts of plugin handling introduced with X-Plane 9 are still very little known. At least to end users.

in X-Plane 8, the world was simple: plugins could only be installed in the X-Plane/Resources/plugins folder and hat to be named *.xpl.
As X-Plane is a multiplatform software, this implied that only plugins matching the platform could be installed. Developers had to provide three different versions of their plugins (one for Windows, one for Linux, one for Mac) and the user had to download the appropriate one and put it into his plugins folder.

Since we all know no one of us reads manuals and we all want ease of installation, with X-Plane 9 the mechanism of "fat plugins" was introduced.

Now developers can put all three versions of their plugin into a folder and the user just drops the whole folder to his Resources/plugins directory. X-Plane will then select the right version of the plugin, based on the name. The plugins now have to be named win.xpl, mac.xpl and lin.xpl. The specific name of the plugin is now the folder name.

This makes installation easier for the user, since he just downloads one archive and drops one folder to his X-Plane installation. Furthermore, it makes migrating of a whole X-Plane installation from one OS to another easier.

The second new concept is aircraft plugins. Often plugins are needed to improve functionality of just one aircraft. In this case, it is unnecessary to keep them loaded all the time, but just when the aircraft is selected.

To achieve this, plugins can now also reside in the plugins/ subfolder of every aircraft, and are automatically loaded when the user selects this aircraft.

Also this makes installation of third-party aircraft easier to the end-user. Instead of having to download and aircraft, install it to the aircraft folder, than download a plugin and install it to the plugins folder, the user now downloads one archive, drops it to the aircraft folder and is done.

It is worth noticing that these technologies are orthogonal: You could have an aircraft-plugin that is non-fat. While this would work, it totally contradicts the "ease of installation" paradigm.

Bottom line for users: If a plugin comes as a folder, don't tear it apart and drop an individual file to your plugins folder as you did it in X-Plane 8. Leave the folder intact. X-Plane will select the right plugin for you.

Bottom line for developers: Create "fat" plugins. Always. No exception. Makes the installation easier for the user and helps keeping the plugins folder nice and organized.

  • del.icio.us
  • Digg
  • email
  • Facebook
  • Google Buzz
  • Identi.ca
  • MisterWong
  • MySpace
  • Orkut
  • Print
  • Reddit
  • RSS
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
10Nov/10Off

Rod Machado on Funny Experiences as a Flight Instructor

We all remember him, the flight instructor in MS FlightSimulator since version 2000.
Here he talks about some really funny things he experienced during has career as a flight instructor. Hilarious!

  • del.icio.us
  • Digg
  • email
  • Facebook
  • Google Buzz
  • Identi.ca
  • MisterWong
  • MySpace
  • Orkut
  • Print
  • Reddit
  • RSS
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
1Nov/10Off

X-Plane 9.3: leaning a piston egine

I just cranked some output from a plugin i wrote for testing purposes into a spreadsheet app and created this neat little chart showing what you can do with the mixture control in a turbo-charged, fuel injected, constant speed prop-equipped airplane engine.

The curves are plotted against the fuel flow (in gallons per hour), which is the scale on the bottom. With mixture, you control how much fuel is getting into the engine (or to be precise, how much fuel per a certain amount (by volume) of air).

This example is almost at sea-level, showing nicely how you can overboost your engine at "best power" mixture, to produce more than rated power.

Notice how temp peaks and falls, power output peaks and falls, and efficiency (specific fuel consumption) gets better with leaner mixture.

Note that when pulling on the red knob, you traverse this chart FROM RIGHT TO LEFT!

I multiplied efficiency by ten and divided exhaust temperature by 10 to fit it on the chart nicely.

Mixture vs temperatures chart

I hope that with X-Plane 9.4 the simulation gets better, because max power in reality is not close to max egt, but 50° to 80°F below on the rich side. Also CHT varies a lot with mixture, peaking at about the same fuel flow as power.
Here is certainly room for improvement in future versions of X-Plane!

  • del.icio.us
  • Digg
  • email
  • Facebook
  • Google Buzz
  • Identi.ca
  • MisterWong
  • MySpace
  • Orkut
  • Print
  • Reddit
  • RSS
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
30Oct/10Off

Passing an object to XPWidgetFunc_t

Programming with the old-school C-API of X-Plane, we got used to passing objects via the void* refCon parameters that are available for every callback function (like flightloop callbacks XPLMRegisterFlightloopCallback() or plugin-owned-data callbacks XPLMRegisterDataAccessor() ).

But the widget callback function

int widgetCallback(XPWidgetMessage, XPWidgetID, long, long);

doesn't have this parameter ! So how do you get an object in there?

Well, the way officially suggested by Supnik and Barbour is a really dirty hack: Store a pointer to the object as a widget property. How? By doing a reinterpret_cast<> on the pointer and setting it as an integer parameter. This is of course a really ugly kludge, but seems to work on all platforms where sizeof(long) == sizeof(void*).

Here's how you do it:

/* When setting up the widget */
XPWidgetID my_widget  = // whatever
YourWidgetClass object = // whatever you invented to handle widgets OO-style
XPSetWidgetProperty(my_widget, xpProperty_Object, reinterpret_cast<long>(&object));
 
/* In the callback function */
int widgetCallback(XPWidgetMessage inMessage, XPWidgetID inWidget, long param1, long param2)
{
    YourWidgetClass* widget = reinterpret_cast<YourWidgetClass*>(XPGetWidgetProperty(inWidget, xpProperty_Object, NULL));
    if (widget)
    {
        // do something with *widget
    }
    return 0;
}

Remember: This is not type-safe. We are lying to the compiler, so if something goes wrong here, we're on our own. No std::bad_cast exception if we screwed it up, only UB.

  • del.icio.us
  • Digg
  • email
  • Facebook
  • Google Buzz
  • Identi.ca
  • MisterWong
  • MySpace
  • Orkut
  • Print
  • Reddit
  • RSS
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
25Oct/10Off

The PLANE_LOADED gotcha

With the X-Plane interplugin messaging API you can have your plugin notified when a new plane is loaded. That is, you can react to either the user loading a new plane or a new plane being introduced via multiplayer.

The callback for receiving interplugin messages is

PLUGIN_API void XPluginReceiveMessage(XPLMPluginID, long inMessage, void* inParam)

and the documentation says

#define XPLM_MSG_PLANE_LOADED 102

This message is sent to your plugin whenever a new plane is loaded. The parameter is the number of the plane being loaded; 0 indicates the user's plane.

So when receiving a callback for the XPLM_MSG_PLANE_LOADED message, you have to check the parameter to be 0 if you want to react to a change of the user's plane.

Unfortunately, the documentation is inconsistent here

The parameter is the number of the plane being loaded

The param being a void* one might think that with this message, this pointer points to an int that holds the plane number, so you can get the behaviour you want as follows:

/* warning: DOING IT WRONG !!!1! */
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID, long inMessage, void* inParam)
{
    if( inMessage == XPLM_MSG_PLANE_LOADED )
    {
        int* planeNo = (int*)(inParam);
        if ( *planeNo == 0) /* BOOOM! SEGFAULT!! */
        {
            // do some stuff
        }
    }
}

I tripped into this pitfall two times and spend almost a day figuring out why X-Plane segfaulted when my plugin was loaded.

Instead what really happens here is that they pass the integer ITSELF as the void*. So you have to check

inParam == 0   /*doing it right*/

instead of

*inParam == 0 /* doing it wrong*/

This is one of the reasons why I strongly encourage the use of the new style C++ casts instead of the old C-casts.

If you write this as C++ code with a C++-style cast, it becomes totally clear what happens here. It is a reinterpret_cast going on, not a static cast!

int planeNo = (*(static_cast<int*>(inParam))); /* wrong */
int planeNo = reinterpret_cast<int>(inParam);   /* right */

Bottom line: If you use void* for passing parameters, the documentation should be very specific on what is going on. The compiler won't help you.

  • del.icio.us
  • Digg
  • email
  • Facebook
  • Google Buzz
  • Identi.ca
  • MisterWong
  • MySpace
  • Orkut
  • Print
  • Reddit
  • RSS
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter