Remote Module - Public Release

I’ve worked on this for the past few hours - but here it is! A module that you can use to fire or connect remotes with one function (instead of using Invoke, Fire, etc.) as well as a method for creating and deleting them.

This is based on a module created by @Eqicness but written entirely from scratch by me.

To set this up:

image
Put the module wherever, make a folder inside of it called “Remotes”, and put all your remotes inside of that folder. Then you can just require the module and run the functions accordingly. This isn’t exactly for beginners, but is very easy to learn and manage!

If you guys have any suggestions be sure to leave them below.

The Script
local m = {}
local Remotes = script.Remotes
local RunService = game:GetService("RunService")

m.DebugMode = true;

function m:Create(RemoteString, Type)
	if not RemoteString then warn("Cannot create remote: Name not provided.") return end
	if not Type then warn("Cannot create remote: Type not provided.") return end
	if type(RemoteString) ~= "string" then warn("Cannot create remote: Name must be a string.") return end
	
	local Types = {["RemoteEvent"] = true; ["RemoteFunction"] = true; ["BindableEvent"] = true; ["BindableFunction"] = true;}
	if not Types[Type] then warn("Cannot create remote: Invalid type.") return end
	
	local Remote = Instance.new(Type)
	if Remotes:FindFirstChild(RemoteString) then
		if Remotes[RemoteString]:IsA(Type) then
			Remote.Name = RemoteString.."-Copy"
			warn("A " ..Type.. "with the name" ..RemoteString.. " already exists. A clone has been made titled "..RemoteString.."-Copy.")
		end	
	else
		Remote.Name = RemoteString
	end
	Remote.Parent = Remotes
	
	if m.DebugMode then print(Type.." "..RemoteString.." created.") end
	
	return Remote
end

function m:Delete(RemoteString)
	local Remote = Remotes:FindFirstChild(RemoteString) 
	if not Remote then
		warn("Remote " ..RemoteString.. "not found. Did you put it in the Remotes directory?")
		return 
	end
	
	if m.DebugMode then print(Remote.ClassName.." "..RemoteString.." deleted.") end
	Remote:Destroy()
	
end

function m:Fire(RemoteString, ...)
	local Remote = Remotes:FindFirstChild(RemoteString) 
	if not Remote then
		warn("Remote " ..RemoteString.. "not found. Did you put it in the Remotes directory?")
		return 
	end
	
	if Remote:IsA("BindableEvent") then
		local succ, err = pcall(function(...) Remote:Fire(...) end)
		if not succ then warn(err) elseif m.DebugMode then print("BindableEvent Fired:"..RemoteString) end	
	elseif Remote:IsA("BindableFunction") then
		local succ, err = pcall(function(...) Remote:Invoke(...) end)
		if not succ then warn(err) elseif m.DebugMode then print("BindableFunction Fired:"..RemoteString) end		
	end
	
	if RunService:IsServer() then
		if Remote:IsA("RemoteEvent") then
			local succ, err = pcall(function(...) Remote:FireClient(...) end)
			if not succ then warn(err) else if m.DebugMode then print("RemoteEvent Fired On Server:"..RemoteString) end return succ end
		elseif Remote:IsA("RemoteFunction") then
			local succ, err = pcall(function(...)
				return Remote:InvokeClient(...)
			end)
			
			if not succ then warn(err) else if m.DebugMode then print("RemoteFunction Fired On Server:"..RemoteString) end return succ end
				
		end
	else
		if Remote:IsA("RemoteEvent") then
			local succ, err = pcall(function(...) Remote:FireServer(...) end)
			if not succ then warn(err) else if m.DebugMode then print("RemoteFunction Fired On Client:"..RemoteString) end return succ end
		elseif Remote:IsA("RemoteFunction") then
			local succ, err = pcall(function(...)
				return Remote:InvokeServer(...)
			end)
			if not succ then warn(err) else if m.DebugMode then print("RemoteFunction Fired On Client:"..RemoteString) end return succ end
		end		
	end
end

