Table of Contents

Step Variables Branch

Step Variables is the third of the four roots of the Execution page's Control Tree, below Mission and Program and above Step Colors: route /execution, Control-Tree path execution/step-variables. It edits the project's step variables — each a C# formula over a step, saved in the project file and registered with the step-property registry on every load and every edit — and lists, read-only, the keys the session registered another way. How to use it is Step Variables; this page is how it is built.

The Nodes

  • The root, item type StepVariableRoot, id execution/step-variables. It is in the default set of expanded nodes; a layout a browser already stored keeps its own.
  • One node per variable of the project's list that has a key, item type StepVariable, id execution/step-variables/<key>. When a project file repeats a key, the second and later variables under it add /<occurrence>, counting the variables under the key from 0: the first keeps the bare id, the second is …/<key>/1. The label is the key as typed.
  • The keys registered at run are not nodes: a play registering keys does not reshape the tree. The root's panel lists them.

The branch's item types come from their own wave module, stepVariableItemTypes.ts, spread into the registry like every other wave's. Unlike the Mission, Program and Tool House waves, which the host and the panels also import, it is reached only through the registry module: the node ids the host and the panels use live in stepVariableIds.ts, which imports nothing.

How the Branch Is Read

The whole branch comes from one GET api/execution/step-variables?lang=: the project's variables, the keys registered at run (their names in the request's language), the unit options, and the registry's generation the lists were read at. The root's builder keeps on the nodes what the panels read, so the panels make no request of their own to show a node. Every read goes through one queue into the one root object the page's first build makes — that build's own read included — so an older answer never lands over a newer one, and a move of the registry's generation while the first build runs is read right behind it. A Show Physics Options flip reads nothing here: on this page it rebuilds the Mission branch in place, and the Step Colors branch reads its key options again once the server has taken the flip. After the first read, three things start one:

  • A structure change from a panel — an add, an apply, a rename, a delete.
  • The registry's generation in the broadcast execution status — it moves when a play begins, when a script, a CSV (a mapping or a CSV program's title row) or a training registers a key, when the project's step variables and color trees are registered again after an edit, and when an edit of a project color tree's names and hues alone is taken in place; either edit may be one made in another tab. A branch read at the generation the status names is not read again; the status's timestamp retries a read that failed. It is the step-property registry's count (LocalProjectService.StepPropertiesGeneration, stepPropertiesGeneration in the status), not the inspecting-slot generation: a batch of values written onto the steps — a mapping's columns, a training's estimates, a script's bulk write — moves the inspecting-slot generation once per batch (NotifyStepValuesWritten) — the scan that follows flags the strip for a recolor (RefreshDrawing) and raises InspectingSlotsScanned, which the ClStrip hub broadcasts as Updated for the Color Index Time Chart (the Individual strip chart) to read again — and leaves this one, so a batch alone never re-reads the branch; the branch sees a mapping or a training through the keys they register. Every move of the registry's generation but one moves the inspecting-slot generation with it: the in-place edit of a project color tree's names and hues moves the registry's generation alone, and no scan follows, the tree's leaf ids being the same (see Where the Written Values Live).
  • A language switch, for the registered keys' names. The unit labels are the same in every language.

A read that withdraws the selected variable moves the selection to the branch's root, never to another variable under the same key, and so does a panel's structure change whose read did not grow the node its selectId names (a rename or an add whose read failed). A read that fails keeps the branch as it was — dropping its nodes would unmount an open editor with its edit, and an empty list would offer a key the project has to New — and the root's panel says the read failed, with a Refresh that reads it again.

The Root's Panel

Two lists. Project: the variables, each row selecting its node, with the engine's verdict on it — compiled, or the reason it is not registered: failed (the formula does not compile), keyTaken (a built-in step property's key, or under MC.), keyRepeated (a key an earlier variable of the list has; the first one counts). Registered at run: the session's other keys, with their source, session (a script, a CSV mapping or a CSV program's title row — the registry does not tell those apart) or training. A color tree is no step variable, and its key is in neither list — the project's trees (StepColorTreeList(API)) and a script's (RegisterStepColorTree(API)) alike.

New step variable checks the key on the spot — not empty, no comma or quote, no /, not . or .., not a key the list holds — and puts the variable with the formula null, as an add only: the request carries If-None-Match: *, so a key another tab added since this list was read is refused with 412 on the key field instead of being overwritten. The new variable's node is selected unless the user selected something while the request ran.

A Variable's Panel

