Index: sdk/lib/io/timer_impl.dart |
=================================================================== |
--- sdk/lib/io/timer_impl.dart (revision 43127) |
+++ sdk/lib/io/timer_impl.dart (working copy) |
@@ -1,441 +0,0 @@ |
-// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-part of dart.io; |
- |
-// Timer heap implemented as a array-based binary heap[0]. |
-// This allows for O(1) `first`, O(log(n)) `remove`/`removeFirst` and O(log(n)) |
-// `add`. |
-// |
-// To ensure the timers are ordered by insertion time, the _Timer class has a |
-// `_id` field set when added to the heap. |
-// |
-// [0] http://en.wikipedia.org/wiki/Binary_heap |
-class _TimerHeap { |
- List<_Timer> _list; |
- int _used = 0; |
- |
- _TimerHeap([int initSize = 7]) |
- : _list = new List<_Timer>(initSize); |
- |
- bool get isEmpty => _used == 0; |
- |
- _Timer get first => _list[0]; |
- |
- bool isFirst(_Timer timer) => timer._indexOrNext == 0; |
- |
- void add(_Timer timer) { |
- if (_used == _list.length) { |
- _resize(); |
- } |
- timer._indexOrNext = _used++; |
- _list[timer._indexOrNext] = timer; |
- _bubbleUp(timer); |
- } |
- |
- _Timer removeFirst() { |
- var f = first; |
- remove(f); |
- return f; |
- } |
- |
- void remove(_Timer timer) { |
- _used--; |
- if (isEmpty) { |
- _list[0] = null; |
- timer._indexOrNext = null; |
- return; |
- } |
- var last = _list[_used]; |
- if (!identical(last, timer)) { |
- last._indexOrNext = timer._indexOrNext; |
- _list[last._indexOrNext] = last; |
- if (last._compareTo(timer) < 0) { |
- _bubbleUp(last); |
- } else { |
- _bubbleDown(last); |
- } |
- } |
- _list[_used] = null; |
- timer._indexOrNext = null; |
- } |
- |
- void _resize() { |
- var newList = new List(_list.length * 2 + 1); |
- newList.setRange(0, _used, _list); |
- _list = newList; |
- } |
- |
- void _bubbleUp(_Timer timer) { |
- while (!isFirst(timer)) { |
- Timer parent = _parent(timer); |
- if (timer._compareTo(parent) < 0) { |
- _swap(timer, parent); |
- } else { |
- break; |
- } |
- } |
- } |
- |
- void _bubbleDown(_Timer timer) { |
- while (true) { |
- int leftIndex = _leftChildIndex(timer._indexOrNext); |
- int rightIndex = _rightChildIndex(timer._indexOrNext); |
- _Timer newest = timer; |
- if (leftIndex < _used && _list[leftIndex]._compareTo(newest) < 0) { |
- newest = _list[leftIndex]; |
- } |
- if (rightIndex < _used && _list[rightIndex]._compareTo(newest) < 0) { |
- newest = _list[rightIndex]; |
- } |
- if (identical(newest, timer)) { |
- // We are where we should be, break. |
- break; |
- } |
- _swap(newest, timer); |
- } |
- } |
- |
- void _swap(_Timer first, _Timer second) { |
- int tmp = first._indexOrNext; |
- first._indexOrNext = second._indexOrNext; |
- second._indexOrNext = tmp; |
- _list[first._indexOrNext] = first; |
- _list[second._indexOrNext] = second; |
- } |
- |
- Timer _parent(_Timer timer) => _list[_parentIndex(timer._indexOrNext)]; |
- Timer _leftChild(_Timer timer) => _list[_leftChildIndex(timer._indexOrNext)]; |
- Timer _rightChild(_Timer timer) => |
- _list[_rightChildIndex(timer._indexOrNext)]; |
- |
- static int _parentIndex(int index) => (index - 1) ~/ 2; |
- static int _leftChildIndex(int index) => 2 * index + 1; |
- static int _rightChildIndex(int index) => 2 * index + 2; |
-} |
- |
-class _Timer implements Timer { |
- // Cancels the timer in the event handler. |
- static const int _NO_TIMER = -1; |
- |
- // Timers are ordered by wakeup time. |
- static _TimerHeap _heap = new _TimerHeap(); |
- static _Timer _firstZeroTimer; |
- static _Timer _lastZeroTimer; |
- |
- // We use an id to be able to sort timers with the same expiration time. |
- // ids are recycled after ID_MASK enqueues or when the timer queue is empty. |
- static int _ID_MASK = 0x1fffffff; |
- static int _idCount = 0; |
- |
- static RawReceivePort _receivePort; |
- static SendPort _sendPort; |
- static int _scheduledWakeupTime; |
- // Keep track whether at least one message is pending in the event loop. This |
- // way we do not have to notify for every pending _firstZeroTimer. |
- static var _messagePending = false; |
- |
- static bool _handlingCallbacks = false; |
- |
- Function _callback; // Closure to call when timer fires. null if canceled. |
- int _wakeupTime; // Expiration time. |
- int _milliSeconds; // Duration specified at creation. |
- bool _repeating; // Indicates periodic timers. |
- var _indexOrNext; // Index if part of the TimerHeap, link otherwise. |
- int _id; // Incrementing id to enable sorting of timers with same expiry. |
- |
- // Get the next available id. We accept collisions and reordering when the |
- // _idCount overflows and the timers expire at the same millisecond. |
- static int _nextId() { |
- var result = _idCount; |
- _idCount = (_idCount + 1) & _ID_MASK; |
- return result; |
- } |
- |
- _Timer._internal(this._callback, |
- this._wakeupTime, |
- this._milliSeconds, |
- this._repeating) : _id = _nextId(); |
- |
- static Timer _createTimer(void callback(Timer timer), |
- int milliSeconds, |
- bool repeating) { |
- // Negative timeouts are treated as if 0 timeout. |
- if (milliSeconds < 0) { |
- milliSeconds = 0; |
- } |
- // Add one because DateTime.now() is assumed to round down |
- // to nearest millisecond, not up, so that time + duration is before |
- // duration milliseconds from now. Using microsecond timers like |
- // Stopwatch allows detecting that the timer fires early. |
- int now = new DateTime.now().millisecondsSinceEpoch; |
- int wakeupTime = (milliSeconds == 0) ? now : (now + 1 + milliSeconds); |
- |
- _Timer timer = new _Timer._internal(callback, |
- wakeupTime, |
- milliSeconds, |
- repeating); |
- |
- if (timer._addTimerToHeap()) { |
- // The new timer is the first in queue. Update event handler. |
- _notifyEventHandler(); |
- } |
- return timer; |
- } |
- |
- factory _Timer(int milliSeconds, void callback(Timer timer)) { |
- return _createTimer(callback, milliSeconds, false); |
- } |
- |
- factory _Timer.periodic(int milliSeconds, void callback(Timer timer)) { |
- return _createTimer(callback, milliSeconds, true); |
- } |
- |
- bool get _isInHeap => _indexOrNext is int; |
- |
- void _clear() { |
- _callback = null; |
- } |
- |
- int _compareTo(_Timer other) { |
- int c = _wakeupTime - other._wakeupTime; |
- if (c != 0) return c; |
- return _id - other._id; |
- } |
- |
- bool get isActive => _callback != null; |
- |
- // Cancels a set timer. The timer is removed from the timer list and if |
- // the given timer is the earliest timer the event handler is notified. |
- void cancel() { |
- _clear(); |
- if (!_isInHeap) return; |
- // Only heap timers are really removed. Others are just dropped on |
- // notification. |
- bool update = (_firstZeroTimer == null) && _heap.isFirst(this); |
- _heap.remove(this); |
- if (update) { |
- _notifyEventHandler(); |
- } |
- } |
- |
- void _advanceWakeupTime() { |
- // Recalculate the next wakeup time. For repeating timers with a 0 timeout |
- // the next wakeup time is now. |
- _id = _nextId(); |
- if (_milliSeconds > 0) { |
- _wakeupTime += _milliSeconds; |
- } else { |
- _wakeupTime = new DateTime.now().millisecondsSinceEpoch; |
- } |
- } |
- |
- // Adds a timer to the heap or timer list. Timers with the same wakeup time |
- // are enqueued in order and notified in FIFO order. |
- bool _addTimerToHeap() { |
- if (_milliSeconds == 0) { |
- if (_firstZeroTimer == null) { |
- _lastZeroTimer = this; |
- _firstZeroTimer = this; |
- return true; |
- } else { |
- _lastZeroTimer._indexOrNext = this; |
- _lastZeroTimer = this; |
- return false; |
- } |
- } else { |
- _heap.add(this); |
- return _firstZeroTimer == null && _heap.isFirst(this); |
- } |
- } |
- |
- |
- static void _notifyEventHandler() { |
- if (_handlingCallbacks) { |
- // While we are already handling callbacks we will not notify the event |
- // handler. _handleTimeout will call _notifyEventHandler once all pending |
- // timers are processed. |
- return; |
- } |
- |
- if ((_firstZeroTimer == null) && _heap.isEmpty) { |
- // No pending timers: Close the receive port and let the event handler |
- // know. |
- if (_receivePort != null) { |
- _EventHandler._sendData(null, _sendPort, _NO_TIMER); |
- _shutdownTimerHandler(); |
- } |
- } else { |
- if (_receivePort == null) { |
- // Create a receive port and register a message handler for the timer |
- // events. |
- _createTimerHandler(); |
- } |
- if (_firstZeroTimer != null) { |
- if (!_messagePending) { |
- _sendPort.send(null); |
- _messagePending = true; // Reset when the port receives a message. |
- } |
- } else { |
- var wakeupTime = _heap.first._wakeupTime; |
- if ((_scheduledWakeupTime == null) || |
- (wakeupTime != _scheduledWakeupTime)) { |
- _EventHandler._sendData(null, _sendPort, wakeupTime); |
- _scheduledWakeupTime = wakeupTime; |
- } |
- } |
- } |
- } |
- |
- static void _handleTimeout(pendingImmediateCallback) { |
- // Fast exit if no timers have been scheduled. |
- if (_heap.isEmpty && (_firstZeroTimer == null)) { |
- assert(_receivePort == null); |
- return; |
- } |
- |
- // Collect all pending timers. |
- var head = null; |
- var tail = null; |
- if (_heap.isEmpty) { |
- // Only immediate timers are scheduled. Take over the whole list as is. |
- assert(_firstZeroTimer != null); |
- assert(_lastZeroTimer != null); |
- head = _firstZeroTimer; |
- tail = _lastZeroTimer; |
- _firstZeroTimer = null; |
- _lastZeroTimer = null; |
- } else { |
- assert(!_heap.isEmpty); |
- // Keep track of the lowest wakeup times for both the list and heap. If |
- // the respective queue is empty move its time beyond the current time. |
- var currentTime = new DateTime.now().millisecondsSinceEpoch; |
- var heapTime = _heap.first._wakeupTime; |
- var listTime = (_firstZeroTimer == null) ? |
- (currentTime + 1) : _firstZeroTimer._wakeupTime; |
- |
- while ((heapTime <= currentTime) || (listTime <= currentTime)) { |
- var timer; |
- // Consume the timers in order by removing from heap or list based on |
- // their wakeup time and update the queue's time. |
- assert((heapTime != listTime) || |
- ((_heap.first != null) && (_firstZeroTimer != null))); |
- if ((heapTime < listTime) || |
- ((heapTime == listTime) && |
- (_heap.first._id < _firstZeroTimer._id))) { |
- timer = _heap.removeFirst(); |
- heapTime = _heap.isEmpty ? |
- (currentTime + 1) : _heap.first._wakeupTime; |
- } else { |
- timer = _firstZeroTimer; |
- assert(timer._milliSeconds == 0); |
- _firstZeroTimer = timer._indexOrNext; |
- if (_firstZeroTimer == null) { |
- _lastZeroTimer = null; |
- listTime = currentTime + 1; |
- } else { |
- // We want to drain all entries from the list as they should have |
- // been pending for 0 ms. To prevent issues with current time moving |
- // we ensure that the listTime does not go beyond current, unless |
- // the list is empty. |
- listTime = _firstZeroTimer._wakeupTime; |
- if (listTime > currentTime) { |
- listTime = currentTime; |
- } |
- } |
- } |
- |
- // Append this timer to the pending timer list. |
- timer._indexOrNext = null; |
- if (head == null) { |
- assert(tail == null); |
- head = timer; |
- tail = timer; |
- } else { |
- tail._indexOrNext = timer; |
- tail = timer; |
- } |
- } |
- } |
- |
- // No timers queued: Early exit. |
- if (head == null) { |
- return; |
- } |
- |
- // If there are no pending timers currently reset the id space before we |
- // have a chance to enqueue new timers. |
- assert(_firstZeroTimer == null); |
- if (_heap.isEmpty) { |
- _idCount = 0; |
- } |
- |
- // Trigger all of the pending timers. New timers added as part of the |
- // callbacks will be enqueued now and notified in the next spin at the |
- // earliest. |
- _handlingCallbacks = true; |
- try { |
- while (head != null) { |
- // Dequeue the first candidate timer. |
- var timer = head; |
- head = timer._indexOrNext; |
- timer._indexOrNext = null; |
- |
- // One of the timers in the pending_timers list can cancel |
- // one of the later timers which will set the callback to |
- // null. |
- if (timer._callback != null) { |
- var callback = timer._callback; |
- if (!timer._repeating) { |
- // Mark timer as inactive. |
- timer._callback = null; |
- } |
- callback(timer); |
- // Re-insert repeating timer if not canceled. |
- if (timer._repeating && (timer._callback != null)) { |
- timer._advanceWakeupTime(); |
- timer._addTimerToHeap(); |
- } |
- // Execute pending micro tasks. |
- pendingImmediateCallback(); |
- } |
- } |
- } finally { |
- _handlingCallbacks = false; |
- _notifyEventHandler(); |
- } |
- } |
- |
- // Creates a receive port and registers an empty handler on that port. Just |
- // the triggering of the event loop will ensure that timers are executed. |
- static _ignoreMessage(_) { |
- _messagePending = false; |
- } |
- |
- static void _createTimerHandler() { |
- assert(_receivePort == null); |
- _receivePort = new RawReceivePort(_ignoreMessage); |
- _sendPort = _receivePort.sendPort; |
- _scheduledWakeupTime = null; |
- _messagePending = false; |
- } |
- |
- static void _shutdownTimerHandler() { |
- _receivePort.close(); |
- _receivePort = null; |
- _sendPort = null; |
- _scheduledWakeupTime = null; |
- _messagePending = false; |
- } |
- |
- // The Timer factory registered with the dart:async library by the embedder. |
- static Timer _factory(int milliSeconds, |
- void callback(Timer timer), |
- bool repeating) { |
- if (repeating) { |
- return new _Timer.periodic(milliSeconds, callback); |
- } |
- return new _Timer(milliSeconds, callback); |
- } |
-} |