/* * JaamSim Discrete Event Simulation * Copyright (C) 2014 Ausenco Engineering Canada Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.jaamsim.events; import java.util.Arrays; /** * EventTree is a custom red-black tree implementation intended to be used as the priority queue * storing Jaamsim's discrete events * @author matt.chudleigh * */ class EventTree { private EventNode root = EventNode.nilNode; private EventNode lowest = null; /////////////////////////////////////////// // Scratch space, used instead of having parent pointers private EventNode[] scratch = new EventNode[64]; private int scratchPos = 0; private void pushScratch(EventNode n) { scratch[scratchPos++] = n; } private void dropScratch(int n) { scratchPos = Math.max(0, scratchPos - n); } // Get the 'nth' node from the end of the scratch (1 being the first) private EventNode getScratch(int n) { return (scratchPos >= n) ? scratch[scratchPos - n] : null; } private void resetScratch() { scratchPos = 0; } EventNode getNextNode() { if (lowest == null) updateLowest(); return lowest; } final void reset() { root = EventNode.nilNode; lowest = null; clearFreeList(); resetScratch(); Arrays.fill(scratch, null); } private void updateLowest() { if (root == EventNode.nilNode) { lowest = null; return; } EventNode current = root; while (current.left != EventNode.nilNode) current = current.left; lowest = current; } final EventNode createOrFindNode(long schedTick, int priority) { if (root == EventNode.nilNode) { root = getNewNode(schedTick, priority); lowest = root; return root; } resetScratch(); EventNode n = root; EventNode newNode = null; while (true) { int comp = n.compare(schedTick, priority); if (comp == 0) { return n; // Found existing node } EventNode next = comp > 0 ? n.left : n.right; if (next != EventNode.nilNode) { pushScratch(n); n = next; continue; } // There is no current node for this time/priority newNode = getNewNode(schedTick, priority); pushScratch(n); newNode.red = true; if (comp > 0) n.left = newNode; else n.right = newNode; break; } insertBalance(newNode); root.red = false; if (lowest != null && newNode.compareToNode(lowest) < 0) { lowest = newNode; } return newNode; } private void insertBalance(EventNode n) { // See the wikipedia page for red-black trees to understand the case numbers EventNode parent = getScratch(1); if (parent == null || !parent.red) return; // cases 1 and 2 final EventNode gp = getScratch(2); if (gp == null) return; EventNode uncle = (gp.left == parent ? gp.right : gp.left); if (uncle.red) { // Both parent and uncle are red // case 2 parent.red = false; uncle.red = false; gp.red = true; dropScratch(2); insertBalance(gp); return; } // case 4 if (n == parent.right && parent == gp.left) { // Right child of a left parent, rotate left at parent parent.rotateLeft(gp); parent = n; n = n.left; } else if (n == parent.left && parent == gp.right) { // left child of right parent, rotate right at parent parent.rotateRight(gp); parent = n; n = n.right; } EventNode ggp = getScratch(3); // case 5 gp.red = true; parent.red = false; if (parent.left == n) { if (gp == root) root = gp.left; gp.rotateRight(ggp); } else { if (gp == root) root = gp.right; gp.rotateLeft(ggp); } } final boolean removeNode(long schedTick, int priority) { // First find the node to remove resetScratch(); lowest = null; EventNode current = root; while (true) { int comp = current.compare(schedTick, priority); if (comp == 0) break; pushScratch(current); if (comp > 0) current = current.left; else current = current.right; if (current == EventNode.nilNode) { return false; // Node not found } } // Debugging if (current.head != null || current.tail != null) throw new RuntimeException("Removing non-empy node"); // We have the node to remove if (current.left != EventNode.nilNode && current.right != EventNode.nilNode) { current = swapToLeaf(current); } // // Verify we have a proper parent list (testing only) // if (scratchPos > 0 && scratch[0] != root) throw new RuntimeException("Bad parent list"); // for (int i = 1; i < scratchPos; ++i) { // // Check the current node is a child of the previous // EventNode child = scratch[i]; // EventNode parent = scratch[i-1]; // if (parent.left != child && parent.right != child) { // throw new RuntimeException("Bad parent list"); // } // } EventNode child = current.left != EventNode.nilNode ? current.left : current.right; EventNode parent = getScratch(1); // Drop the node if (parent != null) { if (parent.left == current) parent.left = child; else parent.right = child; } if (current == root) root = child; boolean currentIsRed = current.red; reuseNode(current); if (currentIsRed) { return true; // We swapped out a red node, there's nothing else to do } if (child.red) { child.red = false; return true; // traded a red for a black, still all good. } // We removed a black node with a black child, we need to re-balance the tree deleteBalance(child); root.red = false; return true; } private EventNode swapToLeaf(EventNode node) { pushScratch(node); EventNode curr = node.left; while (curr.right != EventNode.nilNode) { pushScratch(curr); curr = curr.right; } node.cloneFrom(curr); return curr; } private void deleteBalance(EventNode n) { // At all times the scratch space should contain the parent list (but not n) EventNode parent = getScratch(1); if (parent == null) return; EventNode sib = (parent.left == n) ? parent.right : parent.left; EventNode gp = getScratch(2); // case 2 if (sib.red) { sib.red = false; parent.red = true; if (n == parent.left) parent.rotateLeft(gp); else parent.rotateRight(gp); if (root == parent) root = sib; // update the parent list after the rotation dropScratch(1); pushScratch(sib); pushScratch(parent); gp = getScratch(2); // update the sibling sib = (parent.left == n) ? parent.right : parent.left; } // case 3 if (!parent.red && !sib.left.red && !sib.right.red) { sib.red = true; dropScratch(1); deleteBalance(parent); return; } // case 4 if (parent.red && !sib.left.red && !sib.right.red) { parent.red = false; sib.red = true; return; } // case 5 if (parent.left == n && !sib.right.red && sib.left.red) { sib.red = true; sib.left.red = false; sib.rotateRight(parent); sib = parent.right; } else if (parent.right == n && !sib.left.red && sib.right.red) { sib.red = true; sib.right.red = false; sib.rotateLeft(parent); sib = parent.left; } // case 6 sib.red = parent.red; parent.red = false; if (n == parent.left) { sib.right.red = false; parent.rotateLeft(gp); } else { sib.left.red = false; parent.rotateRight(gp); } if (root == parent) { root = sib; } } final void runOnAllNodes(EventNode.Runner runner) { runOnNode(root, runner); } private void runOnNode(EventNode node, EventNode.Runner runner) { if (node == EventNode.nilNode) return; runOnNode(node.left, runner); runner.runOnNode(node); runOnNode(node.right, runner); } // Verify the sorting structure and return the number of nodes final int verify() { if (root == EventNode.nilNode) return 0; if (EventNode.nilNode.red == true) throw new RuntimeException("nil node corrupted, turned red"); return verifyNode(root); } private int verifyNode(EventNode n) { int lBlacks = 0; int rBlacks = 0; if (n.left != EventNode.nilNode) { if (n.compareToNode(n.left) != 1) throw new RuntimeException("RB tree order verify failed"); lBlacks = verifyNode(n.left); } if (n.right != EventNode.nilNode) { if (n.compareToNode(n.right) != -1) throw new RuntimeException("RB tree order verify failed"); rBlacks = verifyNode(n.right); } if (n.red) { if (n.left.red) throw new RuntimeException("RB tree red-red child verify failed"); if (n.right.red) throw new RuntimeException("RB tree red-red child verify failed"); } if (lBlacks != rBlacks) throw new RuntimeException("RB depth equality verify failed"); return lBlacks + (n.red ? 0 : 1); } // Search the tree and return true if this node is found final EventNode find(long schedTick, int priority) { EventNode curr = root; while (true) { if (curr == EventNode.nilNode) return null; int comp = curr.compare(schedTick, priority); if (comp == 0) { return curr; } if (comp < 0) { curr = curr.right; continue; } curr = curr.left; continue; } } final int verifyNodeCount() { if (root == EventNode.nilNode) return 0; return countNodes(root); } private int countNodes(EventNode n) { int count = 1; if (n.left != EventNode.nilNode) count += countNodes(n.left); if (n.right != EventNode.nilNode) count += countNodes(n.right); return count; } private EventNode freeList = null; private EventNode getNewNode(long schedTick, int priority) { if (freeList == null) { return new EventNode(schedTick, priority); } EventNode ret = freeList; freeList = freeList.left; ret.schedTick = schedTick; ret.priority = priority; ret.head = null; ret.tail = null; ret.left = EventNode.nilNode; ret.right = EventNode.nilNode; ret.red = false; return ret; } private void reuseNode(EventNode node) { // Clear the node node.left = null; node.right = null; node.head = null; node.tail = null; node.left = freeList; freeList = node; } private void clearFreeList() { freeList = null; } }