Problem: the hexagonal refactor churns the backend tree for nine more phases; the UI delivery stack (web/ SPA, cmd/desktop Wails wrapper, compose/web image) must move to its own repo first so doc/layout rewrites land once on a backend-only tree. Change: - New repo git.hubris.network/dtoro/oikos-web (v0.33.0): web/, desktop/ (updateURL repointed to oikos-web releases), compose/, own CI (web + desktop jobs), own deploy script (CI-green gate, TOCTOU guard, version-tagged images, prune-to-3), own webhook receiver on :9798 + launchd unit, own compose project publishing the same 8091:80. - Cutover executed on mac-mini in order: oikos stack's web service stopped+removed, oikos-web project brought up on 8091; outer Caddy untouched (targets the published port) — serving + Authentik flow + /wails 404 quirk verified post-cutover. - Stripped from oikos: web/, cmd/desktop/, compose/web/, desktop CI workflow, ci.yml web job, Makefile ui/desktop/desktop-package/install targets, the compose web service, oikos-web from deploy.sh's fallback prune list; wails + go-keyring dropped from go.mod, vendor synced. - README / CONTRIBUTING / AGENTS.md / .agents dev+operations docs now point at the new repo; mbse + mascot design docs carry a path note. Risk: production SPA serving depends on the new pipeline now; rollback is versioned-image re-up of the old web service from a pre-split checkout (port 8091). Desktop builds installed before the split still check dtoro/oikos releases — one manual reinstall, noted in the oikos-web release notes. Verification: go vet, make test (race), make generate-check, golangci (no new findings; baseline down 400→365); post-cutover curls — localhost:8091 200, /wails/runtime.js 404, outer Caddy 302 Authentik.
207 lines
5.0 KiB
JavaScript
207 lines
5.0 KiB
JavaScript
/**
|
|
* Graphology ForceAtlas2 Layout Supervisor
|
|
* =========================================
|
|
*
|
|
* Supervisor class able to spawn a web worker to run the FA2 layout in a
|
|
* separate thread not to block UI with heavy synchronous computations.
|
|
*/
|
|
var workerFunction = require('./webworker.js');
|
|
var isGraph = require('graphology-utils/is-graph');
|
|
var createEdgeWeightGetter =
|
|
require('graphology-utils/getters').createEdgeWeightGetter;
|
|
var helpers = require('./helpers.js');
|
|
|
|
var DEFAULT_SETTINGS = require('./defaults.js');
|
|
|
|
/**
|
|
* Class representing a FA2 layout run by a webworker.
|
|
*
|
|
* @constructor
|
|
* @param {Graph} graph - Target graph.
|
|
* @param {object|number} params - Parameters:
|
|
* @param {object} [settings] - Settings.
|
|
*/
|
|
function FA2LayoutSupervisor(graph, params) {
|
|
params = params || {};
|
|
|
|
// Validation
|
|
if (!isGraph(graph))
|
|
throw new Error(
|
|
'graphology-layout-forceatlas2/worker: the given graph is not a valid graphology instance.'
|
|
);
|
|
|
|
var getEdgeWeight = createEdgeWeightGetter(
|
|
'getEdgeWeight' in params ? params.getEdgeWeight : 'weight'
|
|
).fromEntry;
|
|
|
|
// Validating settings
|
|
var settings = helpers.assign({}, DEFAULT_SETTINGS, params.settings);
|
|
var validationError = helpers.validateSettings(settings);
|
|
|
|
if (validationError)
|
|
throw new Error(
|
|
'graphology-layout-forceatlas2/worker: ' + validationError.message
|
|
);
|
|
|
|
// Properties
|
|
this.worker = null;
|
|
this.graph = graph;
|
|
this.settings = settings;
|
|
this.getEdgeWeight = getEdgeWeight;
|
|
this.matrices = null;
|
|
this.running = false;
|
|
this.killed = false;
|
|
this.outputReducer =
|
|
typeof params.outputReducer === 'function' ? params.outputReducer : null;
|
|
|
|
// Binding listeners
|
|
this.handleMessage = this.handleMessage.bind(this);
|
|
|
|
var respawnFrame = undefined;
|
|
var self = this;
|
|
|
|
this.handleGraphUpdate = function () {
|
|
if (self.worker) self.worker.terminate();
|
|
|
|
if (respawnFrame) clearTimeout(respawnFrame);
|
|
|
|
respawnFrame = setTimeout(function () {
|
|
respawnFrame = undefined;
|
|
self.spawnWorker();
|
|
}, 0);
|
|
};
|
|
|
|
graph.on('nodeAdded', this.handleGraphUpdate);
|
|
graph.on('edgeAdded', this.handleGraphUpdate);
|
|
graph.on('nodeDropped', this.handleGraphUpdate);
|
|
graph.on('edgeDropped', this.handleGraphUpdate);
|
|
|
|
// Spawning worker
|
|
this.spawnWorker();
|
|
}
|
|
|
|
FA2LayoutSupervisor.prototype.isRunning = function () {
|
|
return this.running;
|
|
};
|
|
|
|
/**
|
|
* Internal method used to spawn the web worker.
|
|
*/
|
|
FA2LayoutSupervisor.prototype.spawnWorker = function () {
|
|
if (this.worker) this.worker.terminate();
|
|
|
|
this.worker = helpers.createWorker(workerFunction);
|
|
this.worker.addEventListener('message', this.handleMessage);
|
|
|
|
if (this.running) {
|
|
this.running = false;
|
|
this.start();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Internal method used to handle the worker's messages.
|
|
*
|
|
* @param {object} event - Event to handle.
|
|
*/
|
|
FA2LayoutSupervisor.prototype.handleMessage = function (event) {
|
|
if (!this.running) return;
|
|
|
|
var matrix = new Float32Array(event.data.nodes);
|
|
|
|
helpers.assignLayoutChanges(this.graph, matrix, this.outputReducer);
|
|
if (this.outputReducer) helpers.readGraphPositions(this.graph, matrix);
|
|
this.matrices.nodes = matrix;
|
|
|
|
// Looping
|
|
this.askForIterations();
|
|
};
|
|
|
|
/**
|
|
* Internal method used to ask for iterations from the worker.
|
|
*
|
|
* @param {boolean} withEdges - Should we send edges along?
|
|
* @return {FA2LayoutSupervisor}
|
|
*/
|
|
FA2LayoutSupervisor.prototype.askForIterations = function (withEdges) {
|
|
var matrices = this.matrices;
|
|
|
|
var payload = {
|
|
settings: this.settings,
|
|
nodes: matrices.nodes.buffer
|
|
};
|
|
|
|
var buffers = [matrices.nodes.buffer];
|
|
|
|
if (withEdges) {
|
|
payload.edges = matrices.edges.buffer;
|
|
buffers.push(matrices.edges.buffer);
|
|
}
|
|
|
|
this.worker.postMessage(payload, buffers);
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Method used to start the layout.
|
|
*
|
|
* @return {FA2LayoutSupervisor}
|
|
*/
|
|
FA2LayoutSupervisor.prototype.start = function () {
|
|
if (this.killed)
|
|
throw new Error(
|
|
'graphology-layout-forceatlas2/worker.start: layout was killed.'
|
|
);
|
|
|
|
if (this.running) return this;
|
|
|
|
// Building matrices
|
|
this.matrices = helpers.graphToByteArrays(this.graph, this.getEdgeWeight);
|
|
|
|
this.running = true;
|
|
this.askForIterations(true);
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Method used to stop the layout.
|
|
*
|
|
* @return {FA2LayoutSupervisor}
|
|
*/
|
|
FA2LayoutSupervisor.prototype.stop = function () {
|
|
this.running = false;
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Method used to kill the layout.
|
|
*
|
|
* @return {FA2LayoutSupervisor}
|
|
*/
|
|
FA2LayoutSupervisor.prototype.kill = function () {
|
|
if (this.killed) return this;
|
|
|
|
this.running = false;
|
|
this.killed = true;
|
|
|
|
// Clearing memory
|
|
this.matrices = null;
|
|
|
|
// Terminating worker
|
|
this.worker.terminate();
|
|
|
|
// Unbinding listeners
|
|
this.graph.removeListener('nodeAdded', this.handleGraphUpdate);
|
|
this.graph.removeListener('edgeAdded', this.handleGraphUpdate);
|
|
this.graph.removeListener('nodeDropped', this.handleGraphUpdate);
|
|
this.graph.removeListener('edgeDropped', this.handleGraphUpdate);
|
|
};
|
|
|
|
/**
|
|
* Exporting.
|
|
*/
|
|
module.exports = FA2LayoutSupervisor;
|