1 // ========================================================================== 2 // Project: SproutCore - JavaScript Application Framework 3 // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 // Portions ©2008-2011 Apple Inc. All rights reserved. 5 // License: Licensed under MIT license (see license.js) 6 // ========================================================================== 7 8 sc_require("tasks/task"); 9 10 /** 11 Runs a set of tasks. Most importantly, has a runWhenIdle option that allows 12 it to run when no user input is occurring. This allows, for instance, preloading 13 bundles while not blocking user interaction. 14 */ 15 SC.TaskQueue = SC.Task.extend({ 16 17 init: function() { 18 var self = this; 19 this._doIdleEntry = function() { 20 self._idleEntry(); 21 }; 22 23 this._suspendCount = 0; 24 this._tasks = []; 25 }, 26 27 /** 28 If YES, the queue will automatically run in the background when the browser idles. 29 */ 30 runWhenIdle: NO, 31 32 /** 33 A limit which, if exceeded, the task queue will wait until a later run 34 to continue. 35 */ 36 runLimit: 50, 37 38 /** 39 The duration between idle runs. 40 */ 41 interval: 50, 42 43 /** 44 If running, YES. 45 */ 46 isRunning: NO, 47 48 /** 49 The minimum elapsed time since the last event. As a rule of thumb, perhaps 50 something equivalent to the expected duration of a task. 51 */ 52 minimumIdleDuration: 500, 53 54 _tasks: null, 55 56 /** 57 Returns YES if there are tasks in the queue. 58 */ 59 hasTasks: function() { 60 return this._tasks.length > 0; 61 }.property('taskCount').cacheable(), 62 63 /** 64 Returns the number of tasks in the queue. 65 */ 66 taskCount: function() { 67 return this._tasks.length; 68 }.property().cacheable(), 69 70 /** 71 Adds the task to the end of the queue. 72 */ 73 push: function(task) { 74 this._tasks.push(task); 75 this.notifyPropertyChange('taskCount'); 76 }, 77 78 /** 79 Removes and returns the first task in the queue. 80 */ 81 next: function() { 82 // null if there is no task 83 if (this._tasks.length < 1) return null; 84 85 // otherwise, return the first one in the queue 86 var next = this._tasks.shift(); 87 this.notifyPropertyChange('taskCount'); 88 return next; 89 }, 90 91 /** 92 Suspends cycling of the queue. Only affects task queues that run when idle, 93 such as the backgroundTaskQueue. 94 */ 95 suspend: function() { 96 this._suspendCount++; 97 }, 98 99 /** 100 Resumes cycling of the queue. 101 */ 102 resume: function() { 103 this._suspendCount--; 104 if (this._suspendCount <= 0) { 105 this._setupIdle(); 106 } 107 }, 108 109 /** 110 @private 111 Sets up idling if needed when the task count changes. 112 */ 113 _taskCountDidChange: function() { 114 this._setupIdle(); 115 }.observes('taskCount'), 116 117 /** 118 When runWhenIdle changes, we need to setup idle again if needed. This allows us to suspend 119 and resume processing of the background task queue. 120 */ 121 _runWhenIdleDidChange: function() { 122 this._setupIdle(); 123 }.observes('runWhenIdle'), 124 125 /** 126 Sets up the scheduled idling check if needed and applicable. 127 @private 128 */ 129 _setupIdle: function() { 130 if ( 131 !this._suspendCount && this.get('runWhenIdle') && 132 !this._idleIsScheduled && this.get('taskCount') > 0 133 ) { 134 setTimeout(this._doIdleEntry, 135 this.get('interval') 136 ); 137 this._idleIsScheduled = YES; 138 } 139 }, 140 141 /** 142 The entry point for the idle. 143 @private 144 */ 145 _idleEntry: function() { 146 this._idleIsScheduled = NO; 147 var last = SC.RunLoop.lastRunLoopEnd; 148 149 // if we are not supposed to run when idle we need to short-circuit out. 150 if (!this.get('runWhenIdle') && !this._suspendCount) return; 151 152 // if no recent events (within < 1s) 153 if (Date.now() - last > this.get('minimumIdleDuration')) { 154 SC.run(this.run, this); 155 SC.RunLoop.lastRunLoopEnd = last; // we were never here 156 } 157 158 // set up idle timer if needed 159 this._setupIdle(); 160 }, 161 162 /** 163 Runs tasks until limit (TaskQueue.runLimit by default) is reached. 164 */ 165 run: function(limit) { 166 this.set("isRunning", YES); 167 if (!limit) limit = this.get("runLimit"); 168 169 var task, start = Date.now(); 170 171 while (task = this.next()) { 172 task.run(this); 173 174 // check if the limit has been exceeded 175 if (Date.now() - start > limit) break; 176 } 177 178 this.set("isRunning", NO); 179 } 180 181 182 }); 183 184 SC.backgroundTaskQueue = SC.TaskQueue.create({ 185 runWhenIdle: YES 186 }); 187