Emesary

From FlightGear wiki
Jump to navigation Jump to search
This article is a stub. You can help the wiki by expanding it.
Emesary ported to Node.js (JavaScript


Background

Right now the replay system only records and replays your "own" aircraft flight dynamics. It would be a significant change/addition and somewhat non-trivial to capture all the data for all the AI objects and replay them as well. We'd have to decide if the recording and playback mechanism should be part of the AI object or something external that grabs the data and then somehow can force the AI object back through it's original path during playback. [1]

A similar problem arises when you have a multi viewer environment where you want to interact with the carrier or when you watch AI traffic across the views ..

In this case each viewer has its own carrier AI traffic. That means the can move independently. The same applies to multiplayer mode.

We need some generic code which is able to track SGSubsystems including their child subsystems and transfer their relevant states over the network. The same interface could be used to store replay replay logs. [2]

Intro

Richard has finished porting his Emesary system to Nasal.

There is a set of notes at [1] that explains the whole thing with a worked example (ACLS).

Emesary is a simple and efficient class based interobject communcation system to allow decoupled disparate parts of a system to function together without knowing about each. It allows decoupling and removal of dependencies by using notifications to cause actions or to query values.

This is in Richard's git repository in his commit, together with the changes to the AI carriers to add ACLS (carrier ILS): https://sourceforge.net/u/r-harrison/fgdata/ci/54165c213f03638a4cb02c848c4f2b234c537f66/

There are many possible applications for Emesary within the system, it solves the problem of how to connect generic instruments to aircraft that have different implementations and properties that need to be used. Richard has been looking at how to abstract out the interface to the MFD and NavDisplay.

Richard has plans to extend this to be able to transmit over mp (probably layered ontop of mp-broadcast). It would also probably work quite well with the HLA FOM to give us a way for models to communicate with other models.[1]

  1. Richard Harrison (Apr 1st, 2016). [Flightgear-devel] Message passing interface for Nasal.

Examples

Setting up a transmitter

var myTransmitter =  Transmitter.new("myTransmitter");

Setting up a notification

var HelloNotification =
{
    new: func(message)
    {
        var new_class = emesary.Notification.new("Hello", message);
        return new_class;
    },
};

Setting up a receiver

var HelloRecipient =
{
    new: func(_ident)
    {
        var new_class = emesary.Recipient.new(_ident);
        new_class.count = 0;
        new_class.Receive = func(notification)
        {
            if (notification.Type == "Hello")
            {
                me.count = me.count + 1;
                print("Hello message received:", notification.message);
                return emesary.Transmitter.ReceiptStatus_OK;
            }
            return emesary.Transmitter.ReceiptStatus_NotProcessed;
        };
        return new_class;
    },
};

Multiplayer bridge

Richard has now got the Emesary multiplayer bridge working ref: http://chateau-logic.com/content/emesary-multiplayer-bridge-flightgear [1]

Code is in the branch of fgdata: https://sourceforge.net/u/r-harrison/fgdata/ci/next-emesary-mp-bridge/tree/

  • Nasal/emesary.nas
  • Nasal/emesary_mp_bridge.nas
  • Nasal/notifications.nas

The multiplayer bridge allows notifications to be routed over MP. 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.

Create an incoming and outgoing in a model with the following lines.

var routedNotifications = [notifications.GeoEventNotification.new(nil)];
var outgoingBridge = 
emesary_mp_bridge.OutgoingMPBridge.new("F-15mp",routedNotifications);
var incomingBridge = 
emesary_mp_bridge.IncomingMPBridge.startMPBridge(routedNotifications);

then any GeoEventNotification will arrive via MP and the transmitter in any registered recipients, ready for handling like this

var EmesaryRecipient =
{
     new: func(_ident)
     {
         var new_class = emesary.Recipient.new(_ident);

         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.FromIncomingBridge)
                 {
                     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");
emesary.GlobalTransmitter.Register(recipient);
  1. Richard Harrison (Apr 25th, 2016). Re: [Flightgear-devel] Message passing interface for Nasal.

Use Cases

With Richard's Emesary system now being ported to Nasal, it would actually make sense to look at it with a focus on FGPythonSys, i.e. how this could be moved to C++ space, and reused by the whole FGScriptingSys interface we've been talking about, because that could make timers, and listeners, entirely unnecessary - while providing the option to run scripts asynchronously from the main loop. It would even be possible to come up with a Canvas mode where a Canvas (FBO) is only updated using an Emesary transmitter, which would mean that these updates could be processed in a background thread, and that OSG could be also much more aggressive about updating Canvas elements/FBOs concurrently. Note that there is nothing Nasal specific about Emesary - it's just a way to come up with a decoupled design that lets components act with eachother without having to know much/anything about their internals, and this kind of decoupling would also be useful for any HLA efforts, because HLA would be just one kind of transport, and one that could greatly benefit from having this separation in place.[1]


Examples

Collection of examples discussed on the forum.

MP Traffic feeds (injection)

JavaScript port

Torsten is successfully using JavaScript via nodejs for a private project with FlightGear. Communication with FGFS runs via websocket and the http/fgcommand interface, everything works as one would expect; the same technique would be possible with Python or any other scripting language that supports websockets, http-get, http-post and has some support for JSON (python has, ruby has, lua has).[2]

Almost every system should be able to run completely independently of the frame rate. What of course would be mandatory is to sync properties at well defined time stamps, this is what the RTI takes care of. [3] almost every system should be able to run completely independently of the frame rate. What of course would be mandatory is to sync properties at well defined time stamps, this is what the RTI takes care of. [4]


Instead of adding just-another-feature we need to strip it down to getting a fast and constand fps rendering engine. Everything else needs to run outside the main loop and has to interact with the core by, say HLA/RTI or whatever IPC we have.[5]


WIP.png Work in progress
This article or section will be worked on in the upcoming hours or days.
See history for the latest developments.
 //---------------------------------------------------------------------------
 //
 //	Title                : EMESARY inter-object communication
 //
 //	File Type            : Implementation File
 //
 //	Description          : Provides generic inter-object communication. For an object to receive a message it
 //	                     : must first register with an instance of a Transmitter, and provide a Receive method
 //
 //	                     : To send a message use a Transmitter with an object. That's all there is to it.
 //  
 //  References           : http://www.chateau-logic.com/content/class-based-inter-object-communication
 //                       : http://chateau-logic.com/content/emesary-efficient-inter-object-communication-using-interfaces-and-inheritance
 //                       : http://chateau-logic.com/content/c-wpf-application-plumbing-using-emesary
 //
 //	Author               : Richard Harrison (richard@zaretto.com)
 //
 //	Creation Date        : 29 January 2016
 //
 //	Version              : 4.8
 //
 //  Copyright � 2016 Richard Harrison           Released under GPL V2
 //
 //---------------------------------------------------------------------------*/

function print () {
 var i, msg="";
 for(i=0; i<arguments.length; i++) 
   msg += arguments[i];
 console.log(msg);
}

function inherit(parent) {
    return Object.create(parent);
}

var emesary = (function() {

var Transmitter =
{
    ReceiptStatus_OK : 0,          // Processing completed successfully
    ReceiptStatus_Fail : 1,        // Processing resulted in at least one failure
    ReceiptStatus_Abort : 2,       // Fatal error, stop processing any further recipieints of this message. Implicitly failed.
    ReceiptStatus_Finished : 3,    // Definitive completion - do not send message to any further recipieints
    ReceiptStatus_NotProcessed : 4,// Return value when method doesn't process a message.
    ReceiptStatus_Pending : 5,     // Message sent with indeterminate return status as processing underway
    ReceiptStatus_PendingFinished : 6,// Message definitively handled, status indeterminate. The message will not be sent any further

    new: function(_ident)
    {
        var new_class = inherit(Transmitter);
        new_class.Recipients = [];
        new_class.Ident = _ident;
        return new_class;
    },

    // Add a recipient to receive notifications from this transmitter
    Register: function (recipient)
    {
        this.Recipients.push(recipient);
    },

    // Stops a recipient from receiving notifications from this transmitter.
    DeRegister: function(todelete_recipient)
    {
        var out_idx = 0;
        var element_deleted = 0;

        for (var idx = 0; idx < this.RecipientCount(); idx += 1)
        {
            if (this.Recipients[idx] != todelete_recipient)
            {
                this.Recipients[out_idx] = this.Recipients[idx];
                out_idx = out_idx + 1;
            }
            else
                element_deleted = 1;
        }

        if (element_deleted)
            this.Recipients.pop();
    },

    RecipientCount: function ()
    {
        return this.Recipients.length;
    },

    PrintRecipients: function ()
    {
        print("Recipient list");
        for (var idx = 0; idx < this.RecipientCount(); idx += 1)
            print("Recpient ",idx," ",this.Recipients[idx].Ident);
    },

    // Notify all registered recipients. Stop when receipt status of abort || finished are received.
    // The receipt status from this method will be 
    //  - OK > message handled
    //  - Fail > message not handled. A status of Abort from a recipient will result in our status
    //           being fail as Abort means that the message was not and cannot be handled, and
    //           allows for usages such as access controls.
    NotifyAll: function(message)
    {
        var return_status = Transmitter.ReceiptStatus_NotProcessed;
	var recipient;
	
        this.Recipients.forEach( function(recipient, index, array) 
        {
            if (recipient.Active)
            {
            var rstat = recipient.Receive(message);
            if(rstat == Transmitter.ReceiptStatus_Fail)
            {
                return_status = Transmitter.ReceiptStatus_Fail;
            }
            else if(rstat == Transmitter.ReceiptStatus_Pending)
            {
                return_status = Transmitter.ReceiptStatus_Pending;
            }
            else if(rstat == Transmitter.ReceiptStatus_PendingFinished)
            {
                return rstat;
            }
//            else if(rstat == Transmitter.ReceiptStatus_NotProcessed)
//            {
//                ;
//            }
            else if(rstat == Transmitter.ReceiptStatus_OK)
            {
                if (return_status == Transmitter.ReceiptStatus_NotProcessed)
                    return_status = rstat;
            }
            else if(rstat == Transmitter.ReceiptStatus_Abort)
            {
                return Transmitter.ReceiptStatus_Abort;
            }
            else if(rstat == Transmitter.ReceiptStatus_Finished)
            {
                return Transmitter.ReceiptStatus_OK;
            }
        }
        });
        return return_status;
    },
    // Returns true if a return value from NotifyAll is to be considered a failure.
    IsFailed: function(receiptStatus)
    {
        // Failed is either Fail || Abort.
        // NotProcessed isn't a failure because it hasn't been processed.
        if (receiptStatus == Transmitter.ReceiptStatus_Fail || receiptStatus == Transmitter.ReceiptStatus_Abort)
            return 1;
        return 0;
    }
};

//
//
// Base class for Notifications. By convention a Notification has a type and a value.
//   SubClasses can add extra properties || methods.
var Notification =
{
    new: function(_type, _value)
    {
        var new_class = inherit(Notification);
        new_class.Value = _value;
        new_class.Type = _type;
        return new_class;
    },
};

var Recipient =
{
    new: function(_ident)
    {
        var new_class = inherit(Recipient);
        if (_ident === undefined || _ident === "")
        {
            _ident = id(new_class);
            print("ERROR: Ident required when creating a recipient, defaulting to ",_ident);
        }
        return Recipient.construct(_ident, new_class);
    },
    construct: function(_ident, new_class)
    {
        new_class.Ident = _ident;
        new_class.Active = 1;
        new_class.Receive = function(notification)
    {
        print("ERROR: Receive function not implemented in recipient ",this.Ident);
        return Transmitter.ReceiptStatus_NotProcessed;
        };
        return new_class;
    },
};

return {
	Transmitter: Transmitter,
	Notification: Notification,
	Recipient: Recipient
};

})();

//
// Instantiate a Global Transmitter
var GlobalTransmitter =  emesary.Transmitter.new("GlobalTransmitter");


 //---------------------------------------------------------------------------
 //
 //	Title                : EMESARY tests
 //
 //	File Type            : Implementation File
 //
 //	Author               : Richard Harrison (richard@zaretto.com)
 //
 //	Creation Date        : 29 January 2016
 //
 //  Copyright � 2016 Richard Harrison           Released under GPL V2
 //
 //---------------------------------------------------------------------------*/

print("Emesary tests");

var TestFailCount = 0;
var TestSuccessCount = 0;

var TestNotification =
{
    new: function(_value)
    {
        var new_class = emesary.Notification.new("TestNotification", _value);
        return new_class;
    },
};
var TestNotProcessedNotification =
{
    new: function(_value)
    {
        var new_class = emesary.Notification.new("TestNotProcessedNotification", _value);
        return new_class;
    },
};
var RadarReturnNotification =
{
    new: function(_value, _x, _y, _z)
    {
        var new_class = emesary.Notification.new("RadarReturnNotification", _value);
        new_class.x = _x;
        new_class.y = _y;
        new_class.z = _z;
        return new_class;
    },
};

var TestRecipient =
{
    new: function(_ident)
    {
        var new_class = emesary.Recipient.new(_ident);
	console.log(new_class);
        new_class.count = 0;
        new_class.Receive = function(notification)
        {
            if (notification.Type == "TestNotification")
            {
                this.count = this.count + 1;
                return emesary.Transmitter.ReceiptStatus_OK;
            }
            return emesary.Transmitter.ReceiptStatus_NotProcessed;
        };
        return new_class;
    },
};

var TestRadarRecipient =
{
    new: function(_ident)
    {
        var new_class = emesary.Recipient.new(_ident);
        new_class.Receive = function(notification)
        {
            if (notification.Type == "RadarReturnNotification")
            {
                print(" :: Test recipient ",this.Ident, " recv:",notification.Type," ",notification.Value);
                print(" ::   ",notification.x, " ", notification.y, " ", notification.z);
                return emesary.Transmitter.ReceiptStatus_OK;
            }
            return emesary.Transmitter.ReceiptStatus_NotProcessed;
        };
        return new_class;
    },
};

function PerformTest(tid, t)
{
    if (t())
    {
        TestSuccessCount = TestSuccessCount + 1;
        print("  Test [Pass] :",tid);
    }
    else
    {
        TestFailCount = TestFailCount + 1;
        print("  Test [Fail] :",tid);
    }
}

var tt = TestRecipient.new("tt recipient");
var tt1 = TestRecipient.new("tt1 recipient1");
var tt3 = TestRecipient.new("tt3 recipient3");
var tt2 = TestRadarRecipient.new("tt2: Radar Test recipient2");

PerformTest("Create Notification", 
            function ()
            {
                var tn = TestNotification.new("Test notification"); 
                return tn.Type == "TestNotification" && tn.Value == "Test notification";
            });

PerformTest("Register tt", 
            function ()
            {
                GlobalTransmitter.Register(tt);
                return GlobalTransmitter.RecipientCount() == 1; 
            });
PerformTest("Register tt1", 
            function ()
            {
                GlobalTransmitter.Register(tt1);
                return GlobalTransmitter.RecipientCount() == 2; 
            });
PerformTest("Register tt2", 
            function ()
            {
                GlobalTransmitter.Register(tt2);
                return GlobalTransmitter.RecipientCount() == 3; 
            });
PerformTest("Register tt3", 
            function ()
            {
                GlobalTransmitter.Register(tt3);
                return GlobalTransmitter.RecipientCount() == 4; 
            });

PerformTest("Notify", 
            function ()
            {
                var rv = GlobalTransmitter.NotifyAll(TestNotification.new("Test notification"));
                return !emesary.Transmitter.IsFailed(rv) && rv != emesary.Transmitter.ReceiptStatus_NotProcessed && tt.count == 1; 
            });

PerformTest("DeRegister tt1", 
            function () 
            {
                GlobalTransmitter.DeRegister(tt1);
                return GlobalTransmitter.RecipientCount() == 3; 
            });

tt1_count = tt1.count;
PerformTest("NotifyAfterDeregister", 
            function ()
            {
                GlobalTransmitter.NotifyAll(TestNotification.new("Test notification"));
                return tt1.count == tt1_count;
            });

tt.Active = 0;
tt_count = tt.count;

PerformTest("Recipient.Active", 
            function ()
            {
                var rv = GlobalTransmitter.NotifyAll(TestNotification.new("Test notification"));
                return !emesary.Transmitter.IsFailed(rv) && rv != emesary.Transmitter.ReceiptStatus_NotProcessed && tt.count == tt_count; 
            });


PerformTest("Test Not Processed Notification", 
            function ()
            {
                var rv = GlobalTransmitter.NotifyAll(TestNotProcessedNotification.new("Not Processed"));
                return rv == emesary.Transmitter.ReceiptStatus_NotProcessed; 
            });


GlobalTransmitter.NotifyAll(RadarReturnNotification.new("Radar notification", "x0","y0","z0"));

if (!TestFailCount)
    print("Emesary: All ",TestSuccessCount," tests passed\n");
else
    print("Emesary: ERROR: Tests completed: ",TestFailCount," failed && ",TestSuccessCount," passed\n");

Related