Skip to content
Snippets Groups Projects
Commit 2693d26d authored by David Sehnal's avatar David Sehnal
Browse files

Added computation "model"

parent e987d73d
No related branches found
No related tags found
No related merge requests found
...@@ -32,5 +32,9 @@ export default { ...@@ -32,5 +32,9 @@ export default {
// } // }
], ],
external: external, external: external,
sourcemap: false sourcemap: false,
onwarn(warning, warn) {
if (warning.code === 'THIS_IS_UNDEFINED') return;
warn(warning); // this requires Rollup 0.46
}
}; };
\ No newline at end of file
...@@ -118,4 +118,20 @@ export function _cif() { ...@@ -118,4 +118,20 @@ export function _cif() {
}); });
} }
_cif(); _cif();
\ No newline at end of file
import Computation from './utils/computation'
const comp = new Computation(async ctx => {
for (let i = 0; i < 3; i++) {
await new Promise(res => setTimeout(res, 500));
if (ctx.requiresUpdate) await ctx.updateProgress('working', void 0, i, 2);
}
return 42;
});
async function testComp() {
const running = comp.runWithContext();
running.subscribe(p => console.log(JSON.stringify(p)));
const ret = await running.result;
console.log('computation returned', ret);
}
testComp();
\ No newline at end of file
/*
* Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
*
* Adapted from https://github.com/dsehnal/LiteMol
* @author David Sehnal <david.sehnal@gmail.com>
*/
class Computation<A> {
run(ctx?: Computation.Context) {
return this.runWithContext(ctx).result;
}
runWithContext(ctx?: Computation.Context): Computation.Running<A> {
const context = ctx ? ctx as Computation.ObservableContext : new Computation.ObservableContext();
return {
subscribe: (context as Computation.ObservableContext).subscribe || Helpers.NoOpSubscribe,
result: new Promise<A>(async (resolve, reject) => {
try {
if (context.started) context.started();
const result = await this.computation(context);
resolve(result);
} catch (e) {
if (Computation.PRINT_CONSOLE_ERROR) console.error(e);
reject(e);
} finally {
if (context.finished) context.finished();
}
})
};
}
constructor(private computation: (ctx: Computation.Context) => Promise<A>) {
}
}
namespace Computation {
const DefaulUpdateRateMs = 100;
export let PRINT_CONSOLE_ERROR = false;
export function resolve<A>(a: A) {
return new Computation<A>(() => Promise.resolve(a));
}
export function reject<A>(reason: any) {
return new Computation<A>(() => Promise.reject(reason));
}
export interface Params {
isSynchronous: boolean,
updateRateMs: number
}
export const Aborted = 'Aborted';
export interface Progress {
message: string;
isIndeterminate: boolean;
current: number;
max: number;
requestAbort?: () => void;
}
export interface Context {
readonly requiresUpdate: boolean,
requestAbort(): void,
/**
* Checks if the computation was aborted. If so, throws.
* Otherwise, updates the progress.
*/
updateProgress(msg: string, abort?: boolean | (() => void), current?: number, max?: number): Promise<void> | void
}
type ProgressObserver = (progress: Readonly<Progress>) => void;
export interface Running<A> {
subscribe(onProgress: ProgressObserver): void;
result: Promise<A>
}
export const ContextWithoutUpdates: Context = {
requiresUpdate: false,
requestAbort() { },
updateProgress(msg, abort, current, max) { }
}
export class ObservableContext implements Context {
private updateRate: number;
private isSynchronous: boolean;
private level = 0;
private abortRequested = false;
private lastUpdated = 0;
private observers: ProgressObserver[] | undefined = void 0;
private progress: Progress = { message: 'Working...', current: 0, max: 0, isIndeterminate: true, requestAbort: void 0 };
private checkAborted() {
if (this.abortRequested) throw Aborted;
}
private abortRequester = () => { this.abortRequested = true };
subscribe = (obs: ProgressObserver) => {
if (!this.observers) this.observers = [];
this.observers.push(obs);
}
requestAbort() {
try {
if (this.abortRequester) {
this.abortRequester.call(null);
}
} catch (e) { }
}
updateProgress(msg: string, abort?: boolean | (() => void), current?: number, max?: number): Promise<void> | void {
this.checkAborted();
if (typeof abort === 'boolean') {
this.progress.requestAbort = abort ? this.abortRequester : void 0;
} else {
if (abort) this.abortRequester = abort;
this.progress.requestAbort = abort ? this.abortRequester : void 0;
}
this.progress.message = msg;
if (isNaN(current!)) {
this.progress.isIndeterminate = true;
} else {
this.progress.isIndeterminate = false;
this.progress.current = current!;
this.progress.max = max!;
}
if (this.observers) {
const p = { ...this.progress };
for (const o of this.observers) setTimeout(o, 0, p);
}
this.lastUpdated = Helpers.getTime();
return new Promise<void>(res => setTimeout(res, 0));
}
get requiresUpdate() {
this.checkAborted();
if (this.isSynchronous) return false;
return Helpers.getTime() - this.lastUpdated > this.updateRate;
}
started() {
this.level++;
}
finished() {
this.level--;
if (this.level < 0) {
throw new Error('Bug in code somewhere, Computation.resolve/reject called too many times.');
}
if (!this.level) this.observers = void 0;
}
constructor(params?: Params) {
this.updateRate = (params && params.updateRateMs) || DefaulUpdateRateMs;
this.isSynchronous = !!(params && params.isSynchronous);
}
}
}
namespace Helpers {
declare var process: any;
declare var window: any;
export const getTime: () => number = (function () {
if (typeof window !== 'undefined' && window.performance) {
const perf = window.performance;
return function () { return perf.now(); }
} else if (typeof process !== 'undefined' && process.hrtime !== 'undefined') {
return function () {
let t = process.hrtime();
return t[0] * 1000 + t[1] / 1000000;
};
} else {
return function () { return +new Date(); }
}
})();
export const NoOpSubscribe = () => {};
}
export default Computation;
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment