1 /// This is a simplified stack implementation optimized to reuse the nodes it creates.
2 ///
3 /// Fluid uses stacks to iterate through tree structures such as `Rope` or `TextRulerCache`. The ability to quickly 
4 /// create new stack items is crucial to their performance, and as such avoiding allocations is a must. For this reason,
5 /// nodes are reused whenever possible, instead of being reclaimed by the GC.
6 module fluid.future.stack;
7 
8 version (unittest)
9     version = Fluid_EnableStackStatistics;
10 
11 /// Implementation of a stack optimized to reduce allocations.
12 ///
13 /// Removing nodes from the stack using `pop` or `clear` will move them to a global stack of "free" nodes. 
14 /// The next time an item is added to the stack, it will reuse one of these previously freed nodes. This recycling 
15 /// mechanism makes it possible to significantly reduce the number of allocations as the program continues.
16 ///
17 /// If version `Fluid_MemoryStatistics` is set, `Stack` will count the number of total nodes it has allocated per 
18 /// type and thread and expose then through `totalNodeCount`.
19 struct Stack(T) {
20 
21     private {
22 
23         /// All stack nodes that are not currently in use.
24         static StackNode!T* _trash;
25 
26         version (Fluid_EnableStackStatistics)
27             static int _totalNodeCount;
28 
29         /// Top of the stack.
30         StackNode!T* _top;
31 
32         /// Bottom of the stack, used to quickly transfer all nodes to the stack with `clear`.
33         /// Warning: Bottom isn't cleared on `pop`, so it doesn't have to be null when empty.
34         StackNode!T* _bottom;
35 
36     }
37 
38     this(T item) {
39         // Stack could support batch operations so this would take a variadic argument
40         push(item);
41     }
42 
43     ~this() {
44         clear();
45     }
46 
47     version (Fluid_EnableStackStatistics) {
48 
49         /// This special field is only available when version `Fluid_MemoryStatistics` is set. `Stack` will then count 
50         /// the number of total nodes it has allocated per type and thread.
51         /// 
52         /// This count can be reset with `resetNodeCount`.
53         /// 
54         /// Returns: Total number of nodes allocated by the stack.
55         static int totalNodeCount() {
56             return _totalNodeCount;
57         }
58 
59         /// Reset `totalNodeCount` back to 0.
60         static void resetNodeCount() {
61             _totalNodeCount = 0;
62         }
63 
64     }
65 
66     /// Returns: True if the stack is empty.
67     bool empty() const {
68         return _top is null;
69     }
70 
71     alias back = top;
72     alias removeBack = pop;
73 
74     /// Returns: The item at the top of this stack.
75     ref inout(T) top() inout
76     in (!empty, "Nothing is at the top of the stack when it is empty")
77     do {
78         return _top.item;
79     }
80 
81     /// Add an item to the stack.
82     /// Params:
83     ///     item = Item to add to the top of the stack.
84     void push(T item) {
85         _top = getNode(_top, item);
86         assert(_top !is _top.next, "A node must not be trashed while still in use");
87 
88         // Mark this as the bottom, if it is so
89         if (_top.next is null) {
90             _bottom = _top;
91             assert(_bottom.next is null);
92         }
93     }
94 
95     /// Remove the item at the top of the stack.
96     /// Returns: The item that was removed.
97     T pop()
98     in (!empty, "`pop` cannot operate on an empty stack")
99     do {
100 
101         auto node = _top;
102 
103         assert(node !is _trash, "Node was already trashed. Was the Stack duplicated?");
104 
105         // Remove the node from the stack
106         _top = node.next;
107 
108         // Trash the node
109         node.next = _trash;
110         _trash = node;
111 
112         return node.item;
113 
114     }
115 
116     /// Empty the stack.
117     ///
118     /// Done automatically when the stack leaves the scope.
119     void clear()
120     out (; empty)
121     do {
122 
123         import core.memory : GC;
124 
125         // Already empty, nothing to do
126         if (empty) return;
127 
128         // If triggered from the GC, the objects cannot be trashed, as they may not be recyclable anymore
129         if (GC.inFinalizer) {
130             _top = null;
131             return;
132         }
133 
134         // Trash the bottom
135         _bottom.next = _trash;
136         _trash = _top;
137         _top = null;
138 
139     }
140 
141     /// Params:
142     ///     item = Item that will be pushed to the top of the stack.
143     void opOpAssign(string op : "~")(T item) {
144 
145         push(item);
146 
147     }
148 
149     /// Returns:
150     ///     A range that allows iterating on the range without removing any items from it. While the range functions,
151     ///     items cannot be removed from the stack, or the range may break, possibly crashing the program. 
152     StackRange!T opIndex() @system {
153 
154         return StackRange!T(_top);
155 
156     }
157 
158     private StackNode!T* getNode(StackNode!T* next, T item) {
159 
160         // Trash is empty, allocate
161         if (_trash is null) {
162             version (Fluid_EnableStackStatistics) {
163                 _totalNodeCount++;
164             }
165             auto node = new StackNode!T(next, item);
166             return node;
167         }
168 
169         // Take an item from the trash
170         else {
171             auto node = _trash;
172             _trash = node.next;
173             *node = StackNode!T(next, item);
174             return node;
175         }
176 
177     }
178 
179 }
180 
181 /// A StackRange can be used to iterate a stack (starting from top, going to bottom) without modifying it.
182 ///
183 /// The stack cannot be modified while it has any range attached to it.
184 struct StackRange(T) {
185 
186     private StackNode!T* node;
187     @disable this();
188 
189     private this(StackNode!T* node) {
190         this.node = node;
191     }
192 
193     /// Returns: True if the range has been emptied.
194     bool empty() const @system {
195         return node is null;
196     }
197 
198     /// Returns: The item at the top of this range.
199     ref inout(T) front() inout @system
200     in (!empty, "Cannot use `front` of an empty range")
201     do {
202         return node.item;
203     }
204 
205     /// Advance to the next item of the range.
206     void popFront() @system
207     in (!empty, "Cannot use `popFront` on an empty range")
208     do {
209         node = node.next;
210     }
211 
212 }
213 
214 private struct StackNode(T) {
215 
216     StackNode* next;
217     T item;
218 
219 }
220 
221 version (unittest) {
222 
223     /// Struct for testing the stack.
224     private struct Test { 
225         int value; 
226         alias value this;
227     }
228 
229 }
230 
231 @("Stack functions correctly")
232 unittest {
233 
234     Stack!Test stack;
235     stack._trash = null;
236     assert(stack.empty);
237     stack.push(Test(1));
238     assert(stack.top == 1);
239     assert(stack._top.next is null);
240     stack.push(Test(2));
241     assert(stack.top == 2);
242     assert(stack._top.next !is null);
243     stack.push(Test(3));
244     assert(stack.top == 3);
245     assert(stack.pop() == 3);
246     assert(stack.pop() == 2);
247     assert(stack.pop() == 1);
248     assert(stack.empty);
249 
250     assert(stack._trash !is null);
251     assert(*stack.getNode(null, Test(1)) == StackNode!Test(null, Test(1)));
252     assert(*stack.getNode(null, Test(2)) == StackNode!Test(null, Test(2)));
253     assert(*stack.getNode(null, Test(3)) == StackNode!Test(null, Test(3)));
254     assert(stack._trash is null);
255     assert(Stack!Test.totalNodeCount == 3);
256     Stack!Test.resetNodeCount();
257     
258 }
259 
260 @("Stack recycles nodes")
261 unittest {
262 
263     Stack!Test stack;
264     stack._trash = null;
265     stack ~= Test(1);
266     stack ~= Test(2);
267     stack ~= Test(3);
268     assert(stack._trash is null);
269     assert(stack._bottom !is null);
270     assert(Stack!Test.totalNodeCount == 3);
271 
272     auto nodes = [stack._top, stack._top.next, stack._top.next.next];
273 
274     auto top = stack._top;
275 
276     // Clear to destroy the stack
277     stack.clear();
278     assert(stack.empty);
279     assert(stack._trash is top);
280     assert(stack._trash == nodes[0]);
281     assert(stack._trash.next == nodes[1]);
282     assert(stack._trash.next.next == nodes[2]);
283 
284     stack ~= Test(4);
285     stack ~= Test(5);
286     stack ~= Test(6);
287     assert(Stack!Test.totalNodeCount == 3);
288     assert(stack._trash is null);
289     assert(stack._top == nodes[2]);
290     assert(stack._top.next == nodes[1]);
291     assert(stack._top.next.next == nodes[0]);
292     assert(stack.pop() == 6);
293     assert(stack.pop() == 5);
294     assert(stack.pop() == 4);
295 
296     stack._trash = null;
297     assert(Stack!Test.totalNodeCount == 3);
298     Stack!Test.resetNodeCount();
299 
300 }
301 
302 @("Multiple stacks can share resources")
303 unittest {
304 
305     Stack!Test a;
306     Stack!Test b;
307 
308     assert(Stack!Test.totalNodeCount == 0);
309     assert(a._trash is null);
310     assert(b._trash is null);
311 
312     a ~= Test(1);
313     a ~= Test(2);
314     b ~= Test(3);
315     b ~= Test(4);
316 
317     assert(Stack!Test.totalNodeCount == 4);
318 
319     assert(a.pop() == 2);
320     assert(a._trash !is null);
321 
322     auto freed = a._trash;
323 
324     b ~= Test(5);
325     assert(b._top is freed);
326     assert(a._trash is null);
327     assert(Stack!Test.totalNodeCount == 4);
328 
329     {
330         Stack!Test c;
331         c ~= Test(6);
332         c ~= Test(7);
333         c ~= Test(8);
334         c ~= Test(9);
335     }
336     assert(Stack!Test.totalNodeCount == 8);
337     assert(a._trash !is null);
338 
339     a.clear();
340     b.clear();
341 
342     foreach (i; 10..18) {
343         a ~= Test(i);
344     }
345     assert(Stack!Test.totalNodeCount == 8);
346     a ~= Test(18);
347     assert(Stack!Test.totalNodeCount == 9);
348 
349     a.clear();
350     Stack!Test.resetNodeCount();
351     Stack!Test._trash = null;
352 
353 }
354 
355 @("Stack can be used with Range API")
356 unittest {
357 
358     import std.algorithm;
359 
360     assert(Stack!Test.totalNodeCount == 0);
361     assert(Stack!Test._trash is null);
362 
363     Stack!Test stack;
364     stack ~= Test(1);
365     stack ~= Test(2);
366     stack ~= Test(3);
367     stack ~= Test(4);
368     stack ~= Test(5);
369 
370     assert(Stack!Test.totalNodeCount == 5);
371     assert(stack[].equal([5, 4, 3, 2, 1]));
372     assert(Stack!Test.totalNodeCount == 5);
373 
374     assert(stack.pop() == Test(5));
375     assert(stack.pop() == Test(4));
376     assert(stack.pop() == Test(3));
377     assert(stack.pop() == Test(2));
378     assert(stack.pop() == Test(1));
379     assert(stack.empty());
380 
381     assert(Stack!Test.totalNodeCount == 5);
382     Stack!Test.resetNodeCount();
383     Stack!Test._trash = null;
384 
385 }