Agent Roadmap
February 2, 2012
You will be working with a lot of code in this class. Not just code that implement algorithms, such as a sort or search, but system code. System code solves large problems and requires organization, documentation, test procedures, and maintenance.
NOTE: The examples in the roadmap may not match exactly the code you are working on; the actual code is an evolving thing. You can let me know about any discrepancies that you find.
Systems have many interacting components. It is not enough just to document each one. We need a description of what the system does, how it works, what its components are, what each component is responsible for, and how they interact. We need this roadmap to help us navigate through the system.
Besides a functional explanation, the roadmap also tells us if there is any design philosophy and where to look for things. A roadmap is a guide to aid your understanding of the system in question. Without a roadmap you have only the code to look at; you’ll soon see how difficult that is.
Agents are autonomous reasoners that receive input, think about the state of their world, and then do things. You are completely familiar with agents because you are one. Think about it. You are part of a world, you receive input through your five senses, and you act on the input.
· Sometimes you act immediately—pulling your hand away reflexively from a fire.
· Sometimes you simply store the input away for later use—studying for a test.
· Sometimes you react to the input by doing something—seeing a green light and proceeding forward.
· Sometimes you queue up things to do later—putting a dinner date together for next week after getting a telephone call.
· Sometimes you do nothing—your teacher gives you advice and you ignore it.
· Sometimes you delegate—you get work and give it to others to do.
So, people are agents, but agents have a software expression as well. And that’s what this document is all about.
Simply stated, autonomy asserts independence. In particular:
· A private memory, and
· The ability to do what it likes when it want to.
As mentioned before people behave autonomously. Our brain is private and we can do what we want (although we often get advice on how to behave). A computer process is close to being an autonomous entity. Its address space—its code plus data—is generally private, and while the CPU is executing it, its code does what it wants.
Usually a process has a single thread of control. Without going into a lot of detail, that means the CPU has one program counter, a pointer to a current instruction within the process. Multi-threaded processes have several program counters, all executing in the same address space. A single-threaded process is doing one thing at a time; a multi-threaded process is doing several things concurrently.
When people say they can multi-task, e.g., drive and talk on their cell phone at the same time, they mean that they are multi-threaded. We can argue about the reality, but it is more than that. As agents, we can receive input, think, and do things simultaneously. In other words agents have at least three threads of control: one for receiving input, one for thinking about what to do, and one for doing.
An agent application is just that, a collection of agents that solves some problem. Again, you know such systems well because you are part of them in real life. Consider a restaurant as a system of agents working together to achieve some goals. There are waiters, busboys, cooks, and cashiers all trying to feed customers. Each type of agent responds to a variety of inputs and has its own actions. Cooks receive inputs from waiters about food orders, cook the food, and tell waiters when the food is ready. Cooks also prepare menus, shop for food, and so forth. Waiters are different. They talk to customers, take their orders, deliver food to them, and give them the check. Each type of agent has its own responsibilities.
An agent application is a collection of loosely coupled agents. That is, agents make requests of one another and give each other information. Then, the individual agent thinks about those input, thinks of actions to do, and does them when it thinks appropriate. The decoupling of the agent’s actions from the requests and data it receives is what makes them loosely coupled. This kind of behavior defines autonomous. [Autonomous also implies its own thread of control. Much more about that later.]
A tightly coupled application is the opposite. The subroutine or method invocation protocol you are familiar with from your programming classes produces tightly coupled applications. That is:
1. The caller invokes a method, [for the rest of this document, I will use the term “method invocation” or “subroutine call” interchangeably.]
2. The caller waits while the method executes,
3. The caller continues once it gets the return value from the method invocation.
Notice that the caller waits for the method to complete, while the method has no life of its own until it is called. This kind of protocol is often called synchronous. Of course, a software agent uses method calls internally, but its external interface—the interface used by other agents—is logically asynchronous. Agents do not respond immediately through a return value. [Agents use messaging to communicate. Much more about that later as well.]
Your first project will use a restaurant application because it is so familiar. We have built many agent-based software applications. One of the first was an operating system for a time-sharing computer. Another was an agent-based controller for computer numerically controlled machines (CNC). And we built many for use in factories. Our factory floor applications had robot agents, conveyor agents, and camera agents, among others.
As gaming techniques becomes more important in serious gaming applications, you will see that agent programming techniques will drive most of them. Sophisticated games often have many independent players, objects, and other components with separate behaviors. Managing all that without agents is hard. We maintain that agents can be used for many, if not all, applications.
A framework is an environment that forces some decisions on you. By accepting those decisions some capabilities are specialized, emphasized, and simplified. For example, Java is a programming framework that imposes an object-oriented methodology. If you choose Java, you are forced to organize your code into classes within inheritance hierarchies, use polymorphism, etc. Java is convenient to use for O-O programming. If you don’t like or want O-O, then you should choose some other programming framework.
An agent framework is an environment that supports the design and programming of agent applications. It’s that simple. We have built agent applications using assembly code, C, Java and manufacturing languages. A framework does not imply a programming language.
Frameworks can be elaborate (with an IDE, extensive libraries, GUI generators, and so forth) or fairly simple (a few base classes and a roadmap, like this framework). However it takes form, the goal of an agent framework is to make agent programming easier.
There is no universal set of requirements for agents. Most definitions seem to mention autonomy and loosely coupled. That’s about it. Each agent designer has his own conception of how to build an agent. Here is mine.
Agents communicate by sending messages to one another. They share no data. Sending real mail is an appropriate metaphor for agent messaging. Here is what happens with real mail:
1. The sender composes a message on paper. The sender and recipient decide upon the format of the message. Obviously, the sender expects the recipient to understand the message.
2. The sender puts the message in an envelope, addresses it to the recipient, and gives it to the Postal service.
3. The Postal service delivers it to the mailbox of the recipient.
4. The recipient processes mail at his convenience: opening one message at a time, reading it, and doing what he feels like in response.
There are several important things to note about this scenario:
· While the message is being composed, the sender has full control of its contents.
· Once the recipient has the message, he has full control of the contents.
· The data in the message is NOT SHARED data. The receiver can only assume what is stated in the message, a subtle but obvious point. For example, if the message says, “I have $20,” the receiver can only assume that at the time of the writing, the sender had $20. He cannot assume that the sender has $20 at the time of the reading.
· The receiver cannot assume that the sender is waiting for a reply. Wouldn’t that be ridiculous in the real world? Once the sender mails the message, he goes off to do other things. That is typical of agents; they are not synchronized with one another; hence the term loosely-coupled.
· The receiver does not have to act immediately on the message. For one thing, the message might be a status update, such as “I have processed 20 orders today.” That may be important to the receiver, but no action is required. If the message was a request, such as “Please check on the availability of product X,” the recipient might do it when he has time. After all, he may be busy, he may delegate it another agent, or he might ignore the request altogether.
Computer Science has formalized message-sending protocols between processes. Computer message systems are very much like real mail systems:
· The mailbox of the recipient is a persistent queue. That means messages are stored permanently until removed by the recipient.
· The recipient process removes messages from the queue when it wants to.
· The recipient parses the message according to whatever format was decided upon. Typically, the recipient will have some kind of case or switch statement that dispatches control to a message handler once the message is parsed. What the message handler does is dependent on the contents and the application.
· This protocol is asynchronous. The message-sending method call may return a value, but the value only indicates the success or failure of the mailing event.
Here’s some pseudo-code to show how messaging works. [We will discuss this pseudo-code in detail in class.]
//Sender pseudo-code for constructing and send a message
from = me;
to = theCook;
Message m = new Message(from, to);
m.insertCommand(“Prepare”);
m.insertParameter(2);
//table number
m.insertParameter(“Hamburger”);
m.send();
... //continue on with other work.
//Recipient pseudo-code for receiving and parsing a
message
While (queue is not empty) do {
Message m = queue.getNextMessage();
from = m.getRecipient();
command = m.getCommand();
switch command {
“Prepare”: {//Prepare
command has two parameters
table = m.getNextParameter();
item = m.getNextParameter();
Prepare(from,table,item);
/*Prepare() is the message
handler
that we dispatch to after
parsing.
We will have a lot to say
about how
Prepare works in both
Agent and
non-agent systems.*/
break; //done with Prepare
}
“someOtherCommand”:
{
...
}
...
}
}
Real messaging is complicated. Do we really have to do all that? What do we do if the queue is empty? Good questions. We’ll talk about these issues later in the implementation.
An agent has three parts, message reception, a scheduler, and a collection of actions. Each part could logically have its own thread of control because, since each can act concurrently. My reality has shown that the scheduler and its actions can be in the same thread, i.e., synchronous. So, the schedule picks and action and executes. When the action is done, the scheduler can pick another action. Threads are NOT part of the agent design; I have built agent applications in single-threaded environments.
Message Reception
Message Reception identifies the messages this agent can receive from other agents--the external interface. For each message type, there will be a dispatch method that corresponds to it. The method name identifies the message type and the parameters of the method are the parameters of the message. Only state setting happens in the message reception methods.
Message reception is an ongoing, concurrent activity within an agent. In other words, the agent will be receiving messages while it is scheduling and performing actions. As we’ll see, this requirement will cause synchronization issues because the three parts of an agent DO share data.
Scheduler
The scheduler looks at its internal state to decide what action to run--it is the brains of the agent. It uses whatever intelligence it needs to select an appropriate action. The internal state of an agent is completely application dependent. It is up to you, the agent designer, to decide what variables and objects you need in order to maintain the state.
For example, a host agent probably has a list of customers waiting to be seated. When the host's scheduler realizes that a table is available, it should schedule the SeatCustomer(customer, table) action.
If the scheduler determines it has nothing to do, it needs to check again only when its internal state changes. Note that this is not really a requirement, just logically obvious. The internal state can only change by way of an incoming message or while carrying out an action. There are several other scheduling considerations that we’ll discuss when looking at a concrete scheduler in section 4.2.2.2.
Actions
An action is the agent code that does something. For example, the SeatCustomer(customer, table) action will (1) send a message to a waiter saying, “Seat this customer at this table.” and (2) update its state to remove the customer from the waiting list and (3) mark the table as occupied. The waiter will then take "ownership" of the customer and do what a waiter should do.
Actions are scheduled by the scheduler. After the agent has finished an action, the scheduler tries to pick another, perhaps the same, action. This goes on as long as the agent is active.
Of course, while that is going on, the agent is receiving message concurrently. We’ll see what being “active” means later.
In a factory application a Robot agent may decide to open its gripper in preparation to picking up a part. The action code for the robot agent would actually energize an input on the physical robot that tells it to open its gripper. Our agent code interfaces to the devices through these DoXYZ() primitives.
We don’t have a physical robot in our class. We have two options:
· We can simulate the physical robot. We can build an animation of the application setting. Then when we want to open the gripper, we'll call a DoOpenGripper() method which will instruct the animation to open the gripper on the animated robot. You will do this later during the factory project.
· If there is no animation, we will use a DoXYZ() method that takes as a string the primitives that the agent can actually carry out. The DoXYZ() method in our application will only print what the agent would be doing if it were real. So, when we energize the gripper input, we simply call DoOpenGripper("Open gripper”), which would print the result in the trace. Since all the print statements go to the same place, we will see all the agents print statements interleaved as they are happening. This will give us a script of what all the agents are doing concurrently.
Only Actions can call DoXYZ() methods. You will be tempted to call them in message reception, but doing so is a serious mistake that we will discuss in class.
Thus far this section has discussed agent requirements, i.e., how an agent works and how one agent communicates with another agent. We also need to answer some configuration questions about an application of agents.
· What agents are in an application? Someone needs to select and deploy the agents in the application.
· How do agents find one another? Agents cooperate with one another in an application. How does an agent know the identity of the other agents in the application? How do agents enter and leave an application? Agent researchers worry about these and other tough questions. We’ll do it more simply as we’ll see.
· How do agents get specific configuration information? An individual agent may need information in order to carry out its work. The information could be hard coded in the agent. More likely, it must come from an outside source. We have two ways to do configuration:
1. A Main Routine – In our v1.0 prototype all the agents can be created and managed from a single Main() routine in a program called Main.java.
2. A
Control Panel – v1.0 also contains a GUI control panel that will allow the
user to interactively manage the application. v3.0 has a control panel and full
restaurant animation.
Systems of agents can be deployed in different ways to suit various non-functional requirements. Since a theoretical agent is a separate process, it can run on its own computer. You just have to find a message protocol (or an accommodation) to enable the communications. Theory aside, it is natural that there is quite a bit of flexibility in agent deployment strategies. Here are two:
· All the agents run in one process, each agent is a separate thread. Messaging is simplified to be simple method calls. This is the strategy for our code.
· Each agent is a separate process, perhaps running on separate machines. Now, messaging must be more formal, perhaps (1) using real messaging with mailboxes or (2) through a distributed method invocation technology like Corba or RMI.
The flexibility of multiple processes was critical to our manufacturing real-time systems. Some agents had to run in “hard” real-time (never miss a deadline), some were real-time (could miss an occasional deadline), and GUI agents were soft real-time (no real deadlines). We had special hardware for the first two cases, used Windows for the user interface, and a specialized real-time operating system for carrying the messages among other things. Agents could be moved from machine to machine depending on the non-functional real-time requirements. New gaming strategies will surely use hybrid techniques: bundling agents in processes, distributing agents on different CPU's within the same machine, and so on.
It is now time to specify a restaurant application. That means describing all the potential agents who play a role in the application. Of course, a complete specification for a real restaurant would be a lot of hard work and beyond the scope of this class. Instead, this section gives only the most basic general requirements. Each deliverable will contain those requirements to be implemented. Remember that requirements specify what is to be done; the design describes how to do it.
Don’t lose focus just because this application is about restaurants. We are using restaurants because you are familiar with them. Designing agents is the same no matter what application you are building. Don’t worry about having to have lots of agents for an application. A large restaurant has dozens of employees. One of our factory applications had over 20 agents in it.
The Host greets customers among other things. Here are some of the host requirements:
· The host assigns customers to working waiters when a table is available.
· The host reassures waiting customers every n minutes.
· The host handles complaints from customers.
· The host manages waiters taking breaks, getting sick, or leaving.
· The host closes the restaurant, does final bookkeeping, supervises cleanup, etc.
The customer is the client of the restaurant. The restaurant’s job is to feed the customer and see to his every need. Customers have requirements as well. In fact, after reading the requirements below, perhaps the Customer should have been named the Person. We'll talk about this in class.
· When he gets hungry, he goes to restaurant, prepares his own food, or visits his mother.
· When at a restaurant he presents himself to the host. Once seated he will order from the menu, and eat the food once it is delivered. Ask for a check, pay and leave.
· If the restaurant is crowded he might leave.
· Perhaps he will make a comment to the host about the quality of the food.
· Perhaps trying to impress his date, he will bribe the host in order to get a good table.
The waiter has the most complicated role in the restaurant. Since he is taking care of potentially many customers, he has to make decisions about who to service and when. Here is what he does:
· He seats customers at tables. He takes their order, sends the order to the chef, serves the food when it is ready, and gives the customer his check.
· If the customer doesn’t like his order, the waiter takes it back and gets customer something else.
· When the customer leaves, he clears the table or perhaps he tell a busboy to do it, if there is a busboy.
The cook cooks food. In normal circumstances:
· When he becomes aware of an order, he cooks it and tells the waiter when the order is ready.
· An item, though it is on the menu, might be unavailable.
· When he runs out of food that he needs, he buys it.
There are many other agents that could be in the restaurant application:
· Busboy – This agent supports the waiter in servicing clients. The busboy often does the serving, handles simple customer requests, clears the table, etc.
· Cashier – This agent takes the check and payment and enters it into the restaurants payment system, usually a cash register.
· Manager – This agent may oversee the restaurant, assign employees, etc. Obviously, if a manager and host are both present, they have to divide responsibilities.
· Head Chef – This agent creates the menu, is responsible for the shopping, oversees the kitchen, etc.
· Sous Chef – This agent supports the head chef, does most of the actual cooking, etc.
· Hat Check – This agent handles coats, hats, etc. Probably is only in the fanciest restaurants.
In real life, some people handle multiple roles. For example, there may be no cashier; the waiter handles those responsibilities. That’s no problem for us. You just design your agents and assign them the responsibilities you want and see if you have a “working” restaurant.
[Please note: Everything to do with v1.0 is optional. In the past it was the starting point for students. Now the starting point is v3.0. Still I suggest that you look at the simple diagram below and spend a few minutes downloading, running, and inspecting v1; it's on the lecture/lab page. v1 has no animation to complicate things. If you want, you can skip to section 4.]
This first deliverable is typical of prototypes: a simple specification that “gets the application going.” No one worries too much about whether all the requirements are fulfilled, or whether it is efficient or beautiful.
MaitreD Requirements:
Requirement 1: Greets Customer
Normal Scenario: When he becomes aware of customer entry into restaurant, he keeps track of them. If a table is available, a waiting customer is seated. MaitreD will be informed of leaving customers.
Customer Requirements:
Requirement 2: Getting Hungry:
Normal Scenario:
When he gets hungry, he goes to the restaurant.
Requirement 3: Eating at a restaurant:
Normal Simplified Scenario: Present yourself to the greeter. When you are seated, simulate getting food and eating it. When done, leave and inform the MaitreD.
General Requirements:
Requirement 4: Many Customers can come into the restaurant “simultaneously.”
Requirement 5: Restaurant has n tables. [Implies
concurrent seating and eating.]
This is a good time to introduce you to UML interaction diagrams. These diagrams show how the objects of your system interact. In our case they will show how the agents interact, since the agents are our primary objects. At this early stage of requirements we only draw some simple pictures that show the messaging behavior for the most important scenarios. This simple prototype only has one behavior, and that is shown in Figure 1, just below.

Figure 1: Interaction Diagram for First Prototype
From a design a programmer is supposed to be able to implement the code. How much detail goes into the design? That depends on you and your programmers. Often a talented programmer does both; in that case the design will be sketchy since he is going to implement it.
A design can be programming language independent and should be. It is often like pseudo-code or logic—just enough to specify how you want the system to work. At a minimum, it should lay out the classes (or data structures in a non object-oriented design), the methods, the objects, constraints, etc. Remember, the programmer is not supposed to be making any “decisions” during implementation. He simply “codes” the design. Mostly, that never happens. Designs aren’t usually that good.
An agent design is very specific: describe the messages, actions, and scheduler. Let's start.
IWantFood(CustomerAgent
customer)
On receipt: Put customer on waiting customer list.
or to be more
specific
On receipt: waitingCustomers.add(customer);
msgLeavingTable(CustomerAgent
customer)
On receipt: Remove customer from the table he was sitting
at.
or to be more
specific
On receipt: if ($ table in tables ' table.getOccupant()
== customer)
then table.setUnoccupied();
[In case those special logic symbols don't print in your browser, here is the same expression with the symbols replaced by English:]
if (there exists table in tables such that table.getOccupant() == customer)
then table.setUnoccupied();
My general feeling is this:
· On first pass of the design I tend to write the more informal statement.
· I ALWAYS make a second pass tightening it up with real variables and logic.
The point is to get the ideas down first, and the details later. No sense bothering too much about variables and types while you are "discovering" the design.
seatCustomer(CustomerAgent customer, Table table) {
Do ("Seating " + customer + " at " + table); //Tracing
table.setOccupant(customer);
//Record the customer at the
table you sit him at
waitingCustomers.remove(customer); // Remove customer
from waiting list.
customer.PleaseSitAtTable( ); // Send the customer a message that he is being seated.
}
Rule1: If there exists a table and customer, so that table is unoccupied and customer is waiting, then seat him at the table.
In First Order Logic, we would write:
if ($ table
in tables ' ~table.isOccupied( ) and $
customer in waitingCustomers)
then perform seatCustomer(customer,
table);
[In case those special logic symbols don't print in your browser, here is the same expression with the symbols replaced by English:]
if (there exists
table in tables such that ~table.isOccupied(
) and there exists a customer in waitingCustomers)
then
perform seatCustomer(customer, table);
List waitingCustomers; //
holds customers as they arrive
Collection tables; // each member is of type Table
private class Table {
CustomerAgent occupiedBy;
int tableNumber;
void setOccupant(CustomerAgent
cust) {}
void setUnoccupied() {}
CustomerAgent getOccupant()
{}
boolean isOccupied() {}
}
The customer agent has a very simple, but flawed design in this prototype. It works, but I don't like it. Why not? The customer agent is an example of a finite state machine (fsm). The implementation doesn't reflect the fsm structure well. Part of the old assignment was to rewrite it. Don't worry about that now. We'll look at the proper written CustomerAgent in the v3.0 delivery.
- Keeping the agents running. Each agent has to receive messages and in parallel, must keep executing the scheduler, which invokes actions. We don't have that control structure designed yet. Since every agent will use the same structure, we'll put the control structure in the base agent, which will be described later.
-
Timer Utilities. This release includes
some timer utilities found in java.util.Timer.
We need the ability to set a timer for some length of time and then execute a
routine when it expires. Of course, the timer runs in its own thread (make sure
you understand this). We’ll explain this mechanism in class. We’ll also how to
implement an anonymous inner class in Java.
- How does the agent get hungry? The customer agent represents a very simplified person that gets hungry and goes to a restaurant to get fed. How does the agent get hungry? The agent has a setHungry() method that does this. Think of it as a message. Perhaps it might look like the following:
public void setHungry()
{
event = gotHungry;
print(“I’m hungry”);
fsm(); //invoke the finite state machine, which
is the schedule
}
Here’s how we use setHungry():
In the Main.java main routine, described in the next section, the setHungry() method is called directly. That gets the customer going. Once he’s hungry, he goes to the restaurant, etc.
After the customer leaves the restaurant, how does he get hungry again? There are three approaches: (1) The main routine in Main.java could somehow do it, but that wouldn’t be too elegant; 2) We could wait for some time to expire and automatically get hungry again. That’s what happens in the prototype. We call the timer routine, which will then call setHungry() again. 3) We could have a user interface with a button that let’s us set the agent to be hungry. You’ll see that in the GUI.
·
How long
does agent eat? We use the same
timer method to simulate eating. In other words, once the agent is seated, we
simulate the eating period by setting a timer. When the timer expires, the
eating is done. You will find many applications for using timers.
The main routine in Main.java creates the agents in the prototype application, sets the maitre’d for the customers, and then sets the customers hungry. Take a look.
public
class Main {
public
static void main(String argv[]) {
/*
The
following four lines create our four agents.
Each
agent instance has a thread in which it runs.
The
thread is created and started by the constructor.
*/
MaitreDAgent maitreD = new MaitreDAgent();
CustomerAgent
c1 = new CustomerAgent(“Fred”);
CustomerAgent
c2 = new CustomerAgent(“Ethel”);
CustomerAgent
c3 = new CustomerAgent(“Ginger”);
/*
The
next three lines tell the customer instances who
the
maitreD is. The maitrD
learns of the customers by
the
first message the customer sends to the maitreD.
*/
c1.setMaitreD(maitreD);
c2.setMaitreD(maitreD);
c3.setMaitreD(maitreD);
maitreD.startAgentThread();
c1.startThread();
c2.startThread();
c3.startThread();
/*
At
this point the agents are started, but are not
doing
anything, because nothing has happened to make
them
do things. So, we (artificially) make each
customer
hungry. c2 is made especially hungry (that
will
make him eat longer.)
*/
c1.setHungry();
c2.setHungerLevel(7);
c2.setHungry();
c3.setHungry();
/*
This
main thread ends leaving the agents to “fend for
themselves.”
You will see that after a customer is fed
and
not hungry, it will set a timer to set it hungry
again
later. That’s why this prototype runs forever.
*/
}
}
To run this code, connect to the release directory and type: ant run
You should see output like this:
[java] Fred: I'm hungry
[java] Ethel: I'm hungry
[java] Ginger: I'm hungry
[java] Ethel: Going to restaurant
[java] Fred: Going to restaurant
[java] Ginger: Going to restaurant
[java] MaitreD: Seating customer Ethel at table 1
[java] Ethel: Received msgSitAtTable
[java] MaitreD: Seating customer Fred at table 2
[java] Fred: Received msgSitAtTable
[java] Fred: Being seated.
[java] Fred: Eating for 5000 milliseconds.
[java] Ethel: Being seated.
[java] Ethel: Eating for 7000 milliseconds.
[java] Fred: Done eating, cookie=1
[java] Fred: Leaving.
[java] MaitreD: customer Fred leaving table 2
[java] MaitreD: Seating customer Ginger at table 2
[java] Ginger: Received msgSitAtTable
[java] Ginger: Being seated.
[java] Ginger: Eating for 5000 milliseconds.
[java] Ethel: Done eating, cookie=1
[java] Ethel: Leaving.
…
The restaurant application as run in the first deliverable is obviously simplified. It was just a quick stop along the development of our restaurant. This is the right time to REPLACE the functionality of the old main() routine of Main.java with a GUI.
As a replacement for the old main() routine, we can think of the GUI as a configurator for the restaurant. That is, it has “buttons” to control the activities and objects in the restaurant. It can also show us status and object information for the application. For this release we want it to be able to do everything that Main.java could do, except with GUI flexibility. The requirements will give the details.
Here are the minimum requirements for the gui.
· GUI can create the MaitreD agent.
· Customers can be added.
· Customers can be set to hungry. (The timing hack in the customer agent that makes him hungry after some amount of time should be removed.)
To run the gui, connect to the release directory and type: ant run.gui
This gui was designed by a student in one of the previous classes.
v3.0 is an implementation of a restaurant. Of all the requirements specified in the section above, some have been selected for design. This is typical during early system specification. First a system is fully specified, then for each implementation (or prototype) some subset of the requirements is selected for design and implementation. Succeeding iterations might add (or change) requirements.
Requirement 1: Getting Hungry:
Normal
Scenario: When he gets hungry, he goes to restaurant and tells the Host he
wants to eat.
Requirement 2: Eating at a restaurant:
Normal Scenario: Present himself to the host. When he is seated by his waiter, he will be presented with a menu. Tell the waiter when you are ready to order, and then tell him your choice. When you get your food, eat it. Tell the waiter when you are done and leaving the restaurant.
There can be many customers.
Requirement 1: Greets Customer
Normal Scenario: When he becomes aware of customer entry into restaurant, he puts customer on waiting list. If table is available, then he tells a working waiter to seat the customer at the free table.
Requirement 2: In charge of tables.
Knows about the tables in the restaurant. Assigns customers to tables. When customer leaves, the waiter will inform the Host that the table is free.
There will be only one host.
Requirement 1: Services Customer
Normal Scenario: Host tells waiter to seat the customer at a table. Once seated the waiter gives the customer a menu. When the customer is ready to order, the waiter takes the customer’s choice. He gives the order to the cook. When the order is ready, the waiter serves the customer. When the customer is done, he tells the waiter that he is leaving. [No check or money collection in v3.0]
There can be many waiters. See section 11.3 about menus.
Requirement 1: Cooks orders
Normal Scenario: Receives orders from the waiter. Cooks them. When an order is ready, the appropriate waiter is notified.
There will be only one cook.
An interaction diagram shows the EXTERNAL behavior of object oriented systems. In agent systems the interaction diagram shows the messaging behavior over an instance of the running system. In general, there is one interaction diagram for each scenario. A system is normally described by many such diagrams.
Each diagram should be simple. There is no branching or alternatives. You can make comments on the diagram. See section 3.1 for an example of an interaction diagram. We will do the v3.0 diagram in class.
The v3.0 application comes with a GUI and animation. From the GUI:
I will do the cook and customer agents in class. You will reverse engineer the host and waiter for homework.
The starting point system is on the lecture lab page. There
is a java and C++ version. Details of downloading and using them will be
discussed in class and be the focus of your first lab.
Now that we have the high-level requirements for our agent application, we can consider how the Agent is designed and implemented. Let's review some of agent basics:
· Agents are autonomous.
· Agents communicate by messaging.
· Agents have three logical parts, which can act concurrently.
· Agents will not share data with one another.
· Agents have actions that are independently schedulable for as long as the agent is active.
Let's review a few software-engineering ideas:
There are other possible requirements that would
influence our design. For example, we might want to be able to run each agent
on a separate computer. Should we anticipate this requirement and consider it
in our design? It certainly seems natural. The answer is not yet; we won’t let
this requirement influence our design.
In early releases of the software, we may decide to 1)
implement a simplified version that does not fulfill all of its requirements,
or 2) compromises some of the requirements slightly. That’s acceptable as long
as the client agrees. The client is usually the customer stakeholder of
the software, the person paying for it. In our class, I am the client. Any
deviation from the requirement must be explained to me.
Early releases are often called prototypes. A prototype can help us learn about the system, uncover new requirements while giving the customer insight into what he has specified. We have learned that early quick releases can be more important than waiting for a delivery that exactly fulfills all the requirements. The early v1.0 system described above is such a prototype.
An agent design must specify four things:
· The agent data;
· The agent messages;
· The scheduling rules;
· The agent actions
The abstract base class, Agent.java, will be described later. For now, all that we have to know is that the above four things must appear in a concrete (derived) agent. For example, the waiter agent might have the following signature:
public
class WaiterAgent extends Agent {...}
Let’s see what goes inside this concrete agent.
In section 2.1 formal messaging was described. I have made a design decision in the prototype that we do not need formal messaging. Our agents will simulate formal messaging as follows:
· An agent’s external interface will be all the message-handling calls. That way no parsing is required; we'll go over this in a future class.
· Message-handling calls return VOID. Its execution must act on the message as it sees fit: store information into its state; put requests on an internal queue; whatever is appropriate. It must not return a value; that is not in the spirit of messaging. And it must return quickly; handlers generally do not do “semantic” processing; they just add information to the state of the agent. The scheduler will call actions to do the actual work.
Here’s pseudo-code to show how this works:
//Sender Code found
in an action of the waiter
Prepare(me,2,“Hamburger”);
//Call message-handler directly.
... //continue on
with other work.
//Recipient
Message-Handler for Prepare (in the cook Interface)
Void Prepare(sender,
table, item){
/*This code does
EXACTLY the same thing as it did
in the dispatch code
in the messaging example of section 2.1*/
...
}
All we are doing is having the sender call the message-handler method directly. No marshalling of the message, no external message queues are used, no persistence of messages. That simplifies some things, but complicates others.
Notice also that execution of the handler code is happening within the sender’s thread of control (make sure you understand this). Of course, that’s not real messaging, but it is a logical simplification. Doing messaging this way depends on three key points:
· The method call must return VOID or we aren’t doing messaging.
· There must be no shared data between code in the sender and code in the recipient. Even though they may in the same address space, they MUST NOT share data. Any structures passed as parameter need to be copied by the recipient since Java passes pointers (make sure you understand this). Otherwise a fundamental agent principle is being violated.
· The message-handling call must return quickly. It should set some state or queue something up for the scheduler to look at. If it tries to do a significant amount of processing, then it isn’t behaving like an agent. Remember that the message-handling method is acting in the sender’s thread of control. In the case of Prepare(sender, table, item) the handler would put this on the cook’s queue of things to do and when its scheduler picked this item, a Cooking action would actually prepare the food and send a message to sender when the item was finished. [Can you figure out why the table number is part of the message?]
So, with this simulation of messaging we have another design decision that violates two requirements.
Requirement: An agent has three parts, each with
its own thread of control.
Violation: Here the message reception part DOES NOT have its own
thread—it is in the sender’s thread.
Accommodation: Since the sender and receiver’s scheduler have different
threads of control, the effect we are looking for, i.e., scheduler and message
reception happen concurrently, is still satisfied.
Requirement: Agents must not share data.
Violation: By having the sender call the recipient’s dispatch routine
directly, the recipient has direct access to the sender’s structures.
Accommodation: If the recipient or the sender COPIES the structure, then
no data is shared and we have the same effect as in real messaging. Which do
you think should do the copying?
Is this acceptable? Yes, if we understand what we are doing, if we document it carefully, and if the client agrees. And since I am the client, anything that satisfies me is all right.
· Java does have a full messaging package. If your application requires it, use it. We just don’t need it for our current purposes.
· If we want to implement the agents as separate processes, not just separate threads, Java has a package called RMI (Remote Method Invocation). Uusing RMI one process can invoke another even if it is executing on a different machine. RMI copies all data structures that are used in remote method calls.
The base agent, as we will see later, is mostly an infinite loop that invokes the scheduler. When you build a concrete agent, you must implement a scheduler. The scheduler holds the logic that decides what an agent can do. Here’s how it should look:
/** Scheduler. Determine what action is called for, and do
it. */
boolean pickAndExecuteAnAction
() {
//Rule 1:
if (condition1) {action1(...); return
true;}
//Rule 2:
if (condition2) {action2(...); return
true;}
return false; //we have tried all our rules and found nothing to do.
//So
return false to main loop of abstract agent and wait.
}
Here is a scheduling rule that might exist for the cook:
if ($ o in orders ' o.isPending())
then (prepareOrder(o);
return true);
The above is read as follows: if there exists an o in orders such that o.isPending() then call prepareOrder(o) and return true.
There are many things to note here:
·
The conditions can be arbitrarily complex. The
above rule is typical: look for the existence of an object that satisfies some
predicate, then pass it to some action.
·
The rules are prioritized by the order in which
they appear in the code. If the scheduler
picks an action, it returns true to the base agent, whose loop invokes
the scheduler again, trying the rules from the top. This is a very simple
scheme that has worked for me in all my agent implementations.
·
This scheduler invokes one action at a time.
Could an agent have two actions active at the same time? In my experience,
whenever I seem to have that need, I look closely and find that I need another
agent. Having agents only executing one action at a time simplifies the agent
design. This one-action-at-a-time is almost a requirement for me, but I let
individual agent designers decide.
·
This scheduler is not concurrent with actions.
That is, this schedule makes a synchronous call to an action and waits for it
to complete before returning and trying to schedule another action. That’s a
requirement violation. But since we have just decided to only schedule one
action at a time, there seems to be no problem. Or is there? Take a moment to
think of a problem that might arise with this design. Warning: an answer is
coming…Suppose you are performing some action when a very high priority message
comes in, say:
PleaseSeat(“Prof. Wilczynski”);
Don’t you want to drop everything and give Prof. Wilczynski the best
service you can? Yes, you do. Your scheduler needs to be able to suspend the
active action, and schedule a new action involving the professor. The scheduler
cannot do this if it is synchronously waiting for actions to complete. It must
be in its own thread monitoring the messages and actions (make sure you
understand this). However, suspending actions and scheduling new ones increases
the complexity of the agent design too much—we may handle this case in a later
version. So, for now, we will recognize the following violation:
Requirement: An agent has three parts,
each with its own thread of control.
Violation: Here the scheduler and actions work in the same thread.
Accommodation: Only one action can be carried out at a time and it
always runs to conclusion, i.e., it cannot be interrupted, canceled, or
restarted.
Finally it’s time to describe the actions the agent actually performs. Each action is encoded in a single method. For example, here is the host code that assigns a customer to a waiter:
private void tellWaiterToSitCustomerAtTable(
MyWaiter waiter, CustomerAgent customer, int tableNum){
print("Telling
" + waiter.wtr + " to sit " + customer +" at table
"+(tableNum+1));
waiter.wtr.msgSitCustomerAtTable(customer, tableNum);
tables[tableNum].occupied = true;
waitList.remove(customer);
nextWaiter = (nextWaiter+1)%waiters.size();
//get next waiter ready
}
As usual there are several things to note:
· This is real Java code, not pseudo-code. Eventually, the implementation has to reach this level. But still, my pseudo-code is very much like Java. That's why I use Java.
· tellWaiterToSitCustomerAtTable() returns void. All actions do.
· tellWaiterToSitCustomerAtTable() is private. Only the scheduler can invoke an agent’s actions. The parameters are Java objects instantiated from Java classes.
· The print() is for tracing only.
· Then we send the waiter a message to seat the customer.
· The next three lines update the agent’s data structures: 1) marking the table as being occupied; 2) removing the customer from the waiting list; 3) setting up the next waiter to be used.
The general agent as described above has its intelligence encoded in its rule-based scheduler. Most agents will be designed this way. However, you may have an agent whose control structure can be more simply modeled as a finite state machine (fsm). Recall that a finite state machine is defined by a transition function that maps (state, event) ---> (state, output). That is, an fsm is a table of the form:
|
|
e1 |
... |
em |
|
s1 |
nextState, someOutput |
... |
nextState, someOutput |
|
s2 |
nextState, someOutput |
|
nextState, someOutput |
|
... |
... |
... |
... |
|
sn |
nextState, someOutput |
|
nextState, someOutput |
where the nextState is one of the s1, s2, ...sn, and the someOutput is an action of our agent. In our fsm agent, message reception sets the event (and other variables as needed), while the scheduler embodies the table. Here is the states and events from the v3 CustomerAgent.
public enum AgentState
{DoingNothing, WaitingInRestaurant, SeatedWithMenu, WaiterCalled, WaitingForFood, Eating};
private AgentState state = AgentState.DoingNothing; //The start state
public enum AgentEvent
{gotHungry, beingSeated, decidedChoice, waiterToTakeOrder, foodDelivered, doneEating};
private AgentEvent event; //Messages set the event
List<AgentEvent> events = new ArrayList<AgentEvent>(); //events are added to list via messages
Here is the first message the customer receives:
/** Sent from GUI to set the customer as hungry */
public void setHungry() {
events.add(AgentEvent.gotHungry);
isHungry = true; // a hack
print("I'm hungry");
stateChanged();
}
Here is the beginning of the fsm scheduler:
protected boolean pickAndExecuteAnAction() {
if (events.isEmpty()) return false;
AgentEvent event = events.remove(0); //pop first element
//Simple finite state machine
if (state == AgentState.DoingNothing){
if (event == AgentEvent.gotHungry) {
goingToRestaurant(); //the output action
state = AgentState.WaitingInRestaurant;
return true;
}
// elseif (event == xxx) {} //if there were other events for this state
}
if (state == AgentState.WaitingInRestaurant) {
if (event == AgentEvent.beingSeated) {
makeMenuChoice();
state = AgentState.SeatedWithMenu;
return true;
}
// elseif (event == xxx) {} //if there were other events for this state
}
if (state ...
...
}
You can see the actions in the code. We will talk more about this in class.
At this point of the software engineering cycle, an O-O design will follow—classes, methods, etc. At the base class level, we only know about the run cycle.
class Agent {
run();//invokes
the scheduler repeatedly
}
Since the base class agent has no application semantics, we can only specify the run cycle (to find and execute an action).
Remember, the idea of design is to describe HOW a system will work as abstractly as possible so as to make sense, be implementable, understandable. The programming details are filled in during the implementation phase. DO NOT CLUTTER UP A DESIGN WITH PROGRAMMING DETAILS. In the design we don't even have to specify the constructor, which is really an implementation detail.
In (very) pseudo-code here is one conception of the natural run-cycle of an agent:
run(){
while
agent is active {
pick an action;
if action is picked
then perform the action;
else sleep until state changes;
}
}
Some would want this tightened up a bit. Perhaps:
run(){
while agent.IsActive() {
action = pickAnAction();
if action
then perform(action);
else sleep() until stateChanged;
}
}
Is this better? Now we have to add AgentIsActive(), pickAnAction(), perform(), sleep()and stateChanged to our base class specification. Notice also that the notion of an agent being "active" has been introduced. Perhaps we should add a routine to deactivate an agent, say deactivateAgent(). Let's do it without thinking too much about this decision. So, our Agent base class design now looks like:
class Agent {
//variables
Action
action;
bool stateChanged;
//methods
run();//invokes
the scheduler repeatedly
deactivateAgent();
bool agentIsActive();
Action pickAnAction();
perform(Action);
}
This tightening up has added types to the method declarations. For years I have created designs without strong typing and survived. However, the trend is clearly toward typing in designs. Let's just do it. That being said, notice that the design is getting harder to read as more programming lingo is introduced.
In any case does the above specification really work? Don't actions take parameters? Yes, they do. This level of pseudo-code is missing them and there is no obvious way of putting them in. [Why?] Perhaps we should abstract the action out of this loop, something like:
run(){
while agentIsActive() {
if ~ pickAndExecuteAnAction()
then sleep() until stateChanged;
}
}
I like this. Now we would have the following design for the base class agent:
class Agent {
//variables
bool stateChanged;
//methods
run();//invokes
the scheduler repeatedly
deactivateAgent();
bool agentIsActive();
bool pickAndExecuteAnAction();
}
What about pseudo-code for the new methods? There isn't any, because at this level of abstraction there is nothing to specify; the intent of the routines is clear. That's all that is needed for design.
Notice how we made several passes to a satisfactory design for the run() method. That was no surprise.
The abstract Agent class was designed to make coding of a concrete agent easy. The abstract class has the code for:
· Managing the agent thread that runs the infinite loop that invokes the concrete scheduler.
· Various utilities.
You should be looking at Agent.java while reading the rest of this section.
The abstract Agent class contains a private class whose signature is:
private class AgentThread extends Thread {...}
And the Agent class has an instance variable to hold the newly created thread:
private AgentThread agentThread;
The agentThread variable will be initialized by the agentThread.start() method:
agentThread = new AgentThread(getName());
agentThread.start() is the method that actually gets
the thread running. Java will create the thread and give control to its run() method. agentThread.start() will be called by the code that
constructs the agent.
What is going on? For one thing, an agent designed this way is not really autonomous. It’s going to be a thread within some other process’ address space. Our prototype has the entire application as one process with each agent in its own thread. So, here we have a typical software engineering occurrence, an implementation decision that violates a requirement.
Requirement: An agent is autonomous.
Violation: Here the agent is running as a thread in an address space
with other agents. The possibility of sharing data is possible.
Accommodation: As we will see, we will be careful NOT to share data.
Then, if each agent gets its own thread, then the concurrency is there by way
of the operating system scheduler (illusion of concurrency).
Also, why doesn’t the Agent class extend Thread directly? What’s the purpose of making the thread an instance variable of the agent? Mostly, it is a design decision to make unit-testing easier. Code that wants to test a class usually does things like the following:
Instantiate the class, e.g. ClassA a = new ClassA();
Then execute the methods of the class and see if they
do the right thing, e.g.
if (a.sum(2,3) != 5) assertFailure();
Doing this kind of unit testing is difficult if the object is actually running in a separate thread (you will try this in your lab). So instead, we make the Agent a regular object and create an instance variable to hold the thread. It’s not as complicated as it looks and we will go over this in great detail in class.
In the Java threading model, a thread is started by invoking the start() method. That tells the operating system to create another thread of control in this address space, and start its execution at its run() method. In other words, run() performs the same function for a thread as main() does for a process.
Our run() routine is tricky. Its intention is to invoke the scheduler for as long as there is something to do. If the scheduler—implemented in pickAndExecuteAnAction()—has nothing to do, it returns false and the agent sleeps until its internal state changes (make sure you understand why this makes sense). The agent uses a classic synchronization mechanism called a binary semaphore to implement this logic. One of the private variables is:
Semaphore stateChange = new
Semaphore(1, true); //start with one permit
Semaphore is a type in Java 1.5's new java.util.concurrent package. We will discuss semaphores in a lecture when we discuss concurrency.
Here is the run() method:
public void run() {
goOn = true;
while
(goOn) {
try
{
// The agent sleeps here until someone calls
stateChanged(),
// which causes a call to stateChange.release(), which wakes up agent.
stateChange.acquire();
//The next while clause is the key to the
control flow.
//When the agent wakes up it will call respondToStateChange()
//repeatedly until it returns FALSE.
//You will see that pickAndExecuteAnAction()
is the agent scheduler.
while (pickAndExecuteAnAction());
}
catch (InterruptedException e) {
//
no action - expected when stopping or when deadline changed
}
catch (Exception e) {
print("Unexpected
exception caught in Agent thread:", e);
}
}
}
goOn is a simple private variable that controls the main loop. As you can see goOn is set to true; a method called stopAgent() turns it off. For as long as goOn is true, the loop continues. The intention is for the agent to run forever until stopAgent() is called or the execution of the whole process is killed.
The loop executes two statements within a try-catch. The first invokes the method acquire() on the semaphore variable stateChange. The call puts the thread to sleep until someone calls the semaphore method release(). The agent waits here until someone calls stateChanged(), which results in a call to stateChange.release(), which wakes up the agent. Who calls stateChanged()? There are two events that change the state of the agent:
· message reception code;
· action code.
We will see examples soon.
Back to the try clause within the while loop. The clause:
while (pickAndExecuteAnAction());
is the key to the control flow. When the agent wakes up it will call the
scheduler via pickAndExecuteAnAction
() repeatedly until it returns FALSE.
You will see that pickAndExecuteAnAction
() is the agent scheduler.
Notice that pickAndExecuteAnAction () is an abstract method in Agent. That means that any concrete class that extends Agent must implement pickAndExecuteAnAction (). The scheduler code must be abstract because details of the scheduler are application dependent.
There are four utility routines:
Do(String
msg);
print(String
msg);
print(String msg, Throwable e);
getName();
Do() is for simulating the agent primitives. It will call print().
The print() routines are for our simulated output (or any output we want).
getName() returns the agent name.
These routines are simple. See the code for details.
There are a few design themes that have been uncovered even with this small amount of code we've specified.
· Design is iterative; new layers being added as details are uncovered in the pseudo-code.
· A good designer can "peek" forward to implementation to make sure his design is implementable. This is the reason that great designers are (or were) great implementers.
· Designs must compile! Messages, variables, and actions must match
·
Designs must work! "Execute" the
design and see if interaction diagrams are satisfied.
A key part of those themes is being able to write great pseudo-code. Great pseudo-code has depth without compromising understanding, simplicity yet still complete. You learn this skill by practice and reading designs and code written by professionals. All designers have a "style" and I'm no exception. So most of what I'm going to show is the result of my 40 years of programming expertise.
My designs make frequent use of abstract Collections, Sets, Lists, Queues, and Stacks. I never use arrays (unless the object has array semantics, like mathematical objects frequently have). Arrays are mostly implementation things.
A good thing for you to get used to is Java's collection
classes. We will be using them often. The Java 1.5 api
documentation is at:
http://docs.oracle.com/javase/1.5.0/docs/api/
Put that link in your bookmarks and go there often for questions about java
classes.
Also, here is a link to information about Java
collections, including a tutorial:
http://docs.oracle.com/javase/1.5.0/docs/guide/collections/
What follows is a brief summary about some of the Collection classes I like to use in design.
A collection represents a group of objects, known as its elements. Some collections allow duplicate elements and others do not. Some are ordered and others unordered. The main operations are:
boolean add(E o)
Ensures that this collection contains the specified element (optional operation). Returns true if this collection changed as a result of the call. (Returns false if this collection does not permit duplicates and already contains the specified element.)
boolean remove(Object o)
Removes a single instance of the specified element from this collection, if it is present (optional operation). More formally, removes an element e such that (o==null ? e==null : o.equals(e)), if this collection contains one or more such elements. Returns true if this collection contained the specified element (or equivalently, if this collection changed as a result of the call).
boolean contains(Object o)
Returns true if this collection contains the specified element. More formally, returns true if and only if this collection contains at least one element e such that (o==null ? e==null : o.equals(e)).
boolean isEmpty()
Returns true if this collection contains no elements.
int size()
Returns the number of elements in this collection. If this collection contains more than Integer.MAX_VALUE elements, returns Integer.MAX_VALUE.
A
Collection
that contains no duplicate elements. More formally, sets contain no pair of
elements e1 and e2 such that e1.equals(e2), and at most one null
element. As implied by its name, this interface models the mathematical set
abstraction.
A SortedSet further guarantees that its iterator will traverse the set in ascending element order, sorted according to the natural ordering of its elements (see Comparable), or by a Comparator provided at sorted set creation time. Several additional operations are provided to take advantage of the ordering.
An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list. Lists are 0-based. Unlike sets, lists typically allow duplicate elements. The additional operators are:
void add(int index, E element)
Inserts the specified element at the specified position in this list (optional operation). Shifts the element currently at that position (if any) and any subsequent elements to the right (adds one to their indices).
int lastIndexOf(Object o)
Returns the index in this list of the last occurrence of the specified element, or -1 if this list does not contain this element. More formally, returns the highest index i such that (o==null ? get(i)==null : o.equals(get(i))), or -1 if there is no such index.
int indexOf(Object o)
Returns the index in this list of the first occurrence of the specified element, or -1 if this list does not contain this element. More formally, returns the lowest index i such that (o==null ? get(i)==null : o.equals(get(i))), or -1 if there is no such index.
E get(int index)
Returns the element at the specified position in this list.
A Collection designed for holding elements prior to processing. Besides basic Collection operations, queues provide additional insertion, extraction, and inspection operations.
Queues typically, but do not necessarily, order elements in a FIFO (first-in-first-out) manner. Among the exceptions are priority queues, which order elements according to a supplied comparator, or the elements' natural ordering, and LIFO queues (or stacks) which order the elements LIFO (last-in-first-out). Whatever the ordering used, the head of the queue is that element which would be removed by a call to remove() or poll(). In a FIFO queue, all new elements are inserted at the tail of the queue. Other kinds of queues may use different placement rules. Every Queue implementation must specify its ordering properties.
As noted above, a stack is just a LIFO (Last In, First Out) queue.
One of the reasons to use abstract Collections and Lists is that it makes it easy to write nice design expressions for finding objects. We've already seen this in the waiter scheduler:
Rule1: If there exists a table and customer, so that table is unoccupied and customer is waiting, then seat him at the table.
In
First Order Logic, we would write:
if ($ table
in tables and $
customer in waitingCustomers ' ~table.isOccupied())
then perform seatCustomer(customer,
table);
[If those special logic symbols don't print in your browser, here is the same expression with the symbols replaced by English:]
if (there exists table in tables and there exists customer in waitingCustomers
such that ~table.isOccupied())
then perform seatCustomer(customer,
table);
Both "there exists" clauses specify the existence of a member of a collection. Then you can use those members for filtering (the "such that" clause", and acting on them (the "perform" clause). This is so easy to implement in Java 1.5 because the new iteration looks very much like the design code.
Sometimes you want to make a statement about every element in a collection. Such statement can be constraints or just conditions on which you want to do something. Here is a constraint that might appear in a unit test:
·
All members in the collection C must be
initialized.
or more formally:
Assert ("c in C, ~null(c))
or if your browser doesn't print that "for-every" symbol:
Assert (for every c in C, ~null(c))
You might use the for-every construct in a scheduling rule. Here is one a waiter might try:
·
if every customer I am responsible for is
eating, then I can take short break.
or more formally:
if (" cust
in Customers ' isEating(cust)) then perform takeABreak("short");
or if your browser doesn't print that "for-every" symbol:
if (for every cust in Customers such that isEating(cust)) then perform takeABreak("short");
The "for-each" and "for-every" logic constructs are very useful is design.
In
concurrent systems, like ours, you must be aware that variables are used in
different threads and may be subject to race condition (these are timing
problems that we will discuss in depth later). Students often use simple
variables in their messaging and it is simply inadequate: they get clobbered
when new messages come before you have "consumed" the original. For
example, suppose you designed your Maitre'D like
this:
CustomerAgent newCustomer=null;
//variable to hold the newly arrived customer.
IWantFood(CustomerAgent customer)
On receipt: Set newCustomer =
customer.
//rule
1
if (there exists table in tables and newCustomer!=null
such that ~table.isOccupied())
then perform seatCustomer(newCustomer, table);
THIS DOESN'T WORK. Your usage of newCustomer might be too late. It might be clobbered by a new message before you get to use it; you would lose track of the first customer. That's why you use collections. YOU MUST UNDERSTAND THIS.
Suppose you are keeping track of food in your restaurant. You currently serve three kinds of food: chicken, steak, and fish. Let's say your design has a message about a delivery of a certain amount of food of one of those types. Here's what I often see in the design.
//variables:
int chicken_count;
int steak_count;
int fish_count;
//messages
Deliver (deliveryType, deliveryCount)
{
if deliveryType
== "chicken" chicken_count+=deliveryCount;
else if deliveryType
== "steak" steak_count += deliveryCount;
else if deliveryType
== "fish" fish_count+= deliveryCount;
}
What if you have dozens of cases? Infinitely better is:
//variables
Collection<Food> foods;
class Food {
type,
count; //type is one of
chicken, steak, fish, …
}
or more succinctly in design in case you don't use the internal Food class:
Collection<{type,count}> foods;
Deliver (deliveryType, deliveryCount)
{
for f in foods where f.type= deliveryType do f.count+=deliveryCount;
}
Notice that my preferred design has only one variable instead of three. Fewer variables is the theme of the next section.
Underappreciated is a careful usage of variables. What I mean is:
· When should you introduce one?
· What should its name be?
· What state information does it hold?
· What are your alternatives?
Students and other inexperienced programmers add variables to designs without thinking about these questions. And then their designs get more complicated. Before I add a variable to a design, I think hard about whether I REALLY need it. I have learned that every single variable takes a lot of maintenance, and unnecessary variables are wasteful. Adding a variable to increase understandability is always valid.
Here's a pattern I have seen often. Suppose you have a Waiter Agent. He is assigned customers by the Host Agent. His responsibilities include seating assigned customers and then waiting on them (taking their order, giving them food, etc.) Here's what I sometimes see:
list<MyCustomer>
assignedCustomers;
list <MyCustomer>
seatedCustomers;
…
class MyCustomer {
Customer c;
int table;
}
bool sitCustomer=F;
bool serveCustomer=F;
bool getFoodForCustomer=F;
…
//Message
SitCustomerAtTable(Customer c, int
table){
assignedCustomers.add(new MyCustomer(c,table));
sitCustomer=T;//means there is at least one customer in assignedCustomers list
}
…
//Action
SeatCustomer(MyCustomer c){
Do
("Seating customer …");
assignedCustomers.remove(c); //did I forget anything?
seatedCustomers.add(c); //remove from one list and add to
another
send
msgPleaseFollowMe to c;
}
…
//Scheduler rules:
if sitCustomer then SeatCustomer(assignedCustomers.first());
if serveCustomer
then …;
if getFoodForCustomer
then …;
…
What's wrong with this?
· There are two lists holding the same kind of information, i.e., customers, but members of the lists are in different states, assigned and waiting to be seated and seated (and presumable waiting to be served). How many of these lists will you need?
· The design uses a different boolean for each action. I suspect this is done to make writing the scheduler easier. Do you think it does? How many of those Booleans will you have?
· The "did I forget anything comment" refers to a common programming error. In this action the designer forget to test whether sitCustomer should be turned off. Very easy to forget since it's not close by.
I understand the above design, but I much, much prefer the following:
list<MyCustomer>
customers;
class MyCustomer {
Customer c;
CustomerState s; //one of assigned, seated, ordering, waitingForFood, etc.
//More
about state variables in next section.
int table;
}
//Message
SitCustomerAtTable(Customer c, int
table){
customers.add(new MyCustomer(c,assigned,table));
}
//Action
SeatCustomer(MyCustomer c){
Do ("Seating customer …");
c.s=seated; //no remove from one list and add to another
send msgPleaseFollowMe
to c;
}
//Scheduler rules:
//Rule 1
if assignedCustomers.hasWaitingCustomers() then SeatCustomer(assignedCustomers.getFirstWaiting());
…
One instance variable instead of five. The variable is more complicated by having the customer state encoded in it, but I claim this is vastly easier to manage and understand. Look how much less code there is.
Are you nervous that hasWaitingCustomers() has to search the list, that assignedCustomers.getFirstWaiting() has to search the list again? Are you worried about efficiency? DO NOT WORRY ABOUT SUCH THINGS DURING DESIGN. Let your programmer worry this. Design is supposed to be elegant. I have a million stories about this.
In the above section I introduced the notion of a state variable in the MyCustomer class that the waiter is maintaining. That seems so natural to me that I do such things routinely.
What state variables might be in a Customer agent? Perhaps:
Each state can only have one value. That's the key. The states are orthogonal, i.e., they are different dimensions of the overall state of the customer. Thus, the "full" customer state with respect to this type of customer is a triple, (stateOfNourishment, stateOfLocation, stateOfPosition). I say "full" because the complete state depends on the application.
The state of the customer from the Host point of view is completely different:
Notice the states are different from the customer triple. The states depend completely on what the Host “cares” about.
AWaiter might have the following state variables:
Again the states are application dependent. Each agent keeps track of what it needs to keep track of.
Java 1.5 has enumerations. State variables are now easy to declare and easy to use. Here's a snippet:
public enum stateOfCustomer { seated, hasMenu, hasOrdered, servedFood, hasCheck };stateOfCustomer soc; //declaring a variable of this type.
soc = hasMenu; //assigning to the variable
Do it carefully especially if the code has a lifetime beyond its first writing (or if
someone else, like a professor or grader, has to look at it). Remember the following points:
The previous sections described our design and implementation. Now we will look at the environment in which our code will run. Real projects have these and many more needs:
These are tasks you do over and over. Typically, we’ve used scripting languages to create Makefiles to mechanize these tasks. Java gives us tools to do a lot of what Makefiles do—not everything, but a lot. Fortunately, there is now even more help:
Both are part of the repertoire of all professional Java programmers.
An aside: Here it is, the first week of class and you must learn about Agents, Ant, and JUnit. This is how life is when working on a system. But, remember, you don’t have to understand everything in depth (even if you had the time) in order to work with and modify code and scripts. A reading understanding of other people’s work is enough. With time and need you will develop the required depth.
You will be asked to read several papers about these systems. Don’t study these papers. Don’t try to understand everything in them. You can’t. Neither can I. Their purpose is to give you a quick profile of what the systems can do and how professionals use them. You will learn some basics by doing the Agent problem.
You mostly need to understand what’s in front of your nose—what your application is doing and the parts of the tools it uses. And even then, if things are working correctly, you may not need to know every detail in depth. Certainly, if you change or add something new, then you need to do some research. You develop depth with these tools by doing, not reading.
1) Go to http://ant.apache.org/
and follow the link to the download.
2) Put the
zip file in a directory, e.g., D:\Program Files\Apache Group
3) Unzip the file. It will create a subdirectory called apache-ant-1.6.1
4) You should see a directory structure like the following:
D:\Program Files\Apache
Group\apache-ant-1.6.1>dir
Volume
in drive D is MAIN
Volume
Serial Number is 007C-CDD0
Directory
of D:\Program Files\Apache Group\apache-ant-1.6.1
07/12/2004
09:53a <DIR> .
07/12/2004
09:53a <DIR> ..
07/12/2004
09:53a <DIR> bin
07/12/2004
09:53a <DIR> docs
07/12/2004
09:53a <DIR> etc
07/12/2004
09:53a <DIR> lib
02/12/2004
02:28p 17,191 KEYS
02/12/2004
02:28p 11,766 LICENSE
02/12/2004
02:28p 3,356
LICENSE.dom
02/12/2004
02:29p 677
LICENSE.sax
02/12/2004
02:29p 2,698 LICENSE.xerces
02/12/2004
02:29p 981 NOTICE
02/12/2004
02:28p 2,657 README
02/12/2004
02:28p 105,439
WHATSNEW
02/12/2004
02:28p 18,478
welcome.html
9 File(s) 163,243 bytes
6 Dir(s) 3,928,408,064 bytes free
You should read the following papers. All are in the ant distribution:
In v1 of the Agent release, to see all the Ant targets, type:
>ant
You should examine the build.xml file and “understand” it. Try some of the targets.
To run the v3 agent release, type:
>ant restaurant
The Java developers of Ant have determined a directory organization for applications. They have thought long and hard about it and we will simply adopt it (but don’t worry, we’ll have you make some changes for later releases). Let’s call the top-level directory for our application agent0-1 (the first number refers to the version of the functionality, the second to the number of the release of that functionality).
Ant uses a file called build.xml for its instructions. It will be located in the top-level directory that houses this application. The build.xml file assumes the following file organization (it may be slightly different than this):
agentV1 Holds build.xml, application jars, readme files.
bin Holds batch files for running the application.
build Holds the classfiles resulting from compilation.
javadoc Holds the javadoc documentation files.
src Holds the source java files.
The subdirectories build, javadoc, and src will follow your package model. We will have two packages:
· “package agent” for the base class and utilities.
· “package restaurant” for the agent application
When you run Ant, you specify a target. The following targets are set up in your build.xml:
compile Compile the source code
test Compiles and tests the source code by running junit tests
run Run the simple command-line agents program Main.java, if there is one
run.gui Run the agents program using the gui, if there is one
restaurant Run the full application with animation
javadoc Create javadoc of the source code
dist Create a full distribution of the project including all the binary jars, source code, and ant files
clean Delete all the compiled files
To
do a compile, you type at the prompt:
>ant compile
To just see the targets, type at the prompt:
>ant
Study the build.xml file and get familiar with it and the Ant program itself.
JUnit is a package that makes a first-class citizen of testing for the Java software development process. Learning to use JUnit is not hard; acquiring the JUnit-style testing discipline is hard.
1) Go to http://www.junit.org/index.htm
and follow the link to the download.
2) Put the
zip file in some directory, e.g., D:\
3) Unzip the file. It will create a subdirectory called junit3.8.1
4) You should see a directory structure like the following:
D:\junit3.8.1>dir
Volume
in drive D is MAIN
Volume
Serial Number is 007C-CDD0
Directory of D:junit3.8.1
07/12/2004
10:10a <DIR> .
07/12/2004
10:10a <DIR> ..
07/12/2004
10:10a <DIR> doc
07/12/2004
10:10a <DIR> javadoc
07/12/2004
10:10a <DIR> junit
09/04/2002
11:29p 15,172
cpl-v10.html
09/04/2002
11:29p 121,070
junit.jar
09/04/2002
11:29p 21,614
README.html
09/04/2002
11:29p 57,635 src.jar
4 File(s) 215,491 bytes
5 Dir(s) 3,928,489,984 bytes
free
5) Look at README.html, but don’t put junit.jar on your classpath.
6) To test the installation, try the following (Be very careful about the directory you are connected to; also notice my explicit classpath):
D:\junit3.8.1>java -classpath
.;junit.jar junit.textui.TestRunner junit.samples.money.MoneyTest
......................
Time: 0.04
OK (22 tests)
You should read these papers:
Notice that the Ant program has a target for javadoc. Java programs can be commented in special ways in order to create the special kind of Java documentation that we are all familiar with. I expect you to enhance all the code with excellent Javadoc comments.
You generally place a javadoc comments above the class definition and before every non-private method. In addition to the text and HTML that is allowed, javadoc supports some special tags that have special meaning to the javadoc output.
@author
Specifies the author of the document
Must use javadoc -author ... to generate in output
@version
Version number of the document
Must use javadoc -version ... to generate in output
@param
Documents a method's arguments
@return
Documents the return type of a method
@throws
Documents the exceptions a method throws
These tags must occur at the beginning of a line and include everything until another tag or the end of the comment.
Here’s an example of a javadoc comment that might appear above a class:
/**
Description of Agent Base class
*
* Lots of good documentation
here ...
*
* @author <A
HREF="mailto:dwilczyn@usc.edu">
* David
Wilczynski</A>
*/
Here’s an example from a Semaphore class for the acquire routine (this routine is not in our current code):
/**
Wait until semaphore is full or the timer expires.
* If the semaphore is already full, return
immediately.
*
Always leaves the semaphore empty.
* @param timer time to wait.
If null, returns immediately.
*
@return true if <code>take()</code> was successful
* (semaphore was full or
became full),
* else false (timer
expired).
*
@throws InterruptedException if interrupted while
waiting
*/
synchronized public boolean
acquire (Deadline timer){
//The code follows here.
...
}
Unit testing is important for many reasons. The most important one for real projects arises from the following development scenario:
You have to find a way to test your agent (component) without benefit of a full system so that you are confident that the integration will go smoothly.
As we know unit testing entails:
For the example we did in class from the Junit paper, the above was easy:
What’s the analog for agents? How do we unit test an agent?
Agents are complex objects that run in a complex environment with other agents. At first glance we should send the agent some messages and see if it “behaves” correctly. There’s a lot to what “behaves” means, but now that we are agent experts, we need to know:
· Is the state of the agent correct prior to receiving a message?
· Are the state variables set correctly on message reception?
· Does the scheduler pick the right actions, if any, to execute?
· Do the actions do the right things? In other words do they update their state correctly and send the right messages.
Let’s investigate each of these in turn and in the context of the Host agent of v3.0 when it is just starting up.
When an agent receives a message, it sets some state and calls stateChanged(). Testing whether it does this correctly is much like the unit testing in the Junit paper. So, for example if we are testing the “I’m hungry(customer)” message: we’d first check to make sure the agent is in a state that we expect. So, we should do some pre-condition testing prior to sending the message. For example:
Now, test to see whether the message was received correctly. We want to check that:
These tests are straightforward, but doing them in a fully running agent is not. After the agent receives the message, the schedule might run before our tests are complete. Right? The agent may start carrying out some actions that change the variables, before we can test them. In other words our test code is in a race condition with the agent. That is definitely not good for testing. How do we fix that?
Simple. Just don't call the startThread() method in your testing code. That will give you full control of each of the agent's components.
Now your test code looks something like this pseudo-code:
Customer
c = new Customer(“John”);
Waiter
w = new Waiter(“Bill”)
Host
h = new Host();
Assert.assertTrue(waitingCustomers
EQ NIL));//Should be no customers on startup
h.msgI’mHungry(c);
//now
comes code to test the variables
Assert.assertTrue(waitingCustomers
NEQ NIL));
//other
tests
…
pickAndExecuteAnAction();
//calls the scheduler
The question now is does the right action fire? This is somewhat complicated. If there are tables empty, you want the waiter assignment action to fire. If the tables are full you want to make sure that nothing happens. So clearly, you have to set up several experiments to check out ALL the possibilities. Let’s look at the case where tables are free and we expect the waiter assignment action to fire.
The first line of code in the action of our agents is a Print or a Do() (which does a print). That simply puts the output in the standard place for VIEWING by you. In the Junit discussion, they said whenever you have a print statement in your code, you should consider writing a test. Here is exactly that opportunity.
That Print statement describes which action is executing and what its parameters are. Instead of just printing it, you should also put it into some public data structure, such as an array of strings, which can be tested (you will be supplied with a Log class for exactly this purpose). Let's assume your action outputs the string “Assigning waiter Bill to sit customer John at table 1.” Then, you can write a test:
Assert.assertTrue(output[0].equals(“Assigning waiter Bill to sit customer John at table 1”));
So, testing that the right action has been called is easy. If the agent changes any state after the action is called, testing will involve techniques we’ve already explored. What about testing whether the agent sends the right messages to other agents and is prepared to receive replies from them? That’s not so easy. Agents send messages to and receive messages from other agents. Testing this seems to mean using the other agents in your test suite. But we already explained how unit testing means we must test without them. And anyhow, using full running agents is hard to control in the testing environment because of automatic mode. We need another technique in our repertoire called Mock Objects.
The Host action is going to assign responsibility of the customer to the chosen waiter. The action will do something like:
1. Output a string describing the intent of the action.
2. Update its waiter data structure to indicate a new customer has been assigned.
3. Update its table data structure
4. Update its customer data structure
5. Send a message to the waiter with the customer and table assignment.
All but 5 are easy to test. Easy in the sense that at least we’ve done that kind of testing before. Testing the message-send means that we have to make sure the message that this Host action composes is compliant with the Waiter’s messaging interface. Doesn’t this imply that we have to have the Waiter in our test suite in order to make sure it is received correctly? It seems to, but Mock Objects come to the rescue.
Mock objects are fake implementations of objects that have the same interface as the real objects. Their purpose is to SIMULATE the real objects strictly for testing. Of course, to use Mock Objects correctly the interfaces must obviously have been determined. Otherwise, how could anyone unit test anything?
Here’s how the Mock Object message handler works:
The point is that the Mock Object is just a hack, a well-thought out hack to be sure, that’s just there for simulation.
Let’s see what the mock Waiter agent needs to do in our current scenario.
In either case you have to test that your Host agent does the right thing on receiving the next message. Your testing code MUST KNOW what is coming back. If two or three responses are possible from the Waiter, it may be simpler to use method b.
This is probably your first introduction to writing simulation code. It’s actually more difficult than real code because understanding all the cases is important, but how to “manipulate” your testing environment to carry them all out can be challenging (and fun).
The agent you are building should not care whether it is running in the real or test environment. Therefore:
The TA’s will help you set all this up in lab next week.
Little has been said about what goes into an action. It is simply a chunk of “schedulable” code. So far in our restaurant application, the “look” of all the actions has been the same:
Sometimes it makes sense for an action to wait for some event to occur, and continue when it occurs. For example, imagine trying to design how a waiter might take the order from a table with several customers. Let’s assume for a moment that a customer can give the waiter a full order in one string. This action might look like the following:
Print
“waiter:” waiter “getting order from customer:” table.customer()
“at” table.number();
c.msgWhatWouldYouLike();
//Let’s
wait, but how??
storeOrder(order); //where did order come from???
Print
“waiter:” waiter.name() “received order” order
“from
customer:” table.customer();
The main difference is that this action does in one piece of code what we would have to do in two actions in our current agent design: one action to “ask” for the order and another to “store” it. We expressively avoided any mechanism like this before because since our scheduler was synchronized with its actions, it might wait forever for the response and be blocked from doing anything else. If we could solve that problem (and we will in the next section), we will have the convenience of more “natural” multi-step actions like the one above.
So, as indicated by the comments above, there are a few problems:
1. How do we actually wait?
2. How does the message from the customer unblock us?
3. Where did the order data come from?
The answer to 1 and 2 is to use a semaphore. And why not? Semaphores were designed to synchronize activities. Here, the waiter wants to wait until the customer places the order. It’s exactly the classic situation in which semaphores are first introduced. Remember?
First the agent must create a semaphore in the same scope as stateChange, something like:
Semaphore
waitForOrder = new Semaphore(0,true);
Then the line of code above that waits will
be:
waitForOrder.acquire()
It will wait until the semaphore is filled by the message reception. Something like:
void msgHereIsMyOrder(String order){
orderResponse.order
= order;
stateChanged();
waitForOrder.release();
}
The orderResponse data structure is another agent global variable used specifically for storing the data that comes from this message as shown above. Now the code for the action can be completed:
Print
“waiter:” waiter “getting order from customer:” table.customer() “at” table.number();
c.msgWhatWouldYouLike();
waitForOrder.acquire();
c.storeOrder(orderResponse.order);
Print
“waiter:” waiter.name() “received order”
orderResponse.order
“from customer:” table.customer();
There are two serious problems here that we will ignore when you implement this for your agents. Can you see them?
· You assumed that the response came from the customer you sent the message to. Will that always be the case? Maybe you should add a Customer parameter to the msgHereIsMyOrder() message handler and store that in orderResponse along with the order. Then your action code could test if it is from the right customer. What if it isn’t?
· Could you possibly get a message from a Customer you were not expecting? If you did, what would happen? [The single global data structure would be clobbered.] How would you solve this? [Have a queue of order-Customer pairs? Need a loop around the take which tests that the message came from the right customer, etc.]
As you can see this gets complicated in a hurry. Let’s not worry about those details for now.
Now that we’ve solved the multi-step action, more complicated actions are available to us. Here’s action code for taking orders from a table with many customers.
Print
“waiter:” waiter.name() “getting orders from table:” table.name();
for
{Customer c : table.customers()) { //java 1.5
iteration
Print “waiter:” waiter.name() “asking
customer:” c.name() “for order”;
c.msgWhatWouldYouLike();
waitForOrder.acquire();
c.storeOrder(orderResponse.order);
Print
“waiter:” waiter.name() “received order”
orderResponse.order
“from customer:” c.name();
}
So, we iterate through each customer at the table, asking and waiting. We could handle the iteration in the scheduler; we scheduler an action that does an “ask and store” for a single customer. Is that better? Why? [More easily interrupted?] We’ll see
Can you think of a different, realistic design that might involve multiple customer order messages arriving in arbitrary order? [Say the waiter “broadcasts” a message to the table: msgWhatWillYouBeOrdering(); Then the responses will come in any order. What will your action look like now?] How would you simulate a broadcast? [Loop through and send each the message. Then wait for replies.] In real life this puts the shared data burden on the customers to order themselves. What is the shared data? [The ear channel of the waiter who can only “hear” one message at a time. If the customers wrote down their order on paper or email, he could take them all “simultaneously.”
All that was relatively easy. Now we have to solve the problem that could arise from a blocked multi-step action.
You could send each agent a stopThread() message. That would work from a “computer science” point of view. But, that is surely inadequate from a “restaurant application” point of view. Here is the most important shutdown requirement:
· Once you close, accept no more customers.
Notice this says nothing about customers currently in the restaurant; you must finish serving them. Obviously, there would be many others about cleanup, closing out the cash register, shutting the restaurant, etc.
In the waiter requirements he hands a menu to the customers. Where do the menus come from? In a real restaurant that issue must be faced. In our implementation, the waiter "holds" the menus.
· How should the initial call to the agent scheduler happen?
· What has to be done if the agent constructor doesn’t call startAgent()?
· Why is pickAndExecuteAnAction() abstract?
· Why are there no message-handling calls in abstract Agent class? Could there be? [Yes, if you wanted all to respond to, say, a Status() message, then you could have an abstract Status() method in the base class.]
· Come up with a design for the requirement, “once you close, accept no more customers.”
· Can you figure out why the table number is part of the message in section 4.2.2.1?
· A single waiter may be servicing many clients. In some scenario, he may seat one customer, and then deliver a food order to another, and so forth. He will service the newly seated customer when the time comes. This, of course, brings up the topic of priorities. Can you come with a priority scheme?
· If no waiters are working, customers queue up with the host. Should the host do anything if the restaurant starts to get busy?
· Can you test the interleaving discussed in section 8.8 using JUnit?
· What is the reason that the customer’s choice is turned into an order in the interaction diagram of section 8.6?
3/8/2005 Added Chapter 9 about multi-step actions and concurrent actions.
3/31/2005 Adding Chapter 10 about RMI agents.
Summer 2006: Redid the roadmap to emphasize design skills.
Summer 2011 Removed v1, removed concurrent actions, starting from v3, added fsm customer agent.
Winter
Break 2012 General updating.