diff --git a/src/script.ts b/src/script.ts index 3a040acf66b2c88711a57ccc7bfa493d84c49e83..5d2d1ce543e64a096b4bb43562931b20f108bf5e 100644 --- a/src/script.ts +++ b/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); diff --git a/src/utils/computation.ts b/src/utils/computation.ts index 7730dd7960880db1e8e9d05d8723b2e7aa68cd63..02c82f90117cc6973f9a9924716be3bb0c95117b 100644 --- a/src/utils/computation.ts +++ b/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; \ No newline at end of file diff --git a/src/utils/scheduler.ts b/src/utils/scheduler.ts new file mode 100644 index 0000000000000000000000000000000000000000..5717fb834e700b9fb5b409e2faf99c6f46ff02e2 --- /dev/null +++ b/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); } +}; diff --git a/tsconfig.json b/tsconfig.json index cac4f750a94085cc8bcb38a0248597209773df73..21016d5620035aa70f28141cd4fcef5f015af6f6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "sourceMap": false, "noUnusedLocals": true, "strictNullChecks": true, - "lib": [ "es6" ], + "lib": [ "es6", "dom" ], "outDir": "build/js/src" }, "include": [ "src/**/*" ]