# State Machines
Load and run dotLottie state machines from C, forward pointer input, drive transitions with typed inputs, and poll transition events.

A state machine turns an animation into something interactive. Instead of playing start to finish, the animation sits in a named state and moves between states in response to input — a pointer entering the canvas, a click, a numeric value crossing a threshold.

The machine is authored alongside the animation and packaged in the `.lottie` file, so behavior lives with the artwork rather than in your code. Your job in C is to load it, forward input, and tick it.

For the concepts behind state machines, see the [state machine explanation](/en/runtimes/distributions/js/v0.x/state-machines) — the model is identical across every dotLottie player.

State machines require the `state-machines` feature, which is enabled by default.

## Load and start

A state machine is a second handle with its own lifetime, borrowed from the player.

```c
dotlottieDotLottieStateMachine *machine =
  dotlottie_state_machine_load(player, "button-states");

if (!machine) {
  /* no machine with that ID, or the state-machines feature is off */
}
```

Load a definition from your own JSON instead with `dotlottie_state_machine_load_data()`.

Starting it takes a security policy for any `OpenUrl` actions the machine might perform:

```c
dotlottie_state_machine_start(machine, "https://lottiefiles.com", true);
```

| Parameter                  | Meaning                                                                     |
| -------------------------- | --------------------------------------------------------------------------- |
| `whitelist`                | Comma-separated list of permitted URL prefixes. `NULL` or `""` permits none |
| `require_user_interaction` | When `true`, a URL only opens as a result of genuine user input             |

<Callout type="warning" title="Treat the URL whitelist as a security boundary">
  A `.lottie` file is data, and a state machine inside one can ask to open a URL. If you load files you did not author,
  pass a restrictive whitelist and keep `require_user_interaction` set to `true`. Passing `NULL` denies all URL opening,
  which is the safest default.
</Callout>

Starting resets playback: the machine stops the player, rewinds to the first frame, and sets speed to `1.0`, forward mode, no looping, and no autoplay. Configure playback _after_ starting, not before.

## Tick the machine, not the player

Once a machine is running, drive it with `dotlottie_state_machine_tick()` instead of `dotlottie_tick()`. It advances the animation and also evaluates transitions and guards.

```c
bool rendered = false;
dotlottie_state_machine_tick(machine, dt, &rendered);

if (rendered) {
  present(buffer, width, height);
}
```

As with `dotlottie_tick()`, `dt` is the elapsed time in **seconds**.

## Forward input

The runtime has no window system, so it cannot observe input itself. Translate your platform's events into canvas coordinates and post them.

```c
dotlottie_state_machine_post_pointer_move(machine, x, y);
dotlottie_state_machine_post_pointer_down(machine, x, y);
dotlottie_state_machine_post_pointer_up(machine, x, y);
dotlottie_state_machine_post_click(machine, x, y);
dotlottie_state_machine_post_pointer_enter(machine, x, y);
dotlottie_state_machine_post_pointer_exit(machine, x, y);
```

Coordinates are in pixels relative to the render target's top-left corner. If you scaled or offset the animation with layout or a viewport, convert from window coordinates before posting, or hit tests will be measured against the wrong area.

### Only post what the machine uses

`dotlottie_state_machine_get_framework_setup()` reports which interactions the loaded machine actually listens for, as a bitmask. Use it to skip the work of tracking input nothing responds to — pointer _move_ especially, which fires constantly.

```c
uint16_t interactions = 0;
dotlottie_state_machine_get_framework_setup(machine, &interactions);

bool needs_move = (interactions & (1 << 4)) != 0;
```

| Bit      | Value | Interaction      |
| -------- | ----- | ---------------- |
| `1 << 0` | 1     | Pointer up       |
| `1 << 1` | 2     | Pointer down     |
| `1 << 2` | 4     | Pointer enter    |
| `1 << 3` | 8     | Pointer exit     |
| `1 << 4` | 16    | Pointer move     |
| `1 << 5` | 32    | Click            |
| `1 << 6` | 64    | On complete      |
| `1 << 7` | 128   | On loop complete |

A value of `0` means the machine is driven entirely by inputs and events rather than pointer interaction.

<Callout type="info" title="These constants aren't in the header">
  The bitmask type is internal to the runtime and `cbindgen` does not emit it, so the values above are the reference.
  Define your own named constants for readability.
</Callout>

## Inputs

Inputs are named variables the machine's guards read. Setting one can trigger a transition immediately.

```c
dotlottie_state_machine_set_numeric_input(machine, "progress", 0.75f);
dotlottie_state_machine_set_string_input(machine, "status", "loading");
dotlottie_state_machine_set_boolean_input(machine, "enabled", true);
```

Read them back with the matching getters:

```c
float progress = 0.0f;
dotlottie_state_machine_get_numeric_input(machine, "progress", &progress);

bool enabled = false;
dotlottie_state_machine_get_boolean_input(machine, "enabled", &enabled);
```

String inputs use the two-call pattern, since they return text:

