# Variables

Both [Local Variables](../../manual/variables/local-variables.md) and [Global Variables](../../manual/variables/global-variables.md) implement `Variable`. Code written against the interface can read, write, reorder, and observe either container.

## The interface

The interface also implements the subscription contract used by change bindings. Its declaration, with the members omitted, is:

```cs
public interface Variable : ISubscription
```

`Variable` exposes list operations and change notifications without requiring the concrete component or asset type. These members read and modify the container:

| Member | Does |
| :--- | :--- |
| `Count` | The number of entries. |
| `IsEmpty` | Whether there are none. |
| `IsLocal` | Whether this is a component rather than an asset. |
| `Get<T>()` | Reads the first entry. |
| `Get<T>(int index)` | Reads an entry by position. |
| `Set<T>(T value)` | Writes the first entry. |
| `Set<T>(int index, T value)` | Writes an entry by position. |
| `TagToIndex(IdString tag)` | Resolves a tag to a position, or `-1`. |
| `GetTag(int index)` / `SetTag(…)` | Reads and writes an entry's tag. |
| `Insert<T>` / `Replace<T>` / `Remove` | Modify the list. |
| `Move` / `Swap` | Reorder it. |
| `Contains<T>` | Tests for a value. |
| `Clear()` | Empties it. |
| `Export()` / `Import(…)` | Round-trip the whole list. |

This sequence finds the `health` entry, reads it as a `double`, and writes the reduced value:

```cs
LocalVariable variables = target.GetComponent<LocalVariable>();

int index = variables.TagToIndex(new IdString("health"));
double health = variables.Get<double>(index);

variables.Set(index, health - 10d);
```

`TagToIndex` returns the position used by both `Get` and `Set`.

!!! warning "Out-of-range indices behave differently per method"
    `Get<T>` returns `default`, and `Replace` / `Remove` / `Move` / `Swap` do nothing. However, `Set<T>(index, value)` **appends a new entry** when the index is out of range. Check the index before writing, because a `TagToIndex` result of `-1` would otherwise grow the list.

`Insert<T>` creates an entry with an empty tag. Follow it with `SetTag` when the new entry needs an identifier.

Entries aren't fixed to a type. When `T` differs from the stored type, `Set<T>` replaces the underlying value object instead of converting it. Writing a `string` into a Number entry therefore turns it into a String entry.

## Tags are IdStrings

Entry tags are `IdString`, a struct that wraps a hash-based `PropertyName`. Declare repeated tags once:

```cs
private static readonly IdString HEALTH = new IdString("health");
```

`HEALTH` can be reused without reconstructing and hashing the tag for each call.

The constructor replaces any character that isn't a letter, digit, `-`, or `_` with `-`. For example, `new IdString("player health")` becomes `player-health`. Comparison is case-sensitive.

!!! warning "Don't construct an IdString per call"
    Constructing an `IdString` allocates memory and hashes its value. Declare repeated tags as `static readonly` fields to avoid that work on every update.

## Numbers are doubles

Number entries store `double`. Reading one as `float` converts the stored value, while reading `double` uses the underlying type directly:

```cs
double exact = variables.Get<double>(index);
float approximate = variables.Get<float>(index);
```

Use `double` in repeated reads unless the consuming API requires `float`.

## Finding by tag

`VariablesManager` maps each tag to the containers holding it. Query one or every registered container with the same `IdString`:

```cs
Variable variable = VariablesManager.Get(HEALTH);
IReadOnlyList<Variable> all = VariablesManager.GetList(HEALTH);
```

A container is listed once per entry carrying the tag. Empty tags are never registered.

Both methods return containers. Call `TagToIndex` on a returned container to find the matching entry's position.

`Get` returns the first match, but that order isn't stable when several containers share a tag. Use `GetList` for multiple matches or resolve a known component directly.

Registration follows the component's enabled state, so queries don't find a disabled **Local Variables** component. `Insert`, `Remove`, `Clear`, `Import`, and `SetTag` also update the registry as the list changes.

## Subscribing to changes

`Variable` implements `ISubscription` for change notifications. Keep the returned `Guid` so the component can unsubscribe:

```cs
private Guid m_Subscription;

private void OnEnable()
{
    using Args args = Args.Get(this.gameObject, this.gameObject);
    this.m_Subscription = this.m_Variables.Subscribe(this.OnChanged, args);
}

private void OnDisable()
{
    this.m_Variables.Unsubscribe(this.m_Subscription);
}

private void OnChanged(Args args)
{
    // …
}
```

The `Args` passed to `Subscribe` is copied internally, so the one you pass can be disposed immediately. The callback receives that copy.

!!! warning "Every Subscribe needs its Unsubscribe"
    Keep the returned `Guid` and release it when your object goes away. A leaked subscription fires into a destroyed component.

## Export and import

`Export()` returns the entries as an array; `Import(entries)` replaces the contents.

You can use them to copy state between containers, capture a snapshot for an undo system, or build a container procedurally. The save system uses the same operations.

## Global variables

`GlobalVariable` is a `ScriptableObject` implementing both `Variable` and `ISaveLoad`.

At startup, `VariablesManager` reads `VariablesRepository`. For each asset, it calls `Initialize()`, registers its tags, and subscribes it to `SaveLoadManager`, which makes the asset participate in saving.

The editor adds assets to the repository on import. A `GlobalVariable` created at runtime isn't included in that process, so the manager doesn't initialize or register it automatically.

## Editing at runtime versus authoring

`Initialize()` clones the authored entries into a separate runtime list, and everything at runtime works on that clone. Writing to a `GlobalVariable` during Play mode never modifies the asset on disk, and values revert when Play mode ends. `ISaveLoad.OnReset` re-clones from the authored entries.

## Where to go next

- **[Custom variable types](../extending/variables.md)** — add a stored value type.
- **[Variables](../../manual/variables/index.md)** — inspect the designer-facing containers.
- **[Save and Load](save-load.md)** — follow how Global Variables register and persist.
