Remote - A lightweight simple Buffer based Networking Module v1.0.2

Buffer Based Remote Version 1.0.2

Hi, I’m Flipitagainpls and over the last 2 weeks I’ve been working on a Remote Project, here is what it’s about. Remote is a modular system that handles the firing of remote events and remote functions. Remote Event module uses a runtime-based transferred firing system which allows for a more dynamic and easy fire system, as well as buffing the data. Remote function however uses 5uphi’s remote function method by creating remote functions using remote events since it’s a safer process,
then the data is buffed and transferred between events to process the correct info.

Newest Fixes:

New Version 1.0.2

Remote Api:

--Functions:

	RemoteEvent.new(eventName: string) - Client
	RemoteEvent.new(eventName: string, unreliable: boolean) - Server
	
	RemoteFunction.new(name: string, callbackFn: RBXScriptConnection?) -- Client/Server
	
--Methods:

	remoteEvent:Fire(...) - Client
	remoteEvent:Fire(player: Player, ...) - Server
	remoteEvent:FireAll(...) - Server
	remoteFunction:Invoke(...) -Server/Client -> (any)

Remote Tutorial:

Here is the tutorial:

–[[
---------------------------------------------------- TUTORIAL ----------------------------------------------------

Remote Function: [
	---------------------- Client ----------------------
	
	local RemoteFunction = require(ReplicatedStorage.Remote).RemoteFunction

	local newFunction = RemoteFunction.new('FirstRemoteFunction')

	local info = newFunction:Invoke()

	print(info)
	---------------------- Server ----------------------
	
	local RemoteFunction = require(ReplicatedStorage.Remote).RemoteFunction

	local newFunction = RemoteFunction.new('FirstRemoteFunction', function(player: Player?, ...)
		return 'Hello World!'
	end)
	
	
	----- Output -----
	Hello World! - Client 
]

Remote Event: [
	---------------------- Client ----------------------
	
	local RemoteEvent = require(ReplicatedStorage.Remote).RemoteEvent

	local newEvent = RemoteFunction.new('RemoteEvent')

	newEvent:Fire('Hello World!')
	
	newEvent.OnFired:Connect(function(...)
		-- callback for fireclient
	end)

	---------------------- Server ----------------------
	
	local RemoteEvent = require(ReplicatedStorage.Remote).RemoteEvent

	local newEvent = RemoteFunction.new('RemoteEvent')
	
	newEvent.OnFired:Connect(function(player: Player, msg: string?)
		print(msg)
		
		newEvent:Fire(player, ...)
		newEvent:FireAll(...)
	end)
	
	
	----- Output -----
	Hello World! - Server 
]

]]

Just Put the Remotes Folder In Replicated Storage And the Remote module wherever you please.
Link: https://create.roblox.com/store/asset/112800451842069/Remote

Source Codes:

