[PythonCAD] more undo
Eric Wilhelm
ewilhelm at sbcglobal.net
Sat Sep 4 15:57:36 CEST 2004
I'm poking at your undo system to see how it works. If I draw a line,
change the color, and then try ctrl+Z, I get this:
<raw_junk>
saveUndoData: ('add', ('point', (4, 1), 159.0, 516.0))
saveUndoData: ('add', ('point', (5, 1), 408.0, 326.0))
saveUndoData: ('add', ('segment', (6, 1), (None, None, None, None), 4,
5))
saveUndoData: ('attr_changed', 'color', (255, 255, 255))
saveUndoData: ('mod', 6)
Traceback (most recent call last):
File "./trunk/Interface/Gtk/gtkmenus.py", line 313, in edit_undo_cb
gtkimage.undo()
File "./trunk/Generic/image.py", line 916, in undo
_layer.undo()
File "./trunk/Generic/entity.py", line 256, in undo
self.__log.undo()
File "./trunk/Generic/logger.py", line 67, in undo
self.execute(True, *_data)
File "./trunk/Generic/layer.py", line 2388, in execute
_obj.undo()
File "./trunk/Generic/entity.py", line 256, in undo
self.__log.undo()
File "./trunk/Generic/logger.py", line 67, in undo
self.execute(True, *_data)
File "./trunk/Generic/segment.py", line 991, in execute
raise ValueError, "Unexpected operation: %s" % _op
ValueError: Unexpected operation: attr_changed
</raw_junk>
I've turned on some of your debugging statements in Generic/logger.py.
Digging around a little further, it seems that each class has an
execute() function, but this is only involved in undo/redo (and then
only with attributes?)
I've looked back through the archives, but can't seem to find an
"executive summary" of how undo/redo works. Is there some
documentation that I'm missing?
From my poking around, it looks like the undo/redo is managed on a
fairly low level (where each change is logged and there must be a
specific action defined for undo/redo of that kind of change.) Is
this correct? If so, how well does this scale as new types of
entities and actions are added? Is there a more generic system that
would work?
Consider this. What if you wanted to create an animation of a
session's editing changes? What if you wanted to create it
backwards? Ignoring the animation issues, how would the code be able
to create the data at each change point? What if you want to undo
the last thing done before the file was last saved?
Global snapshots at each change would be the most generic way, but
also the most expensive. Differences of these snapshots might be
less expensive, but that could be tricky to do efficiently (unless
you work with the persistent directory idea.)
With the logging system, you have to have a record of each change and
a function that is able to undo each particular change. This gives a
foo() and unfoo() pair of functions for each particular action.
Efficient, but hard to code and maintain.
So, global the snapshot/rollback undo is really simple and hard to get
wrong. The local change-specific undo is really CPU efficient and
hard to get right.
Assuming that you don't want to explore a global diff/patch undo
system, what would make the local scheme more robust?
Maybe a scripting/unscripting system? This gives you the added
benefit of a scripting system while simultaneously abstracting the
undo mechanism into a more robust scheme and allowing the undo
history to be persistent across a reboot.
If you build this into the existing logger, a syntax like the
following might work.
entity CHANGE property TO value
And to make it easily unscriptable, you could pass something like this
to the logger.
entity CHANGE property FROM value1 TO value2
So, undo becomes a matter of munging the TO and FROM around.
What about create/delete?
entity ADD type WITH property=value AND property2=value2
entity REMOVE type LOG property=value AND property2=value2
Of course, I'm just messing around with this SQL-like syntax, but you
get the idea. It's verbose and very structured. Designed with
inversion in mind. The point is, if every action is scriptable and
unscriptable, the undo/redo becomes a matter of parsing and running a
script.
Other than the undo system uses, I'm not sure that this sort of macro
scripting is a good idea. After all, you could simply evaluate some
Python, or even have an embedded Perl interpreter. The problem with
these languages is that parsing for the sake of inversion is HARD.
--Eric
--
"But as to modern architecture, let us drop it and let us take
modernistic out and shoot it at sunrise."
--F.L. Wright
More information about the PythonCAD
mailing list