src/map-renderer/main.ts
Rama main · réplica de lectura del repositorio original.
// The map renderer: draws the map tiles the API stores for every card and page
// head, in the web image, started with `node map-renderer/index.mjs` rather than the web
// server's `node server.js`. `scripts/build-map-renderer.mjs` bundles this file
// and everything it imports from the web into that one module.
//
// Configured as the web is - the same `MAP_*` variables and `SITE_URL` - plus
// `PORT`, which is 3000 in the image and 3001 beside a dev server.
import { createRequire } from "node:module";
import { tmpdir } from "node:os";
import path from "node:path";
import { basemapOrigins } from "@/lib/basemap";
import { startDisplay, type Display } from "./display";
import { createEngine, type NativeModule } from "./engine";
import { createResources } from "./resources";
import { createRendererServer } from "./server";
import { rendererSignature } from "./signature";
import { readRendererConfig } from "./style";
// Set by the build: a digest of the sources this bundle was made from.
declare const __MAP_RENDERER_SOURCE_DIGEST__: string;
const log = (message: string) => console.log(`[map-renderer] ${message}`);
// glibc gives every thread that allocates an arena of its own and keeps what
// each frees, and MapLibre Native and Mesa allocate from many threads: two
// arenas hold the renderer to about three quarters of the memory the default
// does. glibc reads the setting only as a process starts, so the renderer
// starts itself again with it, as the same process - which keeps it out of the
// web server, sharing the image, and out of every command that runs this one.
if (
process.platform === "linux" &&
process.env.MALLOC_ARENA_MAX === undefined
) {
process.execve?.(
process.execPath,
[process.execPath, ...process.execArgv, ...process.argv.slice(1)],
{ ...process.env, MALLOC_ARENA_MAX: "2" },
);
}
async function main() {
const port = Number(process.env.PORT || 3001);
const config = readRendererConfig();
// The image's `public/`, which its working directory holds, as the web
// server's does; a checkout's, run from its root.
const publicDir = path.resolve("public");
let display: Display | undefined;
if (process.platform === "linux") {
display = await startDisplay();
process.env.DISPLAY = display.name;
// Each tile is drawn on a new map, and Mesa compiles a new map's
// shaders from nothing unless it may keep them - which, for a user with no
// home directory, it may only where it is told to. Kept, they make a
// fresh map nearly as quick as a warm one.
process.env.MESA_SHADER_CACHE_DIR ??= path.join(
tmpdir(),
"map-renderer-shaders",
);
display.onExit((reason) => {
// Every map drew through it, so there is nothing left to recover:
// exiting is what has the container restarted with a display again.
log(reason);
process.exit(1);
});
}
// Loaded only now: on Linux it needs the display above to draw at all.
const require = createRequire(import.meta.url);
const native: NativeModule = require("@maplibre/maplibre-gl-native");
const nativeVersion: string =
require("@maplibre/maplibre-gl-native/package.json").version;
// The same origins the web's CSP lets a browser fetch this basemap from, by
// the same function, so a host the map cannot reach is one the tiles cannot.
const resources = createResources({
siteUrl: config.siteUrl,
publicDir,
origins: basemapOrigins(config.basemap),
});
const engine = createEngine({ native, config, resources, log });
const signature = await rendererSignature({
sourceDigest: __MAP_RENDERER_SOURCE_DIGEST__,
nativeVersion,
config,
publicDir,
});
const server = createRendererServer({
renderer: {
isHealthy: () => engine.isReady() && (display?.isUp() ?? true),
draw: (payload) => engine.draw(payload),
style: (theme, language) => engine.style(theme, language),
},
signature,
attribution: config.basemap.attribution,
log,
});
server.listen(port, () => {
log(`listening on :${port}, signature ${signature}`);
});
// PID 1 in a container gets no default handlers, so a stop is a SIGTERM
// nothing answers until it is handled here.
const stop = () => {
server.close();
engine.close();
display?.stop();
process.exit(0);
};
process.on("SIGTERM", stop);
process.on("SIGINT", stop);
}
main().catch((error: unknown) => {
log(`Could not start: ${(error as Error).stack ?? error}`);
process.exit(1);
});