Migrating from Blink
BlinkBlox forked from upstream Blink at v0.18.8, and was called Blink until 0.28.0. A schema written
for Blink 0.18.x is a BlinkBlox schema: the language only grew. What changes is the compiler’s name,
the bytes on the wire, a handful of schemas it now refuses, and some runtime behaviour on the server.
The short version
Section titled “The short version”- Install the
blinkbloxcompiler, and the BlinkBlox Editor plugin if you use Studio. See Installation. - Compile your schema. Fix any new errors (below) and read the new warnings.
- Replace deprecated spellings:
Poll: truewithCall: Polling,.Next()with.Iter(), and deleteoption UseColon. - Decide rate limits for what clients send, and install the two server handlers. See Securing the server.
- Ship the regenerated server and client modules together. They do not talk to modules built by upstream Blink, and a client refuses to run against a server built from a different schema.
What stays the same
Section titled “What stays the same”These keep their upstream names on purpose, so that nothing in a game has to be renamed and builds on either side of the 0.28.0 rename still find each other:
| Thing | Name |
|---|---|
| Schema files | .blink |
| Remotes | BLINK_RELIABLE_REMOTE, BLINK_UNRELIABLE_REMOTE in ReplicatedStorage, prefixed by RemoteScope when set |
| Duplicate-instance guard | _G._BLINK |
| Studio plugin output folder | Blink |
| Studio plugin schema storage | ServerStorage.BLINK_CONFIGURATION_FILES |
| Generated API | Fire, FireAll, FireList, FireExcept, On, Iter, Invoke, StepReplication, with the same Casing |
| Options | the options upstream 0.18.x had keep their names and meaning; UseColon is deprecated |
What changes in the tooling
Section titled “What changes in the tooling”| Upstream Blink | BlinkBlox | |
|---|---|---|
| Command-line binary | blink |
blinkblox |
| Rokit | rokit add XopoIII/BlinkBlox blinkblox |
|
| pesde | xopoiii/blinkblox |
|
| Studio plugin | upstream’s plugin | BlinkBlox Editor |
| Release artifacts | blinkblox-<os>-<arch>.tar.xz (.tar.gz on Windows), blinkblox-plugin.rbxm |
Scripts, CI jobs and git hooks that call blink need the new binary name. The flags, including the
new --profile, are listed under Command line.
The wire format is not upstream’s
Section titled “The wire format is not upstream’s”BlinkBlox packs data differently from Blink 0.18.x, so a module built by one cannot decode traffic from a module built by the other:
- 0.21.0 put booleans and optional flags into a shared bitfield, encoded lengths relative to their
minimum, and added
OrderedUnreliable. - 0.27.0 packed
boolean[]eight elements to a byte.
Since 0.23.0 each pair of modules also carries a schema signature, a hash of every event and function and the types they carry, plus a wire-format version. The server publishes it on its reliable remote, and the client checks it when it is required: a mismatch is an error at startup rather than one event silently decoded as another. A server built by upstream Blink publishes no signature at all, so a BlinkBlox client errors after waiting five seconds for one.
The practical rule: regenerate both modules with the same compiler, from the same schema, with the same profile, and ship them together. See Wire compatibility for exactly what the signature covers.
Deprecated spellings
Section titled “Deprecated spellings”Each still works. The first two warn when compiled.
| Deprecated | Replace with | Notice |
|---|---|---|
Poll: true on an event |
Call: Polling |
W3017. The Call value written beside Poll: true is discarded. |
option UseColon |
delete the line | W3017. No generator has ever read it. |
Event.Next() |
Event.Iter() |
Annotated @deprecated in the generated module, so your editor flags it. The two are the same function. |
Schemas the compiler now refuses
Section titled “Schemas the compiler now refuses”Some schemas that compiled on upstream Blink are errors now. Each of these compiled into a module that misbehaved at runtime – usually silently – and is refused at compile time instead. The diagnostics reference has the full list.
| Code | What is refused | What to do |
|---|---|---|
E3001 |
A map keyed by a struct, tagged enum, map, set, array or type pack. Each decoded key is a fresh table that nothing can look up. | Key by a string, number, boolean or enum; move the rest into the value. |
E3004 |
A set flag, enum value or tagged-enum variant named twice. | Remove the duplicate. |
E3005 |
A declaration named SetRateLimitHandler or SetDecodeErrorHandler, which the module now uses; a type-pack element named after a Luau keyword or a name the generated code uses; a type named after a built-in Luau type (number, string, …) or a Roblox type the module uses (Player, Instance, …). |
Rename it. |
E3013 |
An exported type containing an Instance or unknown. |
Drop export, or move the Instance out of the type. |
E3018 |
An unreliable event whose smallest payload cannot fit in an unreliable packet. | Send it Reliable, or shrink it. |
E3021 |
An unbounded array or a map whose elements cost nothing to decode, such as struct {}[]. |
Bound it ([..16]) or give the element a field. |
E3022 |
A type that refers to itself. | Restructure it; references are inlined, so a type cannot contain itself. |
E3024 |
The same option set twice. | Keep one. |
E3029 |
An enum with no values, or more than 256. | An enum travels as one byte. |
E3030 |
More than 256 reliable events and functions, or more than 256 unreliable events. | Each channel numbers its declarations with one byte; upstream sent the 257th as the first. |
E2003 |
A numeric option that is not a positive whole number, or an unknown Casing. |
Fix the value. |
New warnings are worth reading too. W3019 means an unreliable event may exceed the ~900-byte limit
Roblox enforces; bound the field it names.
What a running game will notice
Section titled “What a running game will notice”On the server
Section titled “On the server”- Inbound packets are bounded by default. A client’s packet is dropped if it is over 8192 bytes or
carries more than 256 instances; decoding stops after 64 events in one packet; and each player’s
packets may cost at most 65536 bytes a second. Reliable events are batched per frame, so a client
that fires more than 64 of them in one frame loses the rest of that frame’s. See
MaxPacketSizeandInboundBytesPerSecondif your game sends more. - Rate limits apply only where you declare them. Nothing is rate-limited until you add
Rate,DefaultRateorRequireRates. - Queues are capped. A reliable event that arrives with no listener is queued up to 256, then
dropped without a warning; a call to a function with no listener past 256 is answered with a
failure; a polled event’s queue stops at 256 rows per
Players.MaxPlayers. - Decode failures are silent unless you install
SetDecodeErrorHandler, so that a client sending garbage cannot fill your output. Warnings about malformed, oversized or refused packets are printed at most once a second per player. - The server can invoke a client, with a
From: Serverfunction. Existing functions areFrom: Clientby default and unchanged.
On both sides
Section titled “On both sides”- Invocations time out. An
Invokethat is not answered withinInvocationTimeout(10 seconds) fails the same way an errored handler does, instead of waiting forever. At most 32 may be outstanding at once; the 33rd errors. Fireenforces length bounds. A string, buffer or array longer than its declared bound makesFirethrow even withWriteValidationsoff, instead of sending a length prefix that wrapped. When any write throws, the half-written event is taken back out, so the rest of the frame’s batch is unaffected.- A second
.Onon aSingleevent warns. It still replaces the first listener, and the first listener’s disconnect function stops working. - An error in a Sync listener is raised on its own thread, rather than being reported as a packet that failed to decode and taking the rest of the packet with it.
- Ranged floats refuse NaN, and an open float range such as
f32(0..)is no longer capped at 2^24. Color3channels are rounded and clamped to 0..255, so an HDR channel above 1 arrives as 255 instead of wrapping.- Edit mode keeps the API’s shape. When
RunService:IsRunning()is false every entry point is a stub, andOnreturns a disconnect function andIteriterates zero times, so stories do not error. See Generated API.
