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.