```c
size_t size = 0;
if (dotlottie_state_machine_get_string_input(machine, "status", NULL, &size) == DOTLOTTIE_SUCCESS) {
  char *status = malloc(size);
  dotlottie_state_machine_get_string_input(machine, "status", status, NULL);
  /* ... */
  free(status);
}
```

## Custom events

Where inputs represent state, events represent a moment. Fire one by name:

```c
dotlottie_state_machine_fire_event(machine, "submit");
```

The machine must define a transition listening for that name; unrecognized names are ignored.

## Inspect the machine

```c
size_t size = 0;
if (dotlottie_state_machine_get_current_state(machine, NULL, &size) == DOTLOTTIE_SUCCESS) {
  char *state = malloc(size);
  dotlottie_state_machine_get_current_state(machine, state, NULL);
  printf("current state: %s\n", state);
  free(state);
}
```

`dotlottie_state_machine_get_status()` reports the engine's status the same way.

For reproducible behavior in machines that make random choices, fix the seed:

```c
dotlottie_state_machine_set_seed(machine, 42);
```

## Observe transitions

State machines have their own event queue, drained like the player's:

```c
dotlottieStateMachineEvent event;

while (dotlottie_state_machine_poll_event(machine, &event) == 1) {
  switch (event.event_type) {
    case StateMachineTransition:
      printf("%s -> %s\n",
             event.data.transition.previous_state,
             event.data.transition.new_state);
      break;
    case StateMachineStateEntered:
      printf("entered %s\n", event.data.state.state);
      break;
    case StateMachineNumericInputChange:
      printf("%s: %.2f -> %.2f\n",
             event.data.numeric_input.name,
             event.data.numeric_input.old_value,
             event.data.numeric_input.new_value);
      break;
    default:
      break;
  }
}
```

| Event type                       | Numeric | Payload member  |
| -------------------------------- | ------- | --------------- |
| `StateMachineStart`              | 0       | —               |
| `StateMachineStop`               | 1       | —               |
| `StateMachineTransition`         | 2       | `transition`    |
| `StateMachineStateEntered`       | 3       | `state`         |
| `StateMachineStateExit`          | 4       | `state`         |
| `StateMachineCustomEvent`        | 5       | `message`       |
| `StateMachineError`              | 6       | `message`       |
| `StateMachineStringInputChange`  | 7       | `string_input`  |
| `StateMachineNumericInputChange` | 8       | `numeric_input` |
| `StateMachineBooleanInputChange` | 9       | `boolean_input` |
| `StateMachineInputFired`         | 10      | `input_fired`   |

<Callout type="warning" title="Copy event strings before polling again">
  Every string pointer in a state machine event points into library-owned memory that the next poll call reuses. Use
  `strdup()` or copy into your own buffer for anything you keep beyond the current iteration.
</Callout>

A second queue, drained with `dotlottie_state_machine_poll_internal_event()`, carries messages intended for platform integrations. Most applications can ignore it.

## Stop and release

`dotlottie_state_machine_stop()` halts the machine but keeps the handle usable. `dotlottie_state_machine_release()` destroys it.

```c
dotlottie_state_machine_stop(machine);
dotlottie_state_machine_release(machine);
dotlottie_destroy(player);
```

<Callout type="warning" title="Release the machine before destroying the player">
  The machine holds an internal borrow of the player, so the player must outlive it. Destroying the player first leaves
  the machine handle dangling.
</Callout>

## Putting it together

```c
dotlottieDotLottieStateMachine *machine =
  dotlottie_state_machine_load(player, "button-states");

if (!machine) {
  return 1;
}

dotlottie_state_machine_start(machine, NULL, true);

uint16_t interactions = 0;
dotlottie_state_machine_get_framework_setup(machine, &interactions);
bool needs_move = (interactions & (1 << 4)) != 0;

double last = now_seconds();

for (;;) {
  double current = now_seconds();
  float dt = (float)(current - last);
  last = current;

  input_event in;
  while (next_input_event(&in)) {
    switch (in.kind) {
      case INPUT_MOVE:
        if (needs_move) {
          dotlottie_state_machine_post_pointer_move(machine, in.x, in.y);
        }
        break;
      case INPUT_DOWN:
        dotlottie_state_machine_post_pointer_down(machine, in.x, in.y);
        break;
      case INPUT_UP:
        dotlottie_state_machine_post_pointer_up(machine, in.x, in.y);
        dotlottie_state_machine_post_click(machine, in.x, in.y);
        break;
    }
  }

  bool rendered = false;
  dotlottie_state_machine_tick(machine, dt, &rendered);

  dotlottieStateMachineEvent event;
  while (dotlottie_state_machine_poll_event(machine, &event) == 1) {
    handle_machine_event(&event);
  }

  if (rendered) {
    present(buffer, width, height);
  }
}

dotlottie_state_machine_release(machine);
dotlottie_destroy(player);
```

## Learn more

- [Handle events](/en/runtimes/distributions/native/v0.x/events) — the player's own event queue
- [Memory and safety rules](/en/runtimes/distributions/native/v0.x/memory-and-safety) — handle lifetimes and destruction order
- [C API reference](/en/runtimes/distributions/native/v0.x/api-reference) — full signatures
