todos-contra-el-fuego-web/private/workers/unionWorker.js
vjrj 324936242b
All checks were successful
build-image / build (push) Successful in 12m9s
fix(subsUnion): resolve turf deps by absolute path, not bare specifier
The previous side-effect-import fix didn't work: the app's own npm deps
(installed by `meteor build`) live in programs/server/npm/node_modules,
a SIBLING of programs/server/assets, not an ancestor of it. Node's
require() only walks up parent directories looking for node_modules,
so require('@turf/circle') from the worker asset script could never
find it regardless of what gets imported elsewhere in the app — it
kept failing with "Cannot find module '@turf/circle'" even after that
fix was deployed.

calcUnionAsync.js (Meteor-compiled code, which already knows how to
locate the app's npm deps) now resolves the three turf package paths
itself and hands them to the worker via workerData; the worker
requires them by absolute path instead of bare specifier.
2026-07-30 08:48:50 +02:00

59 lines
2.6 KiB
JavaScript

// Plain CommonJS worker_threads entry point, NOT part of the Meteor imports
// graph (lives under private/ so `meteor build` copies it verbatim into
// programs/server/assets/app/workers/unionWorker.js instead of compiling it).
//
// Runs the CPU-heavy part of the subscriptions geo-union (circle generation +
// sequential turf.union chain) off the main thread. A single tunion() call
// against an already-complex merged polygon can itself take seconds once
// there are thousands of subscriptions — yielding between iterations on the
// main thread doesn't help when one iteration alone blocks that long, so this
// needs a real worker thread, not just cooperative scheduling.
//
// workerData.subs must already be validated (location.lat/lon/distance
// present) and decorated (addNoisy/noNoisy already applied) by the caller.
// workerData.baseUnion, if given, is an already-computed union GeoJSON that
// subs gets merged into (the incremental-add fast path) instead of starting
// from scratch — for a single new subscription this is one tunion() call
// instead of redoing the whole chain over every subscription again.
// workerData.turfPaths gives absolute paths to @turf/circle, @turf/union and
// @turf/truncate — this file lives under programs/server/assets/, while the
// app's own npm deps live in the sibling programs/server/npm/node_modules,
// which plain require('@turf/circle') can never find by walking up parent
// directories. The caller resolves the paths (it's Meteor-compiled code that
// knows how to find them) and hands them over instead.
const { parentPort, workerData } = require('worker_threads');
// eslint-disable-next-line import/no-dynamic-require
const tcircle = require(workerData.turfPaths.circle).default;
// eslint-disable-next-line import/no-dynamic-require
const tunion = require(workerData.turfPaths.union).default;
// eslint-disable-next-line import/no-dynamic-require
const ttrunc = require(workerData.turfPaths.truncate).default;
const truncOptions = { precision: 6, coordinates: 2 };
const run = () => {
const { subs, baseUnion } = workerData;
const circles = subs.map(s => tcircle(
[s.location.lon, s.location.lat],
s.distance,
{ units: 'kilometers', steps: 144 }
));
let unionTemp = baseUnion || null;
let start = 0;
if (!unionTemp && circles.length > 0) {
unionTemp = ttrunc(circles[0], truncOptions);
start = 1;
}
for (let i = start; i < circles.length; i += 1) {
unionTemp = ttrunc(tunion(unionTemp, circles[i]), truncOptions);
}
return unionTemp;
};
try {
parentPort.postMessage({ union: run() });
} catch (e) {
parentPort.postMessage({ error: e.message });
}