Probing and Displaying the Contents of a SwarmObject



The Framework

Probes, ProbeMaps and ProbeDisplays allow the user to dynamically interact with the objects in their simulation. By interaction we mean reading/setting instance variables as well as dynamically generating method calls. The main point being that these interactions are not hardwired into the program code, but occur due to user-generated requests, mainly through the provided GUI.

The key to this capability is the Probe: in general a probe takes an object and either extracts the value of a specific variable, or calls a specific method. For this purpose we provide two subclasses: VarProbe and MessageProbe.

There are two main uses for probes: they can be fed into data-collection objects and serve as interfaces to the objects about which data is being collected (thus keeping the data-collection objects as general as possible) - the Averager class, for example, directly subclasses MessageProbe. Or, they can be used in order to generate a GUI to the individual objects in the simulation (the more common usage).

In order to generate a graphical version of a given probe, the programmer must place it inside a ProbeDisplay, which will automagically generate a window with the appropriate interface. Since, more often than not, the programmer will want to generate windows with more than one variable, we have designed the ProbeDisplay to deal with ProbeMaps rather than individual probes. Thus, by generating a ProbeMap containing exactly the right probes, a user is able to customise the window generated by the ProbeDisplay.

In order to facilitate the creation of all these different objects, the Swarm kernel provides some alternative methods for Probe/Map/Display generation:


Probe

Create Phase Protocol

-setProbedClass: (Class) aClass
This method must be called.

Regular Protocol

-(Class) getProbedClass
-(char *) getProbedType
This method returns the typing of the probed variable/message. The typing is represented using the string-format provided by the Objective-C runtime system ('c' for char, '@' for id, and so on). In general the programmer will know the typing of the variable so this method will probably only be used internally by Swarm kernel code.

-clone: aZone
This method returns a clone of the probe. It is useful in case you got the probe by Library Generation or by the default version of Object Generation. In both cases you will have been given a unique shared instance of the probe - so if you wanted to make changes to the safety status of the probe etc. without affecting the other potential users of the probe, you would first clone it and then make the required changes.

VarProbe

Create Phase Protocol

-setProbedVariable: (char *) aVariable
This method must be called.

Regular Protocol

-setSafety
-unsetSafety
In order to ensure efficiency, the default behaviour of the VarProbe is to "trust" that the object it is being presented with is of an appropriate class (the one specified in setProbedClass, or a subclass of it). However, by calling setSafety, we force the probe to inspect the class of the target object before probing it.

-(char *) getProbedVariable

-(int) getDataOffset
This is another example of a method used(?) exclusively by the Swarm kernel, it returns the offset used to extract the variable from the target object.

-(void *) probeRaw: anObject
-(void *) probeAsPointer: anObject
-(int) probeAsInt: anObject
-(double) probeAsDouble: anObject
-(char *) probeAsString: anObject Buffer: (char *) buffer
These are the methods which actually use the probe. probeRaw simply returns a pointer to the probed variable, whereas the other methods return it as a particular type. probeAsString simply prints the value of the variable into the buffer (in more or less the same format you would expect from a sprintf()).

Note: The VarProbe will complain if you try to do weird type conversions: for example, if the probed variable is of type pointer and you try to use the probeAsDouble method then the return value will be undefined (and you will get an error message on stderr).

-setStringReturnType: returnType
When the probedVariable is of type unsigned char or char, the method probeAsString will, by default, return a string of the format: "'%c' %d". This is meant to reflect the commonplace use of an unsigned char as a small int. However, the user can tailor the format returned by the probe to use only one of the two possible representations.

The legal values for returnType are:

Note: When setting the value of an unsigned char or a char using the setData:ToString: (method described below), the expected format of the string is always "%i" unless CharString was chosen (in which case the format should be "'%c'").

-setData: anObject To: (void *) newValue
-setData: anObject ToString: (const char *) s
The programmer can set the probedVariable via these methods, either by providing a pointer to the new value or a string which the probe will read and convert appropriately.

Note:For the time being, the kernel itself is the only software which uses probes in this fashion, so we can get away with expecting either a string or a pointer. However, if any user should require more types (such as the choice provided in the probeAs series), please email me and I will include them in the following release!


MessageProbe

Create Phase Protocol

-setProbedMessage: (char *) aMessage
-setProbedMessage: (SEL) aSelector
At least one of these methods must be called. Note that due to a bug in gcc-2.7.2 it is unadvisable (though we have not disallowed it) to make a message probe using -setProbedMessage. The next release of gcc will contain a patch for this bug. If you absolutely require the ability to create messageprobes using -setProbedMessage, please email me.

