I will be curious if you feel the same way after a few years of working with it. I am guessing it will be a big source of head-aches and bugs.
No, why ? It's just matter of moving records around, along with some operation codes on how to manage them.
You even don't need to know which kind of data they are, from inside transaction mechanics.
I'm using a sort of "labels" (in my case they're IDs that identify objects inside a big map), but with a flat structure, as I don't need to keep track of solid evolutions nor to manage assemblies.
When I delete an object, I create a "delete" record, which is a container for the object and some data explaining what happened, and put that record inside an undo database.
When I add an object, I create a "create" record, which contains nothing but some data on what happened, and I add it to undo db too.
And so on. The only requirement on objects is that they must be cloneable, as I need a persistent copy of them inside undo database.
On undo, I pop a record from undo db, add to redo db, and apply the changes (reverted) to main db.
The kind of objects is totally ininfluent, as long as they can be cloned.
As I told you, the only "critical" part, but that's for almost everything, even when you delete an object without undo, is to propagate the change if object is tied to others. I don't store the undo db on disk, but I could do it, so making undo persistent between edit sessions.
In all that "record moving" stuffs U++ was very helpful, having developed an analog of std::move long before it was added to c++ standards (the pick_ that you can see in my code), alowing fast code avoiding most copy of objects around.
These are declaration of transaction records :
C++:
// transaction record operation type
typedef enum { nop, create, remove, modify } TransOp;
// transaction record
struct TransRec
{
TransOp op;
UppCadIdx idx;
Upp::One<UppCadDbObject> obj;
TransRec(TransOp _op, UppCadIdx _idx, UppCadDbObject *_obj) : op(_op), idx(_idx), obj(_obj) {}
TransRec() : op(nop), idx(-1), obj(NULL) {}
~TransRec() {}
};
// all operations done inside transaction
Upp::ArrayMap<UppCadIdx, Upp::One<TransRec> > records;
the
C++:
Upp::One<UppCadDbObject> obj;
Holds the object's copy, it's a kind of smart pointer.