Emesary: Multiplayer network bridge for FlightGear


Emesary bridges are a very simple and efficient way to connect two Emesary instances running in different processes. In concept a bridge simply joins a transmitter on a local process to a transmitter on a remote process or system. Neither transmitter is aware of the bridge, and recipients on the remote will simply receive the notification in exactly the same way as if it were transmitted locally.

For FlightGear the multiplayer bridge allows notifications to be routed over the network by layering ontop of the existing FlightGear multiplayer protocol. The model creates an incoming bridge specifying the notifications that are to be received and the bridge will messages from multiplayer models.

The elegance of the bridge is that neither the sender nor the receiver need to know about each other; all notifications just appear in the recipient method where they can be handled. Each aircraft would have one (or more recipients) and just handle the incoming message.


Normally an aircraft model will create both an incoming and outgoing bridge, although some aircraft may only wish to listen or transmit.

Creating an outgoing bridge

All you need is a list of notifications to route, This is a list of preconstructed notifications that are necessary to access the encode / decode methonds.

var routedNotifications = [notifications.GeoEventNotification.new(nil)];

then use the following call. the parameters are as folows:

  1. ident, used to identify the bridge
  2. list of notifictions to route. Often, but not always the same as the incoming notifications to route

By default the outgoing bridge will attach itself to emesary.GlobalTransmitter, but in more advanced usage it is possible to attach to any transmitter.

   var outgoingBridge = emesary_mp_bridge.OutgoingMPBridge.new("F-15mp",routedNotifications);

Creating an incoming bridge

Again a list of notifications to route, you can reuse the same list for both incoming and outgoing.

var routedNotifications = [notifications.GeoEventNotification.new(nil)];

and the following call will cause an incoming bridge for each MP aircraft to be created when each MP item connects. The design of the bridge requires each MP model to have its own bridge (to manage the message indeces)

var incomingBridge = emesary_mp_bridge.IncomingMPBridge.startMPBridge(routedNotifications);

Outgoing routing

There are two basic types of notifications that can be routed over the bridge;

  • Distinct Messages – these are messages where only the last one is important, e.g. position updates.
  • Standard messages – message that relate to an action or event that is unique, such as jettison of tanks. All of these messages will be routed.

A model that wishes to route notifications will need to create an outgoing bridge using the method emesary_mp_bridge.OutgoingMPBridge.new

Incoming routing

Each model specifies a list of messages that it is interested in and capable of handling to the method emesary_mp_bridge.IncomingMPBridge.startMPBridge


The nasal module that declares notifications to be routed over a bridge needs to be included in both the outgoing and the incoming model.

A notification needs to specify methods that to encode and decode the parts of the notification that need to be sent over the bridge. The bridge will itself take care of calling these methods at the right time.

The properties will be packed up and sent over by the bridge using the specified multiplayer generic string. There is a limit of the size of multiplayer packets that can be transmitted so it is important to only send the absolute minimum firstly by good design and secondly by choosing the right encoding, bytes are best.


This notification allows for an easy transmission (and receipt) of a list of properties over MP; it replaces the need to use the sim/generic for properties that aren’t sent over MP by default.

Usage example – this can all go into one Nasal module somewhere.

    var PropertySyncNotification =
       new: func(_ident="none", _name="", _kind=0, _secondary_kind=0)
           var new_class = PropertySyncNotificationBase.new(_ident, _name, _kind, _secondary_kind);
           new_class.addIntProperty("consumables/fuel/total-fuel-lbs", 1);
           new_class.addIntProperty("controls/fuel/dump-valve", 1);
           new_class.addIntProperty("engines/engine[0]/augmentation-burner", 1);
           new_class.addIntProperty("engines/engine[0]/n1", 1);
           new_class.addIntProperty("engines/engine[0]/n2", 1);
           new_class.addNormProperty("surface-positions/wing-pos-norm", 2);
#... etc ...
           return new_class;

and somewhere else (possibly in the same module) add the PropertySyncNotification to the list of routed notifications.

