Guest User

Untitled

a guest
Mar 24th, 2018
74
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 28.46 KB | None | 0 0
  1. using System;
  2. using System.Collections.Immutable;
  3. using System.Reactive;
  4. using System.Reactive.Linq;
  5. using System.Reactive.Subjects;
  6.  
  7. namespace Steellworks.Rx.UndoRedo
  8. {
  9. /// <summary>
  10. /// Defines properties for fetching a value and its inverse.
  11. /// </summary>
  12. /// <typeparam name="T">Type of the value and inverse.</typeparam>
  13. public interface IInvertibleValue<out T>
  14. {
  15. /// <summary>
  16. /// A value, the inverse of which is InverseValue.
  17. /// </summary>
  18. T Value { get; }
  19.  
  20. /// <summary>
  21. /// A value, the inverse of which is Value.
  22. /// </summary>
  23. T InverseValue { get; }
  24. }
  25.  
  26. /// <summary>
  27. /// Basic structure for storing a value and its inverse.
  28. /// </summary>
  29. /// <typeparam name="T">Type of the value and inverse.</typeparam>
  30. public struct ValueAndInverse<T> : IInvertibleValue<T>
  31. {
  32. public ValueAndInverse(T value, T inverseValue) : this()
  33. {
  34. InverseValue = inverseValue;
  35. Value = value;
  36. }
  37.  
  38. public T Value { get; private set; }
  39. public T InverseValue { get; private set; }
  40. }
  41.  
  42. /// <summary>
  43. /// Reactive undo/redo recorder. Seamlessly integrates into reactive circuits, recording and replaying data when
  44. /// an undo or redo is triggered.
  45. /// </summary>
  46. public class ReactiveUndoRedoRecorder : IDisposable
  47. {
  48. private readonly Subject<UndoRedoAction> recordSubject = new Subject<UndoRedoAction>();
  49. private readonly IDisposable updateSub;
  50.  
  51. /// <summary>
  52. /// Instantiates a new UndoRedoRecorder, given streams representing undo and redo triggering events.
  53. /// </summary>
  54. /// <param name="undoTrigger">When this observable produces a value, an undo will occur.</param>
  55. /// <param name="redoTrigger">When this observable produces a value, a redo will occur.</param>
  56. public ReactiveUndoRedoRecorder(IObservable<Unit> undoTrigger, IObservable<Unit> redoTrigger)
  57. {
  58. // explicit wrappers for implicit methods on RecorderStack
  59. Func<RecorderStack, Tuple<Action, RecorderStack>> undo = stack => stack.Undo();
  60. Func<RecorderStack, Tuple<Action, RecorderStack>> redo = stack => stack.Redo();
  61.  
  62. // We want to keep track of the state of the RecorderStack, and also execute actions produced from
  63. // undo or redo operations.
  64. Tuple<Action, RecorderStack> state0 = Tuple.Create(new Action(() => { }), RecorderStack.Empty);
  65.  
  66. // Make the trigger streams produce the corresponding actions on the RecorderStack.
  67. IObservable<Func<RecorderStack, Tuple<Action, RecorderStack>>> undoStream = undoTrigger.Select(_ => undo);
  68. IObservable<Func<RecorderStack, Tuple<Action, RecorderStack>>> redoStream = redoTrigger.Select(_ => redo);
  69.  
  70. // Stream of undo/redo actions which are produced when an Undo or Redo is performed.
  71. IObservable<Action> undoRedoActions =
  72.  
  73. // Merge all streams that will affect the RecorderStack
  74. Observable.Merge(undoStream, redoStream, recordSubject.Select(CreateRecordClosure))
  75.  
  76. // Update the RecorderStack
  77. .Scan(state0, (state, action) => action(state.Item2))
  78.  
  79. // Only propagate the action produced by the RecorderStack update action.
  80. .Select(x => x.Item1);
  81.  
  82. updateSub = undoRedoActions.Subscribe(x => x());
  83. }
  84.  
  85. public void Dispose()
  86. {
  87. updateSub.Dispose();
  88. recordSubject.Dispose();
  89. }
  90.  
  91. private static Func<RecorderStack, Tuple<Action, RecorderStack>> CreateRecordClosure(UndoRedoAction com)
  92. {
  93. return stack => stack.Record(com);
  94. }
  95.  
  96. private void RecordUndoRedoAction(UndoRedoAction com)
  97. {
  98. recordSubject.OnNext(com);
  99. }
  100.  
  101. /// <summary>
  102. /// Applies an accumulator function over an observable sequence and returns each intermediate result. Each
  103. /// intermediate state is also recorded for playback via undo or redo.
  104. /// </summary>
  105. /// <typeparam name="TState">The type of the result of the aggregation.</typeparam>
  106. /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
  107. /// <param name="source">An observable sequence to accumulate over.</param>
  108. /// <param name="initialState">The intitial accumulator value.</param>
  109. /// <param name="accumulator">An accumulator function to be invoked on each element.</param>
  110. /// <returns>Sequence of intermediate aggregation values with undo/redo interleaved.</returns>
  111. public IObservable<TState> RecordScan<TState, TSource>(IObservable<TSource> source, TState initialState, Func<TState, TSource, TState> accumulator)
  112. {
  113. return Observable.Create<TState>(
  114. observer =>
  115. {
  116. // This subject is used from inside of the recorded UndoRedo actions, in order to send replayed values
  117. // back into this accumulation.
  118. var stateUpdateSubject = new Subject<TState>();
  119.  
  120. // The idea here is that we want to handle the state update differently, depending if a new value is
  121. // produced or an old value is being replayed. To facilitate this, we project both sequences into a
  122. // sequence of functions that handle the state update.
  123.  
  124. // If a new value is produced by the source stream, then we also need to perform additional
  125. // subscription logic. This means that we need to produce both a new state and an action to be invoked
  126. // on subscription.
  127.  
  128. // Handler for source observable (new values)
  129. var sourceUpdates = source.Select<TSource, Func<TState, Tuple<TState, Action>>>(
  130. result =>
  131. currentState =>
  132. {
  133. // First we produce a new state by using the accumulator function.
  134. TState newState = accumulator(currentState, result);
  135.  
  136. // We also create a UndoRedoAction for replaying the old and new states
  137. var com = new UndoRedoAction(
  138. () => stateUpdateSubject.OnNext(currentState),
  139. () => stateUpdateSubject.OnNext(newState));
  140.  
  141. // Return the new state, and on subscription record the action.
  142. return Tuple.Create(newState, new Action(() => RecordUndoRedoAction(com)));
  143. });
  144.  
  145. // Handler for replayed values
  146. var replayedUpdates = stateUpdateSubject.Select<TState, Func<TState, Tuple<TState, Action>>>(
  147. // Here, we throw away the current state and replace it with the replayed state. We don't
  148. // need to do anything on subscription, so we store an "empty" action.
  149. result => _ => Tuple.Create(result, new Action(() => { })));
  150.  
  151. return
  152. // Merge both sequences.
  153. Observable.Merge(sourceUpdates, replayedUpdates)
  154.  
  155. // Perform the aggregation. Note that the actual aggregation is performed inside of the handlers
  156. // produced by the merged sequences.
  157. .Scan(Tuple.Create(initialState, new Action(() => { })), (state, handler) => handler(state.Item1))
  158.  
  159. // Subscribe to the aggregation by...
  160. .Subscribe(
  161. result =>
  162. {
  163. // ...first invoking the subscription action,
  164. result.Item2();
  165. // and then passing the intermediate accumulated value to the observer.
  166. observer.OnNext(result.Item1);
  167. },
  168. observer.OnError,
  169. observer.OnCompleted);
  170. });
  171. }
  172.  
  173. /// <summary>
  174. /// Applies an accumulator function over an observable sequence and returns each intermediate result. When
  175. /// undone, replays values from the source observable and combines them with the current state via an
  176. /// inverse accumulator function.
  177. /// </summary>
  178. /// <typeparam name="TState">The type of the result of the aggregation.</typeparam>
  179. /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
  180. /// <param name="source">An observable sequence to accumulate over.</param>
  181. /// <param name="initialState">The intitial accumulator value.</param>
  182. /// <param name="accumulator">An accumulator function to be invoked on each element.</param>
  183. /// <param name="inverseAccumulator">
  184. /// An accumulator function that does the inverse of accumulator, such that the following
  185. /// invariant is satisfied:
  186. ///
  187. /// inverseAccumulator(accumulator(state, a), a) = state
  188. /// </param>
  189. /// <returns>Sequence of intermediate aggregation values with undo/redo interleaved.</returns>
  190. public IObservable<TState> RecordScan<TState, TSource>(IObservable<TSource> source, TState initialState, Func<TState, TSource, TState> accumulator, Func<TState, TSource, TState> inverseAccumulator)
  191. {
  192. return Observable.Create<TState>(
  193. observer =>
  194. {
  195. var stateUpdateSubject = new Subject<Func<TState, TState>>();
  196.  
  197. var sourceUpdates = source.Select<TSource, Func<TState, Tuple<TState, Action>>>(
  198. result =>
  199. currentState =>
  200. {
  201. // First we produce a new state by using the accumulator function.
  202. TState newState = accumulator(currentState, result);
  203.  
  204. // We also create a UndoRedoAction for replaying the old and new states
  205. var com = new UndoRedoAction(
  206. () => stateUpdateSubject.OnNext(state => inverseAccumulator(state, result)),
  207. () => stateUpdateSubject.OnNext(state => accumulator(state, result)));
  208.  
  209. // Return the new state, and on subscription record the action.
  210. return Tuple.Create(newState, new Action(() => RecordUndoRedoAction(com)));
  211. });
  212.  
  213. // Handler for replayed values
  214. var replayedUpdates = stateUpdateSubject
  215. .Select<Func<TState, TState>, Func<TState, Tuple<TState, Action>>>(
  216. result => currentState => Tuple.Create(result(currentState), new Action(() => { })));
  217.  
  218. return
  219. // Merge both sequences.
  220. Observable.Merge(sourceUpdates, replayedUpdates)
  221.  
  222. // Perform the aggregation. Note that the actual aggregation is performed inside of the handlers
  223. // produced by the merged sequences.
  224. .Scan(Tuple.Create(initialState, new Action(() => { })),
  225. (state, handler) => handler(state.Item1))
  226.  
  227. // Subscribe to the aggregation by...
  228. .Subscribe(
  229. result =>
  230. {
  231. // ...first invoking the subscription action,
  232. result.Item2();
  233. // and then passing the intermediate accumulated value to the observer.
  234. observer.OnNext(result.Item1);
  235. },
  236. observer.OnError,
  237. observer.OnCompleted);
  238. });
  239. }
  240.  
  241. /// <summary>
  242. /// Applies an accumulator function over an observable sequence of invertible values and returns
  243. /// each intermediate result. When undone, applies the inverse of the recorded value to the
  244. /// accumulator to generate the new intermediate state.
  245. /// </summary>
  246. /// <typeparam name="TState">The type of the result of the aggregation.</typeparam>
  247. /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
  248. /// <param name="source">An observable sequence to accumulate over.</param>
  249. /// <param name="initialState">The intitial accumulator value.</param>
  250. /// <param name="accumulator">An accumulator function to be invoked on each element.</param>
  251. /// <returns>Sequence of intermediate aggregation values with undo/redo interleaved.</returns>
  252. public IObservable<TState> RecordScan<TState, TSource>(IObservable<IInvertibleValue<TSource>> source, TState initialState, Func<TState, TSource, TState> accumulator)
  253. {
  254. return Observable.Create<TState>(
  255. observer =>
  256. {
  257. var stateUpdateSubject = new Subject<TSource>();
  258.  
  259. var sourceUpdates = source.Select<IInvertibleValue<TSource>, Func<TState, Tuple<TState, Action>>>(
  260. result =>
  261. currentState =>
  262. {
  263. // First we produce a new state by using the accumulator function.
  264. TState newState = accumulator(currentState, result.Value);
  265.  
  266. // We also create a UndoRedoAction for replaying the old and new states
  267. var com = new UndoRedoAction(
  268. () => stateUpdateSubject.OnNext(result.InverseValue),
  269. () => stateUpdateSubject.OnNext(result.Value));
  270.  
  271. // Return the new state, and on subscription record the action.
  272. return Tuple.Create(newState, new Action(() => RecordUndoRedoAction(com)));
  273. });
  274.  
  275. // Handler for replayed values
  276. var replayedUpdates = stateUpdateSubject
  277. .Select<TSource, Func<TState, Tuple<TState, Action>>>(
  278. result => currentState => Tuple.Create(accumulator(currentState, result), new Action(() => { })));
  279.  
  280. return
  281. // Merge both sequences.
  282. Observable.Merge(sourceUpdates, replayedUpdates)
  283.  
  284. // Perform the aggregation. Note that the actual aggregation is performed inside of the handlers
  285. // produced by the merged sequences.
  286. .Scan(Tuple.Create(initialState, new Action(() => { })),
  287. (state, handler) => handler(state.Item1))
  288.  
  289. // Subscribe to the aggregation by...
  290. .Subscribe(
  291. result =>
  292. {
  293. // ...first invoking the subscription action,
  294. result.Item2();
  295. // and then passing the intermediate accumulated value to the observer.
  296. observer.OnNext(result.Item1);
  297. },
  298. observer.OnError,
  299. observer.OnCompleted);
  300. });
  301. }
  302.  
  303. /// <summary>
  304. /// Records all data coming from the given observable sequence, allowing for "replaying" of previous
  305. /// values via undo and redo.
  306. /// </summary>
  307. /// <typeparam name="T">The type of the elements in the source sequence.</typeparam>
  308. /// <param name="source">An observable sequence to record.</param>
  309. /// <param name="initialState">The intial value, to be produced if the first production is undone.</param>
  310. /// <returns>The source observable sequence with undo/redo interleaved.</returns>
  311. public IObservable<T> Record<T>(IObservable<T> source, T initialState)
  312. {
  313. return RecordScan(source, initialState, (_, source1) => source1);
  314. }
  315.  
  316. /// <summary>
  317. /// Given an observable sequence of invertable values, creates a new observable sequence that produces
  318. /// values when the source sequence produces, and also when the source production is undone or redone.
  319. /// </summary>
  320. /// <typeparam name="T">The type of the elements produced by the source and result sequences.</typeparam>
  321. /// <param name="source">An observable sequence of invertible values.</param>
  322. /// <returns>The source observable sequence with undo/redo interleaved.</returns>
  323. public IObservable<T> Record<T>(IObservable<IInvertibleValue<T>> source)
  324. {
  325. return Observable.Create<T>(
  326. observer =>
  327. source.Subscribe(
  328. result => // When the source sequence produces values...
  329. {
  330. // Create an UndoRedoAction from the InvertibleValue
  331. var com = new UndoRedoAction(
  332. () => observer.OnNext(result.InverseValue),
  333. () => observer.OnNext(result.Value));
  334.  
  335. // Record the action onto the RecorderStack.
  336. RecordUndoRedoAction(com);
  337.  
  338. // Produce the value to the observer.
  339. observer.OnNext(result.Value);
  340. },
  341. observer.OnError,
  342. observer.OnCompleted));
  343. }
  344.  
  345. /// <summary>
  346. /// Subscribes to an observable sequence of invertible actions, and also records the actions for replaying
  347. /// via undo or redo.
  348. /// </summary>
  349. /// <param name="source">An observable sequence of invertible actions.</param>
  350. /// <returns>An IDisposable representing the subscription to actions produced by the source sequence.</returns>
  351. public IDisposable RecordAndSubscribe(IObservable<IInvertibleValue<Action>> source)
  352. {
  353. return Record(source).Subscribe(f => f());
  354. }
  355.  
  356. /// <summary>
  357. /// Immutable structure encapsulating the current state of the undo/redo stacks.
  358. /// </summary>
  359. private struct RecorderStack
  360. {
  361. /// <summary>
  362. /// An empty RecorderStack, where nothing has been recorded.
  363. /// </summary>
  364. public static readonly RecorderStack Empty = new RecorderStack(
  365. ImmutableStack.Create<UndoRedoAction>(), ImmutableStack.Create<UndoRedoAction>());
  366.  
  367. private readonly ImmutableStack<UndoRedoAction> redoStack;
  368. private readonly ImmutableStack<UndoRedoAction> undoStack;
  369.  
  370. private RecorderStack(ImmutableStack<UndoRedoAction> undoStack, ImmutableStack<UndoRedoAction> redoStack)
  371. : this()
  372. {
  373. this.undoStack = undoStack;
  374. this.redoStack = redoStack;
  375. }
  376.  
  377. /// <summary>
  378. /// Performs an Undo. This returns the inverse of the action being un-done, and a new RecorderStack
  379. /// where the action has been popped from the undo stack and its inverse pushed onto the redo stack.
  380. /// </summary>
  381. public Tuple<Action, RecorderStack> Undo()
  382. {
  383. if (undoStack.IsEmpty)
  384. return Tuple.Create(new Action(() => { }), this);
  385.  
  386. UndoRedoAction command;
  387. ImmutableStack<UndoRedoAction> newStack = undoStack.Pop(out command);
  388. return Tuple.Create(command.UndoAction, new RecorderStack(newStack, redoStack.Push(command)));
  389. }
  390.  
  391. /// <summary>
  392. /// Performs a Redo. This returns the action being re-done, and a new RecorderStack where the action
  393. /// has been popped from the redo stack and its inverse pushed onto the undo stack.
  394. /// </summary>
  395. public Tuple<Action, RecorderStack> Redo()
  396. {
  397. if (redoStack.IsEmpty)
  398. return Tuple.Create(new Action(() => { }), this);
  399.  
  400. UndoRedoAction command;
  401. ImmutableStack<UndoRedoAction> newStack = redoStack.Pop(out command);
  402. return Tuple.Create(command.RedoAction, new RecorderStack(undoStack.Push(command), newStack));
  403. }
  404.  
  405. /// <summary>
  406. /// Records an UndoRedoCommand. This returns an "empty" action, and a new RecordStack where the undo
  407. /// action has been pushed onto the undo stack, and the redo stack has been erased.
  408. /// </summary>
  409. /// <param name="command">An undo/redo action pair to be recorded.</param>
  410. public Tuple<Action, RecorderStack> Record(UndoRedoAction command)
  411. {
  412. return Tuple.Create(
  413. new Action(() => { }),
  414. new RecorderStack(undoStack.Push(command), ImmutableStack.Create<UndoRedoAction>()));
  415. }
  416. }
  417.  
  418. /// <summary>
  419. /// Basic struct for storing a pair of undo/redo actions.
  420. /// </summary>
  421. private struct UndoRedoAction
  422. {
  423. public readonly Action RedoAction;
  424. public readonly Action UndoAction;
  425.  
  426. public UndoRedoAction(Action undoAction, Action redoAction)
  427. {
  428. UndoAction = undoAction;
  429. RedoAction = redoAction;
  430. }
  431. }
  432. }
  433.  
  434. public static class UndoRedoExtensions
  435. {
  436. /// <summary>
  437. /// Applies an accumulator function over an observable sequence and returns each intermediate result. Each
  438. /// intermediate state is also recorded for playback via undo or redo.
  439. /// </summary>
  440. /// <typeparam name="TState">The type of the result of the aggregation.</typeparam>
  441. /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
  442. /// <param name="source">An observable sequence to accumulate over.</param>
  443. /// <param name="initialState">The intitial accumulator value.</param>
  444. /// <param name="accumulator">An accumulator function to be invoked on each element.</param>
  445. /// <param name="recorder"></param>
  446. /// <returns>Sequence of intermediate aggregation values with undo/redo interleaved.</returns>
  447. public static IObservable<TState> RecordScan<TState, TSource>(this IObservable<TSource> source, TState initialState,
  448. Func<TState, TSource, TState> accumulator, ReactiveUndoRedoRecorder recorder)
  449. {
  450. return recorder.RecordScan(source, initialState, accumulator);
  451. }
  452.  
  453. /// <summary>
  454. /// Applies an accumulator function over an observable sequence and returns each intermediate result. When
  455. /// undone, replays values from the source observable and combines them with the current state via an
  456. /// inverse accumulator function.
  457. /// </summary>
  458. /// <typeparam name="TState">The type of the result of the aggregation.</typeparam>
  459. /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
  460. /// <param name="source">An observable sequence to accumulate over.</param>
  461. /// <param name="initialState">The intitial accumulator value.</param>
  462. /// <param name="accumulator">An accumulator function to be invoked on each element.</param>
  463. /// <param name="inverseAccumulator">
  464. /// An accumulator function that does the inverse of accumulator, such that the following
  465. /// invariant is satisfied:
  466. ///
  467. /// inverseAccumulator(accumulator(state, a), a) = state
  468. /// </param>
  469. /// <param name="recorder"></param>
  470. /// <returns>Sequence of intermediate aggregation values with undo/redo interleaved.</returns>
  471. public static IObservable<TState> RecordScan<TState, TSource>(this IObservable<TSource> source,
  472. TState initialState, Func<TState, TSource, TState> accumulator,
  473. Func<TState, TSource, TState> inverseAccumulator, ReactiveUndoRedoRecorder recorder)
  474. {
  475. return recorder.RecordScan(source, initialState, accumulator, inverseAccumulator);
  476. }
  477.  
  478. /// <summary>
  479. /// Applies an accumulator function over an observable sequence of invertible values and returns
  480. /// each intermediate result. When undone, applies the inverse of the recorded value to the
  481. /// accumulator to generate the new intermediate state.
  482. /// </summary>
  483. /// <typeparam name="TState">The type of the result of the aggregation.</typeparam>
  484. /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
  485. /// <param name="source">An observable sequence to accumulate over.</param>
  486. /// <param name="initialState">The intitial accumulator value.</param>
  487. /// <param name="accumulator">An accumulator function to be invoked on each element.</param>
  488. /// <param name="recorder"></param>
  489. /// <returns>Sequence of intermediate aggregation values with undo/redo interleaved.</returns>
  490. public static IObservable<TState> RecordScan<TState, TSource>(this IObservable<IInvertibleValue<TSource>> source,
  491. TState initialState, Func<TState, TSource, TState> accumulator, ReactiveUndoRedoRecorder recorder)
  492. {
  493. return recorder.RecordScan(source, initialState, accumulator);
  494. }
  495.  
  496. /// <summary>
  497. /// Records all data coming from the given observable sequence, allowing for "replaying" of previous
  498. /// values via undo and redo.
  499. /// </summary>
  500. /// <typeparam name="T">The type of the elements in the source sequence.</typeparam>
  501. /// <param name="initialState">The intial value, to be produced if the first production is undone.</param>
  502. /// <param name="source">An observable sequence to record.</param>
  503. /// <param name="recorder"></param>
  504. /// <returns>The source observable sequence with undo/redo interleaved.</returns>
  505. public static IObservable<T> Record<T>(this IObservable<T> source, T initialState, ReactiveUndoRedoRecorder recorder)
  506. {
  507. return recorder.Record(source, initialState);
  508. }
  509.  
  510. /// <summary>
  511. /// Given an observable sequence of invertable values, creates a new observable sequence that produces
  512. /// values when the source sequence produces, and also when the source production is undone or redone.
  513. /// </summary>
  514. /// <typeparam name="T">The type of the elements produced by the source and result sequences.</typeparam>
  515. /// <param name="source">An observable sequence of invertible values.</param>
  516. /// <param name="recorder"></param>
  517. /// <returns>The source observable sequence with undo/redo interleaved.</returns>
  518. public static IObservable<T> Record<T>(this IObservable<IInvertibleValue<T>> source,
  519. ReactiveUndoRedoRecorder recorder)
  520. {
  521. return recorder.Record(source);
  522. }
  523.  
  524. /// <summary>
  525. /// Subscribes to an observable sequence of invertible actions, and also records the actions for replaying
  526. /// via undo or redo.
  527. /// </summary>
  528. /// <param name="source">An observable sequence of invertible actions.</param>
  529. /// <param name="recorder"></param>
  530. /// <returns>An IDisposable representing the subscription to actions produced by the source sequence.</returns>
  531. public static IDisposable RecordAndSubscribe(this IObservable<IInvertibleValue<Action>> source, ReactiveUndoRedoRecorder recorder)
  532. {
  533. return recorder.RecordAndSubscribe(source);
  534. }
  535. }
  536. }
Add Comment
Please, Sign In to add comment