summaryrefslogtreecommitdiffstats
path: root/js/gdm/batch.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/gdm/batch.js208
1 files changed, 208 insertions, 0 deletions
diff --git a/js/gdm/batch.js b/js/gdm/batch.js
new file mode 100644
index 0000000..ca29fc7
--- /dev/null
+++ b/js/gdm/batch.js
@@ -0,0 +1,208 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+/*
+ * Copyright 2011 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * In order for transformation animations to look good, they need to be
+ * incremental and have some order to them (e.g., fade out hidden items,
+ * then shrink to close the void left over). Chaining animations in this way can
+ * be error-prone and wordy using just ease() callbacks.
+ *
+ * The classes in this file help with this:
+ *
+ * - Task. encapsulates schedulable work to be run in a specific scope.
+ *
+ * - ConsecutiveBatch. runs a series of tasks in order and completes
+ * when the last in the series finishes.
+ *
+ * - ConcurrentBatch. runs a set of tasks at the same time and completes
+ * when the last to finish completes.
+ *
+ * - Hold. prevents a batch from completing the pending task until
+ * the hold is released.
+ *
+ * The tasks associated with a batch are specified in a list at batch
+ * construction time as either task objects or plain functions.
+ * Batches are task objects, themselves, so they can be nested.
+ *
+ * These classes aren't specific to GDM, but were found to be unintuitive and so
+ * are not used elsewhere. These APIs may ultimately get dropped entirely and
+ * replaced by something else.
+ */
+
+const { GObject } = imports.gi;
+const Signals = imports.signals;
+
+var Task = class {
+ constructor(scope, handler) {
+ if (scope)
+ this.scope = scope;
+ else
+ this.scope = this;
+
+ this.handler = handler;
+ }
+
+ run() {
+ if (this.handler)
+ return this.handler.call(this.scope);
+
+ return null;
+ }
+};
+Signals.addSignalMethods(Task.prototype);
+
+var Hold = class extends Task {
+ constructor() {
+ super(null, () => this);
+
+ this._acquisitions = 1;
+ }
+
+ acquire() {
+ if (this._acquisitions <= 0)
+ throw new Error("Cannot acquire hold after it's been released");
+ this._acquisitions++;
+ }
+
+ acquireUntilAfter(hold) {
+ if (!hold.isAcquired())
+ return;
+
+ this.acquire();
+ let signalId = hold.connect('release', () => {
+ hold.disconnect(signalId);
+ this.release();
+ });
+ }
+
+ release() {
+ this._acquisitions--;
+
+ if (this._acquisitions == 0)
+ this.emit('release');
+ }
+
+ isAcquired() {
+ return this._acquisitions > 0;
+ }
+};
+Signals.addSignalMethods(Hold.prototype);
+
+var Batch = class extends Task {
+ constructor(scope, tasks) {
+ super();
+
+ this.tasks = [];
+
+ for (let i = 0; i < tasks.length; i++) {
+ let task;
+
+ if (tasks[i] instanceof Task)
+ task = tasks[i];
+ else if (typeof tasks[i] == 'function')
+ task = new Task(scope, tasks[i]);
+ else
+ throw new Error('Batch tasks must be functions or Task, Hold or Batch objects');
+
+ this.tasks.push(task);
+ }
+ }
+
+ process() {
+ throw new GObject.NotImplementedError(`process in ${this.constructor.name}`);
+ }
+
+ runTask() {
+ if (!(this._currentTaskIndex in this.tasks))
+ return null;
+
+ return this.tasks[this._currentTaskIndex].run();
+ }
+
+ _finish() {
+ this.hold.release();
+ }
+
+ nextTask() {
+ this._currentTaskIndex++;
+
+ // if the entire batch of tasks is finished, release
+ // the hold and notify anyone waiting on the batch
+ if (this._currentTaskIndex >= this.tasks.length) {
+ this._finish();
+ return;
+ }
+
+ this.process();
+ }
+
+ _start() {
+ // acquire a hold to get released when the entire
+ // batch of tasks is finished
+ this.hold = new Hold();
+ this._currentTaskIndex = 0;
+ this.process();
+ }
+
+ run() {
+ this._start();
+
+ // hold may be destroyed at this point
+ // if we're already done running
+ return this.hold;
+ }
+
+ cancel() {
+ this.tasks = this.tasks.splice(0, this._currentTaskIndex + 1);
+ }
+};
+Signals.addSignalMethods(Batch.prototype);
+
+var ConcurrentBatch = class extends Batch {
+ process() {
+ let hold = this.runTask();
+
+ if (hold)
+ this.hold.acquireUntilAfter(hold);
+
+ // Regardless of the state of the just run task,
+ // fire off the next one, so all the tasks can run
+ // concurrently.
+ this.nextTask();
+ }
+};
+Signals.addSignalMethods(ConcurrentBatch.prototype);
+
+var ConsecutiveBatch = class extends Batch {
+ process() {
+ let hold = this.runTask();
+
+ if (hold && hold.isAcquired()) {
+ // This task is inhibiting the batch. Wait on it
+ // before processing the next one.
+ let signalId = hold.connect('release', () => {
+ hold.disconnect(signalId);
+ this.nextTask();
+ });
+ } else {
+ // This task finished, process the next one
+ this.nextTask();
+ }
+ }
+};
+Signals.addSignalMethods(ConsecutiveBatch.prototype);