# Dispatch

<!-- nav:header:start -->
[< Boundaries](boundaries.md) | [Up: Guides](../README.md) | [Index](../README.md) | [Stateful Doubles >](stateful-doubles.md)
<!-- nav:header:end -->

Dispatch is the uniform call-resolution mechanism that sits between every
facade and its implementation. All three facade types (`ContractFacade`,
`BehaviourFacade`, `DynamicFacade`) use the same dispatch infrastructure —
there is one mechanism, one set of handler types, one logging system.

## Dispatch paths

The dispatch path for each facade is selected at **compile time** via the
`:test_dispatch?` and `:static_dispatch?` options:

```
                         ┌──────────────────┐
                         │  Function call   │
                         │  facade.op(args) │
                         └────────┬─────────┘
                                  │
                    ┌─────────────┴─────────────┐
                    │  test_dispatch? true?     │
                    │  (default: not prod)      │
                    └─────────────┬─────────────┘
                     yes          │          no
                ┌─────────────────┴───────────────┐
                ▼                                 ▼
     ┌──────────────────┐              ┌──────────────────┐
     │  call/4          │              │  static_dispatch │
     │                  │              │  ? true?         │
     │  1. NimbleOwner- │              │  (default: prod) │
     │     ship lookup  │              └────────┬─────────┘
     │  2. App.get_env  │                       │
     │  3. Raise        │              yes      │ no
     └──────────────────┘         ┌─────────────┴───┐
                                  ▼                 ▼
                            ┌────────────┐  ┌──────────────┐
                            │  Inlined   │  │ call_config  │
                            │  direct    │  │ /4           │
                            │  call to   │  │              │
                            │  impl      │  │ App.get_env  │
                            │  (zero     │  │ → apply/3    │
                            │  overhead) │  │ → Raise      │
                            └────────────┘  └──────────────┘
```

### Static dispatch

When `static_dispatch?: true` (default in `:prod`) and the implementation is
available in config at compile time, the facade generates inlined direct
function calls to the implementation module. No `NimbleOwnership` and no
`Application.get_env` — the call compiles to identical bytecode to calling
the impl directly. Falls back to `call_config/4` if the config isn't
available at compile time.

### Runtime config dispatch

`DoubleDown.Dispatch.call_config/4` reads
`Application.get_env(otp_app, contract)[:impl]` and calls
`apply(impl, operation, args)`. Used in production when static dispatch
isn't available, and when `test_dispatch?: false`.

### Test handler dispatch

`DoubleDown.Dispatch.call/4` checks NimbleOwnership for a process-scoped
test handler, falling back to config. This is the default in non-prod
environments and is what makes `DoubleDown.Double` work.

### DynamicFacade dispatch

`DynamicFacade.dispatch/3` is a parallel dispatch path — it checks
NimbleOwnership for a test handler, falling back to the original
(backed-up) module. There is no config-based resolution because there is
no contract module to configure. There is no impact outside of test,
because `DynamicFacade` shims are only installed in test.

## Handler types

When `call/4` resolves a test handler, it matches one of three handler
types stored in NimbleOwnership:

| Handler       | Installed via                          | Dispatch logic                                    | Returns                              |
|---------------|----------------------------------------|---------------------------------------------------|--------------------------------------|
| **Module**    | `Double.fallback(contract, module)`    | `apply(impl, operation, args)`                    | whatever the module's function returns; `%Defer{}` unwraps to deferred result |
| **Stateless** | `Double.fallback(contract, fn)`        | `fun.(contract, operation, args)`                 | whatever the function returns; `%Defer{}` unwraps to deferred result |
| **Stateful**  | `Double.fallback(contract, fn, state)` | `fun.(contract, operation, args, state)` — inside `NimbleOwnership.get_and_update` for atomicity | `{result, new_state}`; can wrap result in `%Defer{}` for re-entrant calls |

All three return `{contract, operation, normalize_args(args)}` from
`key/3` and support `Defer` for re-entrant dispatch.

### 5-arity stateful handlers

Stateful handlers can also accept a 5th argument — a read-only snapshot of
all contract states — for cross-contract state access:

```
# 4-arity (default)
fn contract, operation, args, state -> {result, new_state} end

# 5-arity — cross-contract state
fn contract, operation, args, state, all_states -> {result, new_state} end
```

The `all_states` map is keyed by contract module. Handlers can inspect
another contract's state but only update their own via the return value.

## Defer — re-entrant dispatch

Stateful handler functions run inside `NimbleOwnership.get_and_update`,
which holds a lock on the ownership GenServer. If a handler calls another
facade directly, it deadlocks — the second call re-enters the same
GenServer.

`DoubleDown.Dispatch.Defer` solves this. Return a `Defer` from a handler
and the deferred function runs after the lock is released:

```elixir
{DoubleDown.Dispatch.Defer.new(fn ->
  # Runs outside the lock — safe to call other facades
  {:ok, record} = MyApp.Repo.insert(changeset)
  record
end), new_state}
```

`DoubleDown.Double.defer/1` is a convenience wrapper.

## Public API

| Function            | Purpose                                                |
|---------------------|--------------------------------------------------------|
| `call/4`            | Test-aware dispatch (NimbleOwnership → config)         |
| `call_config/4`     | Config-only dispatch                                   |
| `key/3`             | Canonical key for test stub matching (normalized args) |
| `get_state/1`       | Read current stateful handler state for a contract     |
| `restore_state/3`   | Replace a contract's stateful handler state            |
| `handler_active?/1` | Check if a test handler is installed for a contract    |

<!-- nav:footer:start -->

---

[< Boundaries](boundaries.md) | [Up: Guides](../README.md) | [Index](../README.md) | [Stateful Doubles >](stateful-doubles.md)
<!-- nav:footer:end -->
