IV. Objectbase Library

1. Dependencies

Following are the other header files imported by <objectbase.h>:

#import <defobj.h>
#import <activity.h>
The defobj library interface is included to provide the basic object support.

2. Compatibility

  • 1.0.4 => 1.0.5. No changes.

  • 1.0.3 => 1.0.4. The name of this library is now objectbase, it is has been renamed from swarmobject largely to reflect the more generic nature of the library and also motivated by the impending port of Swarm to Windows NT (to avoid filename conflicts with the SwarmObject class). There should be little, or no effect on the user, the only visible change is the fact that the actual library (.a) or (.so) file will now have a different name and the header file name has changed. A symbolic link from objectbase.h to swarmobject.h has been provided in the distribution, to ensure backwards compatibility, however, users should not continue to rely on this being so. Users should port references to swarmobject.h in their code to objectbase.h, because this symlink will be removed in a future release.

    Note: this is no way affects the SwarmObject class which remains the same as in all previous releases.

  • 1.0.0 => 1.0.1. The interface has changed again! EmptyProbeMap is now a subclass of CustomProbeMap, which is subclassed from ProbeMap. And a shortcut create: method was added to that branch.

    Also, a new method was added to ProbeLibrary called isProbeMapDefinedFor that serves to non-invasively test for the existence of a ProbeMap for a given class.

  • Beta => 1.0.0. The new interface for the swarmobject library might cause some problems for apps that worked under the Beta release of Swarm. To get the whole scoop, read the Library Interface Conventions.

3. Usage Guide

3.1. Overview

The objectbase library contains the most basic objects users need to design their agents and swarms. It also serves, at present, as a repository for the probe machinery, which is provided for every SwarmObject. The way the classes in this library are to be used is varied. But, basically, it is provided so that the user will have something to subclass from for her own objects and Swarms.

3.2. Example Usage of SwarmObject

The best way to explain how the library should be used is to walk through an example. So, using Heatbugs, we'll walk through the ways objectbase is used and discuss them. Since more documentation is usually better than less, I'm going to explain things at a low level so that those not familiar with Objective C will understand the discussion. If you already are familiar with Objective C, then you should skip this part.

First off, the basic elements of the Heatbugs simulation are the heatbugs, the model swarm (which bundles the heatbugs), and the observer swarm (which bundles the displays of the probes poking into the model swarm and the heatbugs). The interface files for each show what must be imported and the declaration syntax needed to subclass from SwarmObject.

We'll use Heatbug.h for our discussion here. The first part of the file shows the C-preprocessor imports needed:

#import <objectbase/SwarmObject.h>
#import <space.h>			 
#import "HeatSpace.h"			 
#import <tkobjc/Raster.h>		 

The #import <objectbase/SwarmObject.h>; is included in order to subclass from SwarmObject. However, to provide backwards compatibility, we've placed this import in the library interface file objectbase.h as well, which means one could subclass from SwarmObject by simply importing the objectbase.h file. This is discouraged in order to make the library interfaces as standard as possible.

The next objectbase relevant piece of code in this file is:

@interface Heatbug: SwarmObject
{
  double unhappiness;		
  int x, y;			
  HeatValue idealTemperature;	
  HeatValue outputHeat;		
  float randomMoveProbability;	
  				
  Grid2d * world;		
  int worldXSize, worldYSize;	
  HeatSpace * heat;		
  Color bugColor;		
}

The @interface keyword indicates that you are beginning the definition of the part of an object (a Heatbug in this case) that will be visible to other objects. The Heatbug: SwarmObject indicates that you are calling this object Heatbug and it is a subclass of SwarmObject. What follows between the curly braces ({}) are the instance variables defined for the Heatbug class above and beyond those inherited from the SwarmObject class.

Inside this "agent," we have defined several parameters associated with either the agent, itself, or the space in which it sits. Any data that will need to be present throughout all the behavior and lifetime of the agent should be declared here. Also, anything declared here will be accessible to the probe machinery, and so will be capable of being manipulated and viewed from outside the agent.

