Dependency Tracking and Circular References
TX1 tracks every reference between cells, defines and aggregates. The dependency graph lets the engine:
- Recalculate in the correct order when an input changes.
- Reject circular references before they become a problem.
- Report every formula that references a given define (useful before rename / delete).
How It Works
When a formula is saved, FormulaDependencyService.RegisterFormula(cellId, formula) parses the expression and extracts references:
| Type | Registered as |
|---|---|
L5, #lq5 | Line reference (line 5) |
#var_name | Variable (define) reference |
#cc.Col | Custom column |
Each dependency becomes an edge in a directed graph.
Detecting Cycles
Before the save commits, WouldCreateCircularReference(cellId, formula) runs a depth-first walk. If it finds a cycle (A → B → C → A), the save is rejected and the offending path is shown to the user.
Evaluation Order
GetEvaluationOrder() returns a topological sort of the graph. The engine evaluates cells in that order so upstream values are available when dependents run.
Finding What Depends on a Cell
GetAffectedCells(cellId) returns the list of cells that would need recalculation if the target changed. The engine calls this after any edit and quietly re-evaluates only the affected set — not the whole project.
User-Visible Cycle Detection
| Symptom | Cause |
|---|---|
#CIRC in a cell | That cell is part of a cycle; the chain is shown on hover. |
| Save rejected with "circular reference" toast | A new formula would close a cycle. |
| Inability to delete a define | Other formulas still depend on it. |
Breaking a Cycle
Typical causes and fixes:
| Situation | Fix |
|---|---|
L5 = L10 + 1, L10 = L5 * 2 | Pick one to hold the independent value. |
#a = #b, #b = #a | Replace one with a literal. |
Self-referencing aggregate (L5 = SUM(L1:L10)) — L5 is inside the range | Restrict the range or exclude L5. |
Global and Scoped Scope Interactions
The dependency graph is aware of scope. A scoped parametric's dependencies do not leak into Global scope. This means you can safely name two scoped parametrics the same way and the cycle detector won't confuse them.
Performance
Dependency graph maintenance is O(edges) per edit. Even in large projects (thousands of items, hundreds of defines) updates are instantaneous.
Usage Report
GetParametricUsageAsync(defineId) returns a List<ParametricUsageInfo>:
- The formula text.
- The entity referencing it (item / overhead / breakdown / custom column).
- The full path for user navigation.
Run this before renaming or deleting a define.