Key, name, unit (every PhysicsUnit, shown as Step Properties prints it — mm/s, mm³, None for none — and sent by its name; an unknown name a project file gave is kept as an option of its own, shown as <name> (unknown unit)), format, and the formula in the shared text editor with the step-variable completion context. 500 ms after a change the formula goes to compile-check; the errors are listed under the editor with their line and column, and marked in the editor through CodeMirror's lint state, which the editor component loads only for a consumer that passes diagnostics.

  • Apply sends PUT with the variable's key and occurrence. What is typed while the request is in flight is not part of it and stays; the errors of a refused formula are shown only while the formula is still the text refused. An applied rename re-reads the branch and selects the renamed node, unless the user selected something while it ran.
  • Delete asks (Delete Step Variable), removes this occurrence (DELETE {key}?occurrence=), shows the answer's messages as notifications — the color trees that refer to the key among them — selects the root unless the user selected something meanwhile, and reads the branch again. The edit is dropped first, so the dirty-switch gate does not ask.
  • A refusal is shown on the field its code is about — the key, the unit or the format — and under the editor otherwise, a formula's errors among them; a character a project file cannot hold is shown on the key or the format when the server names one of them, and under the editor when it names the name. Its text is the apiErrors bundle's for the code, with the response's args interpolated; English shows the server's own message.
  • The dirty-switch gate asks before a selection change drops an unapplied edit — Cancel, Discard, Apply & Switch. A switch that does not go through the gate — live Follow, a read that withdraws the node — unmounts the editor, which keeps its edit as a draft in memory, per project and per node; the editor that next mounts on that node takes it back. A prompt still open when its editor unmounts answers Cancel, so the selection change waiting on it ends, refused, instead of waiting for good; a Mission row op never asks this editor — it asks only an open Mission editor's gate. An editor that unmounts while its Apply is in flight keeps nothing then: the Apply, when it answers, keeps what is left unapplied as the draft, under the renamed node if it renamed.
  • A read while the panel is open hands it a new node under the same id, without a remount. With no edit pending the panel takes the server's row; with one, the edit stays, Discard returns to the server's row, and a row other than the one the edit began from is said: Apply would put this version over it.
  • Preview is a button, and on only while a step is selected: POST preview runs the formula as it stands on the selected step and times it on 100 steps from there — the strip's last 100 when fewer follow the step, the whole strip when it is shorter. The result is marked old when the selection moves or the physics is recomputed, including while the answer was on its way.

REST

api/execution/step-variables (Execution/StepVariableController.cs) edits the project's list in memory, one edit at a time under the project's step-definition lock (StepDefinitionEditLock(API), which the refresh after an edit takes too); POST api/project/save writes it.

