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:
- Direct Generation - which leaves the user in charge of all the details.
- Object Generation (Probes/ProbeMaps) - where we use method calls (defined
in SwarmObject) on a given target object to generate the Probe/ProbeMap for
that object. This allows the object to be aware that it is being probed and to
control directly the contents of the Probe/ProbeMap being requested...
- Library Generation (Probes/ProbeMaps) - where the programmer
"checks out" a unique, shared copy of a Probe/ProbeMap from the
probeLibrary object (of class ProbeLibrary) provided by
the kernel. By shared we mean that a similar request made at a different
point in the code, will return a reference to the very same probe instance.
Note that, by default, Object Generation is equivalent to Library
Generation since the default behaviour of SwarmObjects, when requested
to create a ProbeMap, is to return a reference to the probeLibrary's
unique copy!!!
- ProbeDisplayManager Generation (ProbeDisplays) - where the
programmer generates a ProbeDisplay directly, by requesting it from the
probeDisplayManager object (of class ProbeDisplay) provided
by the kernel (in graphics mode). The probeDisplayManager will create the
ProbeDisplay based on a ProbeMap given to it by the probed object.
Note: since the probeDisplayManager is only created in graphics mode, it is
documented in the simtools area of the documentation. Here we emphasise simply
that in order to generate a ProbeDisplay onto an object the programmer need
only write the following line of code:
[probeDisplayManager createProbeDisplayFor: anObject] ;
And that the contents of the returned ProbeDisplay will be based on a ProbeMap
obtained from the probed object itself.
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:
- DefaultString ("'%c' %i")
- CharString ("'%c'")
- IntString ("%i").
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:
-
- The different fields in the ProbeDisplay can be updated by typing in new
values and pressing Return. However, certain fields (containing
pointers or ids, for example) cannot be modified and will generate a beep
if such a modification is attempted.
- If an instance variable/argument slot is defined to hold an object,
then that object can be drag&dropped into another variable/argument slot by
clicking on it with the first mouse button (a small rectangle with the
name of the object will appear - simply drag it to another object-typed
variable/argument slot and release the mouse button).
- Also, if an instance variable/argument slot is defined to hold an object,
then that object can be inspected by clicking the entry for that
variable/argument slot with the third mouse button (a ProbeDisplay
for that object will be generated).
- Available only on the standard ProbeDisplay:
-
- Note that the sunken label at the top of the ProbeDisplay is also active.
By clicking on it with the first mouse button you get a drag&drop'able
representation of self. By clicking on it with the
third mouse button you get a CompleteProbeDisplay to self.
- Available only on the ProbeDisplay:
-
- The green "superclass" button can be used to display the succesive superclasses
of the object being probed.
- The red "hide" button can be used to hide classes which are irrelevant thus
reducing clutter.
- The hide button on the lowest class in the hierarchy has a special meaning
since clicking on it dismisses the entire 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