Nearly every X-Plane plugin accesses one or several of X-Plane’s datarefs at runtime. It’s not uncommon to find code like this:
1void set_data(float value)
2{
3 XPLMDataRef dref = XPLMFindDataRef("what/ever/data/ref/we/need");
4 XPLMSetDataf(dref, value);
5}
Admittedly this works. However, there are a couple of reasons why the code above is not the best idea, if you’re seeking to write a solid and well-performing plugin:
- The code doesn’t check if dref actually points to a dataref before attempting to write to it
- The caller doesn’t get notified about success or failure of the operation
XPLMFindDataRef
is expensive, and should be used only during plugin initialisation
In fact, also X-Plane’s SDK documentation emphasizes the third point:
The task of looking up a data reference is relatively expensive; look up your data references once based on the verbose path strings, and save the opaque data reference value for the duration of your plugin’s operation. Reading and writing data references is relatively fast (the cost is equivalent to two function calls through function pointers).
I tend to organise the code to handle all dataref operations in a dedicated source code file with a fitting header file. In my nomenclature they’re called data.c
and data.h
in most of my projects.
Some plugins only require to handle very few datarefs; in such a case, we can write pretty explicit code:
1#include <math.h>
2
3static XPLMDataRef Dref = NULL;
4static int Initialized = 0;
5
6int init_data(void)
7{
8 Dref = XPLMFindDataRef("what/ever/data/ref/we/need");
9 if (Dref == NULL || !XPLMIsDataRefGood(Dref))
10 {
11 return 0;
12 }
13 Initialized = 1;
14 return 1;
15}
16
17int set_data(float value)
18{
19 if (Initialized && XPLMCanWriteDataRef(Dref))
20 {
21 XPLMSetDataf(Dref, value);
22 return fabs(XPLMGetDataf(Dref) - value) < 0.001;
23 }
24 return 0;
25}
This code looks a lot more complicated, but it addresses the shortcomings of our previous example:
init_data
is called fromXPluginStart
orXPluginEnable
, so the expensiveXPLMFindDataRef
is only called onceinit_data
checks whether the initialisation was successfull, and returns1
if true, otherwise0
set_data
uses the cached reference of the dataref, instead of using its own lookup every time it’s calledset_data
checks whether it’s safe to attempt writing to the datarefset_data
checks if the write operation was successful (i.e. the value of the dataref and the submitted parameter are similar)
In case you wonder about the somewhat funky comparison in set_data
’s return value: this is called an epsilon comparison (comparing two floats for equality is a bad idea). I admit it’s not the most clever implementation, but it will do for our purpose (while an equality comparison could actually trigger a false negative result). Depending the actual purpose and values expected, it might become necessary to implement and ULP-based comparison.
While this approach is pretty effective for a small number of datarefs, it just bloats with an increasing number of datarefs. There are ways to work with these, but that’s for another post – hopefully somewhere soon.