Remotes — Natural type-checking, debounce, and more

Remotes is a network wrapper for Roblox’s remotes which adds a few handy features which I find using all the time.

Setup should be pretty similar to how you code with them already (with the exception of changing how OnServerInvoke works), so it should be natural to all Roblox developers & easy to integrate to!


:page_with_curl: Features

:wavy_dash: Integrated Type Checking — Require clients to send over proper types!

Details & Examples

Values sent from the client to the server can never be trusted. You must implement server-sided sanity checks to ensure that whichever parameter the client sends exists and is of the proper type, depending on what you plan to do with the value.

Remotes lets you pass in a list of parameters, formatted very similarly to Luau type-checking.

-- Imagine a BuyItem function above

Remotes("BuyItem"):OnServerEvent:Connect(function(Request, Name: string, Count: number?)
    local Item = Content.Items[Name]
    if not Item then
        return
    end
    
    BuyItem(Name, Count or 1)
end, {"string", "number?"})

In the example, {"string", "number?"} is the second argument to OnServerEvent, meaning that before the callback runs, Remotes will verify that the first 2 values sent by the client both exist & are of proper types. Just like in Luau, string indicates that the first value is a string, and number? indicates that the second value can be a number or nil.

You can also supply unions in a list format, so if Count could be either a boolean or a number, you would format the list like so:

{"string", {"number", "boolean"}}

:wavy_dash: Debounce — Limit how often a remote can execute server-sided code, either per-player, or globally!

Details & Examples

Tired of making lists for every Connection to OnServerEvent (and so on) to limit how often the server can process a request? Look no further, because Remotes offers a function inside of the Request object, Debounce, which will limit how often the callback will run. This ‘debounce’ is paired per-connection, so if you have two OnServerEvent callbacks with Remote and run Debounce on one of them, the other one will still run independently.

Here’s a common use case: You want to give the client some values which are expensive to calculate, or maybe it’s something that the player can only do every 10 minutes, like opening a chest on the map.

Remotes("OpenChest"):OnServerEvent:Connect(function(Request, Chest: Instance)
    local Player = Request.Client
    Request:Debounce(Player, 600)
    -- To debounce globally, do Request:Debounce(nil, 600)
    
    -- Award player the contents of the Chest & remove it from the map
end, {"Instance"})

You can also use Debounce to resume after certain code has ran… if that’s your thing! (And yes, if your code errors while a debounce is active, it’ll allow for requests again in 5 seconds :slightly_smiling_face:)

Remotes("DoExpensiveCalculation"):OnClientEvent(function(Request)
    Request:Debounce() -- Sets a debounce; callback won't run again
    -- Do expensive stuff!
    Request:Debounce() -- Clears a debounce; callback will run on the next OnClientEvent!
end)

:wavy_dash: Improved OnServerInvoke — Expect a consistent return format back to the client!

Details & Examples

This wrapper restricts how you can return using OnServerInvoke to make working with them safer & more consistent. Instead of the response being a traditional Tuple, think of how HttpService:RequestAsync’s response works.

This is great, as dealing with exceptions from the server is super easy. For example, if you have any pop-ups for your game when you might not have enough cash to buy something, or if you can’t access an area yet, you can always return that message as the exception and display it in your UI!

You’ll never have to deal with questioning how you formatted the response for each remote & if you included a Success value or not.


Client-side

When you call InvokeServer on the client, it will return a Response object which contains details about the request.

-- Format of a Response object
Response = {
    Success: boolean,
    Exception: string?,
    Data: any?
}

Success is whether or not the Request ended in an exception. True indicates that the Request was Closed successfully.
Exception is the error message if an exception was raised on the server.
Data is an optional value for sending back data to the client. This can be whatever you choose.

Note: Remotes doesn’t support Tuple values, so if you have any specific use cases to return one, make sure to pack them into Data.

Exceptions can be raised on the server through the following means.

  • Manually through return Request:Raise(exception: string) to indicate that the server is unable to finish processing the request (e.g. The player doesn’t have enough cash to buy an item)
  • The Luau code on the server ran into its own exception (e.g. workspace:FindFirtChild)
  • The Request didn’t run the callback in the first place due to invalid types being sent to the server, or a Debounce currently being active on the server.
local Response = Remotes("GetCash"):InvokeServer()

Gui.CashLabel.Text = Response.Data.Cash

Server-side

-- Imagine a SessionManager module to import player data

local Remote = Remotes("GetCash")
Remote:OnServerInvoke(function(Request, IncludeCoins: boolean?)
    local Player = Request.Client
    local Session = SessionManager:Get(Player)
    
    if not Session
        return Request:Raise("Session is invalid") -- Can't process this request anymore
    end
    
    return Request:Close({ -- Closing the request successfully & returning Data!
        Cash = Session.Data.Cash,
        Coins = IncludeCoins and Session.Data.Coins
    })
end, {"boolean?"})

:building_construction: Setup

  1. Insert a ModuleScript, name it “Remotes”, and use the code from the GitHub gist. Alternatively, use the Roblox Model.

  2. Insert any RemoteEvents, RemoteFunctions, or UnreliableRemoteEvents you may use for your game under the Remotes module. You can nest remotes into folders as well.

  3. Grab a Remote by requiring the modulle and calling Remotes("RemoteNameHere"). You can use the methods similarly to how you usually would, but with the extra functionality. Each OnServerEvent, OnClientEvent, and OnServerInvoke method returns a Request object which can be used to limit requests (debounce), as well as grabbing the Client that sent the request (if on the server).

  4. Make sure that when using OnServerInvoke, you return either Request:Raise to raise an exception, or Request:Close to close the request & return any values back to the client. Returning values outside of this format isn’t recommended.

  5. For any other questions, look up the examples in this post, the place file below, or write code with it! Remotes has type-checking for Luau so you can read up descriptions on each function.

“Clickin’ Cookies” example place

If you want to see how Remotes can be utilized in a game, feel free to inspect the source of the example place, Clickin’ Cookies!


Hopefully someone can find this resource useful! If you did, or have any complaints, or if it’s just not for you, let me know your thoughts by replying to the post so I can improve my resources in the future!!

:warning: Note: OnClientInvoke was not implemented in this module due to how unsafe it is to call. If you need a response from a client, I recommend using regular RemoteEvents / UnreliableRemoteEvents.

9 Likes

This seems really cool. I will for sure be testing this sometime soon!