Device Interface Guide

Information on getting ACT-R/PM to talk to a (simulated) device.


One of the key differences between "vanilla" ACT-R and ACT-R/PM is that ACT-R/PM has the ability to interact with a simulated device (e.g. a computer). In order for this to work, there must be a simulated device that can communicate with ACT-R/PM.

The simulated device must be a Lisp object, but it can be any Lisp object. This object need not be an MCL window. Some things will be handled automatically if the device happens to be an MCL window, but there is no requirement that the device be one.

In order to communicate with ACT-R/PM, the device Lisp object must have certain methods defined for it, which will be called automatically by ACT-R/PM at the appropriate times. The device has to understand different kinds of messages from ACT-R/PM, such as keystrokes and mouse movements, and it has to be able to tell ACT-R/PM what is currently visible.

Basic Methods

For a Lisp object to be installed as an "RPM device," certain methods need to be defined. Here's what they are, the arguments they take, and what they need to do:

build-features-for (device vis-mod)
Called by RPM whenever it needs to know what's on the screen. The vis-mod parameter is ACT-R/PM's Vision Module, because sometimes a pointer to that will come in handy when building features. This method should return a list of icon features representing what is currently visible. Details on that are provided in the "Icon Construction" section below.

device-handle-click (device)
Called whenever RPM clicks the mouse. Note that it is the device's responsibility to know where the cursor is at all times.

device-handle-keypress (device key)
This will be called by RPM whenever the model presses a key. The identity of the key is passed as the key parameter.

device-move-cursor-to (device xyloc)
Whenever RPM moves the cursor, this method is called. The location to which RPM is moving the cursor is passed as a vector #(X Y) format.

device-speak-string (device string)
Whenever RPM outputs speech, this method will be called. The string spoken is passed in the string parameter.

get-mouse-coordinates (device)
Should return the current location of the mouse cursor, in (X Y) format.

All of the above methods are already implemented for MCL windows in the file "mcl-interface.lisp," though you can redefine them to suit your needs if you want. The following methods are not defined on MCL windows because they are optional:

device-update (device time)
This is optional, and will be called frequently by RPM. Essentially, whenever RPM is ready to have the device do something, it will call this method with the current time (in seconds) passed in the time parameter. This is useful if there are moving objects that need to have their position updated, for example. Because it will be called often, though, this method should do as little computation as possible.

device-update-attended-loc (device xyloc)
Called periodically by RPM to let the device know where RPM is attending, the where being in #(X Y) format in the xyloc parameter. This is not really intended as an interaction channel, but more of a debugging aid, and is optional.

Icon Construction

The icon, as described in the section on the Vision Module, is a list of icon-feature objects which represents what is currently visible. The only way that the Vision Module can know what is currently visible is for the device to tell it, which is handled with the build-features-for method. RPM will call this method whenever it needs a new icon. Thus, to be an RPM device, you must define one of these methods for your device.

This already works somewhat for MCL windows. The build-features-for method for MCL windows calls a method called get-sub-objects which returns a list of the window's subviews, and then build-features-for is called on each of the subviews. Method dispatching figures out what kind of object each subview is and the appropriate method is called for each subview. Methods are currently defined for editable-text-dialog-items, radio-button-dialog-items, button-dialog-items, check-box-dialog-items, and static-text-dialog-items. Thus, to extend processing of an MCL window to new types of views, all that is necessary is a build-features-for method for that class.

The recommended way of handling this is to define a new subclass of VIEW in which all drawing for your custom object is done. Then, you specialize a method of BUILD-FEATURES-FOR for this class of view. This method should return a list of icon-features. This class is defined in the file "vision-module.lisp," and has several subclasses: oval-feature, rect-feature, and line-feature. Buttons, for example, generate oval-features. Your object can be constructed out of these basic features, or, if necessary, you can define more subclasses of icon-feature. One thing you should not have to do, however, is write any code for generating features for text. To do that, simply call the build-string-feats method, also found in "vision-module.lisp."

An example might help. First, the feature in question are arrows, which can either point left or right. You'll need to define a class for the view which draws arrows (you'll also have to define a view-draw-contents method to actually have the arrow drawn on the screen. That's an exercise for the reader.):

(defclass arrow-view (simple-view)
  ((direction :accessor direction)))
You'll normally want a subtype of icon-feature to be put into the icon when the screen is processed:
(defclass arrow-feature (icon-feature)
  ((direction :accessor direction :initarg :direction))
    :isa 'ARROW
    :value 'ARROW
    :dmo-id (gentemp "ARROW")))
Now, to actually have the system build such a feature when an arrow view is encountered, you need to define a build-features-for method, which generates a feature of that class:
(defmethod build-features-for ((self arrow-view) (vis-mod vision-module))
  (let ((my-position (view-loc self)))
    (make-instance 'arrow-feature
      :direction (direction self)
      :x (point-h my-position)
      :y (point-v my-position)
      :attended-p nil
      :screen-obj self)))

The :screen-obj keyword specifies which real object on the screen corresponds to the icon feature. RPM uses this information for things like change detection and tracking.

Of course, we could have made the arrow more complicated and constructed it out of, say, three lines (rather than making an arrow a basic visual feature). If you go that route, one common operation in drawing things on the screen is the use of MCL's line-to function. Rather than building line-features for all those lines yourself, you can call the function rpm-line-to instead. This will both draw the line and create a matching line-feature, adding that feature to the icon. Be sure that the RPM device has been set before calling this, or results can potentially be flaky.

Of course, your device might not be an MCL window, which is the point of the Device Interface. That's OK, it just means that there's a little more work you'll need to do. You'll have to figure out how to carve up what RPM can see into icon-features (or objects that are instances of classes that are subclasses of icon-feature) and return a list of them when build-features-for is called on your device.

One other thing: When the icon features are generated by views and they're in MCL windows, the width and height properties will be filled in automatically by RPM. If your objects are not views in MCL windows, then you'll have to make sure you fill these in so that RPM can compute geometry for Fitts' law stuff. Alternately, if you've subclassed on icon-feature, you can provide an approach-width method for your class which returns the effective Fitts' width of your object (in degrees of visual angle) given an approach angle (in radians, with zero being an approach on the x-axis from zero). You may need to provide that for non-rectangular objects anyway.


Last modified 2002.05.30