Remote Function
--[[
local RemoteFunction , FunctionThreads = {}, {}

---- Services ----

local RunService = game:GetService'RunService'
local ReplicatedStorage = game:GetService'ReplicatedStorage'

---- Modules ----

local Buffer = require(script.Parent.Packages.Buffer)

---- Variables ----

local remotes = ReplicatedStorage.Remotes

---- Functions ----

local function RemoteFunctionErrorHandler(err: string)
	local errorExecutionTime = tostring(DateTime.now():FormatLocalTime('LTS', 'en-us'))
	error(`\n['RemoteFunction]: \n {err} \nexecuted at {errorExecutionTime}`)
end

local function ReturnProcessedCallback(eventName: string?, exhaustTime: number)
	local begin = os.clock()
	repeat 
		task.wait()
		local elapsed = (os.clock() - begin)
		if elapsed > exhaustTime then
			RemoteFunctionErrorHandler('remote function never returned an object')
		end
	until FunctionThreads[eventName]
	return table.unpack(FunctionThreads[eventName])
end

---- Types ----

export type RemoteFunc = {
	Invoke: (self: RemoteFunction, any) -> ()
}

---- Construction ----

local constructor = {
	new = function(
		name: string,
		callbackFn: RBXScriptConnection?
	)
		if RunService:IsClient() then
			local self = {}
			
			self.Function = remotes.Functions:FindFirstChild(name) :: RemoteEvent
			if self.Function then
				self.Function.OnClientEvent:Connect(function(method: string, ...)
					if method == 'Client' then
						local buffed = Buffer.new(...)
						FunctionThreads[name] = buffed
					else
						local returnedProcessed = callbackFn(table.unpack(...))
						if not returnedProcessed then
							RemoteFunctionErrorHandler'remote callback does not return at least 1 value'
						end
						self.Function:FireServer('Server', table.pack(returnedProcessed))
					end
				end)
				
				function self:Invoke(... : any)
					local packed = table.pack(...)
					local buffed = Buffer.new(packed)
					self.Function:FireServer('Client', buffed)
					return ReturnProcessedCallback(name, 4)
				end

				return self :: RemoteFunc
			end
		else
			local self = {}
			if not remotes.Functions:FindFirstChild(name) then
				self.Function = Instance.new'RemoteEvent'
				self.Function.Name = name
				self.Function.Parent = remotes.Functions
			else
				self.Function = remotes.Functions[name]
			end
			function self:Invoke(player: Player?, ... : any)
				local packed = table.pack(...)
				local buffed = Buffer.new(packed)
				self.Function:FireClient(player, 'Server', buffed)
				return ReturnProcessedCallback(name, 4)
			end
			self.Function.OnServerEvent:Connect(function(player: Player?, method: string, ...)
				if method == 'Client' then
					local returnedProcessed = callbackFn(player, table.unpack(...))
					if not returnedProcessed then
						RemoteFunctionErrorHandler'remote callback does not return at least 1 value'
					end
					self.Function:FireClient(player, 'Client', table.pack(returnedProcessed))
				else
					local buffed = Buffer.new(...)
					FunctionThreads[name] = buffed
				end
			end)
			return self :: self
		end
	end,	
}

return constructor]]
Remote Event
--[[local RemoteEvent = {}
RemoteEvent.__index = RemoteEvent

---- Services ----

local RunService = game:GetService'RunService'
local ReplicatedStorage = game:GetService'ReplicatedStorage'

---- Variables ----

local utils = script.Parent.Utils
local packages = script.Parent.Packages
local remotes = ReplicatedStorage.Remotes

local Signal = require(packages.Signal)
local Buffer = require(packages.Buffer)

local ClientProcess = require(script.ClientProcess)
local ServerProcess = require(script.ServerProcess)

---- Functions ----

local function RemoteEventErrorHandler(err: string)
	local errorExecutionTime = tostring(DateTime.now():FormatLocalTime('LTS', 'en-us'))
	error(`\n['RemoteEvent]: \n {err} \nexecuted at {errorExecutionTime}`)
end

local function CreateEventPacket(
	eventName: string,
	unreliable: boolean?
)
	if not remotes.Events.Data:FindFirstChild(eventName) then
		local strVal = Instance.new'StringValue'
		strVal.Parent = remotes.Events.Data
		strVal.Name = eventName
		strVal:SetAttribute('Unreliable', unreliable)
	end
end

local function GetEventPacket(
	eventName: string
)
	local rem: StringValue? = remotes.Events.Data:FindFirstChild(eventName)
	if rem then
		return rem:GetAttribute'Unreliable'
	else --@the first param will be unreliable[bool?], if more data is needed to be shared then it will
		return nil
	end
end

if RunService:IsClient() then
	function RemoteEvent.new(eventName: string)
		assert(eventName, `given value 'eventName' is missing or nil`)
		assert(type(eventName) == 'string', `given value 'eventName' is not a string`)
		
		local unreliable = GetEventPacket(eventName)
		
		if unreliable ~= nil and remotes.Events:FindFirstChild(eventName) then
			local self = setmetatable({}, RemoteEvent)
			
			self.unreliable = unreliable
			self.remote = remotes.Events[eventName]
			
			self.OnFired = Signal.new()
			self.identifier = eventName
			
			self.remote.OnClientEvent:Connect(function(evName: string, ...)
				if evName == eventName then self.OnFired:Fire(...) end
			end)
			
			return self
		else
			RemoteEventErrorHandler('missing event (event not created)')
		end
	end
	
	function RemoteEvent:Fire(...)
		local packed = table.pack(...)
		local buffed = Buffer.new(packed)
		ClientProcess.InsertProcessQueue(self.remote, self.identifier, self.unreliable, buffed)
	end
else
	function RemoteEvent.new(eventName: string, unreliable: boolean)
		assert(eventName, `given value 'eventName' is missing or nil`)
		assert(type(eventName) == 'string', `given value 'eventName' is not a string`)
		
		if remotes.Events:FindFirstChild(eventName) then
			RemoteEventErrorHandler('EventName already used or created')
		else
			local self = setmetatable({}, RemoteEvent)

			if not unreliable then unreliable = false end
			if unreliable then 
				self.remote = utils.UnreliableEvent:Clone()
				self.remote.Parent = remotes.Events
				self.remote.Name = eventName
			else 
				self.remote = utils.Event:Clone()
				self.remote.Parent = remotes.Events
				self.remote.Name = eventName
			end

			self.OnFired = Signal.new()
			self.identifier = eventName
			self.unreliable = unreliable

			self.remote.OnServerEvent:Connect(function(player, evName: string, ...)
				if evName == eventName then self.OnFired:Fire(player, ...) end
			end)

			CreateEventPacket(eventName, unreliable)
			return self
		end
	end     
	
	function RemoteEvent:Fire(player: Player?, ...)
		local packed = table.pack(...)
		local buffed = Buffer.new(packed)
		ServerProcess.InsertProcessQueue(player, self.remote, self.identifier, self.unreliable, buffed)
	end
	
	function RemoteEvent:FireAll(... : any?)
		local buffed = Buffer.new(table.pack(...))
		ServerProcess.InsertProcessQueue(nil, self.remote, self.identifier, self.unreliable, buffed)
	end
end

return RemoteEvent]]