Next come the message prototypes to which this agent will respond. And it is worth noting again that these are in addition to those declared in the SwarmObject superclass. So, not only will other objects be able to send messages to this agent that are declared here, but other objects will be able to send all the messages declared in the objectbase/SwarmObject.h imported previously. The messages prototyped here will dictate what the compiler thinks this object can respond to. Hence, if any part of any of these prototypes differs from the corresponding function definition in the Heatbug.m file, then the compiler will say something like Object: aHeatbug does not respond to xyz, where "xyz" is the name of the message that is being sent to the Heatbug. A script is provided with the Swarm distribution that fixes header file prototypes to match the message declarations in the corresponding ".m" file. This script should be in the $SWARMHOME/bin directory and is called m2h.

One more thing to notice about these prototypes is that some of them are duplicates of what appears in the objectbase/SwarmObject.h file. This means that when the message is called on a Heatbug object, it will execute the method defined here and not the one in the SwarmObject class. In the objectbase library, the following messages are intended to be overridden, as necessry: create:, createBegin:, createEnd, customizeBegin:, customizeEnd, customizeCopy:, describe:, and getInstanceName. Each of these messages do specific things that may change from subclass to subclass of SwarmObject. In this case, however, we're only overriding createEnd. The differences between we implement it in Heatbugs and the default is not that significant. But, it should be pointed out that when overriding certain messages, like createBegin: and createEnd, the new method should call the superclass' version of the message, as well. This is done using the default pointer to the superclass, designated super. The syntax in the Heatbugs case is:

[super createEnd];

The reasons for doing this are related to the object phase protocols used by defobj. If you would like more info on that, see the Swarm User Guide.

Finally, the @end keyword signifies the end of the interface definition. GNU Objective C allows one to leave this off; but, it is not good practice.

And that's it. Of course, there're a few tricky aspects to using the objectbase library that weren't mentioned here. Some of them will be mentioned in the Advanced Usage Notes and the Implementation Notes; but, the best way to learn is to examine the way the demo applications do it and try to make some changes yourself.

3.3. Subclassing from Swarm

Subclassing from the Swarm class works very similar to subclassing from SwarmObject.

3.4. ActivityControl

The ActivityControl object provides much more finely grained control over the execution of an interactive simulation. It addresses both the problems of not being able to stop the simulation at any given point in any given activity and provides an initial step towards a Swarm debugger.

An activity controller can be attached to any activity that is created in a Swarm simulation, including those that are created for use only by the Swarm kernel. The controller then provides the basic activity manipulation messages on that activity, which are: run, stop, next, step, stepUntil, and terminate.

The presence of the ActivityControl object might cause some confusion about what role the ControlPanel should play in the controlled execution of the various schedules. The ControlPanel should still be used for the top-level control of any simulation that is running in a context where random interference is expected (like under a GUISwarm where the user may click a button at any time). The reason this is true is because the ControlPanel sends pseudo-interrupts to the infinite loop we use to perpetuate execution of the top level Swarm (which can only be seen in the form of the go message on a GUISwarm at present). This type of control may change in the future! But, for now, it is how we monitor the control state of the simulation.

Now, having said that, the ControlPanel should no longer be used to run the simulation. It should only be used to instantiate the control context and quit the entire simulation. That means that sometime in the future, the Go and the Stop buttons will be removed from the ControlPanel display. They have been left in for backwards compatibility so that applications that do not use the new ActivityControl will retain their (albeit handicapped) controllability. Also, the current Time Step button will be renamed to Start to be consistent with it's new purpose.

In order to use the new control mechanism, you must place code like the following in the top-level Swarm. (This code was taken from a modified mousetrap demo app.)

observerActCont = [ActivityControl createBegin: [self getZone]];
observerActCont = [observerActCont createEnd];
[observerActCont attachToActivity: [self getSwarmActivity]];
[probeDisplayManager createProbeDisplayFor: observerActCont];

This creates an ActivityControl and attaches it to the top-level activity (in this case an observerSwarm). It also creates a display for the controller. (The probe map for the ActivityControl class is designed within the ActivityControl, itself. This is done because all of these objects are expected to look the same to any outside object.) With this activity controller, you will then be able to run, stop, next, step, stepUntil, and terminate that activity.

There are some tricky aspects to successfully using an ActivityControl object that the Advanced Usage Notes will cover.

4. Advanced Usage Guide

4.1. ProbeMap design

When designing a ProbeMap for a given (subclass of) SwarmObject, inclusion of instance variables or messages defined in the super class might be desirable. The normal ProbeMap design code might look like (this code was taken from the tutorial app called "hello-world"):

