Emesary: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
Line 79: Line 79:


== JavaScript port ==
== 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).<ref>{{cite web
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/34792963/
  |title  =  <nowiki> Re: [Flightgear-devel] A FGPythonSys implementation: The embedded
Python interpreter as a FlightGear subsystem. </nowiki>
  |author =  <nowiki> Torsten Dreyer </nowiki>
  |date  =  Jan 25th, 2016
  |added  =  Jan 25th, 2016
  |script_version = 0.40
  }}</ref>
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. <ref>{{cite web
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/34796183/
  |title  =  <nowiki> Re: [Flightgear-devel] Designing a thread-safe property tree API
(was Re: A FGPythonSys implementation: ...) </nowiki>
  |author =  <nowiki> Torsten Dreyer </nowiki>
  |date  =  Jan 26th, 2016
  |added  =  Jan 26th, 2016
  |script_version = 0.40
  }}</ref>
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. <ref>{{cite web
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/34796183/
  |title  =  <nowiki> Re: [Flightgear-devel] Designing a thread-safe property tree API
(was Re: A FGPythonSys implementation: ...) </nowiki>
  |author =  <nowiki> Torsten Dreyer </nowiki>
  |date  =  Jan 26th, 2016
  |added  =  Jan 26th, 2016
  |script_version = 0.40
  }}</ref>
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.<ref>{{cite web
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/34793778/
  |title  =  <nowiki> Re: [Flightgear-devel] A FGPythonSys implementation: The embedded
Python interpreter as a FlightGear subsystem. </nowiki>
  |author =  <nowiki> Torsten Dreyer </nowiki>
  |date  =  Jan 25th, 2016
  |added  =  Jan 25th, 2016
  |script_version = 0.40
  }}</ref>
{{WIP}}
{{WIP}}



Revision as of 19:38, 11 May 2016

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.

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.

  1. Richard Harrison (Apr 25th, 2016). Re: [Flightgear-devel] Message passing interface for Nasal.

Use Cases

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).[1]

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. [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]


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.[4]


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 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;
    },
};


//
// Instantiate a Global Transmitter
var GlobalTransmitter =  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 = Notification.new("TestNotification", _value);
        return new_class;
    },
};
var TestNotProcessedNotification =
{
    new: function(_value)
    {
        var new_class = Notification.new("TestNotProcessedNotification", _value);
        return new_class;
    },
};
var RadarReturnNotification =
{
    new: function(_value, _x, _y, _z)
    {
        var new_class = 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 = 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 Transmitter.ReceiptStatus_OK;
            }
            return Transmitter.ReceiptStatus_NotProcessed;
        };
        return new_class;
    },
};

var TestRadarRecipient =
{
    new: function(_ident)
    {
        var new_class = 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 Transmitter.ReceiptStatus_OK;
            }
            return 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 !Transmitter.IsFailed(rv) && rv != 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 !Transmitter.IsFailed(rv) && rv != Transmitter.ReceiptStatus_NotProcessed && tt.count == tt_count; 
            });


PerformTest("Test Not Processed Notification", 
            function ()
            {
                var rv = GlobalTransmitter.NotifyAll(TestNotProcessedNotification.new("Not Processed"));
                return rv == 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