Click here to Skip to main content
15,881,559 members
Articles / Programming Languages / C++

Undo and Redo the "Easy" Way

Rate me:
Please Sign up or sign in to vote.
4.95/5 (42 votes)
20 Jun 2004CPOL22 min read 289.5K   8.6K   114  
This article introduces a simple approach to in-memory transactions that can be used to implement Undo and Redo. The technique uses SEH and Virtual Memory and requires only STL and Win32.
#include "stdafx.h"

#include "Transactions\MemManager.h"
#include "Transactions\Allocator.h"

#include <vector>
#include <math.h>

#include <iostream>

using namespace std;

////////////////////////////////////////////////////////////////////////////////
// first, we need some silly classes to work with. for the sake of this 
// code, consider that we can't modify these classes to add seriziation or
// undo/redo semantics, but need to provide undo/redo support.
//
struct Bar {
	Bar() : d(0.0) {}
	~Bar() {}

	struct Foo {
		Foo() : f1(0), f2(0) {}

		int		f1;
		long	f2;
	};

	double		d;
	Foo			v[10];
};

////////////////////////////////////////////////////////////////////////////////
// C++ helpers.  we need to define a helper method to make sure that our
// objects are allocated in the transacted heap.  this is just one way to do
// that.  ideally, you'd want to override operator new() on the class.
//
template <class T>
T* MyNew() { return new (Mm::Allocate(sizeof(T))) T(); }

template <class T>
void MyDelete(T* p) { p->~T(); Mm::Deallocate(p); }

////////////////////////////////////////////////////////////////////////////////
// STL NOTE: specify Mm::Allocator (or your own version) as the allocator 
// for all containers that will be created in the managed memory.
//
typedef std::vector<Bar, Mm::Allocator<Bar> > BarList;
//                       ^^^^^^^^^^^^^^^^^^
 

