Skip to content

A fast, optionally structured networking library

License

Notifications You must be signed in to change notification settings

Mark-Marks/unet

Repository files navigation

CI License: MIT Wally Pesde

What if ByteNet and Squash had a baby?
You get unet.

Installation

  1. pesde add marked/rubine
  2. pesde install
  1. Add it to your wally manifest:
    [dependencies]
    unet = "mark-marks/unet@LATEST" # Replace LATEST with the latest version
  2. wally install

unet isn't currently available for roblox-ts.
Contributions are welcome!

Usage

Creating defined events

Defined events are alike to ByteNet packets.
They're created in namespaces alongside a specific structure for their data.

Defined events will have the best performance and bandwidth, as they use a predetermined structure over dynamically serializing data alongside type information.

-- shared/events.luau
local unet = require("@pkg/unet")
local t = unet.data_types

-- Data structures are defined with Squash
local person = t.record({ -- Record - a table with static keys and values
    name = t.string(20), -- <=20 char string
    age = t.uint(1), -- 1 byet uint (uint8) - 2^8-1 limit
    height = t.uint(1), -- 1 byte uint (uint8) - 2^8-1 limit
    sex = t.string(6), -- <=6 char string
})

-- Namespaces need to have unique names,
-- but each namespace can have the same event names as another.
-- You can also choose to create a separate file for each namespace,
-- and simply return the namespace (`return unet.define_namespace(...)`)
return {
    my_namespace = unet.define_namespace("my_namespace", {
        my_server_event = unet.defined("server->client", person)
        my_unreliable_server_event = unet.defined("server->client", person, "unreliable"),
        my_client_event = unet.defined("client->server", person),
        my_unreliable_client_event = unet.defined("client->server", person, "unreliable"),
    }),
}

Creating undefined events

Undefined events are a shorthand for defined events with an unknown data type, with the exception of being created outside of namespaces and instead on both ends of your code.

They offer slightly worse performance and bandwith over defined events and aren't validated in any way, but they're easier to use DX-wise.

-- server/script_a.luau
local unet = require("@pkg/unet")

-- Names need to be unique regardless of reliability type or script, as
-- undefined events share the same namespace internally.
local my_server_event = unet.undefined("server->client", "my_server_event")
local my_unreliable_server_event = unet.undefined("server->client", "my_unreliable_server_event", "unreliable")
local my_client_event = unet.undefined("client->server", "my_client_event")
local my_unreliable_client_event = unet.undefined("client->server", "my_unreliable_client_event", "unreliable")

-- You can also choose to add types to them, although this doesn't do anything
-- other than giving the luau type system information about them.
local some_event: unet.ServerEvent<{
    name: string,
    age: number,
    height: number,
    sex: string,
}> = unet.undefined("server->client", "some_event")

Using events

Events aren't bidirectional. This was done to improve types and improve their clarity.

Server (server->client directioned) events offer a variety of functions to fire off data:

.send(target: Player, data: T)

-- Sends `my_data` to `player`.
my_event.send(player, my_data)

.broadcast_except(except: Player, data: T)

-- Sends `my_data` to all players except `player`.
my_event.broadcast_except(player, my_data)

.broadcast_to(list: { Player }, data: T)

-- Sends `my_data` to players `player_a` & `player_b`.
my_event.broadcast_to({ player_a, player_b }, my_data)

.broadcast(data: T)

-- Sends `my_data` to all players.
my_event.broadcast(my_data)

Meanwhile, client (client->server directioned) events only expose one function:

.send(data: T)

-- Sends `my_data` to the server.
my_event.send(my_data)

Both kinds of events expose the same kind of interface for listening to sent data, but client events additionally add the player at the end.

.poll()

-- Received data is instantly fired through a signal, and is also stored in a queue.
-- This function returns an iterator for all packets of data stored in the queue.
-- Once a packet of data is iterated over, it's deleted from the queue.
-- This is extremely useful for ECS, where you're running systems every frame.
for index, data in my_event.poll() do
    ...
    -- The current index is now removed from the queue as it was iterated over; you can only poll in one place.
end

-- Alternatively, if it's a `client->server` event:
for index, data, player in my_event.poll() do
    ...
end

.listen(listener: (data: T, player: Player?) -> ()) -> () -> ()

-- Connects the passed listener to the given event's signal.
my_event.listen(function(data)
    ...
end)

-- Alternatively, if it's a `client->server` event:
my_event.listen(function(data, player)
    ...
end)

-- `.listen()` also returns a disconnect function, which you can use to disconnect from the event:
local disconnect = my_event.listen(function(data)
    ...
end)

...

disconnect()
-- No longer listening to `my_event`.

About

A fast, optionally structured networking library

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages