|
| 1 | +# Twin Identity System |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The identity system in AXSharp allows twin objects to be looked up by a unique numeric identity (`ulong`). This is used by components that need to resolve references between twin objects at runtime (e.g. the `[MemberByIdentity]` attribute). |
| 6 | + |
| 7 | +The core class is [`TwinIdentityProvider`](~/api/AXSharp.Connector.Identity.TwinIdentityProvider.yml), which is responsible for assigning, writing, and sorting identity values. |
| 8 | + |
| 9 | +## How identities work |
| 10 | + |
| 11 | +1. During twin construction, each object that implements `ITwinIdentity` is registered via `AddIdentity`. |
| 12 | +2. When the application starts, `ConstructIdentitiesAsync` is called to assign identity values, write them to the PLC, and build a sorted lookup dictionary. |
| 13 | +3. Other parts of the system can then resolve objects by their identity using `GetTwinByIdentity`. |
| 14 | + |
| 15 | +## Constructing identities |
| 16 | + |
| 17 | +```csharp |
| 18 | +await connector.IdentityProvider.ConstructIdentitiesAsync(); |
| 19 | +``` |
| 20 | + |
| 21 | +`ConstructIdentitiesAsync` performs the following steps: |
| 22 | + |
| 23 | +1. **Assign** -- Each identity tag is assigned a `ulong` value using the provided `identityProvider` function. If no function is supplied, a default provider based on `string.GetHashCode()` of the symbol is used. |
| 24 | +2. **Write** -- The assigned values are written to the PLC via `WriteBatchAsync`. Values are always written fresh, regardless of what was previously stored on the PLC. |
| 25 | +3. **Sort** -- The identities are sorted into a `SortedDictionary<ulong, ITwinIdentity>` using the locally assigned values (`Cyclic`), not values read back from the PLC. |
| 26 | + |
| 27 | +This ensures that stale or inconsistent identity values left on the PLC from prior sessions cannot cause errors. |
| 28 | + |
| 29 | +## The `failOnDuplicate` parameter |
| 30 | + |
| 31 | +```csharp |
| 32 | +await connector.IdentityProvider.ConstructIdentitiesAsync(failOnDuplicate: false); |
| 33 | +``` |
| 34 | + |
| 35 | +By default, `ConstructIdentitiesAsync` throws a `DuplicateIdentityException` when two symbols resolve to the same identity value. You can set `failOnDuplicate` to `false` to log a warning and skip the duplicate entry instead. |
| 36 | + |
| 37 | +## Custom identity providers |
| 38 | + |
| 39 | +The default identity provider uses `string.GetHashCode()`, which has two important limitations: |
| 40 | + |
| 41 | +- **Not deterministic across processes** -- In .NET Core/.NET 5+, `string.GetHashCode()` is randomized per process. Identity values will differ between application restarts. |
| 42 | +- **Collision risk** -- `GetHashCode()` returns a 32-bit value. For large PLC programs with many symbols, hash collisions become increasingly likely (birthday paradox). |
| 43 | + |
| 44 | +For production use, supply a custom identity provider that guarantees uniqueness: |
| 45 | + |
| 46 | +```csharp |
| 47 | +await connector.IdentityProvider.ConstructIdentitiesAsync( |
| 48 | + identityProvider: tag => tag.Cyclic = MyStableHashFunction(tag.Symbol) |
| 49 | +); |
| 50 | +``` |
| 51 | + |
| 52 | +A good custom provider should: |
| 53 | +- Produce deterministic values for the same symbol across process restarts. |
| 54 | +- Guarantee uniqueness (or near-uniqueness) across all symbols in the program. |
| 55 | +- Use the full 64-bit `ulong` range to minimize collision probability. |
| 56 | + |
| 57 | +## Troubleshooting |
| 58 | + |
| 59 | +### `DuplicateIdentityException` |
| 60 | + |
| 61 | +This exception is thrown when two different symbols are assigned the same identity value. Common causes: |
| 62 | + |
| 63 | +- **Hash collision** with the default `GetHashCode()`-based provider. Solution: use a custom identity provider with better distribution, or set `failOnDuplicate: false` if identity-based lookups are not critical for your application. |
| 64 | +- **Stale PLC values** (resolved in current version) -- Previously, the identity system would read back values from the PLC after writing. If the PLC returned stale data from a prior session, this could cause spurious duplicate errors. The system now uses the locally assigned values directly, avoiding this issue. |
0 commit comments