Friday 5th June, 2015
The hitch-hiker's guide to raildriver.dll, part 3: simulating a simulator. Under normal circumstances, your plugin talks to one side of RailDriver.dll, and the train simulator talks to the other. But the simulator's API is also well-defined, and you can build software that simulates the simulator. One example of this can be found in the RDShark software referred to in the previous post on this blog, which pretends to be a train simulator so that you can see what your plugin is doing.

This post will tell you how to write software that talks to the 'simulator side' of the RailDriver.dll API. It will use RDShark as an example, so if you are following along at home you may find it useful to get the RDShark code out of github here.

First, mark yourself as connected. Much like a plugin, you are expected to mark yourself as connected when you start paying attention to RailDriver.dll. You do this with the SetRailSimConnected call, thus:

SetRailSimConnected(true);

It's probably good practice to do this as soon as possible before starting listening. RDShark is always listening so it does this when the program starts (here).

Now, you can respond to messages from the plugin. The messages are not magically sent to you. Instead, you have to ask RailDriver.dll regularly whether there are any messages for you. If there are, you should act on them and do so fairly promptly. Remember, if a plugin has asked you for information, it will be blocked until you reply to it! The polling in RDShark is done in a timer event (here).

As noted in the previous installments, there are two kinds of message that a plugin can send to a simulator: either it can request a piece of information (such as the current speed of the train) or it can set a control (such as putting the throttle at 0.5). Your simulator needs to poll for each of these possibilities separately. You will note that RDShark checks first for one possibility, then the other.

First, let's look at information requests, because they're a bit simpler. To recap, these come from a plugin calling GetRailSimValue(someIdentifier, mod). To see if you have one of these messages to respond to, call GetRailDriverGetId. This returns -1 (which in RailDriver.cs I call 'None') if there is no message pending, or the ID if there is. You should use it approximately thus:

RDid id = GetRailDriverGetId();
if (id != RDid.None) {
        // ... process your message here ...
}

To be able to answer the message, you need to know the value of the other parameter: is the simulator requesting the current value, the minimum, or the maximum? You do this using the GetRailDriverGetType() function, which returns the RDmod that the plugin requested, thus:

RDid id = GetRailDriverGetId();
if (id != RDId.None) {
        RDmod mod = GetRailDriverGetType();
        // ... find out the value and send it back ...
}

Note that you should only really call each of these getters once: RailDriver.dll has a nasty habit of having internal side-effects on things that look like simple data retrieval, and it's best not to risk doing things it doesn't expect.

To send the data back, use the SetRailDriverValue(id, value) call. The id parameter should be the same as the id that the plugin requested, and the value parameter should be a float. Therefore, the whole code fragment to respond to get messages is something along the lines of:

RDid id = GetRailDriverGetId();
if (id != RDId.None) {
        float value = 0.0;
        RDmod mod = GetRailDriverGetType();

        // ... here, find out what value you need to send back ...
        // ... and stick it in the 'value' variable

        SetRailDriverValue(id, value);
}

If you look at RDShark's code for handling these messages (here), you'll note that it follows this pattern nearly exactly. I emphasise again: do not do any complicated processing while you're handling messages, because the plugin is completely blocked until you answer!

Now for control messages. To recap, these come from a plugin calling SetRailSimValue(someControl, value). Remember as well that a plugin will never block when sending a control message. This means that there may well be a queue of control changes waiting for you to deal with when you get around to it.

To see whether you have any incoming control messages, use the GetNextRailDriverId(start) function. This is a slightly more complicated function: it starts from its 'start' argument and iterates through all the controls until it finds the next one that has changed. If it finds one, it returns that control ID; otherwise, it returns -1 (None).

If this sounds convoluted, that is because it is convoluted. The easiest way of understanding it is to look at a code fragment that uses it:

RDId id = GetNextRailDriverId(None);
while (id != None) {
        // ... do something useful ...
        id = GetNextRailDriverId(id);
}

To actually read the control change, you need to use GetRailDriverValue(id), which returns the last value that a plugin set for the id parameter. Thus, your code fragment for dealing with incoming control set messages looks like this:

RDId id = GetNextRailDriverId(None);
float value = 0.0;
while (id != None) {
        value = GetRailDriverValue(id);
        // ... do something useful with value ...
        id = GetNextRailDriverId(id);
}

Again, if you look at RDShark, this is very nearly the code it actually uses (here).

posted by Rob Mitchelmore, 15:09 (anchor)