Headless Cocoa Bugs
July 23, 2008 1:47 PM
...is a good name for a rock band.
Last time, we saw how to create nice friendly configuration files for CocoaBugs. Today, I'll be talking about how to use those config files to run ALife simulations headlessly in a new command-line (CLI) client.
We can create a new CLI target by right-clicking the "Targets" section in XCode, and picking Add -> New Target... We want a command-line app, so choose "Shell Tool" from the "Cocoa" section. (This is a "Target for building a command-line tool that uses Cocoa APIs", which is exactly what we want.) I'll name it HeadlessBugs.

This will create a new target with no files associated with it. We need at least a main function for it to actually do anything.
Our default main.m file calls NSApplicationMain, which loads nib files and classes as specified in the Info.plist in the app bundle; this target is a raw executable, with no bundle, Info.plist, or nib files, so we want to make our own. So we'll create a new Objective-C file, headless_main.m.
Since we're not taking advantage of the NSApplicationMain in this file, we need to create our own NSAutoreleasePool. Parsing of command-line options is done with the NSUserDefaults class. As a first guess, we're going to want to support three options:
- Input configuration file
- Output directory path
- Number of generations to run for
Let's try this to begin, for headless_main.m:
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSUserDefaults *args = [NSUserDefaults standardUserDefaults];
NSLog(@"Configuration file: %@", [args stringForKey:@"f"]);
NSLog(@"Output directory file: %@", [args stringForKey:@"o"]);
NSLog(@"Number of steps: %d", [args integerForKey:@"s"]);
[pool release];
return 0;
}
This file can be included in the target by dragging it to the "Compile Sources" phase of the HeadlessBugs target.

After selecting the target and building, we can try it in a terminal:
Glaukopis-Athena:Debug d$ ./HeadlessBugs -f foo.cocoabugs -o ~/Desktop/runs -s 5000
2008-07-29 15:19:56.563 HeadlessBugs[6656:10b] Configuration file: foo.cocoabugs
2008-07-29 15:19:56.565 HeadlessBugs[6656:10b] Output directory file: /Users/d/Desktop/runs
2008-07-29 15:19:56.565 HeadlessBugs[6656:10b] Number of steps: 5000
Whoo! Nearly there.
Now we need to make this actually do something. Unfortunately, as written, we can't just import our AppController code and get plugin classes from there, since the AppController depends on all sorts of GUI stuff that we don't need or care about. So, we're going to factor the plugin loading code out into an ALifePluginLoader class that doesn't depend on anything else. To avoid needless object creation and management, I'm going to make the three plugin related methods class methods:
+ (BOOL)plugInClassIsValid:(Class)plugInClass;
+ (NSMutableArray *)allPlugIns;
+ (NSMutableArray *)allPlugInPaths;
Now we can import ALifePluginLoader.h into our AppController and headless_main.m files, and use them from there.
NSArray *plugins = [ALifePluginLoader allPlugIns];
NSString *configurationFile = [args stringForKey:@"f"];
NSString *outputDirectory = [args stringForKey:@"o"];
int numberOfSteps = [args integerForKey:@"s"];
if (!(configurationFile && outputDirectory && numberOfSteps))
printf("Usage: HeadlessBugs -f <config filename> -o <output directory path> -s <number of steps>\n");
NSDictionary *data = [NSDictionary dictionaryWithContentsOfFile:configurationFile];
NSString *identifier = [data objectForKey:@"identifier"];
Class <ALifeController> selectedPlugin;
for (Class <ALifeController> plugin in plugins) {
if ([[plugin name] isEqual:identifier]) {
selectedPlugin = plugin;
break;
}
}
if (!selectedPlugin)
printf("Plugin class not found. Make sure it's installed.");
Once we've found the selected plugin, we want to run it for the specified number of steps:
NSDictionary *configuration = [data objectForKey:@"configuration"];
id <ALifeController> controller = [[selectedPlugin alloc] initWithConfiguration:configuration];
int step;
for (step = 0; step < numberOfSteps; step++) {
printf("Step %d\n", step);
[controller update];
}
printf("Done.\n");
It works! Of course, despite what the code says, we are far from done. We need a way for the headless app to generate a directory of output information, especially the statistics over time for the run. Doing this will require refactoring the statistics controller to not depend on a window to show its results in, as well as some careful thought about exactly what kinds of data to keep. Soon, I hope!

Leave a comment