Regular Protocol

-(const char *) getProbedMessage

-(int) getHideResult
-setHideResult: (int) val
By setting hideResult to 1, the user is indicating that the result field, in a graphical representation of the message probe, should not be shown. In other words, if a probe display attempts to represent the message probe, it should hide the result window. This is useful, particularly when the return value is defined to be self...

-(char *) getArg: (int) which
Returns a string representation of the n'th argument.
-(int) getArgNum
Returns the number of arguments the message takes.
-(char *)getArgName: (int) which
Returns a string representation of the argument with the given name.
-setArg: (int) which To: (char *) what
Sets the n'th argument of the message - the argument must be provided in string form.

-(int) isResultId
This method is used by the Swarm internals to find out whether the return value of the message is of type object.
-(int) isArgumentId: (int) which
This method is used by the Swarm internals to find out whether a given argument of the message is of type object.

-updateMethodCache: anObject
The message probe will always attempt to optimise the way in which it dispatches dynamic calls to the probed objects. In practice, this means that when the probed message takes no arguments, then the message probe will cache the actual implementation of the method and generate direct C function calls (as opposed to calling -perform, for example) if the user promises that only one class of objects is being probed. The user does this by calling the -updateMethodCache: with an example object of the appropriate target class. This method can be called as often as necessary throughout the lifetime of the message probe.

-dynamicCallOn: target
-dynamicCallOn: target resultStorage: (char **) result
-(int)intDynamicCallOn: target
-(double)doubleDynamicCallOn: target
These are the methods used to actually generate a dynamic message call on the target object. The first method does not return a result, the second will return a dynamically malloc'ed string based representation of the result, whereas the third and fourth methods assume the user knows the exact typing of the method and would like a direct translation into the appropriate datatype.


ProbeMap

Create Phase Protocol

-(Class) getProbedClass
-setProbedClass: (Class) aClass
This probed class must be set before -createEnd is called.

Regular Protocol

-(VarProbe *) getProbeForVariable: (char *) aVariable
-(MessageProbe *) getProbeForMessage: (char *) aMessage
Returns the Probe corresponding to the given variable/message name.

-(int) getNumEntries
This method returns the number of probes in the ProbeMap.

-addProbe: (Probe *) aProbe
-dropProbeForVariable: (char *) aVariable
-dropProbeForMessage: (char *) aMessage
These methods are used to tailor the contents of the ProbeMap. When adding a Probe, the ProbeMap will always make sure that the probedClass of the Probe being added corresponds to the its own probedClass (or a superclass of its own probedClass). When dropping a Probe no class verification takes place since the probe is dropped based on variableName, not actual id value.

-addProbeMap: (ProbeMap *) aProbeMap
-dropProbeMap: (ProbeMap *) aProbeMap
These methods are also used to tailor the contents of a ProbeMap, by "set inclusion" and "set difference" with another ProbeMap. Again, on inclusion, typing is verified (though not at the individual probe level since this is assumed to be correctly maintained by the ProbeMap being included).

Note: A call to dropProbeMap is equivalent to calling dropProbeForVariable foreach variable name present in the ProbeMap being dropped, followed by a call to dropProbeForMessage foreach message name present in the ProbeMap being dropped. Since both dropProbeForVariable and dropProbeForMessage do not perform any form of class checking this allows the programmer to drop variables and messages with a common name using Probes generated from different classes.

-clone: aZone
As in the case of Probes, when ProbeMaps are generated by the default from of Object Generation or by Library Generation, the user is actually given a reference to a shared copy of the ProbeMap. If the user wants to tailor the contents of the ProbeMap without affecting any other uses of the shared ProbeMap s/he should clone it first.

-begin: aZone
This method returns an iterator (index) over the ProbeMap. This index is used in the exact same way any Map index is used. In practice, that means you will probably be writing code of this form:
id index;
Probe *probe;

index = [aProbeMap begin: aZone] ;

while( (probe = [index next]) != nil ){
  ...
}

[index dropFrom: aZone] ;

EmptyProbeMap

This subclass of the ProbeMap is used to create probe maps which are initialised in an emtpy state. In other words, the probed class is set, as is the case with the normal ProbeMap class but upon createEnd no VarProbes or MessageProbes will be present within it. This feature is useful when creating a probe map from scratch (e.g. to be used in conjunction with the -setProbeMap:For: method of the ProbeLibrary).


ProbeLibrary

