Class InputConflictEngine
Decides whether two bindings collide, using rules that need no live input module.
Inherited Members
Namespace: Scylla.Input
Assembly: ScyllaInput.dll
Syntax
public sealed class InputConflictEngine
Remarks
Two bindings conflict when one physical input could fire both in a state where both are live. The test separates facts from policy.
Facts, which are not configurable: bindings in the same semantic slot are alternatives rather than rivals; different device classes are not one physical input; different modifier depths are already disambiguated at run time; modifier composites under different modifiers are different combinations; and different control paths cannot collide at all.
Policy, which the game declares: which parts of the game can be live together. That is the whole of IInputConflictScope, and it is asked last because it is the only test that can be expensive.
The engine reads a precomputed snapshot rather than live objects, which is what lets the same rules run in an editor against a parsed asset and at run time against a live module. A second implementation for the editor would drift from this one within a release, and then the editor would promise something the game does not enforce.
Constructors
InputConflictEngine(IInputConflictScope, in InputConflictPolicy)
Initializes an engine.
Declaration
public InputConflictEngine(IInputConflictScope scope, in InputConflictPolicy policy)
Parameters
| Type | Name | Description |
|---|---|---|
| IInputConflictScope | scope | The scope consulted, or |
| InputConflictPolicy | policy | Which rules apply. |
Properties
Policy
Gets which rules apply and how paths are compared.
Declaration
public InputConflictPolicy Policy { get; }
Property Value
| Type | Description |
|---|---|
| InputConflictPolicy |
Scope
Gets or sets the scope consulted for the collide test. Assigning discards any memoized verdicts, which would otherwise survive a layout swap and lie.
Declaration
public IInputConflictScope Scope { get; set; }
Property Value
| Type | Description |
|---|---|
| IInputConflictScope |
Methods
Evaluate(in InputConflictRecord, in InputConflictRecord)
Evaluates one ordered pair. Pure, allocation-free, and independent of any iteration.
Declaration
public InputConflictReason Evaluate(in InputConflictRecord query, in InputConflictRecord candidate)
Parameters
| Type | Name | Description |
|---|---|---|
| InputConflictRecord | query | The binding being asked about. |
| InputConflictRecord | candidate | The binding it might collide with. |
Returns
| Type | Description |
|---|---|
| InputConflictReason | The reason it does, or the reason it does not. |
Remarks
The tests are a conjunction of independent predicates, so their order affects only cost, never the verdict. They are sequenced cheapest first, with the scope question last because it is the only one that can reach outside the two records.
FindAll(InputConflictSet, List<InputConflictPair>)
Reports every conflicting pair in a set, each pair once.
Declaration
public int FindAll(InputConflictSet set, List<InputConflictPair> results)
Parameters
| Type | Name | Description |
|---|---|---|
| InputConflictSet | set | The snapshot to scan. |
| List<InputConflictPair> | results | List to append pairs to. Not cleared. |
Returns
| Type | Description |
|---|---|
| int | How many pairs were appended. |
Remarks
The whole-model question, for a validation pass rather than a single row. Pairs are emitted with the lower position first and each unordered pair appears exactly once.
Reporting one direction per pair is exact rather than an approximation, but only after the two records the rules read one-sidedly are removed up front: composite containers, which hold no control of their own, and bindings outside the declared columns when the policy hides those. With both filtered, every remaining rule reads its two records symmetrically. The one exception, the scheme-group gate on the device-class rule, sits on a rule that cannot fire, because path equality is tested first and equal paths have equal device prefixes.
There is deliberately no deduplication parameter. The whole-model query in the rebinding manager shares one dedup set across every binding it asks about, so it names each rival action at most once for the entire model and can drop a second genuine pair; that is Known Issue E-009. Reproducing it here would give the quirk a second home and make its eventual fix a two-file change with a compatibility argument. A caller that wants per-action dedup can filter the result, which carries both positions.
Work is proportional to the sum of the squares of the path group sizes, not to the square of the set: a shared control is carried by a handful of bindings, so the quadratic is over a handful.
Exceptions
| Type | Condition |
|---|---|
| ArgumentNullException | Thrown when the set or the result list is |
FindForRecord(in InputConflictRecord, InputConflictSet, List<InputConflictPair>, HashSet<string>, int)
Reports every record in the set that collides with one binding, in set order.
Declaration
public int FindForRecord(in InputConflictRecord query, InputConflictSet set, List<InputConflictPair> results, HashSet<string> dedupByActionID = null, int queryIndex = -1)
Parameters
| Type | Name | Description |
|---|---|---|
| InputConflictRecord | query | The binding being asked about. |
| InputConflictSet | set | The snapshot to scan. |
| List<InputConflictPair> | results | List to append pairs to. Not cleared. |
| HashSet<string> | dedupByActionID | Optional. When supplied, at most one pair is emitted per action already present in it, and each emitted action is added. |
| int | queryIndex | Position of the query in the set, or |
Returns
| Type | Description |
|---|---|
| int | How many pairs were appended. |
Remarks
The deduplication set is supplied by the caller rather than owned here, because callers differ on its lifetime. Asking about one row of a table wants a rival named once across every column of that row, which means carrying one set across several calls.
Scans only the records sharing the query's path when the set can supply that bucket, which is the whole reason the index exists. The results are identical to a full scan, and identical in order: a conflict requires equal paths, every rule tested before that one can only reject, and the bucket is in ascending record order. Nothing outside the bucket could have been appended, and nothing inside it moves.
Exceptions
| Type | Condition |
|---|---|
| ArgumentNullException | Thrown when the set or the result list is |