Jump to content
  • Advertisement
Sign in to follow this  
pinacolada

mvc + undo

This topic is 4841 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hey I'm trying to make a design decision on my current project, and I was wondering if anyone had any insights/suggestions/warnings. This project is in Java by the way. My code is organized as model-view-controller. The model data contains a lot of Node objects that have lots of pointers to each other. In the view data, there is a corresponding view-node object for every model-node (which does stuff like caches the screen position of the thing). Each view-node has a pointer to its model node. Now, I'm about to add undo. I'm doing undo the safe way (and the way that I think everyone does it): just keep an entire copy of the model as backup, and when the user hits undo, swap in this copy. The problem here is that the view data has a bazillion pointers to the model data, and if we suddenly switch models, all of the view objects' pointers become invalid. So here are the solutions I am looking at: 1) Destroy the view, make a new one. Con: I was hoping to have animations and other flashy stuff, which would be handled by the view. If the view is constantly being destroyed, these animations would be trashed (unless I add extra code to retain animation data) 2) Give each object in the model a unique ID, and have the view reference the model's data in terms of IDs. So the view won't even notice if we swap in a different model that is semantically equal. Con: drawing now involves lots of hashtable lookups, also I lose some type-safety 3) Keep the old model, add a copyDataFrom method that attempts to import all the changes from the target model. The view's pointers stay valid. Con: gets hairy if the new model has nodes that the old one doesn't, or vice versa. And anyway, this also requires that model objects get unique IDs, so I know what to copy to what. 4) Store the old view with the model in the undo stack Con: If I scroll to the left, delete a node, and hit undo, then I want the deletion to be undone, but I don't want the scrolling to be undone. Thoughts? Is there a "typical" solution to this problem?

Share this post


Link to post
Share on other sites
Advertisement
Well, you could code so that you store the undo code instead of loading the old model state. Some applications actually do that, though I can't think of any outside of text editors.

Alternately, you can add an extra layer of indirection. Instead of the View interacting with the Model directly, it works through a class or series or classes that has a reference to the Model data. This could be an extra pain in the rear depending on your application.

We could probably give more helpful advice if you mentioned what your application does and briefly how it's structured.

Share this post


Link to post
Share on other sites
Quote:
We could probably give more helpful advice if you mentioned what your application does and briefly how it's structured.


Sure, the basic idea is that it's a graphical editor for editing a graph (the CS kind of graph). So you can create nodes, drag them around, connect them, disconnect them, and delete them. Each node is an object and also each connection is an object. Also, the view keeps view-node and view-connection objects that correspond to their model counterparts.

I want to keep the view seperate because eventually I'd like to have multiple views looking at the same model. Like maybe have an "explore" button, which zooms in on one particular node and only shows you the nodes connected to it. (which would be accomplished by creating a new view)

Quote:
Original post by SiCrane
Well, you could code so that you store the undo code instead of loading the old model state. Some applications actually do that, though I can't think of any outside of text editors.


Yeah, I was thinking about doing that for *some* actions, to keep the undo stack small and to support multiple-level undo. But I'm not sure that I'll be able to support this for all operations. I'm worried that some operations (like, I dunno, a "duplicate everything" operation) are going to be too difficult to do this for, and that eventually I'm going to have to support swapping out the entire model.

Share this post


Link to post
Share on other sites
The more I think of it, the more it seems like attaching IDs to invidiual verticies and edges in the graph is the way to go. These IDs could be used in a message passing interface to the Views that could be used to selectively regenerate what is being displayed even if you keep references directly into the Model data. This requires that the undo/redo levels be delta coded instead of complete save/restores of Model states.

Of course, I'm assuming that your potential uses fall into smaller graphs. Not sprawling million node networks.

Share this post


Link to post
Share on other sites
I maintain an action histroy for any operation that is performed on the model. The history also contains the index to the current action - an undo will decrease the index while redo will increase the index.

Here is a quick example of how the list works.


class ActionItem
{

public:

virtual void Execute () = 0;
virtual void Reverse () = 0;
};

class CreateSphere : public ActionItem
{

public:

CreateSphere (const SphereData &sphere_data);

void Execute ();
void Reverse ();

private:

uint32 m_sphere_id;
SphereData m_sphere_data;
};

CreateSphere::CreateSphere (const SphereData &sphere_data)
{
m_sphere_data = sphere_data;
}

void CreateSphere::Execute ()
{
m_sphere_id = g_model->CreateSphere (m_sphere_data);
}

void CreateSphere::Reverse ()
{
g_model->DeleteSphere (m_sphere_id);
}




Then with the controller:


void OnCreateSphereClick ()
{
SphereData sphere_data;

sphere_data.position = GetCreatePosition ();
sphere_data.radius = GetCreateRadius ();
...

CreateSphere *action = new CreateSphere (sphere_data);

// Adds an action at the current location. ALL actions previously undone are lost.
g_model->ActionManager.AddAction (action);

UpdateUndoRedoMenu ();
}

void OnUndoClick ()
{
g_model->ActionManager.Undo ();

UpdateUndoRedoMenu ();
}

void OnRedoClick ()
{
g_model->ActionManager.Redo ();

UpdateUndoRedoMenu ();
}

void UpdateUndoRedoMenu ()
{
if (g_model->ActionManager.Location () == g_model->ActionManager.First ())
DisableUndoMenu ();

else
EnableUndoMenu ();

if (g_model->ActionManager.Location () == g_model->ActionManager.Last ())
DisableRedoMenu ();

else
EnableRedoMenu ();
}




Of course in reality it is far more complicated, but that should give you a good idea on where to go. Not shown is the pseudo-code for ActionManager.Undo and Redo, but all they do is call "Reverse" on the current action and decrement the action index and call "Execute" on the current action and increments the action index respectivly.

Finally, AddAction calls Execute on the passed action, deletes ALL actions past the current location and then adds the passed action to the end of the list.


Hopefully that all made sense!

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!