What the waveform viewer and the step simulator ask of a simulator, and what a simulator
promises them - whichever process it runs in.
**Make more of the system synchronous rather than accept asynchrony and manage it.** Effort
spent moving work to where it can be answered at once is repaid; complexity taken on to
tolerate an answer that is not there yet never goes away. This is the Elmish discipline, and it
holds up surprisingly well in Issie - with memoisation where recomputing would be too slow, and
a few deliberate breaks in places where it does not matter. A simulator in another process is
the hardest thing to hold to it, and it can still be held: what is genuinely slow becomes a
message to start and a message on completion, with a state in the model between the two, and
everything else stays a question with an answer. That takes care and judgement rather than a
blanket rule, which is why it is written down here rather than assumed.
**Interrogating a simulator is synchronous, and there is no cache between.**
Almost everything the UI asks a simulator is a small question about structure - which instances
are inside this one, which ports does this instance offer, what is this port called and how
wide is it - and the answer is wanted from inside `view`, where nothing can await.
Only three things take long enough that they cannot be answered while a frame is being drawn,
and none of them is a question:
PRE-BUILD, where there is no simulation yet to ask;
BUILD, which elaborates the design;
RUN, which simulates it.
All three are commands, the renderer issues them, and so the renderer knows perfectly well when
it is in one. That is the whole of the problem: every synchronous interrogation answers
immediately in those three states - nothing yet - and the UI draws what it has, which is what
it must do anyway while a build is running. Everywhere else the answer is there to be given.
So there is no read-through cache, no per-question "not yet", and no await in view code. An
earlier design here had all three, on the assumption that talking to another process must make
every question asynchronous. It does not: what makes a question asynchronous is being unable to
answer it, and the three states above are exactly the ones where that is true and are known in
advance.
**What makes that possible is WHERE the answers live.** A question the renderer answers from
what it holds is synchronous by construction. It holds two things:
the DESIGN (`SimTypes.SimulatedDesign`), which settles every question about structure - which
sheet an instance is a copy of, what it is labelled, what is inside it, how many of it there
are - all by walking the design, none by expanding it;
and what the last BUILD produced about ports: which of them carry a waveform, what each is
called, and how wide it is. Widths are why this cannot come from the design alone - a
parameterised sheet resolves them per instance - but they are settled by the build, so they
are known before the first frame that asks.
**Nothing displayed yet is a fine answer**, because it becomes a displayed one. A waveform with
no data draws nothing and is drawn again when its data lands; where the sidecar simulates that
is every view until the first fetch returns. WaveSimSVGs goes further and draws the last window
that ARRIVED rather than freezing on the last one that matched, so a fast scroll keeps moving.
The one place a lot of asking could collide is the waveform data itself, and it does not: every
wave a refresh is missing goes in ONE command, with at most one RAM behind it, sequentially in
one promise, under the one FetchInProgress bit that stops the next message asking again
(WaveSimTop.fetchWhatIsMissing). So the number of things in flight is one, whatever the number
of waveforms - which is what keeps the reads above answerable without arbitration.
The one thing that is genuinely per-instance AND per-build is where a port's data LIES. That is
only ever needed twice: to issue a fetch, which happens in the update function where an await
is already happening, and thereafter as a KEY into what the fetch returned - `Wave.DriverIndex`
indexing `WaveData` and `WaveDrawn`, both of which are renderer-side stores of data already in
hand. Neither is a question put to a simulator during a render.
**Why this interface is narrow and the old one was not.** What the renderer uses today is not
an interface at all: it is field access into a `FastSimulation` - the component store,
`Drivers`, `WaveIndex`. Every one of those is proportional to the
EXPANDED simulation, so promoting them to an interface would oblige a remote simulator to
rebuild all of it renderer-side, which is the cost this exists to remove. So the interface
asks small questions - which instances are inside this one, which ports does this instance
offer, read me this window - and a `FastSimulation` becomes the private business of the
implementation that has one.
**Widths come from here and nowhere else.** Parameters are resolved when a design is
elaborated and can change widths, so a width is a fact about the elaborated INSTANCE, not
about the sheet it instantiates. The renderer must never infer one from a design.
**Truth tables are not one of the two, and never will be.** They always use the renderer's own
simulator, whatever `Model.SimulateInRenderer` says. A truth table is combinational: it builds
one sheet with an array of two cycles (`TruthTableView`, which already asks for exactly the
two `ModelHelpers.Constants.rendererArraySizeWhenSidecarSimulates` allows), then drives the
inputs and reads the outputs once per ROW. That is thousands of round trips for a simulation
small enough to have been built in the renderer anyway - the expansion the sidecar exists to
keep out of this process is not what a truth table has. So the mutual exclusion between the
waveform and step simulators does not extend to truth tables, and neither does this interface.