• Keine Ergebnisse gefunden

5.5 Automatic Undo/Redo

5.5.3 Concept fundamentals

Our concept to extend the Java 3D API, so that it can enable a straightforward development of efficient reversible 3D applications, is scene graph-based and inde-pendent of the application type. It applies the memento pattern and builds on the following simple thought: We extend the classes of the API and make each of them undoable. An object is undoable if it is capable of sequentially recovering its earlier

states; in other words, when it is able to autonomously reverse any actions that have affected and changed its state. Based on the assumption that the scene graph of an application is assembled from undoable objects, each of which can be reached from the root, the traversing of the scene graph only once, and in doing so requiring each object to restore its, say i-th state, this will result in restoring the entire state of the application at timei.

To become undoable, any scene graph object or a NodeComponent in Java 3D is required to implement a specific undoable interface. Figure 20 shows a hierarchy of undoable interfaces that can be implemented by Java 3D classes.

Figure 20: Undoable interface hierarchy

As can be seen, IUndoableObject is the super interface of all other undoable interfaces. It defines, among others, the following two methods saveState() and restoreState(). During the execution, the application is supposed to take a snapshot of the scene graph at the end of each step using a special object called undo/redo manager. To do so, it uses a method called takeSnapshot(). The procedure for taking a snapshot is straightforward: whenever a snapshot is to be taken, the undo/redo manager creates a unique snapshot id. It then traverses the scene graph in pre-order starting at its root, visits every node of the scene graph exactly once and requires it to save its current state by invoking its saveState() method and

5.5. AUTOMATIC UNDO/REDO 107 passing the snapshot id to it.

Each undoable object is expected to implement its own saveState(snapshotID) method in such a way that the method saves the values of the object’s attributes in special containers (see Subsection 5.5.4). Saving the states of all objects, however, will enable us to restore their previous states, yet not their relation amongst each other, i.e., the structure of the scene graph. Therefore, each time the undo/redo manager traverses the graph, it collects structural information that makes the sub-sequent rebuilding of its structure possible. This information involves, for example, the id of each group2 and the types of its children nodes. All of this information is saved in a special data structure calledSnapshot. When the undo/redo manager creates a snapshot object, it assigns it a unique id and saves it in a special ordered list (see Figure 21). The undo/redo manager is the only object that is allowed

Figure 21: An illustration of the structure of the undo/redo manager

to create snapshot objects. Therefore it is important to stress that a scene graph object does not use instances of the Snapshot class to take snapshots of itself, but uses special containers for this purpose instead.

In addition to the structural information, a snapshot object maintains a refer-ence to each node of the scene graph and implements the method reconstructScene-Graph(). To reverse the i-th step of the application, the undo/redo manager needs

2Every undoable scene graph object has a unique id assigned to it at instantiation time. The id of a non-undoable object is its hash code.

to reconstruct the content and structure of the scene graph which was present be-fore executing step (i+ 1). This is the moment in which a snapshot of thei-thstate was taken. To do so, the undo/redo manager invokes reconstructSceneGraph() of snapshot i. This method uses the structural information saved in the snapshot object to reconstruct the entire scene graph of step i. During this process, each time a node is added to the graph being reconstructed, this node is required to in-voke its ownrestoreState() method. This method replaces the values of the current node’s attributes by those which were saved when snapshotiwas taken. Listing B.6 in the appendix shows an implementation of the Snapshot class and the recently mentioned methods.

The question that now arises is: When should an application take a snapshot of its scene graph or, in other words, how is an application step to be defined?

Actually, this is fully dependent upon the application itself. In fact, it is the task of the application programmer to decide where a step starts and where it terminates.

Generally speaking, all actions between two consecutive calls of the takeSnapshot() method belong to one and the same step. The first call marks the start and the second call marks the end of the step. In a 3D modelling tool, for example, adding a geometrical primitive should be captured as one step. In a 3D visualisation of a sorting algorithm, swapping two array elements should also be captured in one step. Hence, a snapshot should be taken before and after a complete action.