Browse Source

Immediate scheduler

David Sehnal 7 years ago
parent
commit
d46610f47e
4 changed files with 222 additions and 14 deletions
  1. 1 1
      src/script.ts
  2. 12 12
      src/utils/computation.ts
  3. 208 0
      src/utils/scheduler.ts
  4. 1 1
      tsconfig.json

+ 1 - 1
src/script.ts

@@ -107,7 +107,7 @@ async function runCIF(input: string | Uint8Array) {
 
 export function _cif() {
     let path = `./examples/1cbs_updated.cif`;
-    path = '../test/3j3q.cif';
+    path = 'c:/test/quick/3j3q.cif';
     fs.readFile(path, 'utf8', function (err, input) {
         if (err) {
             return console.log(err);

+ 12 - 12
src/utils/computation.ts

@@ -5,6 +5,8 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
+import Scheduler from './scheduler'
+
 class Computation<A> {
     run(ctx?: Computation.Context) {
         return this.runWithContext(ctx).result;
@@ -14,7 +16,7 @@ class Computation<A> {
         const context = ctx ? ctx as Computation.ObservableContext : new Computation.ObservableContext();
 
         return {
-            subscribe: (context as Computation.ObservableContext).subscribe || Helpers.NoOpSubscribe,
+            subscribe: (context as Computation.ObservableContext).subscribe || NoOpSubscribe,
             result: new Promise<A>(async (resolve, reject) => {
                 try {
                     if (context.started) context.started();
@@ -35,6 +37,8 @@ class Computation<A> {
     }
 }
 
+const NoOpSubscribe = () => {}
+
 namespace Computation {
     const DefaulUpdateRateMs = 100;
 
@@ -123,7 +127,7 @@ namespace Computation {
         updateProgress(msg: string, abort?: boolean | (() => void), current?: number, max?: number): Promise<void> | void {
             this.checkAborted();
 
-            const time = Helpers.getTime();
+            const time = now();
 
             if (typeof abort === 'boolean') {
                 this.progress.requestAbort = abort ? this.abortRequester : void 0;
@@ -134,7 +138,7 @@ namespace Computation {
 
             this.progress.message = msg;
             this.progress.elapsedMs = time - this.startedTime;
-            if (isNaN(current!)) {
+            if (isNaN(current!) || isNaN(max!)) {
                 this.progress.isIndeterminate = true;
             } else {
                 this.progress.isIndeterminate = false;
@@ -144,23 +148,23 @@ namespace Computation {
 
             if (this.observers) {
                 const p = { ...this.progress };
-                for (const o of this.observers) setTimeout(o, 0, p);
+                for (const o of this.observers) Scheduler.immediate(o, p);
             }
 
             this.lastDelta = time - this.lastUpdated;
             this.lastUpdated = time;
 
-            return new Promise<void>(res => setTimeout(res, 0));
+            return Scheduler.immediatePromise();
         }
 
         get requiresUpdate() {
             this.checkAborted();
             if (this.isSynchronous) return false;
-            return Helpers.getTime() - this.lastUpdated > this.updateRate / 2;
+            return now() - this.lastUpdated > this.updateRate / 2;
         }
 
         started() {
-            if (!this.level) this.startedTime = Helpers.getTime();
+            if (!this.level) this.startedTime = now();
             this.level++;
         }
 
@@ -212,13 +216,11 @@ namespace Computation {
             this.currentChunkSize = defaultChunkSize;
         }
     }
-}
 
-namespace Helpers {
     declare var process: any;
     declare var window: any;
 
-    export const getTime: () => number = (function () {
+    export const now: () => number = (function () {
         if (typeof window !== 'undefined' && window.performance) {
             const perf = window.performance;
             return function () { return perf.now(); }
@@ -231,8 +233,6 @@ namespace Helpers {
             return function () { return +new Date(); }
         }
     })();
-
-    export const NoOpSubscribe = () => {};
 }
 
 export default Computation;

+ 208 - 0
src/utils/scheduler.ts

@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+/**
+ * setImmediate polyfill adapted from https://github.com/YuzuJS/setImmediate
+ * Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, and Domenic Denicola
+ * MIT license.
+ */
+
+function createActions() {
+    type Callback = (...args: any[]) => void;
+    type Task = { callback: Callback, args: any[] }
+
+    const tasksByHandle: { [handle: number]: Task } = { };
+    const doc = typeof document !== 'undefined' ? document : void 0;
+
+    let currentlyRunningATask = false;
+    let nextHandle = 1; // Spec says greater than zero
+    let registerImmediate: ((handle: number) => void);
+
+    function setImmediate(callback: Callback, ...args: any[]) {
+      // Callback can either be a function or a string
+      if (typeof callback !== 'function') {
+        callback = new Function('' + callback) as Callback;
+      }
+      // Store and register the task
+      const task = { callback: callback, args: args };
+      tasksByHandle[nextHandle] = task;
+      registerImmediate(nextHandle);
+      return nextHandle++;
+    }
+
+    function clearImmediate(handle: number) {
+        delete tasksByHandle[handle];
+    }
+
+    function run(task: Task) {
+        const callback = task.callback;
+        const args = task.args;
+        switch (args.length) {
+        case 0:
+            callback();
+            break;
+        case 1:
+            callback(args[0]);
+            break;
+        case 2:
+            callback(args[0], args[1]);
+            break;
+        case 3:
+            callback(args[0], args[1], args[2]);
+            break;
+        default:
+            callback.apply(undefined, args);
+            break;
+        }
+    }
+
+    function runIfPresent(handle: number) {
+        // From the spec: 'Wait until any invocations of this algorithm started before this one have completed.'
+        // So if we're currently running a task, we'll need to delay this invocation.
+        if (currentlyRunningATask) {
+            // Delay by doing a setTimeout. setImmediate was tried instead, but in Firefox 7 it generated a
+            // 'too much recursion' error.
+            setTimeout(runIfPresent, 0, handle);
+        } else {
+            const task = tasksByHandle[handle];
+            if (task) {
+                currentlyRunningATask = true;
+                try {
+                    run(task);
+                } finally {
+                    clearImmediate(handle);
+                    currentlyRunningATask = false;
+                }
+            }
+        }
+    }
+
+    function installNextTickImplementation() {
+        registerImmediate = function(handle) {
+            process.nextTick(function () { runIfPresent(handle); });
+        };
+    }
+
+    function canUsePostMessage() {
+        // The test against `importScripts` prevents this implementation from being installed inside a web worker,
+        // where `global.postMessage` means something completely different and can't be used for this purpose.
+        const global = window as any;
+        if (global && global.postMessage && !global.importScripts) {
+            let postMessageIsAsynchronous = true;
+            const oldOnMessage = global.onmessage;
+            global.onmessage = function() {
+                postMessageIsAsynchronous = false;
+            };
+            global.postMessage('', '*');
+            global.onmessage = oldOnMessage;
+            return postMessageIsAsynchronous;
+        }
+    }
+
+    function installPostMessageImplementation() {
+        // Installs an event handler on `global` for the `message` event: see
+        // * https://developer.mozilla.org/en/DOM/window.postMessage
+        // * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages
+
+        const messagePrefix = 'setImmediate$' + Math.random() + '$';
+        const onGlobalMessage = function(event: any) {
+            if (event.source === global &&
+                typeof event.data === 'string' &&
+                event.data.indexOf(messagePrefix) === 0) {
+                runIfPresent(+event.data.slice(messagePrefix.length));
+            }
+        };
+
+        if (window.addEventListener) {
+            window.addEventListener('message', onGlobalMessage, false);
+        } else {
+            (window as any).attachEvent('onmessage', onGlobalMessage);
+        }
+
+        registerImmediate = function(handle) {
+            window.postMessage(messagePrefix + handle, '*');
+        };
+    }
+
+    function installMessageChannelImplementation() {
+        const channel = new MessageChannel();
+        channel.port1.onmessage = function(event) {
+            const handle = event.data;
+            runIfPresent(handle);
+        };
+
+        registerImmediate = function(handle) {
+            channel.port2.postMessage(handle);
+        };
+    }
+
+    function installReadyStateChangeImplementation() {
+        const html = doc!.documentElement;
+        registerImmediate = function(handle) {
+            // Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
+            // into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
+            let script = doc!.createElement('script') as any;
+            script.onreadystatechange = function () {
+                runIfPresent(handle);
+                script.onreadystatechange = null;
+                html.removeChild(script);
+                script = null;
+            };
+            html.appendChild(script);
+        };
+    }
+
+    function installSetTimeoutImplementation() {
+        registerImmediate = function(handle) {
+            setTimeout(runIfPresent, 0, handle);
+        };
+    }
+
+    // If supported, we should attach to the prototype of global, since that is where setTimeout et al. live.
+    //const attachTo = Object.getPrototypeOf && Object.getPrototypeOf(global);
+    //attachTo = attachTo && attachTo.setTimeout ? attachTo : global;
+
+    // Don't get fooled by e.g. browserify environments.
+    if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+        // For Node.js before 0.9
+        installNextTickImplementation();
+    } else if (canUsePostMessage()) {
+        // For non-IE10 modern browsers
+        installPostMessageImplementation();
+    } else if (typeof MessageChannel !== 'undefined') {
+        // For web workers, where supported
+        installMessageChannelImplementation();
+    } else if (doc && 'onreadystatechange' in doc.createElement('script')) {
+        // For IE 6–8
+        installReadyStateChangeImplementation();
+    } else {
+        // For older browsers
+        installSetTimeoutImplementation();
+    }
+
+    return {
+        setImmediate,
+        clearImmediate
+    };
+}
+
+const actions = (function () {
+    if (typeof setImmediate !== 'undefined') {
+        return { setImmediate, clearImmediate };
+    }
+    return createActions();
+}());
+
+function resolveImmediate(res: () => void) {
+    actions.setImmediate(res);
+}
+
+export default {
+    immediate: actions.setImmediate,
+    clearImmediate: actions.clearImmediate,
+
+    immediatePromise() { return new Promise<void>(resolveImmediate); }
+};

+ 1 - 1
tsconfig.json

@@ -7,7 +7,7 @@
         "sourceMap": false,
         "noUnusedLocals": true,
         "strictNullChecks": true,
-        "lib": [ "es6" ],
+        "lib": [ "es6", "dom" ],
         "outDir": "build/js/src"
     },
     "include": [ "src/**/*" ]