IronRuby @ RubyConf 2009 – Part 3.5: Embedding IronRuby

15 Dec 2009

This is part of a RubyConf 2009 series:
Overview | What sets IronRuby apart? | Sneaking Ruby to the top / Embedding IronRuby | Project status

Imagine you’re building a program to help create animations, visualizations, and other interactive applications. The requirements are simple:

  1. 2D rendering surface with simple primitive shapes
  2. Simple animation support – callbacks for each frame and each object on the canvas
  3. User-loadable “macros” for drawing and animating

A .NET developer can easily code up the first two requirements in C#, but implementing the third will be tricky. What does a macro look like? How do I discover them? How can I make it interactive? Why is this so hard!? This scenario requires the user to input some data, and the program must make an animation out of it; the data of your program is the code. Here are the options most .NET developers would come up with:

  • Domain specific language – people usually cop out here and make it XML-based, which happens to produce the most human-unreadable code ever. This DSL could also be GUI-based, but you won’t get any programmers interesting in extending your app that way.
  • Completely punt on the interactivity and require the macros be .NET DLLs.
  • Find a way to use C# interactively - either using CodeDom to compile and run C# code dynamically (much like ASP.NET does), or code-generate a valid C# class from the snippet, compile it to a DLL (aka shell out to csc.exe), loading that DLL, and finally reflecting over that DLL to call the user’s code. This is, of course, ignoring the question of whether C# is a good macro language or not. It’s worth noting that Mono supports hosting it’s C# compiler, so you could do more dynamic things with C# through Mono.

Given all that talk about Ruby before, let’s try using IronRuby to write these macros. Here’s a C# app to start from:

image

SketchScript starter on GitHub (zip)

For the lazy, get the finished app’s source code (zip), and a binary build (coming soon).

The starter app does ABSOLUTELY NOTHING; just a Window with a Canvas and a bunch of textboxes for coding. Keep in mind this is just a demonstration, and this app could have been written entirely in Ruby, but the point is to show .NET developers how powerful an embedded scripting language can be.

Setting up your environment

.NET developers have choices for development environments: mainly Visual Studio or SharpDevelop, or even the command-line and text-editor (I left out MonoDevelop since Mono doesn’t support the Windows UI stuff I’m doing, but a future version will be able to run in Mono). I’ll be using Visual Studio 2010 Beta 2 for this walkthrough’s screenshots and examples, specifically because it C# 4 has special dynamic language features, but you can also use C# 3 with Visual Studio 2008 (free version here), or just stick to a text-editor and MSBuild.

If you’re using .NET 4.0, sketchscript\sketchscript.sln is the solution file you want to use. The .NET 3.5 version is sketchscript\sketchscript3.5.sln, but seriously, try out .NET 4.0.

I haven’t tested it out in VS2008 yet, so please bare with me. If you’re feeling adventurous, you can fork my git repo, get it working in VS2008, and I’ll pull your changes in.

Adding references to IronRuby

If you downloaded SketchScript from the above link, you’ll find four DLLs required for embedding IronRuby in the sketchscript\ironruby directory. Add those as references to the sketchscript.csproj:

image

In case you’re curious about what each DLL is: IronRuby.dll is the IronRuby compiler, while IronRuby.Libraries.dll is the core libraries of Ruby. Microsoft.Dynamic.dll are the APIs that IronRuby depend on for DLR compiler features, and Microsoft.Scripting.dll is the DLR Hosting API.

If anyone who has used IronRuby before is thinking there are missing DLLs, then you’re right. IronRuby’s Microsoft.Scripting.Core.dll has been integrated into .NET 4.0 as the new System.Core.dll. This also removes the need for Microsoft.Scripting.ExtensionAttribute.dll.

Embedding IronRuby

Now let’s get that code window working; first add some using statements at the top of MainWindow.xaml.cs:

And some fields to hold onto the scripting engine anywhere in the MainWindow class:

Now initialize the scripting engine; add this code to the Loaded event action, after setting the OutputBuffer but before the KeyBindings() call (around line 71):

And lastly let’s run the code when Ctrl-Enter is pressed. Since that’s already set up for us, all we need to do is add this code in the RunCode method, right at the "TODO" comment around line 92:

And that’s it! Now you’ll be able to run some actual Ruby code:

image

Interacting with the host application

Though Ruby code can run, there is no obvious interaction with the host application. That black void of a canvas on the left would be completely useless if it wasn’t accessible from Ruby code, so add this single line to the Loaded event action, before the KeyBinding() call:

Note: if the host application didn’t do this, it would still be possible to get to the canvas from Ruby code, but the script writer would have to do it themselves:

So, as a general rule-of-thumb, have your host program decide what parts to extend to script code, and have the script code only use that object-model, though their may be ways around it.

Trying it out

Now that there’s a way to draw on the canvas, play around with drawing random things on the screen. Here’s a little script I’ve been playing with:

Which draws this:

squares

Ooo, pretty! So without much effort we have a very extendable application. Before you get carried away playing around with making pretty things, there’s one more thing to do to make this app really awesome …

Animation support

While animations could still be built with IronRuby’s native thread support or WPF animations directly from Ruby code, it’d be nice for the host to provide some simple animation support, like a callback that fires for every frame, and even a way to attach animations to any object.

The host currently supports these two callbacks, but they need to be wired up. Add the following code at the bottom of the Loaded event action, before the call to KeyBindings():

And also add this call at the end of the RunCode method:

Lastly we need to implement CatureAnimationCallbacks, by looking for special method names to get a hold of. Look for each_frame as the EachFrame action, and each_object as the EachObject func.

Now 30-times-a-second the host will try to call each_frame, and it will call each_object once for each element on the canvas, store the return value on the actual element, and then try to call an update method on that stored object 30-times-a-second. This lets you either run random animations, or specific behavior for objects.

A good animation example is bouncing, so let's run the script that produced all the squares first, and then run the following code to make them all bounce.

And now it should look something like this:

There are more goodies to run in the features directory, but try writing your own fun little animations. Jim Deville ported the tutorial from Jeff Casimir’s Code of Art talk, which is pretty fun to play with, so if you make your own please post a comment with a screenshot and code!

circle.rb

artclear2 

And there you have it, IronRuby embedded to do interesting things. In case you missed any steps along the way, here's the full diff against the initial download.

Next stop, IronRuby’s status and roadmap.

comments powered by Disqus