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 }