function m:FireAll(RemoteString, ...)
	local Remote = Remotes:FindFirstChild(RemoteString) 
	if not Remote then
		warn("Remote " ..RemoteString.. "not found. Did you put it in the Remotes directory?")
		return 
	end
	if RunService:IsClient() then
		local succ,err = pcall(function(...) Remote:FireAllClients(...)	end)
		if not succ then warn(err) elseif m.DebugMode then print("RemoteEvent Fired All:"..RemoteString) end
	end
end

function m:Connect(RemoteString, CallbackFunction)
	local Remote = Remotes:FindFirstChild(RemoteString) 
	if not Remote then
		warn("Remote " ..RemoteString.. "not found. Did you put it in the Remotes directory?")
		return 
	end
	if not CallbackFunction then
		warn("No callback function provided for remote " ..RemoteString.. ".")
	end
	
	if Remote:IsA("BindableFunction") then
		Remote.OnInvoke = CallbackFunction
		if m.DebugMode then print("BindableFunction Connected:"..RemoteString) end	
	elseif Remote:IsA("BindableEvent") then
		if m.DebugMode then print("BindableEvent Connected:"..RemoteString) end
		return Remote.Event:Connect(CallbackFunction)
	end		
	
	if RunService:IsServer() then
		if Remote:IsA("RemoteEvent") then
			if m.DebugMode then print("RemoteEvent Connected On Server:"..RemoteString) end
			return Remote.OnServerEvent:Connect(CallbackFunction)
		elseif Remote:IsA("RemoteFunction") then
			Remote.OnServerInvoke = CallbackFunction
			if m.DebugMode then print("RemoteFunction Connected On Server:"..RemoteString) end		
		end
	else
		if Remote:IsA("RemoteEvent") then
			if m.DebugMode then print("RemoteEvent Connected On Client:"..RemoteString) end
			return Remote.OnClientEvent:Connect(CallbackFunction)
		elseif Remote:IsA("RemoteFunction") then
				Remote.OnClientInvoke = CallbackFunction
				if m.DebugMode then print("RemoteFunction Connected On Client:"..RemoteString) end							
		end
	end
end

return m
2 Likes

I don’t understand what’s the point of this? You can use remote events without this

1 Like

Ease of access. One function to rule them all, and easy debugging.

1 Like

This seems like a very unnecessary abstraction - why is this really better than directly firing remotes? Are there any other benefits that you can actually get out of this module?

I would be more interested if this did more than just provide a single way of firing and connecting to remotes; for example, the sockets resource.

This is better used in a complete framework rather than standalone. I, for example, can use it from any script in my framework to set up remotes rather than having to actually use a pointer to find a remote and fire it. It saves a lot of time when actually writing my scripts and turns 3-4 lines into 1 line of code. Not only that, this is protected and allows for debugging. To put it simply, it’s just less code for the user to have to write.

Except that there are an abundance of unnecessary lines to run, like doing extra if statements, etc. Theoretically, this slows down code. And it really isn’t necessarily 1 line, I mean you can just make 3 ines into 1 line:

if true then
    return
end

--// Becomes //

