fork download
  1.  
  2. #include <cassert>
  3. #include <vector>
  4. #include <memory>
  5. #include <experimental/any>
  6. #include <experimental/optional>
  7. #include <functional>
  8.  
  9. // for any, any_cast and optional
  10. using namespace std::experimental;
  11.  
  12. template<class TransactionManagerType>
  13. class TransactionManager;
  14.  
  15. template<class TransactionManagerType>
  16. class Action {
  17. public:
  18. virtual ~Action() = default;
  19.  
  20. /**
  21. * Performs the action on the given transaction manager.
  22. * @param transactionManager The transaction manager to perform the transaction on.
  23. */
  24. virtual void perform(TransactionManagerType *transactionManager) noexcept = 0;
  25.  
  26. /**
  27. * Undos the action on the given transaction manager.
  28. * @param transactionManager The transaction manager to undo the transaction on.
  29. */
  30. virtual void undo(TransactionManagerType *transactionManager) noexcept = 0;
  31. };
  32.  
  33. template<class TransactionManagerType, class ActionType>
  34. class Transaction {
  35. public:
  36. explicit Transaction(TransactionManagerType *transactionManager) :
  37. transactionManager(transactionManager), finalized(false) {}
  38.  
  39. virtual ~Transaction() = default;
  40.  
  41. const optional<ActionType> &getAction() const {
  42. return actionOpt;
  43. }
  44.  
  45. void setAction(ActionType action) {
  46. assert(!finalized);
  47.  
  48. if (actionOpt) {
  49. // undo the existing action, if any
  50. actionOpt->undo(transactionManager);
  51. }
  52.  
  53. // perform the new action
  54. action.perform(transactionManager);
  55.  
  56. // set the existing action
  57. actionOpt = action;
  58. }
  59.  
  60. void finalize() {
  61. assert(!finalized);
  62.  
  63. finalized = true;
  64. transactionManager->transactionFinalized(
  65. this,
  66. // implicitly provide the ActionType to transactionFinalized
  67. // using a dummy pointer, to avoid having to explicitly provide
  68. // the transaction's type (which we don't have)
  69. static_cast<ActionType *>(nullptr));
  70. }
  71.  
  72. private:
  73. TransactionManagerType *transactionManager;
  74.  
  75. bool finalized;
  76.  
  77. /**
  78. * The current action, if any.
  79. */
  80. optional<ActionType> actionOpt;
  81. };
  82.  
  83. /**
  84.  * ActionPerformer is a utility class implicitly remembering
  85.  * the type of the action contained in the lambda functions
  86.  * it is created with.
  87.  * This is required to avoid having to add the action type
  88.  * as a template argument, which would make it impossible
  89.  * to store ActionPerformers for different action types
  90.  * on a TransactionManager's undo and redo stack.
  91.  */
  92. template<class TransactionManagerType>
  93. class ActionPerformer {
  94. public:
  95. ActionPerformer(const std::shared_ptr<any> &action,
  96. const std::function<void(TransactionManagerType *)> &undoAction,
  97. const std::function<void(TransactionManagerType *)> &redoAction) :
  98. action(action), undoAction(undoAction), redoAction(redoAction) {}
  99.  
  100. void undo(TransactionManager<TransactionManagerType> *manager) {
  101. undoAction(static_cast<TransactionManagerType *>(manager));
  102. }
  103.  
  104. void redo(TransactionManager<TransactionManagerType> *manager) {
  105. redoAction(static_cast<TransactionManagerType *>(manager));
  106. }
  107.  
  108. private:
  109. /**
  110. * The action to perform.
  111. */
  112. std::shared_ptr<any> action;
  113.  
  114. std::function<void(TransactionManagerType *)> undoAction, redoAction;
  115. };
  116.  
  117. template<class TransactionManagerType>
  118. class TransactionManager {
  119. public:
  120. TransactionManager() = default;
  121.  
  122. virtual ~TransactionManager() = default;
  123.  
  124. template<class ActionType>
  125. Transaction<TransactionManagerType, ActionType> *createTransaction() {
  126. // there must not be a current unfinalized transaction
  127. assert(currentTransaction.empty());
  128.  
  129. currentTransaction = Transaction<TransactionManagerType, ActionType>(
  130. // cast from TransactionManager<TransactionManagerType> to TransactionManagerType.
  131. static_cast<TransactionManagerType *>(this));
  132. return any_cast<Transaction<TransactionManagerType, ActionType>>(&currentTransaction);
  133. };
  134.  
  135. /**
  136. * Undos the previous transaction.
  137. */
  138. void undo() noexcept {
  139. assert(!undoStack.empty());
  140. if (undoStack.empty()) return;
  141.  
  142. auto &performer = undoStack.back();
  143. // undo the action
  144. performer.undo(this);
  145.  
  146. // add the action performer to the redo stack
  147. redoStack.push_back(performer);
  148.  
  149. // remove the transaction from the undo stack
  150. undoStack.pop_back();
  151. }
  152.  
  153. /**
  154. * Redos the next transaction.
  155. */
  156. void redo() noexcept {
  157. assert(!redoStack.empty());
  158. if (redoStack.empty()) return;
  159.  
  160. auto &performer = redoStack.back();
  161. // redo the action
  162. performer.redo(this);
  163.  
  164. // add the action performer to the undo stack
  165. undoStack.push_back(performer);
  166.  
  167. // remove the transaction from the redo stack
  168. redoStack.pop_back();
  169. }
  170.  
  171. /**
  172. * Called by the current transaction when it is finalized.
  173. * @param transaction The transaction that was finalized.
  174. */
  175. template<class TransactionType, class ActionType>
  176. void transactionFinalized(TransactionType *transaction, ActionType *dummy) {
  177. auto &actionOpt = transaction->getAction();
  178. if (actionOpt) {
  179. // clear the redo stack
  180. redoStack.clear();
  181.  
  182. // add the transaction's action to the undo stack
  183. auto action = *actionOpt;
  184.  
  185. // create a shared pointer containing a copy of the action
  186. // as an "any" instance to circumvent the need of a
  187. // template argument for the Action Type in the
  188. // ActionPerformer class, which makes it possible
  189. // to store ActionPerformers of different ActionTypes
  190. // in a vector. The ActionType is retained in the
  191. // lambda function.
  192. auto actPtr = std::make_shared<any>(ActionType(action));
  193. undoStack.emplace_back(actPtr, [actPtr
  194. ](TransactionManagerType *manager) {
  195. any_cast<ActionType>(actPtr.get())->undo(manager);
  196. }, [actPtr](TransactionManagerType *manager) {
  197. any_cast<ActionType>(actPtr.get())->perform(manager);
  198. });
  199. }
  200.  
  201. // clear the current transaction
  202. currentTransaction.clear();
  203. };
  204.  
  205. private:
  206. /**
  207. * The current unfinalized transaction, if any.
  208. */
  209. any currentTransaction;
  210.  
  211. /**
  212. * The stacks of actions to undo and redo.
  213. */
  214. std::vector<ActionPerformer<TransactionManagerType>> undoStack, redoStack;
  215. };
  216.  
  217. #define TRANSACTION_MANAGER(className) class className : public TransactionManager<className>
  218.  
  219.  
  220. TRANSACTION_MANAGER(TestTransactionManager) {
  221. public:
  222. TestTransactionManager() : value(0) {}
  223.  
  224. int value;
  225. };
  226.  
  227. class AdditionAction : public Action<TestTransactionManager> {
  228. public:
  229. explicit AdditionAction(int amount) : amount(amount) {}
  230.  
  231. void perform(TestTransactionManager *transactionManager) noexcept override {
  232. transactionManager->value += amount;
  233. }
  234.  
  235. void undo(TestTransactionManager *transactionManager) noexcept override {
  236. transactionManager->value -= amount;
  237. }
  238.  
  239. int amount;
  240. };
  241.  
  242. int main(int argc, char *argv[]) {
  243. TestTransactionManager manager;
  244. {
  245. auto *transaction = manager.createTransaction<AdditionAction>();
  246.  
  247. transaction->setAction(AdditionAction(5));
  248. transaction->finalize();
  249.  
  250. assert(manager.value == 5);
  251. }
  252.  
  253. {
  254. // create an empty transaction - it should
  255. // not affect the undo/redo behaviour
  256. auto *transaction = manager.createTransaction<AdditionAction>();
  257. transaction->finalize();
  258.  
  259. assert(manager.value == 5);
  260. }
  261.  
  262. {
  263. auto *transaction = manager.createTransaction<AdditionAction>();
  264.  
  265. // modify the action multiple times before finalizing -
  266. // the action should be immediately applied, but undone
  267. // when replaced.
  268.  
  269. transaction->setAction(AdditionAction(3));
  270. assert(manager.value == 8);
  271.  
  272. transaction->setAction(AdditionAction(5));
  273. assert(manager.value == 10);
  274.  
  275. transaction->setAction(AdditionAction(3));
  276. transaction->finalize();
  277.  
  278. assert(manager.value == 8);
  279. }
  280.  
  281. manager.undo();
  282. assert(manager.value == 5);
  283.  
  284. manager.undo();
  285. assert(manager.value == 0);
  286.  
  287. manager.redo();
  288. assert(manager.value == 5);
  289.  
  290. manager.undo();
  291. assert(manager.value == 0);
  292.  
  293. manager.redo();
  294. assert(manager.value == 5);
  295.  
  296. manager.redo();
  297. assert(manager.value == 8);
  298. }
  299.  
Success #stdin #stdout 0s 15248KB
stdin
Standard input is empty
stdout
HERE