View Javadoc

1   package cz.cuni.amis.pogamut.sposh.executor;
2   
3   import cz.cuni.amis.pogamut.sposh.engine.VariableContext;
4   import java.text.MessageFormat;
5   import java.util.HashMap;
6   import java.util.logging.Logger;
7   
8   /**
9    * This class is an {@link IWorkExecutor}, that considers actions to be have state,
10   * in this executor, primitive is working in phases INIT, RUN*, DONE.
11   * <p/>
12   * Thanks to state nature of primitive, primitive is far easier to program 
13   * in discrete time-slice (time slice = primitive can be called multiple times consequentially)
14   * nature of Pogamut. If primitive wasn't notified that it is about to end,
15   * things like switching from primitive "walk" to "shoot" could be troublesome.
16   * Imagine it, "walk" primitive will compute path to destination and suddently
17   * new primitive "shoot" is now called. What about path the bot is following? It
18   * will still follow it, although it is supposed to stop and start shooting.
19   * To handle this correctlty is troublesome for few states, for many states, it is madness.
20   * <p/>
21   * StateWorkExecutor would do this: {@code ..., walk.INIT, walk.RUN*, walk.DONE, shoot.INIT, shoot.RUN*, shoot.DONE....},
22   * primitive walk would have DONE called before shoot.INIT would be called,
23   * allowing it to stop walking. Same thing is valid for state shoot too.
24   * <p/>
25   * Since we have phase DONE to cleanup some stuff before we switch to another,
26   * where another can be nearly anything, what state bot should be in when DONE
27   * phase is DONE. In neutral bot state (precise neutral state is defined by programmer,
28   * in unreal, that would probably be standing, not shooting.).
29   * <p/>
30   * What if we don't want to switch to neutral bot state after primitive is DONE?
31   * Don't, there is no explicit need, and in many situation it is meaningless (such as
32   * primtive "enter_ducts" where bot would entering INIT in standing state, but left 
33   * DONE crouching).
34   *
35   * @see IAction
36   * @author Honza
37   */
38  public class StateWorkExecutor implements IWorkExecutor {
39  
40      /**
41       * Map that maps primtive name to {@link IAction}.
42       */
43      protected final HashMap<String, IAction> actions = new HashMap<String, IAction>();
44      /**
45       * Map that maps primitive name to its respective {@link ISense}.
46       */
47      protected final HashMap<String, ISense> senses = new HashMap<String, ISense>();
48      /**
49       * Primitive that is currently being executed. If no such primitive, null is used.
50       */
51      protected String currentActionName;
52      /**
53       * String representation of current variable context. If ctx is changed, we
54       * have to do done and init phase for the action.
55       * e.g. GoToBase($target = "our") and GoToBase($target = "enemy") have same 
56       * action name, but different target.
57       */
58      protected String currentVariableContext;
59      /**
60       * Log where we put
61       */
62      protected Logger log;
63      
64      public StateWorkExecutor() {
65          this.log = Logger.getLogger(getClass().getSimpleName());
66      }
67  
68      public StateWorkExecutor(Logger log) {
69          this.log = log;
70      }
71  
72      /**
73       * Get logger of this {@link IWorkExecutor}.
74       * @return
75       */
76      public Logger getLogger() {
77          return log;
78      }
79  
80      /**
81       * Is name used in some primtive in this the work executor.
82       * @param name queried name
83       * @return if the name is not yet used in the {@link StateWorkExecutor}.
84       */
85      public synchronized boolean isNameUsed(String name) {
86          return isSense(name) || isAction(name);
87      }
88  
89      /**
90       * Is there an action with the name.
91       * @param name queries name
92       */
93      protected boolean isAction(String name) {
94          return actions.containsKey(name);
95      }
96  
97      /**
98       * Is there a sense with the name.
99       * @param name queries name
100      */
101     protected boolean isSense(String name) {
102         return senses.containsKey(name);
103     }
104 
105     /**
106      * Add new {@link IAction} with primitive name.
107      * @param name name that will be used for this {@link IAction} in posh plan.
108      * @param action primitive that will be executed when executor will be asked to execute primtive with name.
109      * @throws IllegalArgumentException if primitive with name already exists in {@link StateWorkExecutor}.
110      */
111     public synchronized void addAction(String name, IAction action) {
112         if (isNameUsed(name)) {
113             throw new IllegalArgumentException("Primtive with name \"" + name + "\" is already present in executor.");
114         }
115         actions.put(name, action);
116     }
117 
118     /**
119      * Add new {@link ISense} with primtive name.
120      * @param name name of primtive to be associated with passed sense object
121      * @param sense sense object to be used, when sense with name is supposed to execute.
122      */
123     public synchronized void addSense(String name, ISense sense) {
124         if (isNameUsed(name)) {
125             throw new IllegalArgumentException("Primtive with name \"" + name + "\" is already present in executor.");
126         }
127         senses.put(name, sense);
128     }
129 
130     /**
131      * Add primitive, use name from annotations.
132      * @param action primitive that will be added
133      */
134     public void addAction(IAction action) {
135         throw new UnsupportedOperationException("Not supported yet.");
136     }
137 
138     /**
139      * Add primitive, use name from annotations.
140      * @param sense primitive that will be added
141      */
142     public void addSense(ISense sense) {
143         throw new UnsupportedOperationException("Not supported yet.");
144     }
145     
146     private String getSenseName(ISense sense, String primitiveName) {
147     	PrimitiveInfo info = sense.getClass().getAnnotation(PrimitiveInfo.class);
148     	if (info == null) return primitiveName;
149     	if (info.name() == null) return primitiveName;
150     	return info.name();
151     }
152     
153     private String getActionName(IAction sense, String primitiveName) {
154     	PrimitiveInfo info = sense.getClass().getAnnotation(PrimitiveInfo.class);
155     	if (info == null) return primitiveName;
156     	if (info.name() == null) return primitiveName;
157     	return info.name();
158     }
159     
160     @Override
161     public synchronized Object executeSense(String primitive, VariableContext ctx) {
162         if (!isSense(primitive)) {
163         	throw new IllegalArgumentException("Sense \"" + primitive + "\" is not specified in the worker.");
164         }
165         
166         ISense current = senses.get(primitive);
167         Object result = current.query(ctx);
168         
169         log.info(MessageFormat.format("Query: {0}({1}) = {2}", getSenseName(current, primitive), ctx, result));
170         
171         return result;
172     }
173 
174     @Override
175     public synchronized ActionResult executeAction(String actionToExecuteName, VariableContext ctx) {
176         if (!isAction(actionToExecuteName)) {
177             throw new IllegalArgumentException("Action \"" + actionToExecuteName + "\" is not specified in the worker.");
178         }
179 
180         // FIND ACTION TO BE EXECUTED
181         IAction actionToExecute = actions.get(actionToExecuteName);
182         String variableContextToExecute = ctx.toString();
183         // FIND PREVIOUSLY EXECUTED ACTION
184         IAction currentAction = actions.get(currentActionName);
185 
186         // NO PREVIOUS ACTION?
187         if (currentAction == null) {
188         	// YES!
189         	// => set action to be current
190             currentActionName = actionToExecuteName;
191             currentVariableContext = ctx.toString();
192             currentAction = actionToExecute;
193             
194             // => initialize new action
195             log.info(MessageFormat.format("Action: {0}.init({1})", getActionName(currentAction, currentActionName), ctx));
196             currentAction.init(ctx);
197         } else // ACTION SWITCH? Actions are switched when action name is changed or variable context is changed
198         if ((currentAction != null && actionToExecute != currentAction) ||
199                 !variableContextToExecute.equals(currentVariableContext)) {
200         	// YES, NEW ACTION SCHEDULED
201         	// => finalize previous one
202         	log.info(MessageFormat.format("Action: {0}.done({1})", getActionName(currentAction, currentActionName), ctx));
203         	currentAction.done(ctx);
204             
205             // => swap actions
206             currentActionName = actionToExecuteName;
207             currentVariableContext = variableContextToExecute;
208             currentAction = actionToExecute;
209             
210             // => initialize new action
211             log.info(MessageFormat.format("Action: {0}.init({1})", getActionName(currentAction, currentActionName), ctx));
212             currentAction.init(ctx);
213         }
214         
215         // run current action        
216         ActionResult result = currentAction.run(ctx);
217         log.info(MessageFormat.format("Action: {0}.run({1}) = {2}", getActionName(currentAction, currentActionName), ctx, result));
218         
219         // DID ACTION FINISHED?
220         if (result == ActionResult.FINISHED || result == ActionResult.RUNNING_ONCE) {
221         	// YES!
222         	// => finalize it
223         	log.info(MessageFormat.format("Action: {0}.done({1})", getActionName(currentAction, currentActionName), ctx));
224             currentAction.done(ctx);
225             // => nullify that we have previously executed (<= ACTION IS FINISHED)
226             currentAction = null;
227             currentActionName = null;
228             currentVariableContext = null;
229         }
230         
231         return result;
232     }
233 }