Chapter 5. A simple client: SharedPad

SharedPad is an agent that illustrates the basics of writing a MICA agent. This chapter includes a walkthrough of the MICA-related code that is in SharedPad.

SharedPad allows any number of connected agents to share a single notepad and to draw on it using a mouse and/or stylus. Anything drawn on one shared pad is shared amongst the other pads. In addition, state is preserved, so that even if every shared pad disconnects, all the information is kept.

SharedPad uses a single type of Mob: sharedPadLine. As previously discussed, it has four fields that represent the start and end points of a line:

As mentioned previously, there are two interesting methods that need to be implemented for an agent: init() and handleMob(). init() is called -- typically by main() to start the agent.

public void init(MicaProperties mp) throws MicaException {
  at.connect("sharedPad");
  List existingLines
    = at.mobSearch("select m from m in sharedPadLine;");
  for(Iterator i = existingLines.iterator(); i.hasNext();) {
    drawSharedLine((Mob) i.next());
  }
  at.register("sharedPadLine");
}

Figure 5.1. The init() method for SharedPad


The first thing that the agent does is connect to the blackboard. To do this, it uses at, which is a field that stores the agent transport. By calling at.connect("sharedPad"), the agent connects to the blackboard and requests that it should be called sharedPad.

Once connected, we query for all existing lines on the blackboard. This is useful, since if someone has already started drawing, it allows the retrieval of existing doodles. The query uses MicaQL, a simple query language. The above statement says: get all objects m from the blackboard, whose type is sharedPadLine. For each mob, we then use the method drawSharedLine() to draw it. drawSharedLine() is outlined below.

Finally, once we have obtained all the existing data, we now register for any new sharedPadLine mobs that are written to the blackboard, using the at.register() call.

The above method called drawSharedLine(), which is used to actually draw it on to the screen.

public void drawSharedLine(Mob m) {
  drawnLines.add(m);
  int startX = m.getSlot1AsInt("oldX"));
  int startY = m.getSlot1AsInt("oldY"));
  int endX =  m.getSlot1AsInt("newX"));
  int endY =  m.getSlot1AsInt("newY"));
  Graphics g = getGraphics();
  g.drawLine(startX, startY, endX, endY);
}

Figure 5.2. The drawSharedLine() method for SharedPad


drawSharedLine() is an example of how the information stored in a mob can be extracted and used. Firstly, the mob is added to a list of drawn lines -- this isn't for any reason related to the blackboard, but simply so that if our sharedPad window gets covered by other GUI applications we can do an redraw easily. The next four lines extract data from the mob; each converting it into an integer. Note that we use getSlot1AsInt() in this case, since we only want one value for each slot and we want it to be returned as an integer. Finally we obtain the graphics context and draw the line based on the information we extracted from the mob.

The other major method an agent must define is handleNewMob().

public void handleNewMob(Mob m) {
  if(m.getType().equals("sharedPadLine")){
    drawSharedLine(m);
  }
}

Figure 5.3. The handleMob() method for SharedPad


The code first checks to see if the type of the received mob is a sharedPadLine. Strictly speaking this is unnecessary, since sharedPadLines are the only type that is registered for. Once a new sharedPadLine is received, it is drawn, just like the lines that were retrieved from the blackboard initially.

All the functionality now required to implement the drawing of lines already on the blackboard is now complete. This code is now sufficient (together with the supporting Swing and Java boilerplate code) for implementing a passive sharedPad that watches while other sharedPad agents connected to the blackboard draw. What remains is the functionality to write to the blackboard.

This is implemented by having sharedPad object have a MouseDragListener. Every time the mouse is dragged, it calls the following newLine() method:

public void newLine(int oldX, int oldY, int newX, int newY) {
  Mob m = new Mob("sharedPadLine");
  m.addSlot("oldX", String.valueOf(oldX));
  m.addSlot("oldY", String.valueOf(oldY));
  m.addSlot("newX", String.valueOf(newX));
  m.addSlot("newY", String.valueOf(newY));
  at.writeMob(m);
}

Figure 5.4. The newLine() method for SharedPad


The parameters passed to newLine() are used to construct the appropriate mob. For each of the four values, we add a slot, and set the value of the slot to a string value. Finally, once it is all finished; the newly constructed mob is written to the blackboard through the agent transport at.

Once all these components have been put together, with the remainder of the Swing code; we now have an Agent that can share information and retain state. As can be seen, the code is relatively simple.

One interesting note is that a given SharedPad does not have a concept of a "local" and "remote" line; so much so that when a user drags the mouse, a line is not drawn locally; instead the agent just listens for generic sharedPadLine mobs, some of which may have been its own! It would be possible to immediately draw a line as soon as the mouse is dragged and then ignore mobs which was created by the agent itself; indeed, this would lead to the user experiencing a "snappier" response. This was not done in this case for two reasons: (a) it would complicate the code, (b) it is useful to experience the "round-trip time" of going to and from the blackboard to decide if MICA is suitable for "real-time" interaction.