Skip to main content

Engine Protocol (v1)

Wire protocol between an upper-layer service/supervisor and a standalone renderer process. The goal is one transport that behaves the same on Linux and Windows, with a single job in flight per process. Status: Implemented for blockscope serve and blockscope supervise (protocol v1).

Transport

  • Newline-delimited JSON over the worker’s standard input and output.
  • One UTF-8 JSON object per line, terminated by a single \n. Readers must accept \r\n but writers emit \n only.
  • No BOM, no pretty printing, no multi-line objects.
  • stdout carries protocol messages only. Logs and diagnostics go to stderr.
  • Standard input is read as raw bytes and decoded as UTF-8; do not route it through a console code page.
  • v1 uses no sockets. stdio is identical across supported platforms and needs no port allocation, firewall rule, or named-pipe path.

Lifecycle

  • The worker prints exactly one hello line after its material set is ready. The supervisor must not send jobs before hello.
  • One job at a time. The worker processes lines sequentially; the supervisor owns the queue and the concurrency policy.
  • Closing stdin is equivalent to shutdown. The worker finishes the current job, prints bye, and exits 0.

Messages

hello is printed only after the material set is ready, so the supervisor can size its pool from suggestedWorkers and suggestedThreads instead of guessing from the host. Values come from cgroup limits on Linux and GlobalMemoryStatusEx on Windows; see blockscope capacity --json for the same data from the command line.

Render request

Paths must be absolute. The worker rejects relative paths with INVALID_REQUEST, so behaviour does not depend on the worker’s working directory. timeoutMs is deliberately absent: the supervisor enforces timeouts by killing and restarting the worker. The engine cannot reliably interrupt its own rasterizer mid-tile.

Example: single view

Example: multi-view

Render result

  • outputs preserves views order, one entry per view.
  • Output files are {id}.png for a single view and {id}-{yaw}.png for multiple views. The engine never overwrites a result from another job.
  • cacheHit is true when the worker reused a parsed scene and mesh for the same inputHash and lod; parseMs, lodMs, and meshMs are then 0.
  • sceneHash describes the scene that was rendered. With lod > 1 it is the aggregated scene, not the original projection.
  • lod echoes the effective factor. With "lod": "auto" the worker reports the factor it chose, and the cache key stays auto.
  • timings are wall-clock aggregates for the whole job.
  • statistics is advisory; consumers must not depend on fields beyond triangles.

Errors

Parser errors also pass through their stable LitematicErrorCode names, such as INVALID_PALETTE, INVALID_REGION_BOUNDS, and BLOCK_STATE_DATA_LENGTH_MISMATCH. retryable is advisory. A retried job must be idempotent: outputs are written to a fresh directory or overwritten in place, and no state survives a job.

Supervisor

blockscope supervise speaks the same protocol on its own stdio and owns a pool of blockscope serve children:
  • Pool size and threads default to blockscope capacity; --workers, --threads, --queue, and --cache override them.
  • Jobs are queued and dispatched to idle workers. The queue is bounded; a full queue returns BUSY.
  • Repeated inputs are routed to the worker that already cached them, so a second render of the same file skips parsing and meshing.
  • A worker that exits mid-job produces INTERNAL_ERROR and is restarted.
  • A job that exceeds --timeout (default 60 seconds, 0 disables) is killed, retried once on a fresh worker, then answered with TIMEOUT.
  • A worker that exceeds its memory ceiling (85 percent of the available memory divided by the pool size, or --memory in MiB) is killed the same way with MEMORY_LIMIT.
  • Results may arrive out of order because workers run in parallel; match them by id.
  • Killed workers are reaped before replacement, so the pool does not accumulate zombies. A supervisor that dies closes the worker pipes, which makes the workers exit on stdin EOF.

Cross-platform rules

Chinese and other non-ASCII file names are supported because every path crosses the pipe as UTF-8. Supervisors on Windows must read the pipe as UTF-8, not through the console code page. The engine must never depend on a console: no colour codes, no interactive prompts, no reading from a TTY.

Supervisor contract

  • Start N workers, each with T rasterizer threads, such that N * T <= physical cores. Reference: 2 x 6 on a 12-thread machine.
  • Route jobs by inputHash so a repeated input lands on the worker that already holds its snapshot and mesh cache. A cold worker pays the material load once.
  • Queue depth is bounded. When full, reject with a busy response rather than growing memory.
  • Timeout per job, kill and restart the worker, retry once, then surface TIMEOUT.
  • On worker crash, restart it; an in-flight job is retried once.
  • Never write protocol data to a worker’s stdin while a job is in flight.

Versioning

  • protocolVersion is an integer, incremented for breaking changes.
  • New optional request fields and new response fields are not breaking.
  • Removing fields, renaming fields, or changing defaults is breaking.
  • A worker that receives an unknown protocolVersion exits with code 2 and a hello-less stderr message.

Non-goals for v1

  • Cancellation of a running job.
  • Streaming or progressive output.
  • Non-PNG formats.
  • In-process job queues or multi-job batching.
  • Windows peak RSS accounting.

Overrides

serve and supervise accept --force-limits, which raises the parser, mesh, and render caps for that process. It prints a warning on stderr and is meant for offline work on oversized schematics, not for the default service profile.