OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 // This code is adapted from the Timer implementation in the standalone Dart VM |
| 6 // for use in the Mojo embedder. |
| 7 |
| 8 part of core; |
| 9 |
| 10 // Timer heap implemented as a array-based binary heap[0]. |
| 11 // This allows for O(1) `first`, O(log(n)) `remove`/`removeFirst` and O(log(n)) |
| 12 // `add`. |
| 13 // |
| 14 // To ensure the timers are ordered by insertion time, the _Timer class has a |
| 15 // `_id` field set when added to the heap. |
| 16 // |
| 17 // [0] http://en.wikipedia.org/wiki/Binary_heap |
| 18 class _TimerHeap { |
| 19 List<_Timer> _list; |
| 20 int _used = 0; |
| 21 |
| 22 _TimerHeap([int initSize = 7]) |
| 23 : _list = new List<_Timer>(initSize); |
| 24 |
| 25 bool get isEmpty => _used == 0; |
| 26 bool get isNotEmpty => _used > 0; |
| 27 |
| 28 _Timer get first => _list[0]; |
| 29 |
| 30 bool isFirst(_Timer timer) => timer._indexOrNext == 0; |
| 31 |
| 32 void add(_Timer timer) { |
| 33 if (_used == _list.length) { |
| 34 _resize(); |
| 35 } |
| 36 timer._indexOrNext = _used++; |
| 37 _list[timer._indexOrNext] = timer; |
| 38 _bubbleUp(timer); |
| 39 } |
| 40 |
| 41 _Timer removeFirst() { |
| 42 var f = first; |
| 43 remove(f); |
| 44 return f; |
| 45 } |
| 46 |
| 47 void remove(_Timer timer) { |
| 48 _used--; |
| 49 timer._id = -1; |
| 50 if (isEmpty) { |
| 51 _list[0] = null; |
| 52 timer._indexOrNext = null; |
| 53 return; |
| 54 } |
| 55 var last = _list[_used]; |
| 56 if (!identical(last, timer)) { |
| 57 last._indexOrNext = timer._indexOrNext; |
| 58 _list[last._indexOrNext] = last; |
| 59 if (last._compareTo(timer) < 0) { |
| 60 _bubbleUp(last); |
| 61 } else { |
| 62 _bubbleDown(last); |
| 63 } |
| 64 } |
| 65 _list[_used] = null; |
| 66 timer._indexOrNext = null; |
| 67 } |
| 68 |
| 69 void _resize() { |
| 70 var newList = new List(_list.length * 2 + 1); |
| 71 newList.setRange(0, _used, _list); |
| 72 _list = newList; |
| 73 } |
| 74 |
| 75 void _bubbleUp(_Timer timer) { |
| 76 while (!isFirst(timer)) { |
| 77 Timer parent = _parent(timer); |
| 78 if (timer._compareTo(parent) < 0) { |
| 79 _swap(timer, parent); |
| 80 } else { |
| 81 break; |
| 82 } |
| 83 } |
| 84 } |
| 85 |
| 86 void _bubbleDown(_Timer timer) { |
| 87 while (true) { |
| 88 int leftIndex = _leftChildIndex(timer._indexOrNext); |
| 89 int rightIndex = _rightChildIndex(timer._indexOrNext); |
| 90 _Timer newest = timer; |
| 91 if (leftIndex < _used && _list[leftIndex]._compareTo(newest) < 0) { |
| 92 newest = _list[leftIndex]; |
| 93 } |
| 94 if (rightIndex < _used && _list[rightIndex]._compareTo(newest) < 0) { |
| 95 newest = _list[rightIndex]; |
| 96 } |
| 97 if (identical(newest, timer)) { |
| 98 // We are where we should be, break. |
| 99 break; |
| 100 } |
| 101 _swap(newest, timer); |
| 102 } |
| 103 } |
| 104 |
| 105 void _swap(_Timer first, _Timer second) { |
| 106 int tmp = first._indexOrNext; |
| 107 first._indexOrNext = second._indexOrNext; |
| 108 second._indexOrNext = tmp; |
| 109 _list[first._indexOrNext] = first; |
| 110 _list[second._indexOrNext] = second; |
| 111 } |
| 112 |
| 113 Timer _parent(_Timer timer) => _list[_parentIndex(timer._indexOrNext)]; |
| 114 Timer _leftChild(_Timer timer) => _list[_leftChildIndex(timer._indexOrNext)]; |
| 115 Timer _rightChild(_Timer timer) => |
| 116 _list[_rightChildIndex(timer._indexOrNext)]; |
| 117 |
| 118 static int _parentIndex(int index) => (index - 1) ~/ 2; |
| 119 static int _leftChildIndex(int index) => 2 * index + 1; |
| 120 static int _rightChildIndex(int index) => 2 * index + 2; |
| 121 } |
| 122 |
| 123 class _Timer implements Timer { |
| 124 // Disables the timer. |
| 125 static const int _NO_TIMER = -1; |
| 126 |
| 127 // Timers are ordered by wakeup time. |
| 128 static _TimerHeap _heap = new _TimerHeap(); |
| 129 static _Timer _firstZeroTimer; |
| 130 static _Timer _lastZeroTimer; |
| 131 static int _idCount = 0; |
| 132 |
| 133 static RawReceivePort _receivePort; |
| 134 static SendPort _sendPort; |
| 135 static bool _handlingCallbacks = false; |
| 136 |
| 137 Function _callback; |
| 138 int _milliSeconds; |
| 139 int _wakeupTime = 0; |
| 140 var _indexOrNext; |
| 141 int _id = -1; |
| 142 |
| 143 static Timer _createTimer(void callback(Timer timer), |
| 144 int milliSeconds, |
| 145 bool repeating) { |
| 146 _Timer timer = new _Timer._internal(); |
| 147 timer._callback = callback; |
| 148 if (milliSeconds > 0) { |
| 149 // Add one because DateTime.now() is assumed to round down |
| 150 // to nearest millisecond, not up, so that time + duration is before |
| 151 // duration milliseconds from now. Using micosecond timers like |
| 152 // Stopwatch allows detecting that the timer fires early. |
| 153 timer._wakeupTime = |
| 154 new DateTime.now().millisecondsSinceEpoch + 1 + milliSeconds; |
| 155 } |
| 156 timer._milliSeconds = repeating ? milliSeconds : -1; |
| 157 if (timer._addTimerToHeap()) { |
| 158 // The new timer is the first in queue. Update event handler. |
| 159 _notifyEventHandler(); |
| 160 } |
| 161 return timer; |
| 162 } |
| 163 |
| 164 factory _Timer(int milliSeconds, void callback(Timer timer)) { |
| 165 return _createTimer(callback, milliSeconds, false); |
| 166 } |
| 167 |
| 168 factory _Timer.periodic(int milliSeconds, void callback(Timer timer)) { |
| 169 return _createTimer(callback, milliSeconds, true); |
| 170 } |
| 171 |
| 172 _Timer._internal() {} |
| 173 |
| 174 bool get _isInHeap => _id >= 0; |
| 175 |
| 176 void _clear() { |
| 177 _callback = null; |
| 178 } |
| 179 |
| 180 int _compareTo(_Timer other) { |
| 181 int c = _wakeupTime - other._wakeupTime; |
| 182 if (c != 0) return c; |
| 183 return _id - other._id; |
| 184 } |
| 185 |
| 186 bool get _repeating => _milliSeconds >= 0; |
| 187 |
| 188 bool get isActive => _callback != null; |
| 189 |
| 190 // Cancels a set timer. The timer is removed from the timer list and if |
| 191 // the given timer is the earliest timer the native timer is reset. |
| 192 void cancel() { |
| 193 _clear(); |
| 194 if (!_isInHeap) return; |
| 195 assert(_wakeupTime != 0); |
| 196 bool update = (_firstZeroTimer == null) && _heap.isFirst(this); |
| 197 _heap.remove(this); |
| 198 if (update) { |
| 199 _notifyEventHandler(); |
| 200 } |
| 201 } |
| 202 |
| 203 void _advanceWakeupTime() { |
| 204 assert(_milliSeconds >= 0); |
| 205 _wakeupTime += _milliSeconds; |
| 206 } |
| 207 |
| 208 // Adds a timer to the timer list. Timers with the same wakeup time are |
| 209 // enqueued in order and notified in FIFO order. |
| 210 bool _addTimerToHeap() { |
| 211 if (_wakeupTime == 0) { |
| 212 if (_firstZeroTimer == null) { |
| 213 _lastZeroTimer = this; |
| 214 _firstZeroTimer = this; |
| 215 return true; |
| 216 } else { |
| 217 _lastZeroTimer._indexOrNext = this; |
| 218 _lastZeroTimer = this; |
| 219 return false; |
| 220 } |
| 221 } else { |
| 222 _id = _idCount++; |
| 223 _heap.add(this); |
| 224 return _firstZeroTimer == null && _heap.isFirst(this); |
| 225 } |
| 226 } |
| 227 |
| 228 |
| 229 static void _notifyEventHandler() { |
| 230 if (_handlingCallbacks) { |
| 231 // While we are already handling callbacks we will not notify the event |
| 232 // handler. _handleTimeout will call _notifyEventHandler once all pending |
| 233 // timers are processed. |
| 234 return; |
| 235 } |
| 236 |
| 237 if (_firstZeroTimer == null && _heap.isEmpty) { |
| 238 // No pending timers: Close the receive port and let the event handler |
| 239 // know. |
| 240 if (_receivePort != null) { |
| 241 MojoHandleWatcher.timer(_sendPort, _NO_TIMER); |
| 242 _shutdownTimerHandler(); |
| 243 } |
| 244 } else { |
| 245 if (_receivePort == null) { |
| 246 // Create a receive port and register a message handler for the timer |
| 247 // events. |
| 248 _createTimerHandler(); |
| 249 } |
| 250 if (_firstZeroTimer != null) { |
| 251 _sendPort.send(null); |
| 252 } else { |
| 253 MojoHandleWatcher.timer(_sendPort, _heap.first._wakeupTime); |
| 254 } |
| 255 } |
| 256 } |
| 257 |
| 258 static void _handleTimeout(_) { |
| 259 int currentTime = new DateTime.now().millisecondsSinceEpoch; |
| 260 // Collect all pending timers. |
| 261 var timer = _firstZeroTimer; |
| 262 var nextTimer = _lastZeroTimer; |
| 263 _firstZeroTimer = null; |
| 264 _lastZeroTimer = null; |
| 265 while (_heap.isNotEmpty && _heap.first._wakeupTime <= currentTime) { |
| 266 var next = _heap.removeFirst(); |
| 267 if (timer == null) { |
| 268 nextTimer = next; |
| 269 timer = next; |
| 270 } else { |
| 271 nextTimer._indexOrNext = next; |
| 272 nextTimer = next; |
| 273 } |
| 274 } |
| 275 |
| 276 // Trigger all of the pending timers. New timers added as part of the |
| 277 // callbacks will be enqueued now and notified in the next spin at the |
| 278 // earliest. |
| 279 _handlingCallbacks = true; |
| 280 try { |
| 281 while (timer != null) { |
| 282 var next = timer._indexOrNext; |
| 283 timer._indexOrNext = null; |
| 284 // One of the timers in the pending_timers list can cancel |
| 285 // one of the later timers which will set the callback to |
| 286 // null. |
| 287 if (timer._callback != null) { |
| 288 var callback = timer._callback; |
| 289 if (!timer._repeating) { |
| 290 // Mark timer as inactive. |
| 291 timer._callback = null; |
| 292 } |
| 293 callback(timer); |
| 294 // Re-insert repeating timer if not canceled. |
| 295 if (timer._repeating && timer._callback != null) { |
| 296 timer._advanceWakeupTime(); |
| 297 timer._addTimerToHeap(); |
| 298 } |
| 299 } |
| 300 timer = next; |
| 301 } |
| 302 } finally { |
| 303 _handlingCallbacks = false; |
| 304 _notifyEventHandler(); |
| 305 } |
| 306 } |
| 307 |
| 308 // Creates a receive port and registers the timer handler on that |
| 309 // receive port. |
| 310 static void _createTimerHandler() { |
| 311 if(_receivePort == null) { |
| 312 _receivePort = new RawReceivePort(_handleTimeout); |
| 313 _sendPort = _receivePort.sendPort; |
| 314 } |
| 315 } |
| 316 |
| 317 static void _shutdownTimerHandler() { |
| 318 _receivePort.close(); |
| 319 _receivePort = null; |
| 320 _sendPort = null; |
| 321 } |
| 322 } |
| 323 |
| 324 // Provide a closure which will allocate a Timer object to be able to hook |
| 325 // up the Timer interface in dart:isolate with the implementation here. |
| 326 _getTimerFactoryClosure() { |
| 327 return (int milliSeconds, void callback(Timer timer), bool repeating) { |
| 328 if (repeating) { |
| 329 return new _Timer.periodic(milliSeconds, callback); |
| 330 } |
| 331 return new _Timer(milliSeconds, callback); |
| 332 }; |
| 333 } |
OLD | NEW |