Quantcast
Channel: Platform – C# City
Viewing all articles
Browse latest Browse all 16

From C# To Javascript and Back Again, Via ClearScript

$
0
0

Note: This is the second of two articles on integrating C# and scripting languages. You can read the second part (for Ruby) here.

I recently created an engine (I know, I know) to create text-based Interactive Fiction games in C#. My requirement was simple: create a core engine with some core functionality (rooms, commands, and objects) in .NET, and allow end-users to write their games in Javascript. In the process, I picked up a little-known gem called ClearScript as the vehicle of interaction.

Let’s start with my requirements: I should be able to write core functionality classes in C#, instantiate and extend them in ClearScript, and return them back to C# for consumption by the game engine. I also need strong typing of objects (despite the dynamic nature of scripting languages), and the ability to create delegates or functions inside my scripting language (and return those to C#, too).

You can view the source code on GitHub. This post summarizes my implementation, and the challenges I ran though.

Why ClearScript?

ClearScript is far from the most popular Javascript library for C#. Why did I choose it? Because it had a simple syntax, and more importantly, it allowed me to return strongly-typed objects to C# (including Action instances) where Javascript.NET, Jurassic, could not.

JavascriptRunner Class

I created a core “JavascriptRunner” class which encapsulates all the Javascript-centric invocation. It contains a simple constructor, and a method public T Execute<T>(string script, IDictionary<string, object> parameters).

The first parameter (the script) is the easiest part. The second parameter contains any variables from C# that I want to expose to Ruby (this makes up my “API” which users write against).

One issue I ran into is that ClearScript doesn’t support the widely-supported console.log method. To get around this, I simply registered the Console class to it:

engine.AddHostType("Console", typeof(Console));

Here’s how that looks, as of writing:


public JavascriptRunner()
{
    this.engine = new V8ScriptEngine();
    var assembly = System.Reflection.Assembly.Load("MeltDown.Core");

    foreach (var type in assembly.GetTypes())
    {
        engine.AddHostType(type.Name, type);
    }

    // Used for anonymous functions
    engine.AddHostType("Action", typeof(Action));
    // JS doesn't support console.log (JS/V8/Chrome style)
    engine.AddHostType("Console", typeof(Console));
}

public T Execute<T>(string script, IDictionary<string, object> parameters)
{

    foreach (var kvp in parameters)
    {
        // TODO: these are engine-scope. It doesn't reset.
        engine.AddHostObject(kvp.Key, kvp.Value);
    }

    var toReturn = engine.Evaluate(script);

    if (toReturn is T)
    {
        return (T)toReturn;
    }
    else
    {
throw new ArgumentException("Expected " + typeof(T).FullName + " but got " + toReturn.GetType().FullName);
    }
}

Notice I can’t pass in parameters per script execution (unlike the Ruby version). That’s okay too; there’s room to improve.

Converting ClearScript Arrays to C# Lists

The second major hurdle is that ClearScript returns an Array instance instead of an enumeration if I use typical Javascript list syntax, like this: var x = ['a', 17, 2.0 / 3].

To get around this, I created a ScriptHelper class. This class contains two methods: IsArray and ToList<T>. In both methods, I rely on IronRuby-specific implementation, and since the object input is dynamic, I can just call Javascript methods and get back what I want. Here’s how that looks (Javascript only):


public static List<T> ToList<T>(dynamic source)
{
    for (int i = 0; i < source.length; i++)
    {
        object next = source[i];
        found.Add(GetAsType<T>(next));
    }

    return found;
}

public static bool IsArray(dynamic source) {
    return source.constructor.name == "Array";
}

This allows me to write “Javascript-like” code without worrying about types.

Returning Delegates/Functions

The last hurdle was related to functions; given a delegate or function type (like Action), how can I return an object which I can type to a delegate type?

The answer was two parts. First, I need to register the Action class with ClearScript, like so:

engine.AddHostType("Action", typeof(Action));</script>

The second was to wrap any functions into an <code>Action
constructor:


new Action(function() {
    this.Description = "A steaming, hand-burning potato";
})

Conclusion

Given my requirements, I achieved them, and have code that allows me to write very “Javascript-like” code. That’s great; I can leverage C# to take care of some of the leaky abstractions, and everything works.

I suggest you download and play with the full source code from GitHub. You can look at the ScriptRunner.Tests project to see working Ruby code coming back to C#.


Viewing all articles
Browse latest Browse all 16

Trending Articles