1 /**
2  * A collection of nodes.
3  * 
4  * <p>Systems within the engine access the components of entities via NodeLists. A NodeList contains
5  * a node for each Entity in the engine that has all the components required by the node. To iterate
6  * over a NodeList, start from the head and step to the next on each loop, until the returned value
7  * is null.</p>
8  * 
9  * <p>for( var node : Node = nodeList.head; node; node = node.next )
10  * {
11  *   // do stuff
12  * }</p>
13  * 
14  * <p>It is safe to remove items from a nodelist during the loop. When a Node is removed form the 
15  * NodeList it's previous and next properties still point to the nodes that were before and after
16  * it in the NodeList just before it was removed.</p>
17  */
18 module ashd.core.nodelist;
19 
20 import ashd.core.node     : Node;
21 
22 
23 import std.signals; // for Signal template. 
24 
25 // Delegate alias for sort functions
26 alias double delegate(Node, Node) SortFuncType;
27 
28 class NodeList
29 {
30     private
31     {
32         // The first item in the node list, or null if the list contains no nodes.
33         Node mHead;
34 
35         // The last item in the node list, or null if the list contains no nodes.
36         Node mTail;
37     }
38 
39 
40     /// access head node
41     public Node head() { return mHead; }
42 
43     /**
44      * A signal that is dispatched whenever a node is added to the node list.
45      * 
46      * <p>The signal will pass a single parameter to the listeners - the node that was added.</p>
47      */
48     public mixin Signal!( Node ) nodeAdded;
49 
50     /**
51      * A signal that is dispatched whenever a node is removed from the node list.
52      * 
53      * <p>The signal will pass a single parameter to the listeners - the node that was removed.</p>
54      */
55     public mixin Signal!( Node ) nodeRemoved;
56 
57 
58     public this() 
59     {
60         mHead = null;
61         mTail = null;
62     }
63   
64 
65     public void add( Node node_a )
66     {
67         if ( mHead is null )
68         {
69             mHead = mTail = node_a;
70             node_a.next = null;
71             node_a.previous = null;
72         }
73         else
74         {
75             mTail.next = node_a;
76             node_a.previous = mTail;
77             node_a.next = null;
78             mTail = node_a;
79         }
80         nodeAdded.emit( node_a );
81     }
82     
83     public void remove( Node node_a )
84     {
85         if ( mHead == node_a )
86         {
87             mHead = mHead.next;
88         }
89         if ( mTail == node_a )
90         {
91             mTail = mTail.previous;
92         }
93         
94         if ( node_a.previous )
95         {
96             node_a.previous.next = node_a.next;
97         }
98         
99         if ( node_a.next )
100         {
101             node_a.next.previous = node_a.previous;
102         }
103         nodeRemoved.emit( node_a );
104         // N.B. Don't set node.next and node.previous to null because that will break the list iteration if node is the current node in the iteration.
105     }
106 
107     /**
108      * Collect all nodes into array
109      *
110      * Returns:
111      *  Array of nodes
112      */
113     public Node[] allNodes()
114     {
115         Node[] nodes;
116 
117         for ( Node node = mHead; node; node = node.next )
118         {
119             nodes ~= node;
120         }
121         return nodes;
122     }
123 
124     /// Remove all nodes from list
125     public void removeAll()
126     {
127         while( mHead )
128         {
129             Node node = mHead;
130             mHead = node.next;
131             node.previous = null;
132             node.next = null;
133             nodeRemoved.emit( node );
134         }
135         mTail = null;
136     }
137 
138     /**
139      * true if the list is empty, false otherwise.
140      */
141     public bool isEmpty()
142     {
143         return mHead is null;
144     }
145 
146     /**
147      * Swaps the positions of two nodes in the list. Useful when sorting a list.
148      */
149     public void swap( Node node1_a, Node node2_a )
150     {
151         if ( node1_a.previous == node2_a )
152         {
153             node1_a.previous = node2_a.previous;
154             node2_a.previous = node1_a;
155             node2_a.next = node1_a.next;
156             node1_a.next  = node2_a;
157         }
158         else if ( node2_a.previous == node1_a )
159         {
160             node2_a.previous = node1_a.previous;
161             node1_a.previous = node2_a;
162             node1_a.next = node2_a.next;
163             node2_a.next  = node1_a;
164         }
165         else
166         {
167             Node temp = node1_a.previous;
168             node1_a.previous = node2_a.previous;
169             node2_a.previous = temp;
170             temp = node1_a.next;
171             node1_a.next = node2_a.next;
172             node2_a.next = temp;
173         }
174         if ( mHead == node1_a )
175         {
176             mHead = node2_a;
177         }
178         else if ( head == node2_a )
179         {
180             mHead = node1_a;
181         }
182         if ( mTail == node1_a )
183         {
184             mTail = node2_a;
185         }
186         else if ( mTail == node2_a )
187         {
188             mTail = node1_a;
189         }
190         if ( node1_a.previous )
191         {                            
192             node1_a.previous.next = node1_a;
193         }
194         if ( node2_a.previous )
195         {
196             node2_a.previous.next = node2_a;
197         }
198         if ( node1_a.next )
199         {
200             node1_a.next.previous = node1_a;
201         }
202         if ( node2_a.next )
203         {
204             node2_a.next.previous = node2_a;
205         }
206     } // swap()
207 
208 
209     /**
210      * Performs an insertion sort on the node list. In general, insertion sort is very efficient with short lists 
211      * and with lists that are mostly sorted, but is inefficient with large lists that are randomly ordered.
212      * 
213      * <p>The sort function takes two nodes and returns a Number.</p>
214      * 
215      * <p><code>function sortFunction( node1 : MockNode, node2 : MockNode ) : Number</code></p>
216      * 
217      * <p>If the returned number is less than zero, the first node should be before the second. If it is greater
218      * than zero the second node should be before the first. If it is zero the order of the nodes doesn't matter
219      * and the original order will be retained.</p>
220      * 
221      * <p>This insertion sort implementation runs in place so no objects are created during the sort.</p>
222      */
223     public void insertionSort( SortFuncType sortFunction_a )
224     {
225         if ( mHead == mTail )
226         {
227             return;
228         }
229         Node remains = mHead.next;
230         for ( Node node = remains; node; node = remains )
231         {
232             remains = node.next;
233             Node other;
234             for ( other = node.previous; other; other = other.previous )
235             {
236                 if ( sortFunction_a( node, other ) >= 0 )
237                 {
238                     // move node to after other
239                     if ( node != other.next )
240                     {
241                         // remove from place
242                         if ( mTail == node)
243                         {
244                             mTail = node.previous;
245                         }
246                         node.previous.next = node.next;
247                         if (node.next)
248                         {
249                             node.next.previous = node.previous;
250                         }
251                         // insert after other
252                         node.next = other.next;
253                         node.previous = other;
254                         node.next.previous = node;
255                         other.next = node;
256                     }
257                     break; // exit the inner for loop
258                 }
259             }
260             if( !other ) // the node belongs at the start of the list
261             {
262                 // remove from place
263                 if ( mTail == node)
264                 {
265                     mTail = node.previous;
266                 }
267                 node.previous.next = node.next;
268                 if ( node.next )
269                 {
270                     node.next.previous = node.previous;
271                 }
272                 // insert at head
273                 node.next = mHead;
274                 mHead.previous = node;
275                 node.previous = null;
276                 mHead = node;
277             }
278         }
279     }
280     
281     /**
282      * Performs a merge sort on the node list. In general, merge sort is more efficient than insertion sort
283      * with long lists that are very unsorted.
284      * 
285      * <p>The sort function takes two nodes and returns a Number.</p>
286      * 
287      * <p><code>function sortFunction( node1 : MockNode, node2 : MockNode ) : Number</code></p>
288      * 
289      * <p>If the returned number is less than zero, the first node should be before the second. If it is greater
290      * than zero the second node should be before the first. If it is zero the order of the nodes doesn't matter.</p>
291      * 
292      * <p>This merge sort implementation creates and uses a single Vector during the sort operation.</p>
293      */
294     public void mergeSort( SortFuncType sortFunction_a )
295     {
296         if( mHead == mTail )
297         {
298             return;
299         }
300         Node[] lists;
301         // disassemble the list
302         Node start = mHead;
303         Node end;
304         while( start )
305         {
306             end = start;
307             while ( end.next && sortFunction_a( end, end.next ) <= 0 )
308             {
309                 end = end.next;
310             }
311             Node next = end.next;
312             start.previous = null;
313             end.next = null;
314             lists ~= start;
315             start = next;
316         }
317         // reassemble it in order
318         while( lists.length > 1 )
319         {
320             Node merged = merge( lists[0], lists[1], sortFunction_a );
321             lists = lists[2..$] ~ merged;
322         }
323         // find the tail
324         mTail = mHead = lists[0];
325         while( mTail.next )
326         {
327             mTail = mTail.next;    
328         }
329     }
330     
331     public Node merge( Node head1_a, Node head2_a, SortFuncType sortFunction_a )
332     {
333         Node node;
334         Node head;
335         if ( sortFunction_a( head1_a, head2_a ) <= 0 )
336         {
337             head = node = head1_a;
338             head1_a = head1_a.next;
339         }
340         else
341         {
342             head = node = head2_a;
343             head2_a = head2_a.next;
344         }
345         while ( head1_a && head2_a )
346         {
347             if ( sortFunction_a( head1_a, head2_a ) <= 0 )
348             {
349                 node.next = head1_a;
350                 head1_a.previous = node;
351                 node = head1_a;
352                 head1_a = head1_a.next;
353             }
354             else
355             {
356                 node.next = head2_a;
357                 head2_a.previous = node;
358                 node = head2_a;
359                 head2_a = head2_a.next;
360             }
361         }
362         if ( head1_a )
363         {
364             node.next = head1_a;
365             head1_a.previous = node;
366         }
367         else
368         {
369             node.next = head2_a;
370             head2_a.previous = node;
371         }
372         return head;
373     }
374 }