Skip to content

Commit

Permalink
Browser offline support (#1280)
Browse files Browse the repository at this point in the history
  • Loading branch information
lutzroeder committed May 26, 2024
1 parent fa5d22d commit 1a3b633
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 71 deletions.
22 changes: 16 additions & 6 deletions source/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,17 @@ host.BrowserHost = class {
}
}

request(file, encoding, base) {
async request(file, encoding, base) {
const url = base ? (`${base}/${file}`) : this._url(file);
if (base === null) {
this._requests = this._requests || new Map();
const key = `${url}:${encoding}`;
if (!this._requests.has(key)) {
const promise = this._request(url, null, encoding);
this._requests.set(key, promise);
}
return this._requests.get(key);
}
return this._request(url, null, encoding);
}

Expand Down Expand Up @@ -332,7 +341,7 @@ host.BrowserHost = class {
}
}

_request(url, headers, encoding, callback, timeout) {
async _request(url, headers, encoding, callback, timeout) {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
if (!encoding) {
Expand Down Expand Up @@ -664,7 +673,7 @@ host.BrowserHost.BrowserFileContext = class {
}

async require(id) {
return await this._host.require(id);
return this._host.require(id);
}

error(error, fatal) {
Expand Down Expand Up @@ -806,11 +815,12 @@ host.BrowserHost.Context = class {
return this._stream;
}

request(file, encoding, base) {
return this._host.request(file, encoding, base === undefined ? this._base : base);
async request(file, encoding, base) {
base = base === undefined ? this._base : base;
return this._host.request(file, encoding, base);
}

require(id) {
async require(id) {
return this._host.require(id);
}

Expand Down
4 changes: 2 additions & 2 deletions source/electron.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -678,11 +678,11 @@ host.ElectronHost.Context = class {
}

async request(file, encoding, base) {
return await this._host.request(file, encoding, base === undefined ? this._folder : base);
return this._host.request(file, encoding, base === undefined ? this._folder : base);
}

async require(id) {
return await this._host.require(id);
return this._host.require(id);
}

error(error, fatal) {
Expand Down
87 changes: 35 additions & 52 deletions source/grapher.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,16 +173,16 @@ grapher.Graph = class {
}
}

async layout(host) {
const nodes = [];
async layout(worker) {
let nodes = [];
for (const node of this.nodes.values()) {
nodes.push({
v: node.v,
width: node.label.width || 0,
height: node.label.height || 0,
parent: this.parent(node.v) });
}
const edges = [];
let edges = [];
for (const edge of this.edges.values()) {
edges.push({
v: edge.v,
Expand All @@ -196,59 +196,42 @@ grapher.Graph = class {
});
}
const layout = this._layout;
const update = (nodes, edges) => {
for (const node of nodes) {
const label = this.node(node.v).label;
label.x = node.x;
label.y = node.y;
if (this.children(node.v).length) {
label.width = node.width;
label.height = node.height;
}
if (worker) {
const message = await worker.request({ type: 'dagre.layout', nodes, edges, layout }, 2500, 'This large graph layout might take a very long time to complete.');
if (message.type === 'cancel') {
return 'graph-layout-cancelled';
}
for (const edge of edges) {
const label = this.edge(edge.v, edge.w).label;
label.points = edge.points;
if ('x' in edge) {
label.x = edge.x;
label.y = edge.y;
}
nodes = message.nodes;
edges = message.edges;
} else {
const dagre = await import('./dagre.js');
dagre.layout(nodes, edges, layout, {});
}
for (const node of nodes) {
const label = this.node(node.v).label;
label.x = node.x;
label.y = node.y;
if (this.children(node.v).length) {
label.width = node.width;
label.height = node.height;
}
for (const key of this.nodes.keys()) {
const entry = this.node(key);
if (this.children(key).length === 0) {
const node = entry.label;
node.layout();
}
}
for (const edge of edges) {
const label = this.edge(edge.v, edge.w).label;
label.points = edge.points;
if ('x' in edge) {
label.x = edge.x;
label.y = edge.y;
}
}
for (const key of this.nodes.keys()) {
const entry = this.node(key);
if (this.children(key).length === 0) {
const node = entry.label;
node.layout();
}
};
if (host && host.worker) {
return new Promise((resolve) => {
let timeout = -1;
const worker = host.worker('./worker');
worker.addEventListener('message', (e) => {
const message = e.data;
worker.terminate();
update(message.nodes, message.edges);
if (timeout >= 0) {
clearTimeout(timeout);
host.message();
}
resolve('');
});
const message = { type: 'dagre.layout', nodes, edges, layout };
worker.postMessage(message);
timeout = setTimeout(async () => {
await host.message('This large graph layout might take a very long time to complete.', null, 'Cancel');
worker.terminate();
resolve('graph-layout-cancelled');
}, 2500);
});
}
const dagre = await import('./dagre.js');
dagre.layout(nodes, edges, layout, {});
update(nodes, edges);
return Promise.resolve('');
return '';
}

update() {
Expand Down
2 changes: 1 addition & 1 deletion source/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ window.exports.preload = function(callback) {
var modules = [
['view'],
['json', 'xml', 'protobuf', 'hdf5', 'grapher', 'browser'],
['base', 'text', 'flatbuffers', 'flexbuffers', 'zip', 'tar', 'python', 'dagre']
['base', 'text', 'flatbuffers', 'flexbuffers', 'zip', 'tar', 'python']
];
var next = function() {
if (modules.length === 0) {
Expand Down
96 changes: 93 additions & 3 deletions source/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ view.View = class {
this._sidebar = new view.Sidebar(this._host);
this._find = null;
this._modelFactoryService = new view.ModelFactoryService(this._host);
this._modelFactoryService.import();
this._worker = new view.Worker(this._host);
}

async start() {
Expand Down Expand Up @@ -825,8 +827,7 @@ view.View = class {
viewGraph.build(this._host.document, origin);
await this._timeout(20);
viewGraph.measure();
// await viewGraph.layout(null);
const status = await viewGraph.layout(this._host);
const status = await viewGraph.layout(this._worker);
if (status === '') {
viewGraph.update();
const elements = Array.from(canvas.getElementsByClassName('graph-input') || []);
Expand Down Expand Up @@ -1620,6 +1621,72 @@ view.Menu.Separator = class {
}
};

view.Worker = class {

constructor(host) {
this._host = host;
if (this._host.type === 'Browser' || this._host.type === 'Python') {
this._create();
}
}

async request(message, delay, notification) {
this._timeout = -1;
return new Promise((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
if (!this._worker) {
this._create();
}
this._worker.postMessage(message);
this._timeout = setTimeout(async () => {
await this._host.message(notification, null, 'Cancel');
this._cancel(true);
this._create();
delete this._resolve;
delete this._reject;
resolve({ type: 'cancel' });
}, delay);
});
}

_create() {
this._worker = this._host.worker('./worker');
this._worker.addEventListener('message', (e) => {
this._cancel(false);
const message = e.data;
if (message.type === 'error') {
this._reject(new Error(message.message));
} else {
this._resolve(message);
}
delete this._resolve;
delete this._reject;
});
this._worker.addEventListener('error', (e) => {
this._cancel(true);
if (this._reject) {
this._reject(new Error(`Unknown worker error type '${e.type}'.`));
delete this._resolve;
delete this._reject;
}
});
}

_cancel(terminate) {
terminate = terminate || (this._host.type !== 'Browser' && this._host.type !== 'Python');
if (this._worker && terminate) {
this._worker.terminate();
this._worker = null;
}
if (this._timeout >= 0) {
clearTimeout(this._timeout);
this._timeout = -1;
this._host.message();
}
}
};

view.Graph = class extends grapher.Graph {

constructor(view, host, model, options, compound, layout) {
Expand Down Expand Up @@ -5484,12 +5551,12 @@ view.ModelFactoryService = class {
this.register('./server', ['.netron']);
this.register('./pytorch', ['.pt', '.pth', '.ptl', '.pt1', '.pyt', '.pyth', '.pkl', '.pickle', '.h5', '.t7', '.model', '.dms', '.tar', '.ckpt', '.chkpt', '.tckpt', '.bin', '.pb', '.zip', '.nn', '.torchmodel', '.torchscript', '.pytorch', '.ot', '.params', '.trt', '.ff', '.ptmf', '.jit', '.pte', '.bin.index.json', 'serialized_exported_program.json'], ['.model', '.pt2']);
this.register('./onnx', ['.onnx', '.onnx.data', '.onn', '.pb', '.onnxtxt', '.pbtxt', '.prototxt', '.txt', '.model', '.pt', '.pth', '.pkl', '.ort', '.ort.onnx', '.ngf', '.json', '.bin', 'onnxmodel']);
this.register('./tflite', ['.tflite', '.lite', '.tfl', '.bin', '.pb', '.tmfile', '.h5', '.model', '.json', '.txt', '.dat', '.nb', '.ckpt']);
this.register('./mxnet', ['.json', '.params'], ['.mar']);
this.register('./coreml', ['.mlmodel', '.bin', 'manifest.json', 'metadata.json', 'featuredescriptions.json', '.pb', '.pbtxt', '.mil'], ['.mlpackage', '.mlmodelc']);
this.register('./caffe', ['.caffemodel', '.pbtxt', '.prototxt', '.pt', '.txt']);
this.register('./caffe2', ['.pb', '.pbtxt', '.prototxt']);
this.register('./torch', ['.t7', '.net']);
this.register('./tflite', ['.tflite', '.lite', '.tfl', '.bin', '.pb', '.tmfile', '.h5', '.model', '.json', '.txt', '.dat', '.nb', '.ckpt']);
this.register('./tf', ['.pb', '.meta', '.pbtxt', '.prototxt', '.txt', '.pt', '.json', '.index', '.ckpt', '.graphdef', '.pbmm', /.data-[0-9][0-9][0-9][0-9][0-9]-of-[0-9][0-9][0-9][0-9][0-9]$/, /^events.out.tfevents./], ['.zip']);
this.register('./tensorrt', ['.trt', '.trtmodel', '.engine', '.model', '.txt', '.uff', '.pb', '.tmfile', '.onnx', '.pth', '.dnn', '.plan', '.pt', '.dat']);
this.register('./keras', ['.h5', '.hd5', '.hdf5', '.keras', '.json', '.cfg', '.model', '.pb', '.pth', '.weights', '.pkl', '.lite', '.tflite', '.ckpt', '.pb', 'model.weights.npz'], ['.zip']);
Expand Down Expand Up @@ -5999,6 +6066,29 @@ view.ModelFactoryService = class {
}
}
}

async import() {
if (this._host.type === 'Browser' || this._host.type === 'Python') {
const files = [
'./server', './onnx', './pytorch', './tflite',
'./onnx-proto', './onnx-schema', './tflite-schema',
'onnx-metadata.json', 'pytorch-metadata.json', 'tflite-metadata.json'
];
for (const file of files) {
/* eslint-disable no-await-in-loop */
try {
if (file.startsWith('./')) {
await this._host.require(file);
} else if (file.endsWith('.json')) {
await this._host.request(file, 'utf-8', null);
}
} catch {
// continue regardless of error
}
/* eslint-enable no-await-in-loop */
}
}
}
};

view.Metadata = class {
Expand Down
11 changes: 8 additions & 3 deletions source/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const require = async () => {
const worker_threads = await import('worker_threads');
return worker_threads.parentPort;
}
import('./dagre.js');
return self;
};

Expand All @@ -12,9 +13,13 @@ require().then((self) => {
const message = e.data;
switch (message.type) {
case 'dagre.layout': {
const dagre = await import('./dagre.js');
dagre.layout(message.nodes, message.edges, message.layout, {});
self.postMessage(message);
try {
const dagre = await import('./dagre.js');
dagre.layout(message.nodes, message.edges, message.layout, {});
self.postMessage(message);
} catch (error) {
self.postMessage({ type: 'error', message: error.message });
}
break;
}
default: {
Expand Down
7 changes: 3 additions & 4 deletions test/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,7 @@ host.TestHost = class {
throw new Error(`The file '${file}' does not exist.`);
}
if (encoding) {
const buffer = await fs.readFile(pathname, encoding);
return buffer;
return await fs.readFile(pathname, encoding);
}
const buffer = await fs.readFile(pathname, null);
return new base.BinaryStream(buffer);
Expand Down Expand Up @@ -128,11 +127,11 @@ host.TestHost.Context = class {
return this._entries;
}

request(file, encoding, base) {
async request(file, encoding, base) {
return this._host.request(file, encoding, base === undefined ? this._folder : base);
}

require(id) {
async require(id) {
return this._host.require(id);
}

Expand Down

0 comments on commit 1a3b633

Please sign in to comment.