Thursday, October 09, 2008

Put some lipstick on the pig

Mike Ash is voicing his opinion about the state of Mac development tools, in particular the de facto standard, Xcode:

Xcode sucks. If you're reading this blog then this is probably like saying "the sky is blue". You know already. Big surprise. The debugger fails all over the place, autocomplete is broken more often than it works, and the editor won't perform acceptably until you throw four Xeon cores at it.

I've already given up on XCode for editing, I use TextMate and relegated Xcode to the task of project & build management (for which purposes it is about adequate) and debugging. I learned last night that gdb is considerably more robust when not run via Xcode. So, in future, I will probably forgo the Xcode interface and learn gdb directly. At this point I am open to replacing XCode even for build management. Some, like Ciaran Walsh, already have.

I have to say that, as good a concept as it is, even the new Interface Builder (as much as it may be better than the version before) also sucks pretty badly with it's own weird behaviours and glitches. The "special relationship" between Xcode and IB probably doesn't help in this regard. I'm pretty sure that when IB decides to hang opening one of my Xibs it is because of Xcode.

What I often wonder is; Do Apple engineers themselves use Xcode and Interface Builder? Despite what I've heard about the number of people working on the Xcode team I find it hard to believe the tools are well used inside Apple. How could they put up with it?

I'm a hobby coder so only using these tools for a few hours at a time and not for critical work. If I was depending on them day in, day out I'd be outside the Xcode team office with a torch, pitchfork, and a mob of angry villagers.

Maybe it would be an idea for the Logic or Final Cut team to job swap with the Xcode & IB team every now and again and try and put some lipstick on these suckers.

09/10/2008 10:11 by Matt Mower | Permalink | comments:

Scripting Elysium using MacRuby

For a couple of months now my hobby has been working on a Cocoa application, code name "Elysium" for generating music. If you want to read a little of the background I posted about it earlier.

Something I've had in mind, almost from the beginning, was that Elysium should be scriptable. That, through the ability to script parts of the application, you can have more control over the musical strategy. For example a script might regulate how often new playheads are generated in order to control the "energy" of a piece.

But how to script a Cocoa application?

My original plan had been to use Nu however, although I still find Nu an interesting idea, I've not spent much time with it recently and it's continued lack of support for Objective-C garbage collection put me off.

With Nu out of the picture I switched my attention to Javascript. It's not perfect but it's pretty good and the JavascriptCore framework comes with Leopard (which is fine since using GC makes my app Leopard only anyway). With all the news of JSCore performance enhancements via SquirrelFish this seemed a solid choice.

Embedding JSCore is simple, just copy the framework into your project. That's when the fun begins though. As soon as I started trying to use it I realized that JSCore is unbridged. This means that, in order to expose Elysium's Objective-C based objects to Javascript I would have had to implement a set of "facades" that would emulate JS classes.

