Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ActionStack.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 
7 namespace SiliconStudio.ActionStack
8 {
9  /// <summary>
10  /// This class represents a thread-safe stack of action items that can be undone/redone.
11  /// </summary>
12  public class ActionStack : IActionStack
13  {
14  private readonly object lockObject = new object();
15  private readonly int capacity;
16  private readonly List<IActionItem> actionItems = new List<IActionItem>();
17 
18  /// <summary>
19  /// Initializes a new instance of the <see cref="ActionStack"/> class with the given capacity.
20  /// </summary>
21  /// <param name="capacity">The stack capacity. If negative, the action stack will have an unlimited capacity.</param>
22  public ActionStack(int capacity)
23  : this(capacity, null)
24  {
25  }
26 
27  /// <summary>
28  /// Initializes a new instance of the <see cref="ActionStack"/> class with the given capacity and existing action items.
29  /// </summary>
30  /// <param name="capacity">The stack capacity. If negative, the action stack will have an unlimited capacity.</param>
31  /// <param name="initialActionsItems">The action items to add to the stack.</param>
32  public ActionStack(int capacity, IEnumerable<IActionItem> initialActionsItems)
33  {
34  this.capacity = capacity;
35 
36  if (initialActionsItems != null)
37  {
38  foreach (var originalActionItem in initialActionsItems)
39  actionItems.Add(originalActionItem);
40  }
41  // setup inital index
42  ResetIndexOnTop();
43  }
44 
45  /// <inheritdoc/>
46  public IEnumerable<IActionItem> ActionItems { get { return actionItems; } }
47 
48  /// <summary>
49  /// Gets the capacity of this action stack.
50  /// </summary>
51  public int Capacity { get { return capacity; } }
52 
53  /// <summary>
54  /// Gets whether an undo operation can be executed.
55  /// </summary>
56  public bool CanUndo { get { return CurrentIndex > 0; } }
57 
58  /// <summary>
59  /// Gets whether an redo operation can be executed.
60  /// </summary>
61  public bool CanRedo { get { return CurrentIndex < actionItems.Count; } }
62 
63  /// <summary>
64  /// Raised whenever action items are added to the stack.
65  /// </summary>
66  public event EventHandler<ActionItemsEventArgs<IActionItem>> ActionItemsAdded;
67 
68  /// <summary>
69  /// Raised whenever the action stack is cleared.
70  /// </summary>
71  public event EventHandler ActionItemsCleared;
72 
73  /// <summary>
74  /// Raised whenever action items are discarded from the stack.
75  /// </summary>
76  public event EventHandler<DiscardedActionItemsEventArgs<IActionItem>> ActionItemsDiscarded;
77 
78  /// <summary>
79  /// Raised when an action item is undone.
80  /// </summary>
81  public event EventHandler<ActionItemsEventArgs<IActionItem>> Undone;
82 
83  /// <summary>
84  /// Raised when an action item is redone.
85  /// </summary>
86  public event EventHandler<ActionItemsEventArgs<IActionItem>> Redone;
87 
88  /// <summary>
89  /// Gets the index at which the next action item will be added.
90  /// </summary>
91  protected int CurrentIndex { get; private set; }
92 
93  /// <summary>
94  /// Gets whether an undo/redo operation is currently in progress.
95  /// </summary>
96  protected bool UndoRedoInProgress { get; private set; }
97 
98  /// <inheritdoc/>
99  public virtual void Add(IActionItem item)
100  {
101  if (item == null)
102  throw new ArgumentNullException("item");
103 
104  var items = new[] { item };
105  if (UndoRedoInProgress)
106  {
107  OnActionItemsDiscarded(new DiscardedActionItemsEventArgs<IActionItem>(ActionItemDiscardType.UndoRedoInProgress, items));
108  return;
109  }
110 
111  InternalAddRange(items);
112  }
113 
114  /// <inheritdoc/>
116  {
117  if (items == null)
118  throw new ArgumentNullException("items");
119 
120  var cachedItems = items.ToArray();
121  if (cachedItems.Length == 0)
122  return;
123 
124  InternalAddRange(cachedItems);
125  }
126 
127  /// <inheritdoc/>
128  public void Clear()
129  {
130  InternalClear();
131  OnActionItemsCleared();
132  }
133 
134  /// <inheritdoc/>
135  public virtual SavePoint CreateSavePoint(bool markActionsAsSaved)
136  {
137  if (markActionsAsSaved)
138  {
139  int i = 0;
140  foreach (var action in actionItems)
141  {
142  action.IsSaved = i++ < CurrentIndex;
143  }
144  }
145 
146  return CanUndo ? new SavePoint(actionItems[CurrentIndex - 1].Identifier) : SavePoint.Empty;
147  }
148 
149  /// <inheritdoc/>
150  public virtual bool Undo()
151  {
152  UndoRedoInProgress = true;
153  try
154  {
155  IActionItem action;
156  lock (lockObject)
157  {
158  if (CanUndo == false)
159  return false;
160 
161  action = actionItems[--CurrentIndex];
162  action.Undo();
163  }
164 
165  OnUndone(new ActionItemsEventArgs<IActionItem>(action));
166  return true;
167  }
168  finally
169  {
170  UndoRedoInProgress = false;
171  }
172  }
173 
174  /// <inheritdoc/>
175  public virtual bool Redo()
176  {
177  UndoRedoInProgress = true;
178  try
179  {
180  IActionItem action;
181  lock (lockObject)
182  {
183  if (CanRedo == false)
184  return false;
185 
186  action = actionItems[CurrentIndex++];
187  action.Redo();
188  }
189  OnRedone(new ActionItemsEventArgs<IActionItem>(action));
190  return true;
191  }
192  finally
193  {
194  UndoRedoInProgress = false;
195  }
196  }
197 
198  /// <summary>
199  /// Invoked whenever action items are discarded from the stack.
200  /// </summary>
201  /// <param name="e">The arguments that will be passed to the <see cref="ActionItemsDiscarded"/> event raised by this method.</param>
202  protected virtual void OnActionItemsDiscarded(DiscardedActionItemsEventArgs<IActionItem> e)
203  {
204  var handler = ActionItemsDiscarded;
205  if (handler != null)
206  handler(this, e);
207  }
208 
209  /// <summary>
210  /// Invoked whenever action items are added to the stack.
211  /// </summary>
212  /// <param name="e">The arguments that will be passed to the <see cref="ActionItemsAdded"/> event raised by this method.</param>
213  protected virtual void OnActionItemsAdded(ActionItemsEventArgs<IActionItem> e)
214  {
215  var handler = ActionItemsAdded;
216  if (handler != null)
217  handler(this, e);
218  }
219 
220  /// <summary>
221  /// Invoked whenever the action stack is cleared.
222  /// </summary>
223  protected virtual void OnActionItemsCleared()
224  {
225  var handler = ActionItemsCleared;
226  if (handler != null)
227  handler(this, EventArgs.Empty);
228  }
229 
230  /// <summary>
231  /// Invoked Raised when an action item is undone.
232  /// </summary>
233  /// <param name="e">The arguments that will be passed to the <see cref="Undone"/> event raised by this method.</param>
234  protected virtual void OnUndone(ActionItemsEventArgs<IActionItem> e)
235  {
236  var handler = Undone;
237  if (handler != null)
238  handler(this, e);
239  }
240 
241  /// <summary>
242  /// Invoked Raised when an action item is redone.
243  /// </summary>
244  /// <param name="e">The arguments that will be passed to the <see cref="Redone"/> event raised by this method.</param>
245  protected virtual void OnRedone(ActionItemsEventArgs<IActionItem> e)
246  {
247  var handler = Redone;
248  if (handler != null)
249  handler(this, e);
250  }
251 
252  private void InternalClear()
253  {
254  lock (lockObject)
255  {
256  actionItems.Clear();
257  ResetIndexOnTop();
258  OnActionItemsCleared();
259  }
260  }
261 
262  private void InternalAddRange(IActionItem[] items)
263  {
264  lock (lockObject)
265  {
266  // stack pre-cleanup
267  UnsafeDisbranchedCleanup();
268 
269  // add items
270  foreach (var item in items)
271  actionItems.Add(item);
272 
273  // post-cleanup
274  IActionItem[] discarded = null;
275  if (capacity >= 0 && actionItems.Count > capacity)
276  {
277  // stack is overloaded
278  discarded = actionItems
279  .Take(actionItems.Count - capacity)
280  .ToArray();
281  int itemsToRemove = actionItems.Count - capacity;
282  for (int i = 0; i < itemsToRemove; ++i)
283  {
284  actionItems[0].Freeze();
285  actionItems.RemoveAt(0);
286  }
287  }
288 
289  ResetIndexOnTop();
290 
291  // raise event to notify of cleaned up items
292  if (discarded != null)
293  {
294  OnActionItemsDiscarded(new DiscardedActionItemsEventArgs<IActionItem>(
295  ActionItemDiscardType.Swallowed,
296  discarded));
297  }
298 
299  // raise event to notify of added items
300  var added = items;
301  if (capacity >= 0 && items.Length > capacity)
302  added = items.Skip(items.Length - capacity).ToArray();
303 
304  OnActionItemsAdded(new ActionItemsEventArgs<IActionItem>(added));
305  }
306  }
307 
308  private void UnsafeDisbranchedCleanup()
309  {
310  if (CurrentIndex < 0)
311  {
312  // the whole stack has been undone and index is back to the beginning
313  var discarded = actionItems.ToArray();
314  // clean the stack
315  InternalClear();
316 
317  OnActionItemsDiscarded(new DiscardedActionItemsEventArgs<IActionItem>(ActionItemDiscardType.Disbranched, discarded));
318  }
319  else if (actionItems.Count - CurrentIndex > 0)
320  {
321  // the stack has been undone a bit and index points before the last item
322  // copy the discarded items, from CurrentIndex to the action item count
323  var discardedItems = actionItems.Skip(CurrentIndex).Take(actionItems.Count - CurrentIndex).ToArray();
324 
325  // remove items that are in range
326  int itemsToRemove = actionItems.Count - CurrentIndex;
327  for (int i = 0; i < itemsToRemove; ++i)
328  actionItems.RemoveAt(CurrentIndex);
329 
330  ResetIndexOnTop();
331 
332  OnActionItemsDiscarded(new DiscardedActionItemsEventArgs<IActionItem>(ActionItemDiscardType.Disbranched, discardedItems));
333  }
334  }
335 
336  private void ResetIndexOnTop()
337  {
338  CurrentIndex = actionItems.Count;
339  }
340  }
341 }
EventHandler< ActionItemsEventArgs< IActionItem > > ActionItemsAdded
Raised whenever action items are added to the stack.
Definition: ActionStack.cs:66
EventHandler< ActionItemsEventArgs< IActionItem > > Redone
Raised when an action item is redone.
Definition: ActionStack.cs:86
ActionItemDiscardType
This enum describes how an action item is being discarded when the ActionStack.ActionItemsDiscarded e...
EventHandler ActionItemsCleared
Raised whenever the action stack is cleared.
Definition: ActionStack.cs:71
EventHandler< DiscardedActionItemsEventArgs< IActionItem > > ActionItemsDiscarded
Raised whenever action items are discarded from the stack.
Definition: ActionStack.cs:76
Represents a save point marker in the undo/redo action items stack.
Definition: SavePoint.cs:11
void Clear()
Clears the action stack.
Definition: ActionStack.cs:128
static readonly SavePoint Empty
Empty save point.
Definition: SavePoint.cs:30
virtual void Add(IActionItem item)
Adds an action item to the stack. Discards any action item that is currently undone. The action item to add to the stack.
Definition: ActionStack.cs:99
ActionStack(int capacity)
Initializes a new instance of the ActionStack class with the given capacity.
Definition: ActionStack.cs:22
EventHandler< ActionItemsEventArgs< IActionItem > > Undone
Raised when an action item is undone.
Definition: ActionStack.cs:81
virtual void OnActionItemsDiscarded(DiscardedActionItemsEventArgs< IActionItem > e)
Invoked whenever action items are discarded from the stack.
Definition: ActionStack.cs:202
void AddRange(IEnumerable< IActionItem > items)
Adds multiple action items on the stack. Discards any action item that is currently undone...
Definition: ActionStack.cs:115
Base interface for action items.
Definition: IActionItem.cs:10
ActionStack(int capacity, IEnumerable< IActionItem > initialActionsItems)
Initializes a new instance of the ActionStack class with the given capacity and existing action items...
Definition: ActionStack.cs:32
virtual SavePoint CreateSavePoint(bool markActionsAsSaved)
Creates a save point at the current index of the action stack. Indicate whether to set the IActionIte...
Definition: ActionStack.cs:135
virtual void OnRedone(ActionItemsEventArgs< IActionItem > e)
Invoked Raised when an action item is redone.
Definition: ActionStack.cs:245
Base interface to for an action stack.
Definition: IActionStack.cs:11
virtual bool Redo()
Redoes the first action item that is currently undone. True if an action could be redone...
Definition: ActionStack.cs:175
virtual void OnActionItemsCleared()
Invoked whenever the action stack is cleared.
Definition: ActionStack.cs:223
virtual void OnUndone(ActionItemsEventArgs< IActionItem > e)
Invoked Raised when an action item is undone.
Definition: ActionStack.cs:234
Item discarded because an undo/redo operation is currently in progress.
This class represents a thread-safe stack of action items that can be undone/redone.
Definition: ActionStack.cs:12
virtual bool Undo()
Undoes the last action item that is currently done. True if an action could be undone, False otherwise.
Definition: ActionStack.cs:150
virtual void OnActionItemsAdded(ActionItemsEventArgs< IActionItem > e)
Invoked whenever action items are added to the stack.
Definition: ActionStack.cs:213