COMP 310
Spring 2019

Lec14: Command Dispatching

Home  Info  Canvas   Java Resources  Eclipse Resources  Piazza

Reference: Ballworld provided code (Includes documentation about the IDispatcher and IObserver interfaces and implementations)

Command-Based Updating

Instead of merely telling the ball to perform an invariant update method (single method to move, paint, bounce, etc) with an invariant parameter (the Graphics object) being passed, the dispatcher sends out a variant command object that the ball invariantly executes.

A command to the ball to do something is defined as such:

package ballworld.model;

/**
 * Interface that represents commands sent through the dispatcher to process the balls
 * 
 */
@FunctionalInterface
public abstract interface IBallCmd {
    /**
     * The method run by the ball's update method which is called when the ball is updated by the dispatcher.
     * @param context The ball that is calling this method.   The context under which the command is to be run.
     * @param disp The Dispatcher that sent the command out.
     */	
    public abstract void apply(Ball context, IDispatcher<IBallCmd> disp);
}

For instance, the dispatcher may send out a command to update the state:

_dispatcher.notifyAll(_updateStateCmd);

An anonymous inner class can be used above for the IBallCmd implementation without any loss of generality.  A typical dispatch using an anoymous inner class would look something like:

_dispatcher.updateAll(new IBallCmd() {

	@Override
	/**
	 * Do stuff with the ball
	 */
	public void apply(Ball context, IDispatcher<IBallCmd> disp) {
		// Whatever you want to do with the context Ball!			
	}				
});

// Or more simply, using lambda expressions:

_dispatcher.updateAll( (context, disp)->{
                                             // Whatever you want to do with the context Ball!
                                        });
                                        
// IMPORTANT: Above, the cast to IBallCmd is *required* due to the input type of notifyAll is Object 
// and without the cast to IBallCmd, the compiler cannot infer the typing of the lambda function.
// This cast is not needed if the dispatcher is generically typed to take IBallCmds 
// (see the link at the end of this page)

How do you think that anonymous inner classes can be used to communicate additional information (i.e. additional input parameters) to the balls?   Remember that the one free parameter that updateAll() used to accept is now taken up with the command being passed!

All the ball ever does is to run the command, passing itself and the Dispatcher:

/**
 * The update method called by the main ball Dispatcher to notify all the balls to perform the given command.
 * The given command is executed.
 * @param o The Dispatcher that sent the update request.
 * @param cmd The IBallCmd that will be run.
 */
public void update(IDipatcher<IBallCmd> disp, IBallCmd cmd) {
    cmd.apply(this, disp);
}

What would the method body of an IBallCmd.apply() implementation look like if you

With this, a ball can be made to do whatever you want it to do, whenever you want it to do it!

 

Switching Over To Command-based Dispatching

For now, let's just replicate our current behavior but with a command-based dispatching.

  1. Using the constructs above, change the update code of your ball to take an IBallCmd as its input parameter that it then simply "applies".  
  2. Change the code of in the model for the call to the dispatcher's updateAll method.   Use an anonymous inner class to define the IBallCmd to be sent to the balls (Why do we need an anonymous inner class here?)     Where do you think the code that used to be in the ball's update method now resides?

When you are done, your BallWorld system should run identically as before, but with a new door open to even more possibilities!

Did you have to touch your view code at all?   The controller code?   Why or why not?

For the adventurous: By installing a second Timer object, can you decouple the painting process from the updating of the ball state?   In doing so, can you fix the problem of resizing and animated GIFs affecting the behavior of the balls?

Making Balls Talk to Eachother

Above, we saw that sending commands to the balls enables us to make them do anything the model wants them to do.   But does it matter who sent the command?   What if it was another ball that sent the command?   Couldn't that ball make the other balls do something?  Or the sending ball be affected by the other balls?

All we need to do is to enable a ball to get a hold of the IDispatcher.   If it has that, it can send out a command just like the model.

With two simple changes to our current system, we can modify the Ball's updateState method and the IUpdateStrategy.updateState method to take the IDispatcher as an input parameter.   The latter change will unfortunately instigate a rather tedious chore of modifying all the concrete IUpdateStrategy implementations.  (Can you explain this phenomenon in terms of variant/invariant?).

The update strategy is modified to accept the IDispatcher:

public interface IUpdateStrategy {
	/**
	 * Update the state of the context Ball.
	 * @param context  The context (host) Ball whose state is to be updated
	 * @param dispatcher  The Dispatcher who sent the command that is calling through to here.
	 */
	public abstract void updateState(Ball context, IDispatcher<IBallCmd> dispatcher);
}

In the Ball, we pass the Dispatcher along to the update strategy.   The IBallCmd is responsible for passing it to the Ball's updateState method.

/**
 * Update the state of the ball.   Delegates to the update strategy.
 * @param dispatcher The Dispatcher that sent the command that is calling this method.
 */
public void updateState(IDispatcher<IBallCmd> dispatcher){
	_updateStrategy.updateState(this, dispatcher); // update this ball's state using the strategy.		
}

Now an IUpdateStrategy is free to send commands out through the dispatcher to all the other ball -- including itself.

Always check if you are receiving your own command!

Consider the following update strategy that spawns new balls when a ball contacts another.:

public class SpawnStrategy implements IUpdateStrategy {

	private int count = 0; // tick counter that counts out the delay before another ball can be spawned.
	private int delay = 100;  // tick delay which increases at each spawn to keep total spawn rate from exponentially exploding.

	@Override
	public void updateState(final Ball context, IDispatcher<IBallCmd> dispatcher) {
	
		if (delay < count++) {
			dispatcher.updateAll(new IBallCmd() {

				@Override
				public void apply(Ball other, IDispatcher<IBallCmd> disp) {

					if (count != 0 && context != other) {
						if ((context.getRadius() + other.getRadius()) > 
						    context.getLocation().distance(other.getLocation())) {
							disp.addObserver(new Ball(
									new Point(context.getLocation()), 
									context.getRadius(),
									new Point(-context.getVelocity().x+1,-context.getVelocity().y+1), 
									context.getColor(),
									context.getContainer(), 
									new SpawnStrategy(), 
									context.getPaintStrategy()));
							count = 0;
							delay *= 5;
						}
					}
				}

			});
		}
	}

}

// Or, using lambda expressions:
public class SpawnStrategy implements IUpdateStrategy {

	private int count = 0;
	private int delay = 100;

	@Override
	public void updateState(final Ball context, IDispatcher<IBallCmd> dispatcher) {

		if (delay < count++) {
			dispatcher.updateAll((other, disp) -> {

					if (count != 0 && context != other) {
						if ((context.getRadius() + other.getRadius()) > context
								.getLocation().distance(other.getLocation())) {
							disp.addObserver(new Ball(
									new Point(context.getLocation()), 
									context.getRadius(),
									new Point(-context.getVelocity().x+1,-context.getVelocity().y+1), 
									context.getColor(),
									context.getContainer(), 
									new SpawnStrategy(), 
									context.getPaintStrategy()));
							count = 0;
							delay *= 5;
						}
					}
				}
			);
		}
	}

}



© 2019 by Stephen Wong