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:
- 2D rendering surface with simple primitive shapes
- Simple animation support – callbacks for each frame and each object on the canvas
- 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:
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:
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:
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:
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!
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.