todos-contra-el-fuego-web/private/workers/unionWorker.js
vjrj 110ad66e86
All checks were successful
build-image / build (push) Successful in 13m24s
feat(subsUnion): incremental union merge for new subscriptions
A full recreate() over 7k+ subscriptions takes ~20 minutes even in a
worker thread — fine for not blocking the site, but means a new zone
takes ages to show up on the map. Since union only grows when adding a
circle, a new subscription can just be merged into the union already
stored (one turf.union call) instead of rebuilding the whole chain.

changed/removed still trigger a full recreate() (union can't be
"subtracted" from safely), and incrementalAdd falls back to a full
recreate whenever the stored count isn't exactly one behind what's
expected, rather than risk merging into stale state. Both paths now
share one serialized queue (FIFO for adds, coalesced for recomputes)
so they never race on the same stored union.
2026-07-30 08:07:00 +02:00

49 lines
2 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.
const { parentPort, workerData } = require('worker_threads');
const tcircle = require('@turf/circle').default;
const tunion = require('@turf/union').default;
const ttrunc = require('@turf/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 });
}