Skip to content
TwinScope0.3.10

Running a comparison

A comparison is a job, not a function call. It runs in its own process, reports where it has got to, stops when you tell it to, and cannot take the window down with it.

Click to enlarge
Every long job is interruptible. A percentage, the stage it is in, and a cancel that is honoured.

Where the work happens#

Comparisons are CPU-bound — a line diff over a large pair, a pixel loop, a recursive folder walk — and the window must not freeze while one runs. So the engines run in a separate utility process:

  • It is a plain Node process. It has no Electron APIs and no DOM, and it reads any input that was too large to travel with the request. Engines never import fs themselves — filesystem access is injected into them, which is also what lets one engine run somewhere else entirely.
  • It is spawned on your first comparison and reused afterwards, so the second comparison does not pay the startup cost.
  • If it hangs or dies, it can be killed without taking the app with it.

The path a comparison takes:

StepWhere
Compare or The window
The request is validated against a schemaMain process, at the IPC boundary
The engine is resolved so the UI can name it before work beginsMain process
The worker picks the engine and runs itUtility process
Progress, then done or errorBack to the window as events

One job at a time#

TwinScope compares two things, so it runs one comparison. A second request supersedes the first rather than queueing behind it.

The superseded job is not torn down mid-flight, so its progress events keep arriving for a moment. They are discarded: every event carries the job id it belongs to, and an event from a job that is no longer the current one is dropped rather than allowed to clobber the new job's state. You never see a percentage from a comparison you replaced, or a result that belongs to the previous pair.

Live progress#

While a job runs you get a percentage, a stage label under the bar, and the same percentage in the status bar. The stage names come from the engine, so they describe real work:

EngineStages you will see
Textreadingcomparing linesdone
JSONreadingparsingcomparing structuredone
Foldersscanning <path>scanned N files (every 500) → comparingdone
Imagesreadingdecodingcomparing pixelsfinding regionsrendering difference
Binarymeasuringhashingdone

Cancelling#

Cancel appears in the toolbar for the whole time a job is running.

Cancellation is cooperative: the host aborts a signal, and each engine checks that signal between units of work — after reading its inputs, between chunks, between bands of pixels. That is what makes a cancel instant in practice without the app having to kill anything.

The workspace then shows Comparison cancelled rather than a failure, because you did not hit a bug — you changed your mind. There is no Copy details button on a cancellation, for the same reason.

If the engine process crashes#

What happensWhat you see
The worker exits with a job in flightThe comparison fails with The comparison engine stopped unexpectedly. Try again.
You run another comparisonA fresh worker is spawned automatically — no restart, no reload
The worker fails to start three timesThe comparison engine failed to start repeatedly. Restart TwinScope to try again.

The third case is deliberate: a spawn loop that keeps retrying forever produces an app that looks broken and says nothing. Three attempts, then a sentence you can act on.

A failure that is not a crash gets the engine's own message. A file that was deleted between loading it and comparing it, for instance, fails with a message naming that file — and the failure panel has a Copy details button that puts the reason, both input names and the engine on your clipboard. See Reading a result.

Answers that come back immediately#

Some comparisons do not need diffing at all, and pretending otherwise wastes your time.

CaseWhat happens
The same content on both sidesThe text engine compares the two strings, finds them equal and returns without diffing: 0 changes, plus the line count
Two empty filesThe same short-circuit: 0 changes, 0 lines
Two identical binariesA size match, then one hash per side — These files are byte-for-byte identical
Two binaries of different sizesNo hashing at all: a size difference is already conclusive
Two identical imagesThese images are identical at this threshold

A large text pair asks first#

Over 10 MB on either side, the Compare button does not run the comparison — it shows the size, warns that this will take a few seconds, and waits for Compare anyway. A progress bar that sits still for eight seconds is indistinguishable from a hang, and being asked is better than guessing.

Folder and image comparisons skip the question: their cost has nothing to do with input byte size.

Limits#

Every limit here stops instead of hanging, and says which limit it hit.

LimitValuePast it
Input carried over IPC10 MBOnly the path travels; the worker reads the file
Bytes handed to the image engine64 MBRefused, with the file name and its size
Text lines, both sides together400,000The text engine stops and says the pair is too large for the line-by-line view
Image dimensions4096 px on the longest sideScaled down before comparing, and the result says it was
Folder entries scanned50,000 per sideThe scan goes partial, the summary strip carries a partial scan chip, and the result says so
Folder depth40 levelsStops descending, and says so in the result
JSON nodes500,000The tree stops growing rather than exhausting memory
JSON depth100 levelsDeeper nodes are marked not compared