blob: a08f173620a1036ea76c67caedf3f0b8f2908824 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {ImageRequestTask} from './image_request_task.js';
/**
* Scheduler for ImageRequestTask objects. Fetches tasks from a queue and
* processes them synchronously, taking into account priorities. The highest
* priority is 0.
* @constructor
*/
export function Scheduler() {
/**
* List of tasks waiting to be checked. If these items are available in
* cache, then they are processed immediately after starting the scheduler.
* However, if they have to be downloaded, then these tasks are moved
* to pendingTasks_.
*
* @type {Array<ImageRequestTask>}
* @private
*/
this.newTasks_ = [];
/**
* List of pending tasks for images to be downloaded.
* @type {Array<ImageRequestTask>}
* @private
*/
this.pendingTasks_ = [];
/**
* List of tasks being processed.
* @type {Array<ImageRequestTask>}
* @private
*/
this.activeTasks_ = [];
/**
* Map of tasks being added to the queue, but not finalized yet. Keyed by
* the ImageRequestTask id.
* @type {Object<string, ImageRequestTask>}>
* @private
*/
this.tasks_ = {};
/**
* If the scheduler has been started.
* @type {boolean}
* @private
*/
this.started_ = false;
}
/**
* Maximum download tasks to be run in parallel.
* @type {number}
* @const
*/
Scheduler.MAXIMUM_IN_PARALLEL = 5;
/**
* Adds a task to the internal priority queue and executes it when tasks
* with higher priorities are finished. If the result is cached, then it is
* processed immediately once the scheduler is started.
*
* @param {ImageRequestTask} task A task to be run
*/
Scheduler.prototype.add = function(task) {
if (!this.started_) {
this.newTasks_.push(task);
this.tasks_[task.getId()] = task;
return;
}
// Enqueue the tasks, since already started.
this.pendingTasks_.push(task);
this.sortPendingTasks_();
this.continue_();
};
/**
* Removes a task from the scheduler (if exists).
* @param {string} taskId Unique ID of the task.
*/
Scheduler.prototype.remove = function(taskId) {
const task = this.tasks_[taskId];
if (!task) {
return;
}
// Remove from the internal queues with pending tasks.
const newIndex = this.newTasks_.indexOf(task);
if (newIndex != -1) {
this.newTasks_.splice(newIndex, 1);
}
const pendingIndex = this.pendingTasks_.indexOf(task);
if (pendingIndex != -1) {
this.pendingTasks_.splice(pendingIndex, 1);
}
// Cancel the task.
task.cancel();
delete this.tasks_[taskId];
};
/**
* Starts handling tasks.
*/
Scheduler.prototype.start = function() {
this.started_ = true;
// Process tasks added before scheduler has been started.
this.pendingTasks_ = this.newTasks_;
this.sortPendingTasks_();
this.newTasks_ = [];
// Start serving enqueued tasks.
this.continue_();
};
/**
* Sorts pending tasks by priorities.
* @private
*/
Scheduler.prototype.sortPendingTasks_ = function() {
this.pendingTasks_.sort(function(a, b) {
return a.getPriority() - b.getPriority();
});
};
/**
* Processes pending tasks from the queue. There is no guarantee that
* all of the tasks will be processed at once.
*
* @private
*/
Scheduler.prototype.continue_ = function() {
// Run only up to MAXIMUM_IN_PARALLEL in the same time.
while (this.pendingTasks_.length &&
this.activeTasks_.length < Scheduler.MAXIMUM_IN_PARALLEL) {
const task = this.pendingTasks_.shift();
this.activeTasks_.push(task);
// Try to load from cache. If doesn't exist, then download.
task.loadFromCacheAndProcess(
this.finish_.bind(this, task), function(currentTask) {
currentTask.downloadAndProcess(this.finish_.bind(this, currentTask));
}.bind(this, task));
}
};
/**
* Handles finished tasks.
*
* @param {ImageRequestTask} task Finished task.
* @private
*/
Scheduler.prototype.finish_ = function(task) {
const index = this.activeTasks_.indexOf(task);
if (index < 0) {
console.warn('ImageRequestTask not found.');
}
this.activeTasks_.splice(index, 1);
delete this.tasks_[task.getId()];
// Continue handling the most important tasks (if started).
if (this.started_) {
this.continue_();
}
};