Server-receiver Framework

Previous Forward-reverse Framework section showed how to define undoable operations. Although it covers the fundamental functionalities, it is not convenient to use – you always have to pass the old state and the new state to the function. For more complicated functions, the arguments will be more cumbersome.

In most cases, you will pass the current state of an instance as the old state. In the previous example,

from collections_undo import UndoManager

class A:
    mgr = UndoManager()

    def __init__(self):
        self.x = 0

    def set_value(self, x):
        return self._set_value(x, self.x)

    @mgr.undoable
    def _set_value(self, x, x_old):
        self.x = x

    @_set_value.undo_def
    def _set_value(self, x, x_old):
        self.x = x_old

the set_value() method uses self.x to describe current instance state.

def set_value(self, x):
    return self._set_value(x, self.x)

Define serve/receiver interface

A new framework for this type of implementation is the “server-receiver” framework. The most important point here is that the “set_value” function is called both in do and undo, as long as proper “old_value” is provided.

self.set_value(new_value)  # do
self.set_value(old_value)  # undo

Providing new_value is totally dependent on user input so it is beyond management by the undo manager. What an undo manager should do is to record the current state as the old_value before actually calling the set_value() function. Therefore, all you have to do is to define a function that will serve the current state as arguments.

Here’s the precise definition of “server” and “receiver”.

  • Server – a function that returns the current state as positional and keyword arguments.

  • Receiver – a function that receive arguments and do something (you can consider it identical to the forward function in the last section).

Note

The signature of server, receiver and the returned values of server must be the same.

Example of using the interface

Following example uses the server-receiver interface to implement the previous example. Note that again, the definition is very similar to property.

from collections_undo import UndoManager, arguments

class A:
    mgr = UndoManager()

    def __init__(self):
        self.x = 0

    @mgr.interface  # receiver
    def set_value(self, x):
        # this function should look identical to what you do without thinking
        # of undoing this.
        self.x = x

    @set_value.server  # define the server
    def set_value(self, x):  # x is useless here
        return arguments(self.x)

When a value is set

a = A()
a.set_value(10)

the current state (self.x == 0) is recorded (served) to the undo manager by the server a.set_value._server(10) before actually setting the new value. When this operation is undone,

a.mgr.undo()

receiver function receives the arguments from the previously called server. This undo operation is almost equivalent to the following:

args, kwargs = a.set_value._server(10)
a.set_value(*args, **kwargs)