|  | // 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 "isolate_patch.dart"; | 
|  |  | 
|  | // 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<_Timer>(_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 _NO_TIMER = -1; | 
|  |  | 
|  | // We distinguish what kind of message arrived based on the value being sent. | 
|  | static const _ZERO_EVENT = 1; | 
|  | static const _TIMEOUT_EVENT = null; | 
|  |  | 
|  | // Timers are ordered by wakeup time. Timers with a timeout value of > 0 do | 
|  | // end up on the TimerHeap. Timers with a timeout of 0 are queued in a list. | 
|  | 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 const _ID_MASK = 0x1fffffff; | 
|  | static int _idCount = 0; | 
|  |  | 
|  | static RawReceivePort _receivePort; | 
|  | static SendPort _sendPort; | 
|  | static int _scheduledWakeupTime; | 
|  |  | 
|  | static bool _handlingCallbacks = false; | 
|  |  | 
|  | Function _callback; // Closure to call when timer fires. null if canceled. | 
|  | int _wakeupTime; // Expiration time. | 
|  | final int _milliSeconds; // Duration specified at creation. | 
|  | final 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 = VMLibraryHooks.timerMillisecondClock(); | 
|  | int wakeupTime = (milliSeconds == 0) ? now : (now + 1 + milliSeconds); | 
|  |  | 
|  | _Timer timer = | 
|  | new _Timer._internal(callback, wakeupTime, milliSeconds, repeating); | 
|  | // Enqueue this newly created timer in the appropriate structure and | 
|  | // notify if necessary. | 
|  | timer._enqueue(); | 
|  | 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; | 
|  |  | 
|  | 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 heap if it is a | 
|  | // non-zero timer. Zero timers are kept in the list as they need to consume | 
|  | // the corresponding pending message. | 
|  | void cancel() { | 
|  | _callback = null; | 
|  | // Only heap timers are really removed. Zero timers need to consume their | 
|  | // corresponding wakeup message so they are left in the queue. | 
|  | if (!_isInHeap) return; | 
|  | bool update = _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 = VMLibraryHooks.timerMillisecondClock(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Adds a timer to the heap or timer list. Timers with the same wakeup time | 
|  | // are enqueued in order and notified in FIFO order. | 
|  | void _enqueue() { | 
|  | if (_milliSeconds == 0) { | 
|  | if (_firstZeroTimer == null) { | 
|  | _lastZeroTimer = this; | 
|  | _firstZeroTimer = this; | 
|  | } else { | 
|  | _lastZeroTimer._indexOrNext = this; | 
|  | _lastZeroTimer = this; | 
|  | } | 
|  | // Every zero timer gets its own event. | 
|  | _notifyZeroHandler(); | 
|  | } else { | 
|  | _heap.add(this); | 
|  | if (_heap.isFirst(this)) { | 
|  | _notifyEventHandler(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Enqueue one message for each zero timer. To be able to distinguish from | 
|  | // EventHandler messages we send a _ZERO_EVENT instead of a _TIMEOUT_EVENT. | 
|  | static void _notifyZeroHandler() { | 
|  | if (_sendPort == null) { | 
|  | _createTimerHandler(); | 
|  | } | 
|  | _sendPort.send(_ZERO_EVENT); | 
|  | } | 
|  |  | 
|  | // Handle the notification of a zero timer. Make sure to also execute non-zero | 
|  | // timers with a lower expiration time. | 
|  | static List _queueFromZeroEvent() { | 
|  | var pendingTimers = new List(); | 
|  | assert(_firstZeroTimer != null); | 
|  | // Collect pending timers from the timer heap that have an expiration prior | 
|  | // to the currently notified zero timer. | 
|  | var timer; | 
|  | while (!_heap.isEmpty && (_heap.first._compareTo(_firstZeroTimer) < 0)) { | 
|  | timer = _heap.removeFirst(); | 
|  | pendingTimers.add(timer); | 
|  | } | 
|  | // Append the first zero timer to the pending timers. | 
|  | timer = _firstZeroTimer; | 
|  | _firstZeroTimer = timer._indexOrNext; | 
|  | timer._indexOrNext = null; | 
|  | pendingTimers.add(timer); | 
|  | return pendingTimers; | 
|  | } | 
|  |  | 
|  | 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 there are no pending timers. Close down the receive port. | 
|  | if ((_firstZeroTimer == null) && _heap.isEmpty) { | 
|  | // No pending timers: Close the receive port and let the event handler | 
|  | // know. | 
|  | if (_sendPort != null) { | 
|  | _cancelWakeup(); | 
|  | _shutdownTimerHandler(); | 
|  | } | 
|  | return; | 
|  | } else if (_heap.isEmpty) { | 
|  | // Only zero timers are left. Cancel any scheduled wakeups. | 
|  | _cancelWakeup(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Only send a message if the requested wakeup time differs from the | 
|  | // already scheduled wakeup time. | 
|  | var wakeupTime = _heap.first._wakeupTime; | 
|  | if ((_scheduledWakeupTime == null) || | 
|  | (wakeupTime != _scheduledWakeupTime)) { | 
|  | _scheduleWakeup(wakeupTime); | 
|  | } | 
|  | } | 
|  |  | 
|  | static List _queueFromTimeoutEvent() { | 
|  | var pendingTimers = new List(); | 
|  | if (_firstZeroTimer != null) { | 
|  | // Collect pending timers from the timer heap that have an expiration | 
|  | // prior to the next zero timer. | 
|  | // By definition the first zero timer has been scheduled before the | 
|  | // current time, meaning all timers which are "less than" the first zero | 
|  | // timer are expired. The first zero timer will be dispatched when its | 
|  | // corresponding message is delivered. | 
|  | var timer; | 
|  | while (!_heap.isEmpty && (_heap.first._compareTo(_firstZeroTimer) < 0)) { | 
|  | timer = _heap.removeFirst(); | 
|  | pendingTimers.add(timer); | 
|  | } | 
|  | } else { | 
|  | // Collect pending timers from the timer heap which have expired at this | 
|  | // time. | 
|  | var currentTime = VMLibraryHooks.timerMillisecondClock(); | 
|  | var timer; | 
|  | while (!_heap.isEmpty && (_heap.first._wakeupTime <= currentTime)) { | 
|  | timer = _heap.removeFirst(); | 
|  | pendingTimers.add(timer); | 
|  | } | 
|  | } | 
|  | return pendingTimers; | 
|  | } | 
|  |  | 
|  | static void _runTimers(List pendingTimers) { | 
|  | // If there are no pending timers currently reset the id space before we | 
|  | // have a chance to enqueue new timers. | 
|  | if (_heap.isEmpty && (_firstZeroTimer == null)) { | 
|  | _idCount = 0; | 
|  | } | 
|  |  | 
|  | // Fast exit if no pending timers. | 
|  | if (pendingTimers.length == 0) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // 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; | 
|  | var i = 0; | 
|  | try { | 
|  | for (; i < pendingTimers.length; i++) { | 
|  | // Next pending timer. | 
|  | var timer = pendingTimers[i]; | 
|  | 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. Or the pending zero timer has been canceled earlier. | 
|  | 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._enqueue(); | 
|  | } | 
|  | // Execute pending micro tasks. | 
|  | var immediateCallback = _removePendingImmediateCallback(); | 
|  | if (immediateCallback != null) { | 
|  | immediateCallback(); | 
|  | } | 
|  | } | 
|  | } | 
|  | } finally { | 
|  | _handlingCallbacks = false; | 
|  | // Re-queue timers we didn't get to. | 
|  | for (i++; i < pendingTimers.length; i++) { | 
|  | var timer = pendingTimers[i]; | 
|  | timer._enqueue(); | 
|  | } | 
|  | _notifyEventHandler(); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void _handleMessage(msg) { | 
|  | var pendingTimers; | 
|  | if (msg == _ZERO_EVENT) { | 
|  | pendingTimers = _queueFromZeroEvent(); | 
|  | assert(pendingTimers.length > 0); | 
|  | } else { | 
|  | assert(msg == _TIMEOUT_EVENT); | 
|  | _scheduledWakeupTime = null; // Consumed the last scheduled wakeup now. | 
|  | pendingTimers = _queueFromTimeoutEvent(); | 
|  | } | 
|  | _runTimers(pendingTimers); | 
|  | // Notify the event handler or shutdown the port if no more pending | 
|  | // timers are present. | 
|  | _notifyEventHandler(); | 
|  | } | 
|  |  | 
|  | // Tell the event handler to wake this isolate at a specific time. | 
|  | static void _scheduleWakeup(int wakeupTime) { | 
|  | if (_sendPort == null) { | 
|  | _createTimerHandler(); | 
|  | } | 
|  | VMLibraryHooks.eventHandlerSendData(null, _sendPort, wakeupTime); | 
|  | _scheduledWakeupTime = wakeupTime; | 
|  | } | 
|  |  | 
|  | // Cancel pending wakeups in the event handler. | 
|  | static void _cancelWakeup() { | 
|  | assert(_sendPort != null); | 
|  | VMLibraryHooks.eventHandlerSendData(null, _sendPort, _NO_TIMER); | 
|  | _scheduledWakeupTime = null; | 
|  | } | 
|  |  | 
|  | // Create a receive port and register a message handler for the timer | 
|  | // events. | 
|  | static void _createTimerHandler() { | 
|  | assert(_receivePort == null); | 
|  | assert(_sendPort == null); | 
|  | _receivePort = new RawReceivePort(_handleMessage); | 
|  | _sendPort = _receivePort.sendPort; | 
|  | _scheduledWakeupTime = null; | 
|  | } | 
|  |  | 
|  | static void _shutdownTimerHandler() { | 
|  | _receivePort.close(); | 
|  | _receivePort = null; | 
|  | _sendPort = null; | 
|  | _scheduledWakeupTime = null; | 
|  | } | 
|  |  | 
|  | // 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | _setupHooks() { | 
|  | VMLibraryHooks.timerFactory = _Timer._factory; | 
|  | } |