> ## Documentation Index
> Fetch the complete documentation index at: https://flux-docs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Architecture

> The seven modules that make up Flux, and the path a single Fire takes.

Flux is seven modules. Three are public-facing and four are internal.

```mermaid theme={null}
graph TD
    Root["Flux (root)<br/><i>public entry point</i>"]
    Server["Server<br/><i>server-side channel</i>"]
    Client["Client<br/><i>client-side channel</i>"]
    Bridge["Bridge<br/><i>batching + flush</i>"]
    Buffers["Utils/Buffers<br/><i>encode / decode</i>"]
    Types["Utils/Types<br/><i>schema constructors</i>"]
    Remotes["Utils/Remotes<br/><i>remote resolution</i>"]

    Root --> Server
    Root --> Client
    Root --> Types
    Server --> Bridge
    Client --> Bridge
    Server --> Buffers
    Client --> Buffers
    Server --> Remotes
    Client --> Remotes
    Bridge --> Buffers
    Bridge --> Remotes
    Types --> Buffers
```

## The modules

<AccordionGroup>
  <Accordion title="Flux, the entry point" icon="door-open">
    A thin façade. It exports the `Server<Args..., Out...>` and
    `Client<Args..., Out...>` types, re-exports the `Types` namespace, and guards each
    constructor against being called in the wrong context. It holds no state.

    See [`Flux`](/api/flux).
  </Accordion>

  <Accordion title="Server, the server-side channel" icon="server">
    Owns the server's listener table, invoke-handler table and schema table. All three
    are keyed by event name and all are module-level, so they are shared by every
    `Server` object with the same name. Installs the `OnServerEvent` handlers on first
    use and dispatches decoded packets.

    See [`Server`](/api/server).
  </Accordion>

  <Accordion title="Client, the client-side channel" icon="desktop">
    The mirror of `Server`, plus the pending-request table that makes `Invoke` work: an
    incrementing request id per name, a parked thread, and a timeout timer.

    See [`Client`](/api/client).
  </Accordion>

  <Accordion title="Bridge, batching and flush" icon="layer-group">
    Hands out the `Writer` a send should append to, and flushes accumulated writers on
    `Heartbeat`. Keeps four groups of writers on the server (reliable per-player,
    unreliable per-player, reliable broadcast, unreliable broadcast) and two on the
    client.

    See [`Bridge`](/api/internals/bridge).
  </Accordion>

  <Accordion title="Utils/Buffers, encode and decode" icon="binary">
    All wire I/O. Pooled `Writer` and `Reader` objects, primitive read and write pairs,
    Roblox datatype pairs, the varint codec, the packet header codec, and the dynamic
    `WriteAny`/`ReadAny` encoder. Compiled `--!native --!optimize 2`.

    See [`Buffers`](/api/internals/buffers).
  </Accordion>

  <Accordion title="Utils/Types, schema constructors" icon="ruler">
    A namespace of factories that each return a `{ Write, Read }` pair. Nothing more, so
    a schema is just an array of these.

    See [`Types`](/api/types).
  </Accordion>

  <Accordion title="Utils/Remotes, remote resolution" icon="tower-broadcast">
    Finds or creates the `Flux_Reliable` and `Flux_Unreliable` instances and caches them.
    The server creates, the client waits.

    See [`Remotes`](/api/internals/remotes).
  </Accordion>
</AccordionGroup>

## Two remotes, not two hundred

The central design decision: Flux creates exactly one `RemoteEvent` and one
`UnreliableRemoteEvent` for the entire game, no matter how many events you declare.

<Tabs>
  <Tab title="Raw remotes">
    ```
    ReplicatedStorage/Remotes/
      Chat              (RemoteEvent)
      Damage            (RemoteEvent)
      Purchase          (RemoteFunction)
      PlayerPosition    (UnreliableRemoteEvent)
      ... one per message type
    ```

    Each instance replicates, each call is its own network event, and each carries its
    own per-call overhead.
  </Tab>

  <Tab title="Flux">
    ```
    ReplicatedStorage/Flux/Utils/Remotes/
      Flux_Reliable     (RemoteEvent)
      Flux_Unreliable   (UnreliableRemoteEvent)
    ```

    Every event travels over these two. The event name rides inside the payload as a
    length-prefixed string, and many events share one flush.
  </Tab>