probeMap = [CustomProbeMap createBegin: [self getZone]];
[probeMap setProbedClass: [person class]];
probeMap = [probeMap createEnd];
[probeMap addProbe: [probeLibrary getProbeForVariable: "room"
  				 inClass: [person class]]];
[probeMap addProbe: [probeLibrary getProbeForVariable: "party"
  				 inClass: [person class]]];
[probeMap addProbe: [probeLibrary getProbeForVariable: "name"
  				 inClass: [person class]]];
[probeMap addProbe: [probeLibrary getProbeForVariable: "stillhere"
  				 inClass: [person class]]];
[probeMap addProbe: [probeLibrary getProbeForVariable: "listOfFriends"
  				 inClass: [person class]]];
[probeMap addProbe: [probeLibrary getProbeForVariable: "myColor"
  				 inClass: [person class]]];
[probeLibrary setProbeMap: probeMap For: [person class]];
[probeDisplayManager createProbeDisplayFor: person];

where room, party, name, stillhere, listOfFriends, and myColor are instance variables declared in the interface to the Person subclass. And Person is a subclass of Agent2d, which is a subclass of SwarmObject.

Now let's say that there are two variables declared in Agent2d that you want to put into this custom probe in addition to the ones you've picked out of Person. Call them x and y. The way to add them to the probeMap is to add the following two lines of code to the above.

[probeMap addProbe: [probeLibrary getProbeForVariable: "x"
  				 inClass: [Agent2d class]]];
[probeMap addProbe: [probeLibrary getProbeForVariable: "y"
					 inClass: [Agent2d class]]];

And that's it! The two superclass-declared variables, which are, in fact, instance variables of the instance of the subclass, are now included in the probe.

In addition, a convenience message has been added to the CustomProbeMap interface to compress the above rather cluttered mechanism into one message. This convenience message can be used in the usual case where a ProbeMap will consist of variables and messages from the same class. For example, the first part of the custom probe creation above can be shortened to:

probeMap = [CustomProbeMap create: [self getZone] forClass: [person class]
      withIdentifiers: "room", "party", "name", "stillhere",
                       "listOfFriends", "myColor", NULL];
And if the user wanted messages in the probe as well, it could be extended to:
probeMap = [CustomProbeMap create: [self getZone] 
                          forClass: [person class]
                   withIdentifiers: "room", "party", "name",
                       "stillhere", "listOfFriends", "myColor", 
                       ":",
                       "setWorld:Room:Party:",
                       "setPerson:Topic_array:ShowSpeech:",
                       NULL];

At present, this message doesn't search the superclasses for the message names listed here. But, that will soon be rectified.

4.2. ActivityControl Issues

It is completely reasonable to assume that explicit control can be had over all the activities in a simulation. However, at present, this control is limited because the context in which an activity runs determines how it behaves. To understand how an ActivityControl is to be used, we will have to explore the behavior of activities in context. (For a more complete explanation of the behavior of activities, see the activity library documentation.)

There are two ways to get an activity started, one can activate the activity in nil or in some other activity. So called "top-level" activities are activated in nil and are intended to be independent of the sophisticated scheduling activity that dictates the execution of actions in any other context in the simulation. I.e. the only activities that should be activated in nil are those sets of events that are expected to preserve the same behavior no matter what goes on in any other part of the simulation.

The other type of activity, those activated in some other activity, is intended to be an integral part of its owner activity. However, this doesn't mean that it must depend on the outcome of actions happening in the owner activity. In fact, an ActionPlan can be designated as containing actions that are capable of being processed in parallel, via code like the following:

[anActionPlan setDefaultOrder: Concurrent];
But these activities are still intended to be meshed with their owner activities. In other words, they are part and parcel of the same model or simulation.

Now, the operational effect of activating an activity in nil is that it will not be meshed with the rest of the Swarm activity structure. This gives the user (or process) complete control over the execution of that activity. A run on that activity will run either to completion or until a stop flag is set by a sequence of events purely internal to that activity. Or, one can stop it from the outside with a message from something like an ActivityControl.

