SafeFunctions - A RemoteFunction option! (ModuleScript)

Hello Programmers!

Here I am publishing my ModuleScript called “SafeFunctions”, showing what its do and more!

SafeFunctions is a ModuleScript created to offer a safe communication between Clients and Server, simulating a “new version” of a RemoteFunction. Generally we always avoid use the “InvokeClient” method, but when working with this module, it is not more a possible game-breaker!

Method:

This ModuleScript uses a RemoteEvent (internally called of “DataTransfer”) to send data between clients and the server. For example,if the server invokes a client, the server-side part of module will send a signal to client-side part of module with the ‘name’ of a “pseudo-RemoteFunction” and some parameters (it works as a “InvokeClient”); The client must bind a function in client-side part of module, and when client receive the signal from server, its will search for a binded function with the ‘name’ parameter, if it exists then its call the function and send a signal to server-side of module with parameters (which will be interpreted as a “return” from client-side); At end, the “return” from client is returned to the caller of the “InvokeClient” script.

LINK: https://create.roblox.com/marketplace/asset/9265179372

Server-Side’s example:

local SafeFunctions = require(game:GetService("ReplicatedStorage").SafeFunctions)

game.Players.PlayerAdded:Connect(function(Player)
	local ClientAnswer = SafeFunctions:InvokeClient(Player, "Hello")
	print(string.format("%s said: %s", Player.Name, ClientAnswer))
end)

Client-Side’s example:

local SafeFunctions = require(game:GetService("ReplicatedStorage").SafeFunctions)

SafeFunctions:BindToInvokeClient("Hello", function()
	return "Hello server!"
end)

(These examples can be found inside the module when you insert it in your game)

I know it is for a very specific use, but I think that it can help at development of a greater system/framework :slight_smile:

Please report any problem with this module, and feel free to suggest anything to improve the module. All tips are well-come!

7 Likes

Is it safe to use when the player leaves?

Yes. If you don’t specify a timeout, it will automatically stop the invoke after 5 seconds by default.

1 Like

Cool module. If you plan on updating it in the future, it might be worth it to optimize the function names. From what I’ve heard from others, sending an entire function name string through a single RemoteEvent is less efficient than just having multiple RemoteEvents for each function, since Roblox will optimize each RemoteEvent for you. Maybe have it use some sort of enum system instead?

1 Like

I won’t update as often, but at some days ago I was thinking about the performance of RemoteEvent, and I decided to maintain one RemoteEvent to entire module for while, because I always think that with less objects is better. But someday I may test this into a benchmark and change the module to improve his effectiveness

what does this use to prevent exploits on remotefunctions

This module only simulates a common RemoteFunction with RemoteEvents, preventing infinite-yielding problems. Out of this, is your work to filter this RemoteFunction just like any other common RemoteEvent, because its impossible to the module do this automatically for all systems.

you are just doing more work when you can do the same thing with my method (sorry for self advertising) of securing remotefunctions which dosent require any remoteevents

Your method only calls InvokeClient into a different thread and returning if not reached the timeout, what is potentially dangerous yet because this InvokeClient’s call can yield forever the thread of task.spawn. This module does it by another way, which you can open it to see how.

Can i ask why we avoid InvokeClient? I’m new to remote functions and I think roblox would at least keep their code secure

it also counters for infinite yields as the main thread has a timeout for the task.spawn thread.

InvokeClient stops an execution of a function until the client invoked return something to server. If it doesn’t return (like when the player leaves the game during invoke) the server will await forever causing a potentially performance issue in your game. I’ve create this module to try resolve this problem

wish roblox would just make it where if a invoke keeps on yielding when a player leaves it would reset the invoke and start again

The thread created with task.spawn() doesn’t depend of main function to be cancelled, which means sub-thread can continue executing (in this case yielding) even if main function returned or no.

what do you mean? the spawned function dosent return no or anything. the main thread has a timeout but will be waiting for the variable to be changed by the subthread.

Here.

local InvokerSignal = Instance.new("BindableEvent") -- Tested PureSignal vs. Bindable, same results.

local function TimedInvoke(Timeout, Remote, Target, ...)
	assert(Remote and Remote:IsA("RemoteFunction"))
	assert(Target and Target:IsA("Player"))
	local Timeout = Timeout or 10
	local Wrapper
	Wrapper = task.spawn(function(...)
		local Yielded = 0
		local Results
		local Invoked = task.spawn(function(...)
			_, Results = pcall(Remote.InvokeClient, Remote, Target, ...)
		end, ...)
		while (not Results) do
			if Yielded >= Timeout then
				coroutine.close(Invoked)
				break
			end
			Yielded += task.wait()
		end
		InvokerSignal:Fire(Results)
		task.defer(coroutine.close, Wrapper);
	end, ...)
	return InvokerSignal.Event:Wait()
end

That’s all. That’s all it takes.

Use:

local Remote = ...; -- RemoteFunction here.
-- Assumes Player is a Player.
-- 10 Seconds is default, but you can customize it.
local Result = TimedInvoke(10, Remote, Player, Args)
-- Will either timeout and return nil or return the result of InvokeClient.

This will kill the threads if they exceed the yield time limit.
This kills the Wrapper thread after execution as well.


Also

@commitblue, your implementation will leave suspended threads running, taking up server memory over time, as well as performance. It’s a memory leak and it’s not ideal.
This is what OP was trying to tell you.

1 Like

Oh. then you can just use bindable events to :disconnect the event (Or by using maid, janitor lol)