views:

214

answers:

6

I'm developing a Java desktop flight simulation. I need to record all the pilot actions as they occur in the cockpit, such as throttle controls, steering, weapon deployment, etc. so that I can view these events at a later time (or stream them live).

I'd like to add a visual replay feature on the playback of the events so I can visually see the cockpit as I move forward and backward in time. There's no problem with the replay as long as I play back the event in chronological order, but the rewind is a little trickier.

How would you implement the rewind feature?

+2  A: 

You could use a variant of the Command Pattern and have each one of your pilot actions implement an undo operation.

For example if your pilot made the action steer left (simple, i know) the inverse of it would be steer right.

public interface IPilotAction {
    void doAction(CockpitState state);
    void undoAction(CockpitState state);
}

public class ThrottleControl implement IPilotAction {

     private boolean increase;
     private int speedAmount;

     public ThrottleControl(boolean increase, int speedAmount) {
         this.increase = increase;
         this.speedAmount = speedAmount;
     }

     public void doAction(CockpitState state) {
         if (increase) {
            state.speed += speedAmount;
         } else {
            state.speed -= speedAmount;
         }
     }

     public void undoAction(CockpitState state) {
         if (increase {
             state.speed -= speedAmount;
         } else {
             state.speed += speedAmount;
         }
}
Kirschstein
+3  A: 

I would use a modified Memento pattern.

The difference would be that I would have the Memento object store a list of all of the pilot actions.

The Memento pattern is typically used for rolling back (undo), however in your case I could see it applying as well. You would need to have the pilot actions be store-able states as well.

AlbertoPL
Would this approach have acceptable performance for a flight sim? For example if I'm playing back events for 20 min and then decide to jump to time 2 min, I'd have to undo 18 min worth of events. There could potentially be hundreds of events to undo.
It depends. The more data you store in each individual action, the greater response you'll get, at the cost of memory. You could store the time interval in which each action occurred as well as ALL data you'd need for the event to take place, then look through the list in chronological order until you find the state you were in at that time period. You'd then have to load all of those variables that are stored. This will take up a bit of memory, but it'll save on performance.
AlbertoPL
A: 

What you're looking for is actually a blend of the Command and Memento patterns. Every pilot action should be a command that you can log. Every logged command has, if req'd, a memento recording any additional state that (A) is not in the command, and (B) cannot reliably be reconstructed. The "B" is important, there's some of this state in pretty much any non-trivial domain. It needs to be stored to recover an accurate reconstruction.

If you merge these concepts, essentially attaching a memento to each command, you'll have a fully logged series of deterministic events.

I discussed this at more length in a different answer. Don't be afraid to substantially adapt the design patterns to your specific needs. :)

RE Performance Concerns:

If you expect jumping a number of minutes to be a frequent case, and after implementation you show that it's an unworkable performance bottleneck, I would suggest implementing an occasional "snapshot" along with the logging mechanism. Essentially save the entire application state once every few minutes to minimize the amount of log-rolling that you need to perform. You can then access the desired timeframe from the nearest saved state. This is analogous to key frames in animation and media.

Greg D
So you would implement the rollback feature (memento/command) and snapshot feature? Would a 10 sec interval snapshot system not make the rollback obsolete?
Snapshots are going to, implicitly, be much bigger than a changelog (the memento/command blend). If you want instant move, and you have the memory to burn, you could do it that way, but it's a tradeoff, right? Time vs. Memory.You're the only person who knows how much of each is acceptable to burn. (Though taking a full appstate snapshot every 10 secs seems likely to exhaust resources *very* quickly). I suggest avoiding premature optimization. Take it one step at a time. Get either appstate snapshots or logging working, then move to the other, and then balance between them as needed.
Greg D
A: 

Not a direct answer, but check out discussion of implementing undo. Mostly they will be about text editors, but the same principles should apply.

It helps if you prefer immutability. Undoing complex changes is difficult. Even automated systems have performance problems (Software Transaction Memory, STM).

Tom Hawtin - tackline
A: 

Make sure that you've implemented the simulation in such a way that the simulation's "state" is a function. That is, a function of time.

Given an initial state at time T0, you should be able to construct the simulation frame at time Tn for any n. For example, an initial stationary state and no events (yet) might equal the identity function, so Tn == Tn+1.

Given some pilot action event at time Ta, you should be able to construct a frame Ta+n for any n. So you think of events as modifying a function that takes a time value as argument and returns the frame of the simulation for that time.

I would implement the history of events as a Zipper of (time, function) pairs representing the control state of the simulation. The "current" state would be in focus, with a list of future states on the right, and past states on the left. Like so:

([past], present, [future])

Every time the simulation state changes, record a new state function in the future. Running the simulation then becomes a matter of taking functions out of the future list and passing the current time into them. Running it backwards is exactly the same except that you take events out of the past list instead.

So if you're at time Tn and you want to rewind to time Tn-1, look into the past list for the latest state whose time attribute is less than n-1. Pass n-1 into its function attribute, and you have the state of simulation at time Tn-1.

I've implemented a Zipper datastructure in Java, here.

Apocalisp
A: 

you can just store the state at every instance. 1kb for state (wind speed, object speeds + orientation / control input states, x 30fps x 20 min ~ 36megs. 1kb of state would let you record about 16 objects (pos / speed / angular speed / orientation / and 5 axis of control / effect)

that may be too much for you, but it will be easiest to implement. there will have to be no work done at all to recreate state (instant acecss), and you can interpolate between states pretty easy (for faster / slower playback). for disk space you can just zip it, and that can be done while recording, so while playing that memory is not being hogged.

a quick way to save space would be to paginate the recording file, and compress each bin separately. ie one zip stream for each minute. that way you would only have to decompress the current bin, saving a bunch on memory, but that depends how well your state data zips.

recording commands and having your class files implement multiple directions of playback would require a lot of debugging work. slowing / speeding up playback would also be more computationally intensive. and the only thing you save on is space.

if thats a premium, there are other ways to save on that too.

aepurniet