Request What it does
GET ?lang= the project's variables with their state and errors and the occurrence of a repeated key, the keys registered at run (never a color tree's), the unit options, the generation
PUT {key}?occurrence= adds, changes or renames (a body key other than the route's); compiles first and refuses a formula that does not compile, with its errors; If-None-Match: * makes it an add only
DELETE {key}?occurrence= removes the variable the occurrence names, or every one under the key without it; when no variable is left under the key, messages carries StepVariable-Delete--ReferencedByColorTree, a warning that names, by key, the project's color trees whose terms or leaves' value keys refer to it and says they read it as no value until something registers the key again: the trees are read before the refresh, without asking the registry, so the warning comes even when a built-in property has the key, or a registration of the session, set aside while the variable held the key, takes it back
POST compile-check the formula's errors, from a compilation that loads nothing
POST preview the formula's value on a step, and the time one pass over the strip takes

A refusal carries a code — StepVariableKeyEmpty, StepVariableKeySlash, StepVariableKeyDotSegment, StepVariableKeyTaken, StepVariableKeyRepeated, StepVariableInvalidCharacter, StepVariableBadFormat, StepVariableUnknownUnit, StepVariableCompileFailed, StepVariableNotFound, StepVariableNoStep, StepVariableStepNotBuiltYet, or the shared NoProjectLoaded with no project open (a 200 with success: false on GET, a 400 on PUT, DELETE and preview) — and the args its message names; StepVariableCompileFailed carries the formula's errors instead. Preview's two step refusals carry stepIndex and stripCount: StepVariableStepNotBuiltYet answers the next index a built step takes once a play began — the play stopped before building that step, as at a pause on a collision, or is building it (running) — and StepVariableNoStep any other index without a built step, a position the play has not filled, dropped at a stop or failed to build included.

Where the Written Values Live

A value written onto a step under a key — step["key"] = value in a script's step-built handler, a CSV program's data columns, a CSV mapping's columns, a training's per-step estimates (AngleOffset_deg, TrainingErrRatio), a script's bulk write — is not a field of the step. It is a row of the step cache, the per-instance SQLite file the run writes every step's luggage and physics briefs to, in two tables of that file (HiNc/SqliteUtils/SqliteStepStorage.cs); a step in memory keeps no field for it: while the step's consignment is live the values ride on the consignment, and after that the rows hold them (the runner keeps the last published step's as well, until the play ends). The registry reads a written value ahead of a variable's function.

  • Two tables. StepVariableKey has one row per key a value was ever written under since the file was last reset — KeyId INTEGER PRIMARY KEY, Key TEXT NOT NULL UNIQUE — and StepVariable one row per (key, step): KeyId, StepIndex, a Value that is never NULL, PRIMARY KEY (KeyId, StepIndex), WITHOUT ROWID. Both are cleared and reset with the file. The key table is mirrored in memory (IsStepVariableKeyWritten, WrittenStepVariableKeys), the one thing of these tables that is: a key never written costs no query — a read under it answers no row at once, and the scan below reads no row for it. A key's row is created inside the transaction of the first value written under it and reaches the mirror only after the commit; a delete alone creates none.
  • What a value is. StepVariableValue.TryNormalize decides. A number of any numeric type is stored as a double, a REAL — a value written as an int or a float reads back as a double, in memory and from the store alike — and a bool as 1 / 0; a string is a TEXT; a NaN, which the driver refuses to bind as a REAL, goes in as its eight raw bytes (a BLOB) and reads back bit for bit, so a handler's 0.0 / 0.0 never refuses a batch; null deletes the step's row under the key; a value of any other type is not stored, and the session says so once per key, StepVariable-Write--NotStored.
  • The write paths. During a play, a step-built handler's step["key"] = value and a CSV program's data columns (StateActRunner, through the same indexer) land on the step's live consignment (StepConsignment.WrittenValues, a WrittenStepValues allocated on the first write), and the runner's writer takes them once and writes their rows in the same transaction as the step's luggage and briefs' rows (SqliteStepStorage.InsertStepConsignments): the handler's write costs no write of its own. A value written after the writer took the consignment's values, or onto a step whose consignment is gone — a script's step["key"] = value after the play — goes through IMachiningService.WriteStepVariable: one row, INSERT OR REPLACE, a transaction of its own — a batch of one, which moves the inspecting-slot generation as any batch does (below), so a loop of such writes moves it once per write — and into the runner's copy of the last published step's values when the step is that one (a step object the strip no longer holds writes nothing). A CSV mapping (CsvNcStep.MapSingleByCsvFile), a training (MillingTraining) and a script's bulk write (SessionShell.WriteStepVariables(IEnumerable<StepVariableWrite>), which cuts the sequence into segments itself) write batches of StepVariableWrite(StepIndex, Key, Value) through the host's batch write (IMachiningService.WriteStepVariables; the script's through LocalProjectService.WriteStepVariablesFromScript, the same core, its stored keys noted as script-written), one transaction per batch of StepVariableWrite.SegmentLength = 4,096 writes (the last batch the remainder; a mapping adds a step's columns whole and cuts the batch at the step that reaches 4,096); a batch the store refuses lands nothing and is reported as StepVariable-Write--Fault. Every batch that lands moves the inspecting-slot generation once (LocalProjectService.NotifyStepValuesWritten), and a scan follows 200 ms after the last move: the slots of a written key are refilled, and the Color Index Time Chart (the Individual strip chart) and the colours refresh, once per batch at most, not once per write. The step-property registry's generation — the one this branch watches — does not move with a write: a mapping moves it by registering its new columns once its batches are written (MappedStepColumns.Register), a training by registering its two keys. Every move of that generation but one moves the inspecting-slot generation with it (BumpStepPropertiesGeneration): a registered key may be the inspected one, or its definition. The exception is an edit of a project color tree's names and hues alone, taken in place (LocalProjectService.TryUpdateStepColorTreeAppearance): it moves the registry's generation only (BumpStepPropertiesGenerationOnly) and leaves the slots as they are, the tree's leaf ids being the same.
  • The read path. step["key"] answers a pin the calling thread holds for the step first (the scan's and the step-file export's rows in hand), then the step's live consignment, then — for a step the host's strip holds, and no other — the runner's copy of the last published step's values (so a handler's preStep["key"], for a key the handler wrote on that step, reads no row), then the host's store, one row read; a key never written reads null without a read, and so does a key the pin lacks on a step without a live consignment, since a pin holds every key the step has a row under.
  • The scan. The inspecting-slot scan (LocalProjectService.ScanInspectingSlots) visits the strip 2,048 steps at a time. When the inspecting key is a written one — or the scan's probe found the key reading written values, a function or a project formula over a written key — it reads every key's rows of the segment in one range read (SqliteStepStorage.FindStepVariables(begin, end): a range search of the value table's primary key under each key of the key table, never a scan of the value table) and pins each step's whole row on its thread while the key's getter fills the slot; a step without a row at or below the writer's watermark (SqliteStepStorage.CommittedStepIndex, read before the rows) is pinned empty, one above it is left to its consignment or the store. So a chart or a colouring of a written key never reads one row per step; WriteStepFile pins the rows per segment the same way.
  • After a recompute. The step-physics recompute rewrites the briefs, never the written values: a running sum a handler accumulated from the played physics reads as it was written. A recompute that wrote names the keys a script or a CSV program stored a value under (LocalProjectService.ScriptWrittenStepVariableKeys: a handler's or a later indexer write, a script's bulk write, a CSV program's data columns — not a mapping's or a training's batches — those still in the key table), in one warning, StepPhysics-Recompute--WrittenValuesKept.

Source Code Path

See HiNC App Anatomy for git repository links.

Web Application

HiNC-2025-webservice (Quasar CLI SPA):

  • wwwroot-src/src/components/controlTree/stepVariableItemTypes.ts — the branch's two item types and the builder that reads the whole branch in one request and keeps a failed read's branch.
  • wwwroot-src/src/components/controlTree/StepVariableRootPanel.vue — the two lists, the read-failed note, New step variable as an add only.
  • wwwroot-src/src/components/controlTree/StepVariablePanel.vue — the editor: the fields, the compile-check, Apply, Delete, the refusals, the dirty-switch gate, the drafts, the preview.
  • wwwroot-src/src/components/controlTree/stepVariableIds.ts, stepVariableRefusals.ts, stepVariableDrafts.ts — the node ids, the refusals' fields and the on-the-spot key checks, the drafts.
  • wwwroot-src/src/components/controlTree/useControlTreeHost.ts — buildExecutionRoots(), the branch's read queue, the generation and language watches, and a panel's post-op selection that yields to the user's (StructureChangeRequest.yieldsToUser).
  • wwwroot-src/src/api/stepVariables.ts — the client of the REST above; wwwroot-src/src/api/http.ts — the refusal codes' bundle keys.
  • wwwroot-src/src/components/widgets/TextEditor.vue — the diagnostics the editor marks.
  • Execution/StepVariableController.cs — the REST.

HiAPI Engine

  • HiNc/MachiningProcs/StepVariable.cs — one variable of the project file's list.
  • HiNc/MachiningProcs/StepVariableCompiler.cs — the formulas' compilation, the compile-only check, the outcomes kept per formula text.
  • HiNc/MachiningProcs/LocalProjectService.ProjectStepVariables.cs — the project's variables compiled, registered and refreshed after an edit.
  • HiNc/MachiningProcs/LocalProjectService.ProjectStepColorTrees.cs — ReportStepColorTreesReferencing, the project's color trees that refer to a removed variable's key (StepVariable-Delete--ReferencedByColorTree).
  • HiNc/SqliteUtils/SqliteStepStorage.cs — the step cache's two tables of written values, StepVariableKey and StepVariable: the key mirror, the NaN as eight raw bytes, the batch write in one transaction, the range reads and the writer's watermark.
  • HiMech/MachiningSteps/MachiningStep.cs — the indexer this[string]: the read order (pin, live consignment, the runner's last published values, the store) and the write order (normalise, consignment, host).
  • HiMech/MachiningSteps/WrittenStepValues.cs, HiMech/MachiningSteps/StepConsignment.cs — the values written onto a step while its consignment is live, taken once by the writer for the step's rows.
  • HiMech/MachiningSteps/StepVariableWrite.cs — one write of a batch (StepIndex, Key, Value) and SegmentLength, 4,096; HiMech/MachiningSteps/StepVariableValue.cs — what a value normalises to.
  • HiNc/MachiningProcs/SessionShell.cs — WriteStepVariables and ReadStepVariables, the script's bulk write and range read.
  • HiNc/MachiningProcs/LocalProjectService.cs — the host: TryGetStepVariable, WriteStepVariables with StepVariable-Write--NotStored, NotifyStepValuesWritten, the inspecting-slot scan, ScriptWrittenStepVariableKeys and StepPhysics-Recompute--WrittenValuesKept.

See Also