////////////////////////////////////////////////////////////////////////////////
// LET'S GIVE IT A TRY!
//
// This method demonstrates a number of ways that transactions get used, and
// serves as a unit test for development of Mm.  If you find problems with
// Mm, please try to include a snipit of code for this method that will 
// serve to verify the fix.  It isn't required, but it would be nice of you. :)
//
int DoWork(int argc, char* argv[], char** envp) 
{ 
	// 1 - the trivial (but important) case of an empty transaction.  success 
	// here is not crashing
	//
	Mm::OpenTransaction();
	Mm::CommitTransaction();

	Mm::Undo();
	Mm::Redo();

	// 2 - A simple test of allocating some memory and copying a string there.
	// The string should still be there after an undo + redo cycle. Note that 
	// the pointer "p" is not in managed memory and won't be transacted, but the
	// memory it points to IS transacted.  If you don't get that, try again 
	// because it is a critical concept.
	// 
	// Note that allocations and deallocations change memory, and need to be
	// put inside transactions or they will cause unhandled exceptions.
	//
	Mm::OpenTransaction();
	static char* text = "This is a test.";
	static unsigned int len = strlen(text);
	void* p = Mm::Allocate(len+1);
	memcpy(p, text, len);
	((char*)p)[len] = '\0';
	Mm::CommitTransaction();

	Mm::Undo();
	Mm::Redo();
	int ok1 = strcmp((char*)p, text);
	if (ok1 != 0) throw;

	// 3 - Modify the string and test the contents after undo, and again
	// after redo.
	//
	Mm::OpenTransaction();
	((char*)p)[4] = '\0';
	Mm::CommitTransaction();

	Mm::TXNID txn3 = Mm::GetLastTransactionId();

	Mm::Undo();
	int ok2 = strcmp((char*)p, text);
	if (ok2 != 0) throw;

	Mm::Redo();
	int ok3 = strcmp((char*)p, "This");
	if (ok3 != 0) throw;

	// 4 - Deallocate the string.  success means it's still there after undo.
	// Remember that p isn't transacted, so after deallocation (and redo) it 
	// points to deallocated memory. Danger!
	//
	Mm::OpenTransaction();
	Mm::Deallocate(p);
	Mm::CommitTransaction();

	Mm::Undo();
	
	int ok4 = strcmp((char*)p, "This");
	if (ok4 != 0) throw;

	Mm::Redo();

	// 5 - Make increasingly large allocations and immeadiately deallocate 
	// each. A better memory manager wouldn't need to allocate so much 
	// virtual address space to handle this. So write one and send it to
	// me. :)  As long as we don't crash, consider this good. 
	// 
	Mm::OpenTransaction();
	for (unsigned int s = 0; s < 24; ++s) {
		size_t size = pow(2,s);
		void* p = Mm::Allocate(size);
		Mm::Deallocate(p);
	}
	Mm::CommitTransaction();

	Mm::Undo();
	Mm::Redo();

	// 6 - Make the same allocation over and over again using the C++
	// helper to put the object in transacted memory.  We shouldn't crash
	// (heh) and only a couple pages should be touched no matter how 
	// many times we do this.
	Mm::OpenTransaction();
	for (s = 0; s < 100; ++s) {
		Bar* b = MyNew<Bar>();
		MyDelete(b);
	}
	Mm::CommitTransaction();

	Mm::Undo();
	Mm::Redo();

	// 7 -- Make an STL container in transacted memory and fill it with
	// Bar objects.  If you use STL, be careful not to put a container with
	// transacted memory on the stack durning a transaction.  Best case it
	// causes the transaction to bloat in size, worse case you end up with
	// a container that is out of sync with its contents and crashes.  This 
	// should be a pretty big transaction.
	Mm::OpenTransaction();
	BarList* pbl = MyNew<BarList>();
	for (unsigned int i = 0; i < 10000; ++i) {
		Bar b;
		(*pbl).push_back(b);
	}
	Mm::CommitTransaction();
	
	Mm::Undo();
	Mm::Redo();

	// 8 - Make some random changes to the objects in the container above.
	Mm::OpenTransaction();
	for (unsigned int k = 0; k < 100; ++k) {
		(*pbl)[rand()%i].d = 3.14159;
		(*pbl)[rand()%i].v[rand()%10].f1 = 11;
		(*pbl)[rand()%i].v[rand()%10].f2 = 22;
	}		
	Mm::CommitTransaction();

	Mm::Undo();

	// all objects in the container should be back to their original state

	Mm::Redo();

	// 9 - Delete the container
	Mm::OpenTransaction();
	MyDelete(pbl);
	Mm::CommitTransaction();

	Mm::Undo();
	Mm::Redo();

	cout << "# We should have 9 transactions below, and loads of free memory" << endl;
//	Mm::DumpStats(cout);

	// 10 - undo back to the point we modified the string (#3) and 
	// verify that the string is restored.  Note that in the process we
	// undo the STL container back into, then out of, existence while
	// moving back in time. 
	while (Mm::GetLastTransactionId() != txn3) Mm::Undo();
	
	int ok5 = strcmp((char*)p, "This");
	if (ok5 != 0) throw;

	// memory stats dumped to cout should indicate the memory state after 
	// transaction 3.
	//
	cout << "# We should have 9 transactions below, with the undo marker between #3 and #4." << endl;
	cout << "# The amount of free memory should match that availble after transaction #3." << endl;
//	Mm::DumpStats(cout);

	// memory stats dumped to cout should STILL indicate the memory state 
	// after transaction 3.
	//
	Mm::OpenTransaction();
	Mm::CancelTransaction();
	cout << "# We should have 9 transactions below, with the undo marker between #3 and #4." << endl;
	cout << "# (Same as above.)" << endl;
//	Mm::DumpStats(cout);

	// NOW memory stats dumped to cout should indicate the memory state 
	// after transaction 3 plus all the other memory added in subsequent
	// transactions (which can be released since the redo stack has
	// been dumped).  The transaction won't be empty since we have to
	// update the free list prior to committing.
	//
	Mm::OpenTransaction();
	Mm::CommitTransaction();
	cout << "# We should have 4 transactions below, with the undo marker after #4." << endl;
	cout << "# Transaction #4 should show some pages touched, even though we don't" << endl;
	cout << "# explicitly change anything during the transaction." << endl;
//	Mm::DumpStats(cout);

	return 0; 
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
A compiler warns of bogasity, ignore it at your peril. Unless you've done the compiler's job yourself, don't criticize it.

Comments and Discussions