Line by line this does the following

  1. setup a list of notifications that are to be routed over MP to any models that have a bridge installed in the assigned string property index.
  2. setup a new transmitter that will be used only for notifications that are to be bridge over MP. You can bridge any transmitter, but by having a specific transmitter it is easier to understand.
  3. setup an outgoing bridge, specifying the notifications that are to be routed. Any other notifications that pass through the transmitter specified will not be routed over the bridge
  4. setup a corresponding incoming bridge, again specifying the property index used for transmission. By default the notifications received will be routed through the global transmitter unless there is a fourth parameter specifying the transmitter to use
  5. creates a property sync notification – this will send all of the required properties over MP using the much more space efficient notification. Currently there is no interpolation on received properties so all values properties will simply jump to the latest received value. It is necessary to send this notification to the transmitter at regular intervals to ensure that the MP values are recent.
var routedNotifications = [notifications.PropertySyncNotification.new(nil), notifications.GeoEventNotification.new(nil)];
var bridgedTransmitter = emesary.Transmitter.new("outgoingBridge");
var outgoingBridge = emesary_mp_bridge.OutgoingMPBridge.new("F-14mp",routedNotifications, 19, "", bridgedTransmitter);
var incomingBridge = emesary_mp_bridge.IncomingMPBridge.startMPBridge(routedNotifications, 19);
var f14_aircraft_notification = notifications.PropertySyncNotification.new("F-14"~getprop("/sim/multiplay/callsign"));

During the aircraft loop; note that this uses some shortcuts internally so that the act of sending or receiving something inherited from ProperySyncNotificationBase will transmit the current properties and upon receipt immediately populate the appropriate part of the property tree relative root, usually ai/models/multiplayer[#]/.


Standard GeoEventNotification

There is a new standard notification that is part of the distribution to notify of something that happens at a specific geographic location. This could be many things (there is a list inside Nasal\notification.nas). An example could be a cargo drop using the 1000kg classification. Each event sent will be one of the following, Created, Moved, Deleted. Upon receipt the model can perform the appropriate action.
I’ve got a working example of (what I’ve called) a GeoEventNotification. This is basically something that happened at a position.

So to notify of droptanks

   var m = notifications.GeoEventNotification.new(me.variant, "DT-610", 1, 48 + pylon_index);

This will usually be received by the player’s aircraft over MP. The magic numbers in the above are documented inside notification.nas

Handling bridged messages

Each bridged message is received in the normal way, usually via the GlobalTransmitter, the only difference is that bridged messages are marked as being bridged so that an outgoing bridge knows not to route it out again.

Example recipient

var EmesaryRecipient =
    new: func(_ident)
        var new_class = emesary.Recipient.new(_ident);
        new_class.ansn46_expiry = 0;
        new_class.Receive = func(notification)
            if (notification.NotificationType == "GeoEventNotification")
                print("received GeoNotification from ",notification.Callsign);
                print ("  pos=",notification.Position.lat(),notification.Position.lon(),notification.Position.alt());
                print ("  kind=",notification.Kind, " skind=",notification.SecondaryKind);
                    if(notification.Kind == 1)# created
                        if(notification.SecondaryKind >= 48 and notification.SecondaryKind <= 63)
                            # TBD: animate drop tanks
                return emesary.Transmitter.ReceiptStatus_OK;
            return emesary.Transmitter.ReceiptStatus_NotProcessed;
        new_class.Response = ANSPN46ActiveResponseNotification.new("ARA-63");
        return new_class;
# Instantiate receiver. 
var recipient = EmesaryRecipient.new("F-15-recipient");


In summary, an aircraft sends a notification and all other MP aircraft can receive it (if they want to). This makes inter-aircraft communication possible and with a nicely structured API. It’s also worth noting that because Emesary is doing all of the work if there is a future improvement then your code will not need to change to take advantage of it.

This will allow a set of standard modules to animate items such as droptanks falling, pilots ejecting, etc. Once there is a standard module for something it can simply be included into normal module load.