</Tabs>

## Following a single Fire

Here is the whole path for `Damage:Fire(player, humanoid, 250)`.

<Steps>
  <Step title="Resolve a writer">
    `Server:Fire` calls `Bridge.Writer(true, player)`. The bridge looks up its reliable
    per-player map and returns the existing `Writer` for that player, or takes a fresh
    one from the pool.

    Nothing is sent yet.
  </Step>

  <Step title="Write the channel name">
    `Buffers.WriteString(w, "Damage")` writes a varint length followed by the raw bytes.
    This is how the receiver knows which listeners to run.
  </Step>

  <Step title="Write the packet header">
    One byte: the two-bit packet type (`1` for an event) in the high bits, the argument
    count in the low six. Counts of 63 or more escape to a trailing varint.
  </Step>

  <Step title="Write the arguments">
    With a schema, `schema[i].Write(w, arg)` for each argument, with no type tags.
    Without one, `Buffers.WriteAny(w, arg)`, which writes a tag byte then the value,
    narrowing integers to the smallest width that fits.

    Instances are the exception. They are appended to the writer's side array and only
    their index goes into the buffer.
  </Step>

  <Step title="Accumulate">
    `Fire` returns. Any further sends to the same player on the reliable channel, from
    any event, append to this same writer, one after another.
  </Step>

  <Step title="Flush on Heartbeat">
    Once at least 1/60s of accumulated time has passed, `Bridge.FlushAll` finalises each
    writer: it copies the used bytes into an exactly-sized buffer, fires
    `Flux_Reliable:FireClient(player, buffer, instances)`, and returns the writer to the
    pool.
  </Step>

  <Step title="Decode on the client">
    `Client`'s `OnClientEvent` handler wraps the buffer in a pooled `Reader` and loops
    `while r.cursor < r.len`, reading a name, a header and that many arguments each pass,
    so every packet in the batch is handled.
  </Step>

  <Step title="Dispatch">
    For each decoded packet, listeners registered under that name are invoked with
    `task.spawn`, iterating the list from the end backwards. `Once` and `Wait` entries
    remove themselves. The reader goes back to the pool.
  </Step>
</Steps>

## State lives in the module, not the object

Every table that matters is module-level and keyed by event name.

| Table                           | Module             | Keyed by                              |
| ------------------------------- | ------------------ | ------------------------------------- |
| `Listeners`                     | `Server`, `Client` | event name                            |
| `InvokeHandlers`                | `Server`           | event name                            |
| `Schemas`                       | `Server`, `Client` | event name                            |
| `Requests`, `RequestIdCounters` | `Client`           | event name, then request id           |
| `ReliableMap`, `UnreliableMap`  | `Bridge`           | `Player`, or `"SERVER"` on the client |
| `WriterPool`, `ReaderPool`      | `Buffers`          | free lists                            |

<Info>
  This is why two `Flux.Server("Chat")` calls behave as one channel. The objects are
  separate tables, but every lookup they perform goes through `self._id` into the shared
  module state. The object is little more than a typed handle carrying a name and a
  schema.
</Info>

<Warning>
  It is also why a name collision between two unrelated systems silently crosses their
  traffic, and why assigning `OnInvoke` twice on the same name replaces rather than adds.
  Namespace your event names in a large place.
</Warning>

## Next steps

<CardGroup cols={2}>
  <Card title="Batching" icon="layer-group" href="/concepts/batching">
    Flush timing, and what it means for latency.
  </Card>

  <Card title="Serialization" icon="binary" href="/concepts/serialization">
    How values become bytes.
  </Card>
</CardGroup>
