State

The state of an agent is a user-defined set of variables in the agent class whose values are saved whenever the agent is moved or swapped or a configuration containing that agent is saved. The only restriction on the types that can be used in a state is that the classes must implement java's Serializable interface. The state is saved via java's serialization process, but the exact format of that storage is up to the programmer; furthermore, if necessary, the programmer can override the serialization

Connect the two agents in Module Manager. Set each of them to run on the computer you're running your NetController and to use the AgentServer. Save the configuration as waveform.cfg and execute the system. You'll notice that the square wave it produces has a frequency of 2Hz and a duty cycle of 50%. While these parameters are editable in the source code, it's annoying to have to recompile the agent just to do so. Furthermore, that's not much help if a user only has the class file. So we'll make these two parameters part of the agent's state.

Let's not worry yet about the editing part and look at what's just necessary to make those variables part of the state.

The first required method we need to add is dumpState(). It takes each of the internal data members that are part of the state (as determined by the programmer) and places them within a java Object (in this case an array of Objects, which is fine since an array is a subclass of Object). This function is used to save the state of the agent.

/**********************************************************************
serializes my state into an object.
@param none
@return Object holding the state
@exception none
**********************************************************************/
public Object dumpState(){
   Object state[]=new Object[2];
   state[0]=new Float(this.frequency);
   state[1]=new Float(this.dutyCycle);
   return (state);
} /* dumpState() */

The second required method is processInternalState(). This method is used to restore the state of the agent. It takes the Object produced by dumpState(), pulls each of the data members out, and sets the internal data members of the agent accordingly.

/**********************************************************************
takes a dumped object and loads it into my state.
@param none
@return true == OK, false == bad.
@exception none
**********************************************************************/
  public boolean processInternalState( Object statearr ){
    boolean retval=true;
    float period;
    Object[] state;
    System.out.println(getID()+" is processing internal state...");
    if (statearr!=null){
        state=(Object[])statearr;
    }
    else{
        state=(Object[])getDefaultState();
    }
    try{
        this.frequency=((Float)state[0]).floatValue();
        this.dutyCycle=((Float)state[1]).floatValue();

        period=1000.0f/frequency;
        this.periodHigh=period*this.dutyCycle;
        this.periodLow=period*(1-this.dutyCycle);
    }catch(Exception e){  
        retval=false;  
    }
    return (retval);
  } /* processInternalState() */

We also add a third method, getDefaultState(), that is used if the retrieval of the state fails or is null (as it often is initially if a state editor is not used in Module Manager). It is not strictly necessary here but is very helpful to give a user an idea of the format of the state and also is used by the default state editor we'll be using later.

/*******************
returns an object that can be used as the default state
@param none
@return Object containing state
@exception none
*********************************/
  public Object getDefaultState(){
    Object[] state=new Object[2];
    state[0]=new Float(1.0f);
    state[1]=new Float(0.5f);
    return state;
  }

Additionally, we need to remove from allocateInternals() the lines of code initializing the variables that will now be part of the state.

**********************************************************************
Description: allocates data internals.
@param none
@return nada
@exception none
**********************************************************************/
protected void allocateInternals(){
   this.level=false;
} /* allocateInternals() */

Side note: I personally tend to store state as an array of Objects. One reason for this is that is compatible with the default state editor we'll encounter later. But there is no restriction to this.You can store your state however you like, as long as it is a valid java Object.

It also should be noted that the order in which the setup functions are called is initialize(), allocateInternals(), processInternalState(), and initOutputVals(). This means that we didn't actually have to clear out the contents of allocateInternals(), since they would be overridden in processInternalState(), but it is much better practice to allocate only variables that have no dependence on the state in allocateInternals(), since it is conceivable that the order those functions are called could be changed in the future.

Recompile your agents. Now go back to ModuleManager. Launch waveform.cfg. If you've done everything right, it should behave exactly the same as it did the last time, except now you'll notice in NetController that at the beginning of execution it prints the statement we included that the agent is processing its internal state. That's our clue that we've set up the state correctly. But since this agent does nothing to edit its own state, this isn't very impressive yet. That's where the state editor comes in.

Prev Next


Copyright 2000, Carnegie Mellon University
This page written by Jonathan Jackson

Last modified: Thu May 18 19:11:32 EDT 2000