Gum gum-tool-variable-references
Reference guide for Gum's variable reference system — Excel-like cross-instance/cross-element variable binding using Roslyn-parsed assignment syntax. Load this when working on VariableReferenceLogic, EvaluatedSyntax, ApplyVariableReferences, VariableChangedThroughReference, or the VariableReferences VariableListSave.
git clone https://github.com/vchelaru/Gum
T=$(mktemp -d) && git clone --depth=1 https://github.com/vchelaru/Gum "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/gum-tool-variable-references" ~/.claude/skills/vchelaru-gum-gum-tool-variable-references && rm -rf "$T"
.claude/skills/gum-tool-variable-references/SKILL.mdVariable References
Variable references are Gum's system for keeping variables in sync across instances and elements, like cell references in a spreadsheet. A user writes
X = SomeOtherObject.X and the left side stays updated whenever the right side changes.
Storage
Variable references are stored as a
VariableListSave<string> on a StateSave, with Name set to "VariableReferences" (or "InstanceName.VariableReferences" for instance-scoped references). Each string entry is one assignment line.
Syntax
LeftProperty = RightSide
- Left side: An unqualified property name on the owning instance/element (e.g.
,X
,FontSize
).Red - Right side: A variable path, which can be:
- Local:
(same element)OtherInstance.X - Cross-element:
(slash-separated element path)Components/MyComp.InstanceName.Width - Expressions:
,OtherInstance.Width + 10
,OtherInstance.Width * 2!OtherInstance.Visible - Literals:
X = 42
- Local:
- Comments: Lines starting with
are skipped. Invalid lines are auto-commented on validation failure.// - Shorthand: Writing just
(no left side) auto-expands toOtherInstance.X
.X = OtherInstance.X - Color expansion:
auto-expands to separateColor = OtherInstance.Color
,Red
,Green
assignments.Blue
Roslyn Parsing
The syntax is parsed as C# via Roslyn. Slashes in element paths are converted to
global:: qualified names before parsing (Components/Foo becomes global::Components.Foo) and converted back after. The EvaluatedSyntax class handles conversion (ConvertToCSharpSyntax / ConvertToSlashSyntax) and recursive evaluation of the right-side expression tree.
Architecture
SetVariableLogic (variable change entry point) ├─ calls VariableReferenceLogic.DoVariableReferenceReaction() │ ├─ Validates lines (GetIndividualFailures) │ ├─ ElementSaveExtensions.ApplyVariableReferences() — writes hard values to StateSave │ ├─ Finds all elements that reference this element (via ObjectFinder.GetElementReferencesToThis) │ ├─ Applies references on those elements too (cascade) │ └─ DoVariableReferenceReactionOnInstanceVariableSet() — deep propagation for tunneled vars └─ calls VariableReferenceLogic.ReactIfChangedMemberIsVariableReference() └─ ModifyLines() — auto-expansion and qualification of newly entered references
Key Classes
| Class | Location | Role |
|---|---|---|
| | Tool-side orchestration: validation, reaction to changes, line expansion |
| Same directory | Roslyn-based expression parser/evaluator; resolves right-side values via |
(partial) | | — two overloads: one for (save-class, tool-time), one for (runtime) |
| Same directory as logic | Wires delegate so the runtime can use Roslyn evaluation |
Two Apply Paths
ApplyVariableReferences has two overloads:
-
overload (tool-time): IteratesElementSave
entries, evaluates right sides, writes hard values into theVariableListSave
viaStateSave
. FiresSetValue
delegate when a value actually changes, which routes throughVariableChangedThroughReference
— this triggers downstream reactions (font generation, etc.).PluginManager.Self.VariableSet -
overload (runtime): Similar iteration but callsGraphicalUiElement
on the runtime object. Used for wireframe preview in the tool and at game runtime.referenceOwner.SetProperty(left, value)
Right-Side Evaluation
GetRightSideValue resolves the right side of an assignment:
- In the tool:
is set byCustomEvaluateExpression
to useMainVariableGridPlugin
(Roslyn parsing with full expression support).EvaluatedSyntax - At runtime (no tool): Falls back to
with simple dot-path lookup — no expression support, just direct variable resolution.RecursiveVariableFinder
Hard Values — Runtime Implications
Variable references write hard values into the
StateSave. This means at game runtime (where ApplyVariableReferences on the GraphicalUiElement runs once at load time), the referenced values are already baked into the save data. References are not dynamically re-evaluated at game runtime when the source value changes — they are a tool-time binding mechanism. The runtime ApplyVariableReferences(GraphicalUiElement) overload exists primarily for the tool's wireframe preview.
Cross-Element References and Cascading
When a variable changes,
DoVariableReferenceReaction finds all elements that reference the changed element via ObjectFinder.GetElementReferencesToThis (filtered to ReferenceType.VariableReference). It then applies variable references on those elements too, creating a cascade. Modified elements are auto-saved.
Deep Propagation
DoVariableReferenceReactionOnInstanceVariableSet handles a subtler case: when an instance's base element has variable references internally, and the changed variable tunnels through. It walks the reference graph to find which inner-instance variables need updating and writes the values directly into the container's state.
Validation
GetIndividualFailures checks each line for:
- Parseable assignment syntax
- Forbidden left-side names (
,Name
,BaseType
)DefaultChildContainer - Left-side variable existence
- Right-side evaluability
- Type compatibility (with casting support for numeric types)
- Root variable matching for unit/alignment types (prevents mixing XUnits with YUnits, etc.)
Invalid lines are auto-commented with
// prefix and a message is shown to the user.
Known Gaps
- Font generation:
(inCollectRequiredFonts
) andHeadlessFontGenerationService
do not resolve variable references. If a font property (Font, FontSize, etc.) is set via a variable reference, the font file may not be generated for that value. The tool-time path works becauseRecursiveVariableFinder
firesVariableChangedThroughReference
, but headless/CLI font generation could miss these. (See issue #2414)PluginManager.VariableSet - Runtime support: The Roslyn expression evaluator has been extracted into
(Runtimes/GumExpressions/
NuGet). Games can opt in to expression support and useGum.Expressions
to propagate changes at runtime. See theApplyAllVariableReferences
skill for details.gum-runtime-variable-references