Is it bad practice to assign each effect/client-based module its own remote for easy server-to-client communication? I tried various network modules but they seem to rack up my ping UP TO 4000MS.
I thought of giving each effect its own remote like this.
_G._connectedNetworks = {} :: {RemoteEvent}
for _,v in game:GetService("ReplicatedFirst")._Modules:GetChildren() do
if v:IsA("ModuleScript") then
local p = Instance.new("UnreliableRemoteEvent")
p.Name = v.Name
p.Parent = ReplicatedStorage
_G._connectedNetworks[v.Name] = p
end
end
-- e.g
_G._connectedNetworks.a4a_doMessage:FireAllClients("Hi", "", false)
for _,v in ReplicatedStorage:GetChildren() do
if v:IsA("UnreliableRemoteEvent") then
print(v:GetFullName())
v.OnClientEvent:Connect(function(...)
print(v:GetFullName())
if type(_G._clientEffects[v.Name]) == "table" then
_G._clientEffects[v.Name][...]()
return
end
_G._clientEffects[v.Name](...)
end)
end
end
This is actually worse. It’s better to funnel calls through one remote that way Roblox batches the calls together. This is what most networking libraries do alongside other optimizations. Additionally, don’t use _G in your scripts as it is very slow.
If you need help finding a networking library I personally recommend Red 2.0, it’s what I use and I find it super efficient yet intuitive and easy to use.
Also I’m not sure if bandwidth usage is directly correlated to your ping rather than your connection to the server, make sure you’re looking at the correct network sending/receiving bars when considering your bandwidth usage. That aside, if you are experiencing high bandwidth usage it could be due to unnecessary calls you’re making in your code or an oversight.
local net = Instance.new("UnreliableRemoteEvent")
net.Name = "EffectSignal"
net.Parent = game.ReplicatedStorage
_G._clientEffects = {}
net.OnClientEvent:Connect(function(effect, ...)
local fx = _G._clientEffects[effect]
if type(fx) == "table" then
fx[1](...)
else
fx(...)
end
end)
function sendEffect(effect, ...)
net:FireAllClients(effect, ...)
end
sendEffect("a4a_doMessage", "Hi", "", false)
4000ms ping sounds like you’re flooding the network; UnreliableRemoteEvent uses bandwidth and firing many per second can overload Roblox’s system causing drops and backlog.
It’s very bad practice, the more remote calls the more overhead you need, and the more overhead, the more data you need to send over to client through network
What you should do is use only numbers to communicate between server and client, and compress them into the buffers, also you should send only neccesary data and don’t use _G as it’s unoptimized, modules are better solution to this
I’ve been thinking into getting into Buffer lately but it requires types and you can’t pass nil arguments and sometimes events would just “cancel”.
Though on ping issue, only I seem to be in issue, other testers don’t seem to run into any lag.
local a = {}
local g = Instance.new("UnreliableRemoteEvent", game:GetService("ReplicatedStorage"))
for _,v in {"a4a_d", "a4a_c", "a4a_p"} do
a[v] = {}
local n = a[v]
function n:FireAllClients(...)
g:FireAllClients(v, ...)
end
function n:FireClient(Player:Player ,...)
g:FireClient(Player, ...)
end
end
_G._connectNetworks = a
a.a4a_d:FireAllClients("test")
One of the key takeaways is that _G is less performant than other alternatives, measured to be about 2.4x slower (source is icy_oasis, 2023). But beyond performance, _G introduces tighter coupling and hidden dependencies between scripts, which can make your code harder to debug and scale over time.
Modules are now the industry standard because they offer better structure, encapsulation, and tools support like autocomplete and IntelliSense.
In your case, you could simply turn this into a ModuleScript that returns the a table, then require it wherever it’s needed instead of relying on _G.
If you intend for all events to have the first argument as the event name (v), both methods must follow that pattern.
local a = {}
local g = Instance.new("UnreliableRemoteEvent", game:GetService("ReplicatedStorage"))
for _,v in {"a4a_d", "a4a_c", "a4a_p"} do
a[v] = {}
local n = a[v]
function n:FireAllClients(...)
g:FireAllClients(v, ...)
end
function n:FireClient(p, ...)
g:FireClient(p, v, ...)
end
end
_G._connectNetworks = a
a.a4a_d:FireAllClients("test")
game:GetService("ReplicatedStorage"):WaitForChild("UnreliableRemoteEvent").OnClientEvent:Connect(function(...)
print(...)
end)
Now both .FireAllClients() and .FireClient() send v as the first parameter, keeping the event parsing logic consistent on the client.
I showed you a way to make your _G global a module. Considering your original issue and the fact it persists, have you tried or at least tested that method?
The last script was following your script. This isn’t how I like to do things myself. I use a more top down method when programing like this…
local rs = game:GetService("ReplicatedStorage")
local g = Instance.new("UnreliableRemoteEvent", rs)
local a = {}
for _, v in {"a4a_d", "a4a_c", "a4a_p"} do
a[v] = {}
local n = a[v]
function n:FireAllClients(...)
g:FireAllClients(v, ...)
end
function n:FireClient(p, ...)
g:FireClient(p, v, ...)
end
end
_G._connectNetworks = a
a.a4a_d:FireAllClients("test")
g.OnClientEvent:Connect(function(...)
print(...)
end)
Things are defined once and used accordingly. I feel it’s more efficient, less error-prone, and less wordy for a cleaner-looking script overall.
The reason I intentionally avoided modules is because I thought it would create a new remote everytime it is called, but that wasn’t the case, so I’m just gonna use them.