if true then return end
m:Create(
    "Event", 
    "RemoteEvent"
)
1 Like
local NewRemote = Instance.new("RemoteEvent")
NewRemote.Name = "NewRemote"
NewRemote.Parent = game:GetService("ReplicatedStorage"):FindFirstChild("Remotes)

vs

m:Create("NewRemote", "RemoteEvent")
1 Like

Honestly, I don’t see any problem in doing the first one. It’s just unnecessary abstraction to be honest, also you don’t have to do findfirstchild if you know remotes exists.

2 Likes

I always do findfirstchild or waitforchild; just a force of habit.

findfirstchild is 20% slower than the dot operator, its up to you if you want to change said habit

But, as I said, it’s better used in frameworks. Here’s an example of how I use it:

Frameworks direct flow in a game. Typically, a good framework will already come equipped with networking methods as part of its set up process and so you can fit in with the API that it sets. They too remain clear on exactly what you’re doing by using differently named methods based on if you’re firing/invoking to the server or to the client.

It’s better to write more code and to be clear about what your code is doing, rather than focus on wanting less code and obscuring away that clarity. Even libraries that deal with remotes don’t consolidate all remote capabilities into single functions.

The example you gave Crundee is also somewhat biased? Not every game is going to create remotes in the script and they’ll be available in the DataModel. A more realistic comparison is finding a remote in a remotes folder and using the Create function of your module which are identical behaviours. Less lines of code does not == better necessarily.

The create function is an extra addition; the main purpose of the module are the :Fire(), :Connect(), and :FireAll() methods provided. if you could provide some constructive criticism (or examples as to how others may handle this sort of module), please do so.

The question I’m having is how this improves development workflow beyond being an unnecessary abstraction that affects readability. The constructive criticism is that this module should be doing more than just making an abstraction on a simple process that doesn’t need abstraction. :upside_down_face:

You can check out the following resources which handle networking if you want to get a better understanding of what I’m trying to say here: that the provided resources do more than just supply alternatives for remote calling.

Alternative for remotes with features for improving networking:

Framework that controls networking between source code: *

Libraries that do exactly what this resource does and more: **


* See the following framework-level methods: RegisterEvent, RegisterClientEvent, Fire, FireClient, FireAllClients, FireOtherClients, ConnectEvent, ConnectClientEvent and Service Client Table (accessible via documentation website).

** Regarding the Nevermore repository: while these do exactly what your resource does, they also do more. They also provide clarity as needed when you’re requiring them in your code.

First is to acknowledge that RemoteEvent and RemoteFunction items are separated, as they should be, because there’s no real reason to be consolidating the API. They aren’t the same and shouldn’t be treated the same way either, which is the implication of your resource. Instance: the Connect method (why is this even a method when you’re not using the self parameter?). You connect to events, but you don’t connect to RemoteFunctions - you define their invocation function.

The second thing is the extensibility they provide on top of that. See the modules prefixed by Promise. You’ll need to delve into Promises to get a better understanding of what they are and how they can help in the process of fetching remotes here, if that kind of a circumstance arises in your game (script creates a nonexistent remote that other scripts/LocalScripts require).

1 Like

I’ll take these into account and update accordingly.

I made this module around a year ago now. Looks like you just added a bunch of debug lines, pcalls, compatibility for bindable events (even though it’s meant for replication?), and a method to create remotes via script instead of just putting them in a folder beforehand. I originally made this module simply because I was really tired of referencing remotes and typing :FireServer or :FireClient all the time. I just wanted something simple that went with my framework and was fast to use. The only way this module improves workflow is by getting rid of the need to reference remotes and having shorter, more unified methods. It’s in no way an elegant solution or something I’d release to the public.

Here's my original code:
local rs = game:GetService("RunService")

local m = {}

local function GetRemote(name)
	local remote = m.Remotes[name]
	if not remote then
		warn("Remote", name, "not found. Did you add it to the Remotes folder?")
	end
	return remote
end

function m:Fire(name, ...)
	local remote = GetRemote(name)
	
	if remote then
		if rs:IsServer() then
			if remote:IsA("RemoteEvent") then
				remote:FireClient(...)
			elseif remote:IsA("RemoteFunction") then
				return remote:InvokeClient(...)
			end
		else
			if remote:IsA("RemoteEvent") then
				remote:FireServer(...)
			elseif remote:IsA("RemoteFunction") then
				return remote:InvokeServer(...)
			end
		end
	end
end

function m:FireAll(name, ...)
	local remote = GetRemote(name)
	
	if remote then
		if rs:IsServer() then
			if remote:IsA("RemoteEvent") then
				remote:FireAllClients(...)
			end
		end
	end
end

function m:Connect(name, callback)
	local remote = GetRemote(name)
	
	if remote then
		local signal
		
		if rs:IsServer() then
			if remote:IsA("RemoteEvent") then
				signal = remote.OnServerEvent
			elseif remote:IsA("RemoteFunction") then
				remote.OnServerInvoke = callback
			end
		else
			if remote:IsA("RemoteEvent") then
				signal = remote.OnClientEvent
			elseif remote:IsA("RemoteFunction") then
				remote.OnClientInvoke = callback
			end
		end
		
		if signal then
			return signal:Connect(callback)
		end
	end
end

return m
2 Likes

I will (most likely) make a more open method for firing remotes with different methods, most likely using a class system. I’ll definitely be redoing this.

1 Like