Skip to content

Latest commit

 

History

History
217 lines (151 loc) · 8.2 KB

File metadata and controls

217 lines (151 loc) · 8.2 KB

ByteNet Max

An upgraded buffer-based networking system

ByteNet Max is an upgraded version of @ffrostfall's ByteNet which relies on strict type-checking to serialise your data into buffers before deserialising it on the other end, feeding it back to your Luau code. But what makes ByteNet Max different from ByteNet? ByteNet Max supports queries (RemoteFunctions) which are client to server requests for data. This brings you an extremely optimised experience for RemoteFunctions, using minimal data to increase send and receive speeds. ByteNet Max lives up to ByteNet's idea of making networking simple, easy and quick. The API is simple and minimalistic, helping you grasp the concepts of ByteNet Max pretty quickly!

Installation

Get ByteNet Max on the Roblox Creator Store, or on Wally (WARNING: v1.0.0 is the FIRST version of ByteNet Max, due to an error I made. The latest version is always the one shown on the title of this post)!

Performance

ByteNet Max lives up to the standards of ByteNet, performing incredibly well compared to other networking libraries such as BridgeNet2. The conversion to a buffer reduces memory usage significantly, helping optimise and speed up data transfer.

Documentation

ByteNet Max follows the same architecture as ByteNet, hence the documentation for RemoteEvents (packets) is the exact same and can be found here: Documentation.

Due to ByteNet documentation being outdated, here is a simple setup guide for a remote event.

Packets ModuleScript: Create a ModuleScript that will hold your namespaces.

local ByteNetMax = require(path.to.ByteNetMax)

return ByteNetMax.defineNamespace("Main", function()
	return {
		packets = {

			Test = ByteNetMax.definePacket({
				value = ByteNetMax.struct({
					Action = ByteNetMax.string,
					Data = ByteNetMax.string
				}),
			})
		}
	}

end)

Server listener: Create a simple script to listen to this event, and prints out what the client sent.

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local BytePackets = require(path.to.Packets_ModuleScript)


BytePackets.packets.Test.listen(function(data, plr)
	
	-- prints the action
	print(data.Action)
	
	-- prints some other data
	print(data.Data)
	
	
end)

Localscript that sends information: This is just a simple script that sends over your input to the server.

local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local BytePackets = require(path.to.Packets_ModuleScript)

UserInputService.InputBegan:Connect(function(Input, gameProcessed)
	
	if gameProcessed or Input.KeyCode.Name == "Unknown" then return end
	
	
	BytePackets.packets.Test.send({
		Action = Input.KeyCode.Name,
		Data = 'Whatever data you want to go along with the action here'
	})
	
end)

Max-specific Documentation

However, it adds a new system named queries. This is the ByteNet equivalent of a RemoteFunction. To begin, you can create a ModuleScript to define a namespace under which your packets and queries will be held:

local ByteNetMax = require(path.to.ByteNetMax)

return ByteNetMax.defineNamespace("PlayerData", function()
    return {
       packets = {}, -- not necessary to include this table if there's no values in it.
       queries = {
			GetCoins = ByteNetMax.defineQuery({
				request = ByteNetMax.struct({
					message = ByteNetMax.string
				}),
				response = ByteNetMax.struct({
					coins = ByteNetMax.uint8
				})
			})
		},		
    }
end)

Then, in a local script, you can invoke the query like so:

local QueryModule = require(path.to.QueryModule)

local Coins = QueryModule.queries.GetCoins.invoke({
	message = "Can I please get the coins value?"
})

print(Coins)

In a server script, you can receive the query and return the appropriate information, like so:

local QueryModule = require(path.to.QueryModule)

QueryModule.queries.GetCoins.listen(function(data, player)	
    print(data.message) -- prints "Can I please get the coins value?"
	return {coins = player.leaderstats.Coins.Value}
end)

If you want to disconnect the listener in the future, you can assign the function to a variable:

local QueryModule = require(path.to.QueryModule)
local Listener 

Listener = QueryModule.queries.GetCoins.listen(function(data, player)	
    print(data.message) -- prints "Can I please get the coins value?"
	return {coins = player.leaderstats.Coins.Value}
end)

Listener() -- disconnects listener

The above function works with packets too!

It's that simple!

Auto dataType (easy mode)

ByteNetMax.auto is a flexible dataType for when you just want to send something quickly without deciding the exact type first.

It writes a 1-byte type marker, then automatically picks a compact serializer for the value you pass.

That means numbers use the smallest fitting numeric format (for example uint8/int8/int16/etc) when possible.

Good for: quick prototypes, mixed payloads, and newer developers getting started.

Best practice: if your payload shape is fixed, struct(...) with explicit field types is still ideal.

Example packet using auto:

local ByteNetMax = require(path.to.ByteNetMax)

return ByteNetMax.defineNamespace("AutoDemo", function()
	return {
		packets = {
			DebugValue = ByteNetMax.definePacket({
				value = ByteNetMax.auto,
			}),
		},
	}
end)

Example sends:

local AutoDemo = require(path.to.AutoDemo)

AutoDemo.packets.DebugValue.send(123)
AutoDemo.packets.DebugValue.send("hello")
AutoDemo.packets.DebugValue.send(true)
AutoDemo.packets.DebugValue.send(Vector3.new(1, 2, 3))
AutoDemo.packets.DebugValue.send(nil)

auto currently supports: nil, boolean, number, string, Vector2, Vector3, Color3, CFrame (and falls back to unknown/reference behavior for values like Instance, buffer, and custom userdata).

Packets & Queries can co-exist under the same namespace, just make sure you define the packets and queries table in defineNamespace. If you don't require packets, you can leave it out and just define the queries table, and vice versa.

IMPORTANT: You must require the ModuleScript you created on both the server and client! This is to initialise server side & client side dependencies for a secure network.

Some extra functions

ByteNet Max also adds extra functions for both packets & queries for better control over your code. You can now use .listenOnce() and .disconnectAll() to call a function once or disable all callbacks connected to a packet/query (equivalent to the :Disconnect() and :Once() functions from Roblox)

Using the example above, .listenOnce() is used the same way as .listen() :

local QueryModule = require(path.to.QueryModule)

QueryModule.queries.GetCoins.listenOnce(function(data, player)	 -- this callback only runs once, before disconnecting.
    print(data.message) -- prints "Can I please get the coins value?"
	return {coins = player.leaderstats.Coins.Value}
end)

The .disconnectAll() function can be used to completely erase every callback created through .listen():

local QueryModule = require(path.to.QueryModule)

QueryModule.queries.GetCoins.disconnectAll() -- disconnects all callbacks

Contact

Contact me on Twitter or Discord (username: elitriare), or just on this thread to report bugs or request features. I haven't fully tested this across different types of experiences, so your feedback is extremely useful!

This project is open-source.