Coverage Report - net.cscott.sdr.ChoreoEngine
 
Classes in this File Line Coverage Branch Coverage Complexity
ChoreoEngine
0%
0/39
0%
0/6
2.81
ChoreoEngine$1
0%
0/5
N/A
2.81
ChoreoEngine$2
0%
0/8
N/A
2.81
ChoreoEngine$3
0%
0/1
N/A
2.81
ChoreoEngine$DanceThread
0%
0/42
0%
0/12
2.81
ChoreoEngine$InputThread
0%
0/53
0%
0/33
2.81
 
 1  
 package net.cscott.sdr;
 2  
 
 3  
 import java.util.concurrent.BlockingQueue;
 4  
 import java.util.concurrent.ConcurrentHashMap;
 5  
 import java.util.concurrent.ConcurrentMap;
 6  
 import java.util.concurrent.CountDownLatch;
 7  
 import java.util.concurrent.LinkedBlockingQueue;
 8  
 
 9  
 import net.cscott.jutil.MultiMap;
 10  
 import net.cscott.sdr.CommandInput.InputMode;
 11  
 import net.cscott.sdr.CommandInput.PossibleCommand;
 12  
 import net.cscott.sdr.HUD.MessageType;
 13  
 import net.cscott.sdr.calls.BadCallException;
 14  
 import net.cscott.sdr.calls.CallDB;
 15  
 import net.cscott.sdr.calls.DanceProgram;
 16  
 import net.cscott.sdr.calls.DanceState;
 17  
 import net.cscott.sdr.calls.Dancer;
 18  
 import net.cscott.sdr.calls.DancerBezierPath;
 19  
 import net.cscott.sdr.calls.DancerPath;
 20  
 import net.cscott.sdr.calls.Evaluator;
 21  
 import net.cscott.sdr.calls.Formation;
 22  
 import net.cscott.sdr.calls.Program;
 23  
 import net.cscott.sdr.calls.TimedFormation;
 24  
 import net.cscott.sdr.calls.ast.Apply;
 25  
 import net.cscott.sdr.calls.ast.Expr;
 26  
 import net.cscott.sdr.calls.ast.Seq;
 27  
 import net.cscott.sdr.util.Fraction;
 28  
 
 29  
 /** 
 30  
  * {@link ChoreoEngine} specifies the interface through which the choreography
 31  
  * engine communicates with the rest of the SDR application.
 32  
  * @author C. Scott Ananian
 33  
  * @version $Id: ChoreoEngine.java,v 1.2 2006-11-10 00:56:07 cananian Exp $
 34  
  */
 35  0
 public class ChoreoEngine {
 36  
     private DanceThread danceThread;
 37  
     private DanceState ds;
 38  
     private final DanceFloor danceFloor;
 39  
     private final ScoreAccumulator score;
 40  
     private final HUD hud;
 41  
     public final Mode mode;
 42  
     public ChoreoEngine(DanceProgram dp, Formation f, DanceFloor danceFloor,
 43  
                         ScoreAccumulator score, HUD hud,
 44  0
                         CommandInput input) {
 45  0
         this.danceFloor = danceFloor;
 46  0
         this.score = score;
 47  0
         this.hud = hud;
 48  0
         this.mode = new Mode(input, this);
 49  0
         this.ds = new DanceState(dp, f);
 50  0
         this.danceThread = null;
 51  0
         switchToMenu();
 52  0
         new InputThread(input, this, score).start();
 53  0
     }
 54  
 
 55  0
     private BlockingQueue<DanceThread> danceQueue =
 56  
         new LinkedBlockingQueue<DanceThread>();
 57  
 
 58  
     private synchronized void waitUntilStopped() {
 59  0
         if (danceThread == null) return;
 60  0
         danceThread.stopDancing();
 61  
         while (true) {
 62  
             try {
 63  0
                 danceThread.join();
 64  0
                 danceThread = null;
 65  0
                 return;
 66  0
             } catch (InterruptedException e) {
 67  
                 /* keep waiting */
 68  0
             }
 69  
         }
 70  
     }
 71  
 
 72  
     private void startNewDanceThread(DanceThread dt) {
 73  0
         danceQueue.add(dt); // this defines ordering
 74  0
         synchronized (this) {
 75  0
             waitUntilStopped();
 76  0
             assert danceThread == null;
 77  
             while (true)
 78  
                 try {
 79  0
                     dt = danceQueue.take();
 80  0
                     break;
 81  0
                 } catch (InterruptedException e) { /* repeat */ }
 82  0
             dt.start();
 83  0
             danceThread = dt;
 84  0
         }
 85  0
     }
 86  
 
 87  
     public CountDownLatch switchToMenu() {
 88  0
         final CountDownLatch done = new CountDownLatch(1);
 89  0
         new Thread() {
 90  
             @Override
 91  
             public void run() {
 92  0
                 Evaluator e = new Evaluator.Standard
 93  
                     (new Seq(new Apply(Expr.literal("_attract"))));
 94  0
                 startNewDanceThread
 95  
                     (new DanceThread(new DanceProgram(Program.PLUS), e));
 96  0
                 done.countDown(); // signal
 97  0
             }
 98  
         }.start();
 99  0
         return done;
 100  
     }
 101  
     public CountDownLatch switchToDancing() {
 102  0
         final CountDownLatch done = new CountDownLatch(1);
 103  0
         new Thread() {
 104  
             @Override
 105  
             public void run() {
 106  
                 // XXX
 107  0
                 Evaluator e = new Evaluator.Standard
 108  
                     (new Seq(new Apply(Expr.literal("nothing"))));
 109  0
                 synchronized (ChoreoEngine.this) {
 110  0
                 startNewDanceThread
 111  
                     (new DanceThread(new DanceProgram(Program.PLUS), e));
 112  0
                 score.resetScore(Program.PLUS);
 113  0
                 }
 114  
                 //hud.setNotice("Let's go!", 5000);
 115  0
                 done.countDown(); // signal.
 116  0
             }
 117  
         }.start();
 118  0
         return done;
 119  
     }
 120  
     
 121  
     /**
 122  
      * Given a {@link TimedFormation} representing the "current" dancer
 123  
      * formation, perform the given call.  The result will be a list of
 124  
      * future formations, sorted by the time at which they occur (with the
 125  
      * earliest first).  No dancer will turn more than 1 wall (90 degrees)
 126  
      * between formations. All times in the {@code TimedFormation}s will be
 127  
      * absolute.
 128  
      * @param unparsedCall  The (sequence of) call(s) to perform.
 129  
      * @return a time-stamped list of result formations, with absolute times.
 130  
      * @throws BadCallException if (some part of) the given call is impossible
 131  
      *  from the given start formation.
 132  
      */ 
 133  
     public MultiMap<Dancer,DancerPath>
 134  
     execute(String unparsedCall, ScoreAccumulator score)
 135  
     throws BadCallException {
 136  0
         if (true) return null;
 137  
         Apply call = CallDB.INSTANCE.parse(ds.dance.getProgram(), unparsedCall);
 138  
         Evaluator e = new Evaluator.Standard(new Seq(call));
 139  
         // XXX: we don't actually want to do evaluateAll(); we want to
 140  
         //      return a continuation which will do evaluate() as needed,
 141  
         //      so that predicates such as "exist more calls" will work right.
 142  
         //assert false : "unimplemented"; // XXX: unimplemented
 143  
         // List<TimedFormation> returned from call evaluation will be
 144  
         // relative.  We will need to convert to an absolutely-timed list
 145  
         // before we return it.
 146  
         return null;
 147  
     }
 148  0
     public Apply lastCall() { return null; }
 149  0
     public Formation currentFormation() { return null; }
 150  
 
 151  
     class DanceThread extends Thread {
 152  0
         private final ConcurrentMap<String,String> props =
 153  
             new ConcurrentHashMap<String,String>();
 154  0
         private final Fraction MARGIN = Fraction.TWO; // dance two beats ahead
 155  
         private final DanceState initialDanceState;
 156  
         private final Evaluator initialEvaluator;
 157  
         private boolean isEnding, goingHome;
 158  0
         DanceThread(DanceProgram dp, Evaluator e) {
 159  0
             props.put("call-pending", "false");
 160  0
             this.initialDanceState = new DanceState
 161  
                 (dp, Formation.SQUARED_SET, props);
 162  0
             this.initialEvaluator = e;
 163  0
             this.isEnding = false;
 164  0
             this.goingHome = false;
 165  0
             this.setDaemon(true);
 166  0
         }
 167  
 
 168  
         public synchronized void stopDancing() {
 169  0
             this.isEnding = true;
 170  0
             props.put("call-pending", "true");
 171  0
         }
 172  
         private synchronized boolean isEnding() {
 173  0
             return this.isEnding;
 174  
         }
 175  
 
 176  
         @Override
 177  
         public void run() {
 178  0
             DanceState ds = initialDanceState;
 179  0
             Evaluator e = initialEvaluator;
 180  0
             Fraction offsetTime = danceFloor.waitForBeat(Fraction.ZERO);
 181  
             // round to multiple of 8 beats so we start on a phrase.
 182  0
             offsetTime = Fraction.valueOf((offsetTime.intValue()/8)*8 + 8);
 183  
 
 184  0
             while (e != null) {
 185  0
                 e = e.evaluate(ds);
 186  0
                 ds.syncDancers();
 187  0
                 for (Dancer d : ds.dancers()) {
 188  0
                     Fraction startTime = offsetTime;
 189  0
                     for (DancerPath dp : ds.movements(d)) {
 190  0
                         DancerBezierPath dbp = dp.bezier(startTime);
 191  0
                         startTime = startTime.add(dp.time);
 192  0
                         danceFloor.addPath(d, dbp);
 193  0
                     }
 194  0
                 }
 195  0
                 offsetTime = offsetTime.add(ds.currentTime());
 196  0
                 danceFloor.waitForBeat(offsetTime.subtract(MARGIN));
 197  0
                 ds = ds.cloneAndClear();
 198  0
                 if (this.isEnding()) {
 199  0
                     if (!goingHome) {
 200  
                         // go home!
 201  0
                         e = new Evaluator.Standard(new Seq(new Apply(Expr.literal("go home"))));
 202  0
                         goingHome = true;
 203  
                     }
 204  
                 } else { // !isEnding
 205  0
                     if (e==null) {
 206  
                         // align dancers to the beat
 207  0
                         offsetTime = Fraction.valueOf(offsetTime.floor());
 208  
                         // do nothing for a beat
 209  0
                         e = new Evaluator.Standard(new Seq(new Apply(Expr.literal("nothing"))));
 210  
                         // subtract timeliness points.
 211  0
                         score.dancersWaiting();
 212  
                     }
 213  
                 }
 214  
             }
 215  0
             danceFloor.waitForBeat(offsetTime);
 216  0
         }
 217  
     }
 218  0
     private class InputThread extends Thread {
 219  
         private final CommandInput input;
 220  
         private final ChoreoEngine choreo;
 221  
         private final ScoreAccumulator score;
 222  
 
 223  
         InputThread(CommandInput input, ChoreoEngine choreo,
 224  0
                      ScoreAccumulator score) {
 225  0
             this.input = input; this.choreo = choreo; this.score = score;
 226  0
             this.setDaemon(true);
 227  0
         }
 228  
 
 229  
         private void doOneInput() {
 230  
             PossibleCommand pc;
 231  
             while (true) {
 232  
                 try {
 233  0
                     pc = input.getNextCommand();
 234  0
                     break;
 235  0
                 } catch (InterruptedException e) {
 236  0
                     continue; // try again!
 237  
                 }
 238  
             }
 239  0
             InputMode im = pc.getMode();
 240  
             // XXX what if mode is new?
 241  0
             for ( ; pc != null; pc = pc.next()) {
 242  0
                 if (processInput(pc))
 243  0
                     return;
 244  
             }
 245  0
             processFail();
 246  0
         }
 247  
         private boolean processInput(PossibleCommand pc) {
 248  0
             String thisGuess = pc.getUserInput();
 249  0
             System.err.println("PROCESS INPUT "+thisGuess);
 250  0
             if (thisGuess == null)
 251  0
                 return false;
 252  0
             if (thisGuess == PossibleCommand.UNCLEAR_UTTERANCE) {
 253  0
                 hud.setMessage("I couldn't hear you", MessageType.ADVICE);
 254  0
                 return false;
 255  
             }
 256  
             // XXX look at mode, process command
 257  0
             if (pc.getMode().mainMenu() && thisGuess.equals("square up"))
 258  0
                 mode.switchToDancing();
 259  0
             return true;
 260  
         }
 261  
         private void processFail() {
 262  
 
 263  0
         }
 264  
 
 265  
         private void doNextCall() throws InterruptedException {
 266  0
             String bestGuess = null, message = null;
 267  
             // get the next input
 268  0
             PossibleCommand pc = input.getNextCommand();
 269  
             // go through the possibilities, and see if any is a valid call.
 270  0
             while (pc != null) {
 271  0
                 String thisGuess = pc.getUserInput();
 272  0
                 if (thisGuess == PossibleCommand.UNCLEAR_UTTERANCE) {
 273  0
                     hud.setMessage("I couldn't hear you", MessageType.ADVICE);
 274  0
                     return;
 275  
                 }
 276  0
                 switch (mode.getMode()) {
 277  
                 case MAIN_MENU:
 278  0
                     if (thisGuess.equals("square up"))
 279  0
                         mode.switchToDancing();
 280  
                     break;
 281  
                 case DANCING:
 282  
                     try {
 283  
                         //sendResults(choreo.execute(thisGuess, score));
 284  
 
 285  
                         // this was a good call!
 286  0
                         sendToHUD(thisGuess);
 287  0
                         score.goodCallGiven(choreo.lastCall(), choreo.currentFormation(), pc.getStartTime(), pc.getEndTime());
 288  0
                         return;
 289  0
                     } catch (BadCallException be) {
 290  0
                         if (bestGuess==null ||
 291  
                             (message==null && be.getMessage()!=null)) {
 292  0
                             bestGuess = thisGuess;
 293  0
                             message = be.getMessage();
 294  
                         }
 295  
                         // try the next possibility.
 296  
                     }
 297  
                 }
 298  0
                 pc = pc.next();
 299  0
             }
 300  
             // if we get here, then we had a bad call
 301  
             // (none of the possibilities were good)
 302  
             // XXX IF MODE IS MAIN MENU
 303  0
             assert bestGuess != null;
 304  0
             if (message==null) message="Unknown problem";
 305  0
             score.illegalCallGiven(bestGuess, message);
 306  0
             sendToHUD(bestGuess+": "+message);
 307  0
             return;
 308  
         }
 309  
         private void sendToHUD(String s) {
 310  
             // TODO: write me!
 311  0
             System.err.println("HUD: "+s);
 312  0
         }
 313  
 
 314  
         @Override
 315  
         public void run() {
 316  
             while (true) {
 317  0
                 doOneInput();
 318  
             }
 319  
         }
 320  
     }
 321  
 }