Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ function config(output) {
"SQLiteDatabaseClient",
"Workbook",
"ZipArchive",
"ZipArchiveEntry"
"ZipArchiveEntry",
"Runtime",
"RuntimeError",
"Variable",
"Module",
"Library",
"Inspector",
]
}
})
Expand Down
16 changes: 13 additions & 3 deletions src/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {RuntimeError} from "./errors";
import identity from "./identity";
import rethrow from "./rethrow";
import {variable_invalidation, variable_visibility} from "./runtime";
import Variable, {TYPE_DUPLICATE, TYPE_IMPLICIT, TYPE_NORMAL, no_observer} from "./variable";
import Variable, {TYPE_DUPLICATE, TYPE_IMPLICIT, TYPE_NORMAL, no_observer, variable_stale} from "./variable";

export default function Module(runtime, builtins = []) {
Object.defineProperties(this, {
Expand Down Expand Up @@ -59,8 +59,18 @@ async function module_value(name) {
v._observer = true;
this._runtime._dirty.add(v);
}
await this._runtime._compute();
return v._promise;
return module_revalue(this._runtime, v);
}

// If the variable is redefined before its value resolves, try again.
async function module_revalue(runtime, variable) {
await runtime._compute();
try {
return await variable._promise;
} catch (error) {
if (error === variable_stale) return module_revalue(runtime, variable);
throw error;
}
}

function module_derive(injects, injectModule) {
Expand Down
16 changes: 8 additions & 8 deletions src/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import generatorish from "./generatorish";
import load from "./load";
import Module from "./module";
import noop from "./noop";
import Variable, {TYPE_IMPLICIT, no_observer} from "./variable";
import Variable, {TYPE_IMPLICIT, no_observer, variable_stale} from "./variable";

const frame = typeof requestAnimationFrame === "function" ? requestAnimationFrame
: typeof setImmediate === "function" ? setImmediate
Expand Down Expand Up @@ -243,7 +243,7 @@ function variable_compute(variable) {

// Compute the initial value of the variable.
function define(inputs) {
if (variable._version !== version) return;
if (variable._version !== version) throw variable_stale;

// Replace any reference to invalidation with the promise, lazily.
for (var i = 0, n = inputs.length; i < n; ++i) {
Expand All @@ -268,20 +268,19 @@ function variable_compute(variable) {
// already have been invalidated here, in which case we need to terminate the
// generator immediately!
function generate(value) {
if (variable._version !== version) throw variable_stale;
if (generatorish(value)) {
if (variable._version !== version) return void value.return();
(invalidation || variable_invalidator(variable)).then(variable_return(value));
return variable_generate(variable, version, value);
}
return value;
}

promise.then((value) => {
if (variable._version !== version) return;
variable._value = value;
variable._fulfilled(value);
}, (error) => {
if (variable._version !== version) return;
if (error === variable_stale) return;
variable._value = undefined;
variable._rejected(error);
});
Expand All @@ -306,14 +305,15 @@ function variable_generate(variable, version, generator) {
// successful, reject the variable, compute downstream variables, and return.
function recompute() {
const promise = compute((value) => {
if (variable._version !== version) return;
if (variable._version !== version) throw variable_stale;
currentValue = value;
postcompute(value, promise).then(() => runtime._precompute(recompute));
variable._fulfilled(value);
return value;
});
promise.catch((error) => {
if (variable._version !== version) return;
if (error === variable_stale) throw error;
if (variable._version !== version) throw variable_stale;
postcompute(undefined, promise);
variable._rejected(error);
});
Expand All @@ -331,7 +331,7 @@ function variable_generate(variable, version, generator) {
// When retrieving the first value from the generator, the promise graph is
// already established, so we only need to queue the next pull.
return compute((value) => {
if (variable._version !== version) return;
if (variable._version !== version) throw variable_stale;
currentValue = value;
runtime._precompute(recompute);
return value;
Expand Down
10 changes: 10 additions & 0 deletions src/variable.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,13 @@ function variable_undefined() {
throw variable_undefined;
}

export function variable_stale() {
throw variable_stale;
}

function variable_rejector(variable) {
return function(error) {
if (error === variable_stale) throw error;
if (error === variable_undefined) throw new RuntimeError(variable._name + " is not defined", variable._name);
if (error instanceof Error && error.message) throw new RuntimeError(error.message, variable._name);
throw new RuntimeError(variable._name + " could not be resolved", variable._name);
Expand Down Expand Up @@ -167,6 +172,11 @@ function variable_defineImpl(name, inputs, definition) {
this._name = name;
}

// If this redefined variable was previously evaluated, invalidate it. (If the
// variable was never evaluated, then the invalidated value could never have
// been exposed and we can avoid this extra work.)
if (this._version > 0) ++this._version;

runtime._updates.add(this);
runtime._compute();
return this;
Expand Down