Sunday, November 17, 2013

Scripting a C# application with JavaScript and Jurassic Part II

In this second part I wanted to test and show how to build an asynchronous api between the C# and JavaScript world.
See previous blog post: Scripting a C# application with JavaScript and Jurassic.

My C# application is pretending to monitor stuff and calls a JavaScript plug in. The plug in
  • Initializes a function notifyMe() that will be called back each time an event occur
  • Calls the monitorEvent(), method which will block the JavaScript execution, but call the callback notifyMe() when needed.
/*
 * Main.js, JavaScript plug in to implement a custom code when an event occur
 */
function main() {

    var counter = 0;

    function notifyMe(time) {

        print("[{0}]Notified {1}".format(++counter, time));
        return true;
    }
    
    print("Monitoring, press ESC to stop the monitoring.")
    MonitorEngine.monitorEvent("stuff", notifyMe);
}

In the pure tradition of JavaScript there is only one thread executing everything, therefore when the callback function notifyMe() is called we are safe to access everything. Obviously the closure is working and we can use the variable counter, declared in the parent function.

My current implementation of the MonitorEngine object is really simple and does not support nesting asynchronous methods because I use one global variable.
In the future I may use a stack to remember the nested callback function, or pass the JavaScript callback function directly to the C# world rather than utilizing the global function __sys__callback__func if Jurassic allow it.
/*
    Create a singelton object named MonitorEngine
    No multi threading support obviouly. It is JavaScript after all.
 */

// Having one global variable does not support nesting asynchronous method call
// TODO: Use a stack. Every asynchronous method would first push on the stack
// the current call back function, initialize the new callback and at the restore
// the previous call back.
var __sys__callback__ = null;

function __sys__callback__func() {

    if(__sys__callback__ !== null) {
        return __sys__callback__.apply(this, arguments);
    }
    else 
        return false;
}

var MonitorEngine = (function () {

    var _monitorEngine = {};

    _monitorEngine.monitorEvent = function (eventType, callBack)  { 

        // Initialize internally the callback function
        if(!sys.isUndefined(callBack)) 
            __sys__callback__ = callBack;

        var r = __monitorEvent(eventType); 
    }
    return _monitorEngine;
 })();

And to finish the C# code
class Program
{
    static Random _rnd = new Random();

    private static int GetRandomTime()
    {
        return _rnd.Next(1, 999);
    }

    static void Main(string[] args)
    {
        var engine = new Jurassic.ScriptEngine();

        // Export the print method to the JavaScript world
        engine.SetGlobalFunction("print", new Action<object>((o) => {
            Console.WriteLine(o == null ? "null" : o.ToString());
        }));

        // Export my own api to the JavaScript world
        engine.SetGlobalFunction("__monitorEvent", new Func<string, bool>((eventType) => {
            try
            {
                Console.WriteLine("Monitor event:{0}".format(eventType));
                while (true)
                {
                    if (Console.KeyAvailable)
                        if (Console.ReadKey().Key == ConsoleKey.Escape)
                            break;
                    var time = GetRandomTime();
                    System.Threading.Thread.Sleep(time);
                    // Call back internal generic function 
                    var r = engine.CallGlobalFunction<int>("__sys__callback__func", time); 
                }
                return true;
            }
            catch (System.Exception ex) {
                Console.WriteLine(ex.ToString());
                Console.ReadKey();
            }
            return false;
        }));
            
        var jsSource = new StringBuilder(1000);

        // My personal JavaScript libraries
        var a = Assembly.GetExecutingAssembly();
        jsSource.Append(DS.Resources.GetTextResource("dictionary.js"   , a)).AppendLine();
        jsSource.Append(DS.Resources.GetTextResource("list.js"         , a)).AppendLine();
        jsSource.Append(DS.Resources.GetTextResource("stack.js"        , a)).AppendLine();
        jsSource.Append(DS.Resources.GetTextResource("string.js"       , a)).AppendLine();
        jsSource.Append(DS.Resources.GetTextResource("stringbuilder.js", a)).AppendLine();
        jsSource.Append(DS.Resources.GetTextResource("sys.js"          , a)).AppendLine();

        // Create a nice JavaScript wrapper for my C# Api exposed
        jsSource.Append(DynamicSugar.DS.Resources.GetTextResource("MonitorEngine.js", a)).AppendLine();

        jsSource.Append(DynamicSugar.DS.Resources.GetTextResource("Main.js",  a)).AppendLine();
        jsSource.Append("main();"); // Call the main function

        engine.SetGlobalValue("ExitCode", 0);

        try
        {
            engine.Execute(jsSource.ToString());
        }
        catch(Jurassic.JavaScriptException ex)
        {
            Console.WriteLine("Error:{0}, Line:{1}\r\n{2}".format(ex.Message, ex.LineNumber, ex.Source));
        }
        Console.WriteLine(engine.GetGlobalValue<int>("ExitCode"));
    }
}

The source code is available at jura2.zip

 Follow Up 

 In the zip file jura2.v2.zip, I add the possibilty to call asynchronous method in a nested fashion.

No comments:

Post a Comment