The modules
Flux, the entry point
Flux, the entry point
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.Server, the server-side channel
Server, the server-side channel
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.Client, the client-side channel
Client, the client-side channel
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.Bridge, batching and flush
Bridge, batching and flush
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.Utils/Buffers, encode and decode
Utils/Buffers, encode and decode
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.Utils/Types, schema constructors
Utils/Types, schema constructors
A namespace of factories that each return a
{ Write, Read } pair. Nothing more, so
a schema is just an array of these.See Types.Utils/Remotes, remote resolution
Utils/Remotes, remote resolution
Finds or creates the
Flux_Reliable and Flux_Unreliable instances and caches them.
The server creates, the client waits.See Remotes.Two remotes, not two hundred
The central design decision: Flux creates exactly oneRemoteEvent and one
UnreliableRemoteEvent for the entire game, no matter how many events you declare.
- Raw remotes
- Flux
Following a single Fire
Here is the whole path forDamage:Fire(player, humanoid, 250).
1
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.2
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.3
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.4
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.5
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.6
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.7
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.8
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.State lives in the module, not the object
Every table that matters is module-level and keyed by event name.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.Next steps
Batching
Flush timing, and what it means for latency.
Serialization
How values become bytes.