What all this means is that, while one can attach an ActivityControl to any activity, only the "top-level" activities (those having been activated in nil) are going to respond well to it. Any sub-activity will respond half-heartedly, if at all. For example, in the Mousetrap demo distributed with Swarm, an ActivityControl has been placed on both the ObserverSwarm and the ModelSwarm activities. Now, if one sends a run message to the ActivityControl that is attached to the observerSwarm's activity, the entire model continues to run to completion, unless the user sends a stop message. However, if the sim is stopped at some point, a run message to the modelSwarm's activity will have no effect at all. (Note: If you do this via the activity controllers, you see the currentTime variable get updated; but, the actual run message to the activity, itself, has no effect.)

So, the rule of thumb, for the present, is to attach ActivityControl objects only to "top-level" activities, like the ObserverSwarm.

5. Subclassing Reference

The main classes defined here that are intended to be subclassed by users are Swarm and SwarmObject. The probing functionality provided here is mainly for use within the Swarm kernel. However, this probe machinery should be used when designing any interface between Swarm and any other agent or device.

SwarmObject. SwarmObject is the base class for all interactive agents in Swarm. It defines the standard behavior for Swarmstyle agents, which includes hooks for creation, probing, zoned memory allocation, and destruction.

Swarm. The Swarm class encapsulates all of the abstract execution machinery in the activity library, as well as the notion of a group of related objects. Metaphorically, a "Swarm" is a combination of a collection of objects and a schedule of activity over those objects. Hence, the Swarm class provides the behavior necessary for creating such an object and starting up the schedule.

Further details on subclassing are also described in the Usage Guide

6. Interface Design Notes

Unfortunately, this interface has not undergone a rigorous design review. As such it is subject to change in the future. However, there are rumours that this library will be integrated into the defobj library anyway. So, even though little thought was given to the design of this interface (and it is not likely to be worthwhile designing a robust interface at this time), it was implemented in order to provide a first step towards bringing all the various libraries in line with the standard set by defobj.

Along these lines, a few notes are relevant.

  1. Probes may become an inherent part of any object.

  2. ActivityControls will become a part of a larger set of tools used for debugging Swarm models.

7. Implementation Notes

  1. Right now, Probe's rely on a special method, getInstanceName, that has to be implemented in any probe-able object in order to get anything other than the class name of that object into the object designation widget. However, a more general capability has been added to defobj to give a meaningful name to any object. Probes will be changed to take advantage of this new capability.

  2. In the ActivityControl the frequency of the message updateStateVar is very high. It is sent at least once every cycle and every time a message is sent to the ActivityControl. This is unsatisfactory. Some of these messages can be pruned out of the object.

  3. The probeMap designed for use with an ActivityControl was chosen fairly arbitrarily. Right now, it serves as a default for the class. A user can override it by designing a new one and inserting it into the probeLibrary.

  4. Errors specific to objects in the objectbase library need to be gathered and initialized like those in the defobj library.

Table of Contents
ActivityControl --  A class that provides an object interface to an activity.
CompleteProbeMap --  A subclass of ProbeMap whose initial state contains the VarProbes and MessageProbes of the requested target class but also those of all its subclasses.
CompleteVarMap --  A subclass of ProbeMap, whose initial state contains no MessageProbes.
CustomProbeMap --  A subclass of ProbeMap, whose initial state is empty unlike the default probeMap initial state which contains all the VarProbes of the requested target class.
DefaultProbeMap --  A subclass of ProbeMap, whose initial state contains all the VarProbes of the requested target class and also those of all its superclasses.
EmptyProbeMap --  A CustomProbeMap to be used for building up ProbeMaps from scratch.
MessageProbe --  A class that allows the user to call a given message on any candidate that is an instance of, or inherits from, a given class.
Probe --  An abstract superclass of both VarProbe and MessageProbe.
ProbeConfig --  Protocol for configuration of Probes, ProbeMaps, and the ProbeLibrary.
ProbeLibrary --  A (singleton) Class, whose instance is used as a container for a global mapping between classnames and their 'default' ProbeMaps. These defaults can be changed by the user, thus allowing him/her to customize the default contents of the ProbeDisplays generated when probing objects.
ProbeMap --  A container class for Probes used to specify the contents of a ProbeDisplay.
Swarm --  A temporal container.
SwarmObject --  A superclass of most objects in a Swarm simulation that provides support for probing.
VarProbe --  A class that allows the user to inspect a given variable in any candidate that is an instance of, or inherits from, a given class.
General --  Support for Swarm objects and probing

Documentation and Implementation Status

Revision History (objectbase)