2

I'm developing an application that produces "files" that are ultimately just a list of groups of transactions: an event store. I was wondering if there was a name for the way I'm handling undo/redo. Or if there's another common way to implement such functionality.

For example, a file with three "nodes" might look something like this:

"transactionGroups": [
  {
    "isUserOwned": true,
    "transactions": [
      {
        "op": "nodeAdded",
        "frame": 0,
        "rotation": 132,
        "length": 435,
        "scale": 100
      }
    ]
  },
  {
    "isUserOwned": true,
    "transactions": [
      {
        "op": "nodeAdded",
        "frame": 0,
        "rotation": 144,
        "length": 363,
        "scale": 100
      }
    ]
  },
  {
    "isUserOwned": true,
    "transactions": [
      {
        "op": "nodeAdded",
        "frame": 0,
        "rotation": 163,
        "length": 311,
        "scale": 100
      }
    ]
  }

In the spirit of "there is no delete", I have devised a way to implement undo/redo by adding an "undo" transaction that references a transaction relative to its position in the store. Basically, it tells the file builder to ignore a referenced transaction when constructing the current state of the file.

To redo an operation, an undo transaction is just undone again.

"transactionGroups": [
  {
    "isUserOwned": true,
    "transactions": [
      {
        "op": "nodeAdded",
        "frame": 0,
        "rotation": 132,
        "length": 435,
        "scale": 100
      }
    ]
  },
  {
    "isUserOwned": true,
    "transactions": [
      {
        "op": "nodeAdded",
        "frame": 0,
        "rotation": 144,
        "length": 363,
        "scale": 100
      }
    ]
  },
  {
    "isUserOwned": true,
    "transactions": [
      {
        "op": "nodeAdded",
        "frame": 0,
        "rotation": 163,
        "length": 311,
        "scale": 100
      }
    ]
  },
  // Undoes the last two transactions
  {
    "isUserOwned": true,
    "transactions": [
      {
        "op": "undo",
        "transaction": 1
      }
    ]
  },
  {
    "isUserOwned": true,
    "transactions": [
      {
        "op": "undo",
        "transaction": 3
      }
    ]
  },
  // Redoes the last two undo transactions
  {
    "isUserOwned": true,
    "transactions": [
      {
        "op": "undo",
        "transaction": 1
      }
    ]
  },
  {
    "isUserOwned": true,
    "transactions": [
      {
        "op": "undo",
        "transaction": 3
      }
    ]
  }
]
9
  • 1
    hmm this seems bad. surely you should 'undo' with a real transaction. in this case 'nodeRemoved' undoing us different from not doing
    – Ewan
    Nov 21, 2016 at 13:13
  • 1
    I don't understand why you wouldn't explicitly mark redos as redos. Right now undoing is not idempotent which leads to significant issues between concurrent writers (and even for a single writer). It's also not clear to me how this would interact with checkpointing. Having an undo/redo be an inverse transaction doesn't have an issue with checkpointing. Checkpointing, admittedly, isn't an issue if the "groups" are intended to be processed atomically. Nov 21, 2016 at 21:27
  • 1
    In a different vein, what happens if you undo something that other transactions depend on? Those dependencies may (in fact, appear) not to be explicit, i.e. you may undo a transaction whose result was the basis for the code to choose to produce one transaction instead of another. Nov 21, 2016 at 21:34
  • 2
    The act of undoing is generally not as simple as ignoring a portion of the past. The only way what you did could work is that if you only ever allow to undo transactions from the most recent to the oldest. An undo/redo event should only ever affect it's previous transaction. Is that what you are doing?
    – plalx
    Nov 21, 2016 at 22:08
  • 1
    Also, a redo would have to cause the entire file state to be destroyed and restored as you cannot re-apply past events in the future.
    – plalx
    Nov 21, 2016 at 22:16

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.