If there is anything you would like me to add to the api (that you think is useful) I will. Also, whoever decides to use this please give me feedback and do benchmarks to see what the performance looks like. So far I’ve compared it to roblox’s default remote event and its given me good results, so please feel free to show me your results.

2 Likes

You should add bindable events , but good module!

1 Like

I didn’t think bindables would have very much purpose 1. because its a Remote module and 2. because bindables are already fast due to them just sending info right over. Theres no need to use buffers on them either because it would kinda slow down the process.

1 Like

Is this just a RemoteEvent wrapper? I don’t see why anybody should use this over other free networking solutions or just using RemoteEvents directly.

2 Likes

No. It uses a runtime safe event firing, as well as packing and buffering data. This is better to use over Roblox because Roblox doesn’t do either of those things. Same for remote function, it uses buffers. This just like ByteNet but it buffs all the data you send instead of you having to specify which buffer type you’re using.

2 Likes

Is this more performant to BN2?

This looks like any other networking module that exists, except it’s pretty much worse since it uses metatables. Perfectly avoidable case too.

I tried using this module for some use cases of mine (like constantly sending cframes over remotes from the client to server for camera replication), however I noticed that you can’t send CFrames… or tables… or anything beside strings and numbers, really.

If I may, I have made a module for this kinda thing called BufferConverter, which can serialize and deserialize most common Roblox datatypes (tables, CFrames, Vector3s, even Instances.) Heres the link:

I’d say its alike ByteNet2, but Byte Net has you Write the buffer types yourself, while this module buffs everything for you.

Thank you, I totally forgot to implement vectors and cFrames into my buffer module, and I’ll be working on it right now.

Why would metatables be an issue when warp, byteNet, sleitnick Comm, 5uphi’s RemoteFunction and mostly all networking modules use metatables?

Newest Fixes:

New Version 1.0.2