Module configuration
June 24, 2008 9:07 PM
CocoaBugs aims to provide a framework for user interface, statistics gathering, and generation of configuration files for headless operation on cluster nodes for a variety of artificial life models. Facilitating all of these options requires intimate knowledge of the models which will be, one hopes, written for CocoaBugs in the future; managing this sort of knowledge in a flexible, scalable way is a significant challenge.
Currently, the codebase is centered mostly around the Packard Bugs model. In fact, more than three-quarters of the source files for CocoaBugs are for Packard's model: many views, a few controllers, tons of models. The remaining source files still share a fair amount of coupling with the Packard Bugs model.
Some of this has already been factored out. For example, the viewing and generation of statistics in general is managed by a StatisticsController class, which manages a StatisticsView. The StatisticsController can keep track of changing values from almost any other object, aided by Cocoa bindings and key-value observation.
Here's how that works. A StatisticsController has a property, the 'source', which can be any object at all. Once this property is set, the app controller can send the statistics controller a message to register for a key path with a certain name, for example,
[myStatsController registerForPath:@"population"
name:@"World Population"];
In the code for this method, the stats controller sets up a new StatisticsView, and creates a StatisticsData object to hold the collated statistics, keeping track of the specified object with the string name. It then adds itself as an observer for the specified key path:
[source addObserver:self
forKeyPath:path
options:NSKeyValueObservingOptionNew
context:NULL];
Now, whenever the value referenced by the key path changes, our statistics controller is notified of the change:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
for (NSString *key in stats) {
if ([keyPath isEqual:key]) {
[[stats objectForKey:keyPath] addDataSet:[change objectForKey:NSKeyValueChangeNewKey]];
}
}
}
On the model side, we define another class (called WorldStatistics for the Packard Bugs model) which will serve as the data source for the statistics controller. This object knows about the Packard Bugs world, and periodically collects new statistics. It keeps track of which statistics are relevant to the current model:
- (NSDictionary *)descriptions;
{
return [NSDictionary dictionaryWithObjectsAndKeys:
@"Population", @"population",
@"Births (total)", @"births",
@"Deaths (total)", @"deaths",
@"Average age", @"averageAge",
@"Mortality age", @"mortalityAge",
@"Gene survival", @"geneSurvival", nil];
}
The app controller can ask the CocoaBugs controller for an instance of its statistics object, get these descriptions, and tell its statistics controller to listen for changes to those values. In the Packard Bugs statistics collector object, we have a single method that collects the relevant statistics:
- (void)updateStatistics;
{
// population
self.population = [NSSet setWithObject:[NSNumber numberWithInt:[world.bugs count]]];
// births
self.births = [NSSet setWithObject:[NSNumber numberWithInt:[world.maternity count]]];
...
}
When this method is called and the properties are updated, the statistics controller is notified of the changes through key-value observation and updates the data models and views properly. This gives a passable, model-independent way of keeping track of model-dependent statistics.
However, a few limitations are already obvious. Note especially that the observed value is expected to be an NSSet of NSNumbers. This makes the statistics nice and flexible---we can have many-valued statistics---but it introduces some suboptimal overhead for the common case of keeping track of single data points like population or birth rate.
Moreover, there's no way to chart more exotic statistics: for example, the population variation graph for the Packard Bugs model must be handled entirely within its module, since there is no way to register for statistics other than time-based scatter plots.

Keeping all statistics data as an NSSet was necessary to give the statistics module whatever flexibility it has now, but to allow these modifications we need to go beyond the current method of statistics configuration.
And of course, statistics are only one of the many model-specific options that need to be managed by our general framework. Letting the app controller know what kinds of statistics to expect from a model is a very similar problem to letting it know what the configuration options for the model are, what kind of view to prepare for the model, and similar issues.
We'd like to find a system that is flexible enough to allow a variety of statistics options, and hopefully one that could be extended to allow the management of configuration data beyond raw statistics as well.
Next time, an introduction to .plists, and a possible way out of this jam.

Leave a comment