Jump to content
  • Advertisement
Sign in to follow this  

Custom editor undo/redo system

This topic is 943 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

As the title says I'm writing a custom editor and I'm currently thinking how should I approach the undo/redo problem.

Currently have absolutely nothing implement in terms of "game objects" "scene nodes" ect.

This is what I've got:



I've got a rendering system, an asset system, a few demo wnds displaying some info, transform gizmos ect.

Also I'm not developing any particular game, just doing stuff that are needed for almost every game (so let's think of 3D jump'n'run ala Ratchet and Clank or Rayman).


This is how I currently imagine the editor: 
A window similar to that "Model Preview Window" called "Object Template Builder" or "Prefab builder" - in that window I'll assemble the enemies. I will add and position the models that are needed for that game object. I'll add components or create a generic tweakable parameter to that object type(a float or an integer or a ref to another object).

Later Ill take those "object templates" and place them on the world in order to build a level, and eventual tweak a parameter to some specific object instance.

My mesh/model files are pretty flexible and they are whole scene on their own. I've written a generic parameter class that has can be animated (example is a float with static value of 5, and an animation [t=0,v=10], [t=1.2, v=113]...) so basically I can reuse that class for my "object templates".

The mesh/model files is simply serialized json header + a binary blob at the end of the file for the big data.

And finally my question:


In order to make the editor usable, I must support undo/redo. I have a few ideas, but nothing seems smart enough:


1 - on every "meaningful" change I'll serialize the whole scene(or if possible just and object). Undo/Redo will simply de-serialize the scene/object.

2 - The same as 1, but instead of saving the whole scene, I would store only the diff with my previous state (any suggestions for C++ library that does text diff?)

3 - Take the 'maya' approch where for ever operation: "change parameter value", "delete object", "add object", (even terrain editing if any) I implement a command class that has undo/redo. (for some reason I dont like this).

4 - Your suggestions!

Edited by imoogiBG

Share this post

Link to post
Share on other sites

You could have a look how editors do this. I have only read GEF somewhat, and afaik it does your "3". However being in the Eclipse and Java world, it might be more generic than you need.


Some other ideas:


- your 1+2: use a version control system to do the storage


- store the oldest version, and the sequence of modifications to the current state (ie don't implement undo, do a forward modification sequence instead). How feasible that is depends on how fast you can do "redo".


- In reference-based languages (Java, Python), a common trick is to use sharing of state.

All data you make is treated as being read-only after creation. If you want to change something, you make a new version of the modified part. The non-modified parts simply point to your previous state (being read-only, they won't change, so it's safe to share it). From the modified part up to the root all gets copied, you get a new pointer for each next state, except most of it is shared between the states. You simply use a different pointer for undo/redo, and drop any state you don't need any more.

The garbage collector will take care of cleaning up.


Now the latter is somewhat complicated in C++, shared pointers will probably do the trick, or else you can add a c++ garbage collector library.

Share this post

Link to post
Share on other sites

If you already have support for serializing/deserializing (assuming you can read/write to a binary blob that can exist in memory), then #1 is the easiest (almost trivial to implement) - but it might come at an unacceptable performance cost once your scene gets large. 

Edited by phil_t

Share this post

Link to post
Share on other sites

In my application (Curver, see signature), I use the third approach. However, it doesn't have to be complicated. For example, to modify the properties of an object, the undoable command class looks like this:

class CmdModifyCurveProperties : public Command
	Curve::Properties m_properties;
	Curve* m_pCurve;
	Execute( Curve& curve )
		m_pCurve = &curve;
		m_properties = curve.properties;
	Undo() { SwitchProperties(); }
	Redo() { SwitchProperties(); }
		Curve::Properties p = m_pCurve->properties;
		m_pCurve->properties = m_properties;
		m_properties = p;

pDocument->AddCommand( (new CmdModifyCurveProperties)->Execute( theCurve ) );
// now we can change the properties however we want and the changes will be
// undone when the command is undone.
theCurve.properties.thickness = 0.5;
theCurve.properties.color = black;

Of course, multiple commands can be grouped together for performing multiple tasks that are to be undone and redone as a unit:

auto cmdGroup = new CommandGroup;
cmdGroup->AddCommand( (new CmdCreateCurve( &theCurve ))->Execute() );
cmdGroup->AddCommand( (new CmdSelectCurve( &theCurve ))->Execute() );
cmdGroup->AddCommand( (new CmdCreateCircle( &theCircle))->Execute() );
cmdGroup->AddCommand( (new CmdSplitCurve( &theCircle, 0.5, &theDetachedPart))->Execute() );
pDocument->AddCommand( cmdGroup );

The approach in Maya seems to be complicated because it is used with scripting as well to implement undoable commands, and the interface is intended for plug-in authors instead of for direct use internally.


But there is no approach that is right for all projects. As Alberth mentioned, you will have to think about of object ownership and guaranteeing existence, so it's not without headaches. Good luck.

Share this post

Link to post
Share on other sites

Assume you have decoupled your editor into a scene description (model) and a UI presentation (view), There are now two ways you can implement Undo/Redo:


In the first scenario your scene description (model) doesn't know anything about undo/redo and all changes to the scene inside the UI layer go through commands that are stored on a command stack which can be re-winded. E.g. The scene class has a addNode() function and in the UI you create a command that adds a new node to the scene and removes it again in redo.The Qt Undo framework is indeed a nice example for this approach. This is also a nice blog post on this approach:



In the second scenario the undo/redo is implemented in the model directly. E.g. whenever you change a node the old state is somehow serialized and can be reset if needed. Note that you don't need to store the whole scene, but only the sub-tree that is about to change. The advantage of this approach is that is easier to implement and you cannot mess up undo/redo in the UI by forgetting changing the scene through commands instead of the scene model API. On the downside it is intrusive in the scene description. Mikko has a short blog post about this approach here:





Edited by Dirk Gregorius

Share this post

Link to post
Share on other sites

The command pattern approach is an oft-cited solution.


If you're interested, Sean Parent gave a talk Entitled "Inheritance is the Base Class of Evil" (Channel 9 link) which is a brief 24 minutes. Its all about the benefits of preferring composition over inheritance and value semantics over reference semantics -- these things are fundamental to his overhauling of Photoshop's undo/redo, and he gets more specific about how that works by the end (I think from about the midpoint on, but its been awhile since I've watched it. Regardless I recommend watching the whole thing -- its short, and its informative enough that I've watched it a handful of times over the 30 months its been available).


Here's a youtube link as well in case that's more convenient, but I think the Channel 9 video is better quality; the youtube video is a third-party upload.


Also, Sean's presentations are always great, and never a poor way to spend a lunch break.

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.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!