The normal Swarm simulation will probably only ever contain one instance of this class, namely the probeLibrary object. This object is used for Library Generation of Probes and ProbeMaps: its role is to cache one unique "official" ProbeMap for every Class ever probed during a run of Swarm. These ProbeMaps are generated lazily as they are requested by the following methods:

-getProbeMapFor: (Class) aClass
-getProbeForVariable: (char *) aVariable inClass: (Class) aClass
-getProbeForMessage: (char *) aMessage inClass: (Class) aClass
These methods are used to "check out" the appropriate Probes from the probe library. Note that the returned probes will be cached, so you must make sure to clone them if you want to make modifications to the probes without affecting the result of future requests for those same probes.

-getCompleteProbeMap: (Class) aClass
This method returns a ProbeMap containing Probes for all the instance variables of the given Class (including inherited variables). Since we are assuming that normal, single-level ProbeMaps will be more widely used, the current implementation of ProbeLibrary does not cache CompleteProbeMaps. However, if we find that there is a demand for CompleteProbeMaps, we will cache them as well.
-setProbeMap: aMap For: (Class) aClass
In those cases where the user wants to set the standard probe map directly, s/he can provide the probe map using this method, and it will be cached as though it were produced by the library itself.

ProbeDisplay

The ProbeDisplay class is meant to serve a dual role, represented by the two example windows on this page:

On the one hand if an object to be probed is specified without any particular ProbeMap being specified, then the ProbeDisplay generated will provide an exploratory, debugging window of class CompleteProbeDisplay (top).

On the other hand, if a ProbeMap is specified then the ProbeDisplay follows exactly the specification as represented by the contents of the ProbeMap (above). When used in this manner, ProbeDisplays can generate tailored interfaces to objects (so for example, we have purposefully hidden certain instance variables in the HeatbugModelSwarm class, and have shown only one of the methods which the class understands).

Interface Usage

Common to both the standard ProbeDisplay and the CompleteProbeDisplay:

Available only on the standard ProbeDisplay:

Available only on the ProbeDisplay:

Create Phase Protocol

-setProbedObject: anObject
This method must be called.

-setProbeMap: (ProbeMap *) probeMap
This is an optional create phase method - if no probeMap is specified the ProbeDisplay will ask the probedObject for a ProbeMap using the getProbeMap method described below... The default behaviour of this method will be to return the probeLibrary's copy of the probeMap for the class of the target object.

Regular Protocol

-getProbedObject
-getProbeMap

-update
This method maintains consistency between the values of the probedObject's variables and the values which are displayed in the ProbeDisplay. Ideally, this method should be called every time the object is modified by the simulation. In practice, the user schedules an update on the probeDisplayManager which in turn communicates to all the active ProbeDisplays in the system.

ProbeDisplayManager

The normal Swarm simulation will probably only ever contain one instance of this class, namely the probeDisplayManager. This object is used for automatic generation of ProbeDisplays: given an object it will attempt to get a ProbeMap from the object. If the object does not respond to the method -getProbeMap (i.e. it does not inherit from SwarmObject), the probeDisplayManager will query the probeLibrary for a ProbeMap. It will then create a ProbeDisplay to the target object, and from then on, until the Probe Display is removed it will update the ProbeDisplay when receiving an update message.

-addProbeDisplay: pd
-removeProbeDisplay: pd
These methods are used to specify which Probe Displays should be managed by the ProbeDisplayManager.

-update
This method will recursively send an update message to all the Probe Displays managed by the ProbeDisplayManager.

-createProbeDisplayFor: (id) anObject
-createCompleteProbeDisplayFor: (id) anObject
These methods are used which allow a one-step create of Probe Displays onto a given object.

SwarmObject Support for Probing

As mentioned earlier, all SwarmObjects can generate Probes/ProbeMaps. By default, they achieve this by "checking out" the appropriate references from the probeLibrary.

-(const char*) getInstanceName
If you want to put a specific title on the ProbeDisplay window associated with your object, simply override this method. By default it returns the class name of the object. This is why the default ProbeDisplay window title is always the class name of the probed object.

Note: SwarmObjects do not actually have an instance variable called "instanceName" - so if you want to store a name for your object, you will need to provide your own storage at the subclass level and rewrite getInstanceName to refer to this variable.

-getProbeForVariable: (char *) aVariable
-getProbeForMessage: (char *) aMessage

-getProbeMap
-getCompleteProbeMap


Manor Askenazi <manor@santafe.edu>
Last modified: Sun Mar 12 00:42:56 MST 1996