Networking

A module used to simplify client-server communication

Important Note

Networking in Roblox can be an annoying process, especially if you have to wire it all together yourself. That's why Quebec has a feature-rich networking library.

Events

The networking library supports two kinds of events: typed and untyped events. Untyped events essentially accept every argument while typed events have strict types for the arguments. Here's an example:

local Networking = require(script.Parent.Quebec.modules.Networking)

-- calling :client() on the event will return the client-sided executor.
-- it was added to improve autocompletions and a way to share events without declaring
--    them multiple times. refer to "Organizing"

local Untyped = Networking.event("Equipitem"):client()

Untyped:fire("Sword", 1, 2, 3) -- this works, as it accepts every argument

Untyped:connect(function (...: any)
    print(...) -- prints args
end)

-- note: the parenthesis around the "string, number" are cosmetic in this case
--       but are good practice to group type packs.
local Typed: Networking.Event<(string, number)> = Networking.event("DropItem")
local TypedExecutor = Typed:client()

TypedExecutor:connect(function (item: string, amount: number)
    print("dropping", tostring(amount), "x", item)
end)

TypedExecutor:fire("Sword", 1) -- works
TypedExecutor:fire("Sword", false) -- this would show a warning due to type mismatch

Please remember that Quebec does NOT perform runtime type checks.

Functions

The library also supports RemoteFunctions. Just like the events, there are typed and untyped ones. However, this time the parentheses aren't optional. Let me show you:

local Networking = require(script.Parent.Quebec.modules.Networking)

-- calling :client() on the function will return the client-sided executor.
-- it was added to improve autocompletions and a way to share events without declaring
--    them multiple times. refer to "Organizing"

local Untyped = Networking.func("EquipItem"):client()

-- you can connect to the function like so:
Untyped:connect(function (...: any): ...any
    return 1, 2, 3
end)

local success = Untyped:fire("Sword", 1, 2, 3) -- accepts any, returns any
local success, a, b, c = Untyped:fire() -- this would work aswell

type Args = (string, number)
type Returns = boolean

local Typed: Networking.Func<Args, Returns> = Networking.func("DropItem")
-- we call this separately for simplicity
local TypedExecutor = Typed:client()

TypedExecutor:connect(function (a0: string, a1: boolean): boolean
    print(a0, a1)
    
    return true
end)

local success = TypedExecutor:fire("Sword", 1) -- works. the type of "success" is now boolean

-- technically, you could also use inline types:

local Typed2: Networking.Func<(string, string), (number, number)> =
    Networking.func("CountWords")
local Typed2Executor = Typed2:client()
    
local wordCountA, wordCountB = Typed2Executor:fire("hello world", "does this count")

Typed2Executor:connect(function (word1: string, word2: string)
    return 1, 2 -- pseudo returns
end)

-- any type mismatch would show a warning again

Organizing

We recommend to create an Events ModuleScript somewhere in ReplicatedStorage where you save all your events so both the server and client can access the same events:

local Networking = require(script.Parent.Quebec.modules.Networking)

return {
    EquipItem = Networking.event("EquipItem") :: Networking.Event<string, number>,
    DropItem = Networking.event("DropItem"),
    
    -- you can also put functions here, or create a seperate "Functions" ModuleScript
    BuyItem = Networking.func("BuyItem") :: Networking.Func<(string), (boolean, number)>,
    GetItemData = Networking.func("GetItemData"),
}

You can then access any executor like this:

local Events = require(Events)

-- on the server:
local EquipItem = Events.EquipItem:server()

-- or on the client:
local DropItem = Events.DropItem:client()

Executor Methods

All executor methods are called using a colon. So: executor:fire() instead of executor.fire() !

Access a client-side executor by calling :client() on the selected event or function. To access the server-sided executor, call :server() on the selected event or function.

Events

Args... is a type placeholder for the arguments type pack. If you use an untyped event, then this will always be ...any .

Signature
Description

fire(player: Player | { Player }, Args...): ()

Fires the event with the given arguments to the selected player or players

broadcast(Args...): ()

Fires the event with the given arguments to all players

except(player: Player | { Player }, Args...): ()

Fires the event with the given arguments to all players except the given ones

connect(callback: (player: Player, Args...) -> ()): RBXScriptConnection

Connects the callback to the event's OnServerEvent

Functions

Args... and Returns... are type placeholders for the arguments and returns type packs. If you use an untyped function, this will always be ...any for both.

Functions also don't support broadcasting due to the fact that RemoteFunctions return values. However, we may add functionality like this in a later update in which you can query the return result of a player.

Signature
Description

invoke(player: Player, ...Args): ...Returns

Invokes the function on the specified players with the given arguments and returns the defined returns

connect(callback: (player: Player, ...Args) -> ...Returns): ()

Connects the callback to the event's OnServerInvoke

Last updated