There is an example, JSPong, and while you know it's not impossible it's pretty tedious (and not helped by JSCore very thinly documented, and I'm being generous there) and results in a heavy stink of pollution of your model classes. In short, right now, using JSCore as a Cocoa scripting language is a mess. Worse still I knew, from the Nu experience, that it was totally unnecessary mess.

While casting around for how to bridge Javascript I came across F-Script. F-Script is a Smalltalk like language that is beautifully bridged with Cocoa. In less than an hour I was scripting Elysium. For example here is a callback that tells a note not to play on beats divisible by 7.

[:noteTool :playhead | noteTool hex layer beatCount rem: 7 == 0 ifTrue:[ noteTool setSkip:YES ]]

This is an F-Script block (which will be familiar to anyone who has used Smalltalk or Ruby) where the noteTool and playhead arguments are Cocoa objects being passed, transparently, from Objective-C. Equivalently the F-Script code can call the setSkip: method in the other direction.

The F-Script Block class even used a category on NSString so you could write:

[[@"some f-script code" asBlock] value]

to evaluate arbitrary F-Script code from Objective-C. Very nice.

However as nice as this was F-Script felt very alien and I could see it being a stumbling block to other people playing with Elysium (due out RSN). All the time I was using F-Script I was thinking "If only I could get Javascript bridged like this."

In the end I hunted down the author of the Leopard BridgeSupport tool, Laurent Sansonetti to ask him. His reaction was "Why not use Ruby?"

Why not indeed?

Well because I've done it before and it doesn't work very well.

"But what about MacRuby?"

It turns out that MacRuby is getting pretty real, is bridged just like F-Script, uses Objective-C collections and even shares the Objective-C garbage collector (which JSCore would not have done). Given that I love Ruby and would prefer to write it than F-Script or Javascript I was sold.

So, you've read the boring pre-amble. How is it done?

First you have a choice, use MacRuby 0.3 (the last release) or Trunk. Laurent is still working on the GC code which can make trunk unreliable but, for various reasons, that's where I ended up. While working on this Laurent cooked up a MacRuby API for Objective-C which is a pretty good reason to pick trunk (or wait for 0.4).

So checkout MacRuby trunk:

svn co http://svn.macosforge.org/repository/ruby/MacRuby/trunk MacRuby

By default MacRuby is setup to be installed in /Library/Frameworks which we don't want. We want it ready to embed in an app, so from the command line:

rake BUILD_AS_EMBEDDABLE=true
DESTDIR=/tmp/build rake install

this will "install" MacRuby out of the way so you can pick out the framework and delete the rest. From your Cocoa project, copy the framework in.

cp -R /tmp/build/Library/Frameworks/MacRuby.framework .

So, now you have the framework. In your Xcode project (or wherever) add MacRuby.framework as a linked framework (don't forget to also add it to your copy frameworks build phase).

From here it depends on what you want to do but I will describe what I did.

I wanted the user to be able to edit small snippets of code that would be attached as callbacks in various places (e.g. when a layer runs or has run, or when a tool is about to run). My experience with F-Script told me I wanted some kind of RubyBlock class and a similar category on NSString for making them.

In my code I have an NSWindowController subclass to handle inspectors that allow callback management for example:

#import "RubyBlock.h" // in turn #import <MacRuby/MacRuby.h>

- (void)editWillRunScript:(ELTool *)_tool_ {
  RubyBlock *block;
  if( !( block = [[_tool_ scripts] objectForKey:@"willRun"] ) ) {
    block = [[NSString stringWithFormat:@"do |%@Tool,playhead|\n# write your callback code here\nend\n", [_tool_ toolType]] asRubyBlock];
    [[_tool_ scripts] setObject:block forKey:@"willRun"];
  }
  [block inspect];
}

Here I steal another trick from F-Script in providing inspect on the RubyBlock class and an associated ScriptInspectorController (and Xib) that pops up an editing window. Right now it's no more than an NSTextView with an 'ok' and 'cancel' buttons but it's still a nice to have.

This block encapsulates a Ruby proc and can be called via one of:

[block eval]
[block evalWithArg:x]
[block evalWithArg:x arg:y];

and of course we could add more or a generic that takes an array or something.

Under the hood the RubyBlock class uses one method from Laurents shiny new MacRuby class to generate the proc:

- (void)setSource:(NSString *)_source_ {
  source = _source_;
  NSString *procSource = [NSString stringWithFormat:@"proc %@", source];
  proc = [[MacRuby sharedRuntime] evaluateString:procSource];
}

Pretty simple. We make a proc out of anything we're passed. Now I also added (with a lot of help from Laurent) some methods to MacRuby via category for eval'ing procs. I won't go into the implementation here but it means, for example, that RubyBlock can evaluate as simply as:

- (id)evalWithArg:(id)_arg1_ arg:(id)_arg2_ {
  return [[MacRuby sharedRuntime] evalProc:proc arg:_arg1_ arg:_arg2_];
}

I'll be making the source to RubyBlock and the categories NSString+AsRubyBlock and MacRuby+EvalProcs available either separately or as part of MacRuby itself. If you're in a hurry send me an email and I can share them with you now.

The upshot is that by embedding MacRuby I can now very simply create Ruby procs that can be called from Objective-C using a convenient syntax and that can interact with the Objective-C quite naturally. This is a big usability win, not least because it means I can write Ruby for my scripts.

I'm very grateful to Laurent Sansonetti both for his herculean efforts with MacRuby and for his patient efforts with me.

For those of you who are interested in generative music a public beta release of Elysium is imminent.

09/10/2008 22:14 by Matt Mower | Permalink | comments: