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