What if ByteNet and Squash had a baby?
You get unet.
pesde add marked/rubine
pesde install
- Add it to your wally manifest:
[dependencies] unet = "mark-marks/unet@LATEST" # Replace LATEST with the latest version
wally install
unet isn't currently available for roblox-ts.
Contributions are welcome!
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"),
}),
}
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")
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:
-- Sends `my_data` to `player`.
my_event.send(player, my_data)
-- Sends `my_data` to all players except `player`.
my_event.broadcast_except(player, my_data)
-- Sends `my_data` to players `player_a` & `player_b`.
my_event.broadcast_to({ player_a, player_b }, my_data)
-- Sends `my_data` to all players.
my_event.broadcast(my_data)
Meanwhile, client (client->server
directioned) events only expose one function:
-- 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.
-- 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
-- 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`.