Class InputControlAliasResolver
Resolves alternative control spellings to one canonical path, using the Input System's layout database.
Inherited Members
Namespace: Scylla.Input
Assembly: ScyllaInput.dll
Syntax
public sealed class InputControlAliasResolver : IInputControlAliasResolver, IDisposable
Remarks
A control can answer to several names. buttonSouth on a gamepad is also
a and cross; rightAlt on a keyboard is also AltGr. A live
device resolves all of them to the same control, but nothing rewrites the authored
string, so any rule that compares paths sees unrelated bindings. This class supplies
the missing rewrite.
The index is built from the layout database on first use and keyed by device layout,
because the same spelling means different things on different devices: a is the
south face button on a gamepad and the A key on a keyboard. A flat table would collide
and one of the two would win arbitrarily.
Four categories are deliberately left out, each because indexing it would equate two paths that cannot both resolve to one control, and a wrong equivalence reports a conflict between controls that are genuinely separate:
- Variant-specific controls, which exist only on a device instantiated with that variant.
- Array controls, which the layout builder expands into numbered children without carrying the aliases across, so the unexpanded name is a key no path spells.
- Any spelling that is also the real name of another control in the same layout, which would otherwise rewrite a correct path into a different control.
- Ambiguous spellings, where one alias or usage is carried by two controls and there is no single canonical answer.
Controls that modify an inherited control are kept, which inverts the filter Unity's
own control picker applies. That is intentional and load-bearing: a modifying item is
one whose name is a nested path, and attaching an alias to an inherited child control
is exactly what such an item is for. The mouse scroll/x item is the only source
of the scroll/horizontal alias, and skipping modifying items would drop it.
Base layouts are not walked, because LoadLayout(string) already returns a fully merged layout. Walking them would double-index, and because merging replaces a control's alias array rather than unioning it, would resurrect aliases that a derived layout deliberately dropped.
Normalizing allocates the two substrings a dictionary lookup needs. That is accepted rather than cached: normalization runs once per binding when a conflict snapshot is rebuilt, not once per comparison, so it is not on the path that the snapshot design exists to keep cheap.
Main thread only, because the layout database is Unity API. Instances subscribe to layout changes, so a test that creates one should dispose it.
Constructors
InputControlAliasResolver(InputControlAliasScope)
Initializes a resolver. The index is built on first use rather than here, so constructing one costs nothing and never touches the layout database at a point where it may not be ready.
Declaration
public InputControlAliasResolver(InputControlAliasScope scope = InputControlAliasScope.AliasesAndUsages)
Parameters
| Type | Name | Description |
|---|---|---|
| InputControlAliasScope | scope | Which alternative spellings to treat as equivalent. |
Properties
AmbiguousKeyCount
Gets how many spellings were dropped because two controls in one layout claimed them. Builds the index. A diagnostic: a nonzero value is expected and not a fault.
Declaration
public int AmbiguousKeyCount { get; }
Property Value
| Type | Description |
|---|---|
| int |
IndexedLayoutCount
Gets how many device layouts contributed at least one entry. Builds the index.
Declaration
public int IndexedLayoutCount { get; }
Property Value
| Type | Description |
|---|---|
| int |
Scope
Gets which alternative spellings this resolver treats as equivalent.
Declaration
public InputControlAliasScope Scope { get; }
Property Value
| Type | Description |
|---|---|
| InputControlAliasScope |
Shared
Gets the shared resolver used by the conflict feeds. Built on first use and kept for the lifetime of the domain, so the layout scan is paid once.
Declaration
public static InputControlAliasResolver Shared { get; }
Property Value
| Type | Description |
|---|---|
| InputControlAliasResolver |
Version
Gets a counter incremented whenever the index is invalidated, so callers that cache normalized paths can tell when to discard them.
Declaration
public int Version { get; }
Property Value
| Type | Description |
|---|---|
| int |
Methods
CopyEntriesTo(List<InputControlAliasEntry>)
Appends every entry of the index, so a caller can assert or inspect the whole table.
Declaration
public int CopyEntriesTo(List<InputControlAliasEntry> results)
Parameters
| Type | Name | Description |
|---|---|---|
| List<InputControlAliasEntry> | results | The list to append to. Not cleared by this method. |
Returns
| Type | Description |
|---|---|
| int | The number of entries appended. |
Remarks
Order is unspecified, because it comes from two dictionaries. Sort before comparing.
Exceptions
| Type | Condition |
|---|---|
| ArgumentNullException | Thrown when the list is |
Dispose()
Unsubscribes from layout change notifications. Idempotent.
Declaration
public void Dispose()
Remarks
Shared is meant to live for the domain's lifetime and should not be disposed. This exists so a test that constructs its own resolver does not leave a subscription behind.
Invalidate()
Discards the index so it is rebuilt on next use, and bumps Version.
Declaration
public void Invalidate()
Remarks
Deliberately does not rebuild. Layout registration happens in bursts at startup and on domain reload, and rebuilding on each notification would scan the database hundreds of times to answer a question nobody has asked yet.
Normalize(string)
Rewrites a control path to its canonical spelling.
Declaration
public string Normalize(string controlPath)
Parameters
| Type | Name | Description |
|---|---|---|
| string | controlPath | The path to normalize. |
Returns
| Type | Description |
|---|---|
| string | The canonical path, or the input unchanged. Never |
Remarks
Returns the input untouched for anything it cannot resolve with certainty: a path
with no device prefix, a device usage such as
<XRController>{LeftHand}/trigger, an unknown layout, and a spelling
that is not in the index. That last case covers canonical paths, which is what
makes the operation idempotent.