I tried to make my own networking module for my game, and it’s going well, automated remotefunction creation is working, but the problem is that the remotes are only created after the function is called, so sometimes it can get blocked due to locking remote creation, so how can i detect what type of function it is, using __newindex, so i can call that function the moment it’s created?
If you want to check the function type, this might be a bit tedious, but you can try looking either for the self
parameter in the function’s parameter list or checking for colon notation.
local meta = {}
function meta:__newindex(key: any, value: (...any) -> (..any))
--get the function code
local source = debug.info(value, "s")
--shorten it down to avoid nested functions
source = string.sub(source, --[[however much you want to shorten it]])
--search for the colon notation in its declaration
local matchString = string.match(source, "function[%w_ ]+:[ %w_]+%([%w%a%d_, :]+%)")
--search for the dot in the declaration
local match2 = string.match(source, "function[%w_ ]+%.[ %w_]+%(self")
if matchString or match2 then
--do stuff
end
end
This will only work for functions declared that way, and it’s really tedious and I wouldn’t recommend it. If you explain more about your issue, I’ll try to think of a better solution. If you need me to explain those string patterns, also just ask.
You can try also by setting a metatable on your Network module, the __newindex
function identifies whether the added function is meant for the client or server and then instantiates the appropriate RemoteFunction
This can also be an example:
local Network = {}
setmetatable(Network, {
__newindex = function(table, key, value)
if type(value) == "function" then
local remote = Instance.new("RemoteFunction")
remote.Name = key
remote.Parent = game:GetService("ReplicatedStorage")
remote.OnServerInvoke = value
rawset(table, key, function(...) return remote:InvokeClient(...) end)
else
rawset(table, key, value)
end
end
})
Where it can be used like this:
Server Script
-- ServerScript.lua
local Network = require(game.ReplicatedStorage.NetworkModule)
Network.SendMessageToClient = function(player, message)
if not player then
error("Player not provided!")
end
print("Received message from client:", message)
return "Server received your message."
end
Local Script
-- LocalScript.lua
local Network = require(game.ReplicatedStorage.NetworkModule)
local success, response = pcall(function()
return Network.SendMessageToClient(game.Players.LocalPlayer, "Hello Server!")
end)
if success then
print(response) -- Outputs: "Server received your message."
else
warn("Failed to send message:", response)
end
Basis: The __newindex Metamethod
while i was making the module, i was using the Knit documentation as a basis on how i should do it (as its one of the only network modules i know), so it got some pretty similar features, like, client functions would be created like this
function test.client:Get()
return self.server:Get()
end
the remotes are automatically created when the .server is accessed, but as it’s stored inside a function, the .server value will only be acessed after being called, which can be after the remote creation window has ended, so i wanted my network module to instantly call any client function from the server side instantly after its creation, so that it creates the remotes instantly
btw these are how the metatables are structured so far
setmetatable(Client, {
-- TODO: make proper function initialization
__newindex = function(t, key, val)
rawset(t, key, val)
end,
})
setmetatable(Client.server, {
__newindex = function() warn("cannot explicitly create a server value!") end,
__index = function(_, key)
warn("accessing "..key)
local parentType = typeof(Service[key])
local prefix = `{string.sub(string.upper(tostring(parentType)), 1, 3)}_`
local remote : RemoteFunction = clientFetchFolder:FindFirstChild(prefix..key)
if not remote then
remote = Instance.new("RemoteFunction")
remote.Name = prefix..key
remote.Parent = clientFetchFolder
end
return runService:IsClient() and remote:InvokeServer() or Service[key]
end,
})
It would be like this
-- Tables to hold client and server functions
self.client = {}
self.server = {}
-- Metatable for client to intercept function definitions
setmetatable(self.client, {
__newindex = function(tbl, key, value)
if type(value) == "function" then
-- Determine if it's a RemoteFunction or RemoteEvent based on naming convention or additional parameters
-- For simplicity, let's assume functions ending with "Event" are RemoteEvents
local isFunction = not key:find("Event$")
-- Create Remote
local remote = createRemote(key, isFunction)
-- Define the client-side function
tbl[key] = function(...)
if isFunction then
return remote:InvokeServer(...)
else
remote:FireServer(...)
end
end
-- Define the server-side handler
if isFunction then
self.server[key] = function(player, ...)
return value(player, ...)
end
remote.OnServerInvoke = self.server[key]
else
self.server[key] = function(player, ...)
value(player, ...)
end
remote.OnServerEvent:Connect(self.server[key])
end
else
error("Only functions can be assigned to client.")
end
end,
})
-- Prevent explicit creation of server values
setmetatable(self.server, {
__newindex = function(tbl, key, value)
error("Cannot explicitly create a server value! Define functions on 'client' instead.")
end,
__index = function(tbl, key)
-- Access to server functions should be direct
return self.server[key]
end,
})
return self
Then you can use the module or integrate it as is
Server
-- ServerManager.lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local NetworkModule = require(ReplicatedStorage:WaitForChild("NetworkModule"))
local network = NetworkModule.new()
-- Define a RemoteFunction named "GetData"
function network.client:GetData(player, dataId)
print(player.Name .. " requested data with ID:", dataId)
-- Fetch and return data
local data = "Data for ID: " .. tostring(dataId)
return data
end
-- Define a RemoteEvent named "PlayerJoinedEvent"
function network.client:PlayerJoinedEvent(player, joinTime)
print(player.Name .. " joined at:", joinTime)
-- Handle player join event
end
print("Network remotes have been set up.")
Client
-- NetworkClient.lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local NetworkModule = require(ReplicatedStorage:WaitForChild("NetworkModule"))
local network = NetworkModule.new()
-- Function to request data from the server
local function requestData(dataId)
local data = network.client:GetData(dataId)
print("Received from server:", data)
end
-- Function to notify the server when the player joins
local function notifyPlayerJoined()
local joinTime = os.time()
network.client:PlayerJoinedEvent(joinTime)
print("Notified server of player join at:", joinTime)
end
-- Example usage
requestData(42)
notifyPlayerJoined()
Full Module Example
-- NetworkModule.lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local NetworkModule = {}
NetworkModule.__index = NetworkModule
-- Create folders to store remotes
local RemoteFolder = Instance.new("Folder")
RemoteFolder.Name = "NetworkRemotes"
RemoteFolder.Parent = ReplicatedStorage
-- Utility function to create RemoteFunctions or RemoteEvents
local function createRemote(name, isFunction)
if isFunction then
local remote = Instance.new("RemoteFunction")
remote.Name = name
remote.Parent = RemoteFolder
return remote
else
local remote = Instance.new("RemoteEvent")
remote.Name = name
remote.Parent = RemoteFolder
return remote
end
end
function NetworkModule.new()
local self = setmetatable({}, NetworkModule)
-- Tables to hold client and server functions
self.client = {}
self.server = {}
-- Metatable for client to intercept function definitions
setmetatable(self.client, {
__newindex = function(tbl, key, value)
if type(value) == "function" then
-- Determine if it's a RemoteFunction or RemoteEvent based on naming convention or additional parameters
-- For simplicity, let's assume functions ending with "Event" are RemoteEvents
local isFunction = not key:find("Event$")
-- Create Remote
local remote = createRemote(key, isFunction)
-- Define the client-side function
tbl[key] = function(...)
if isFunction then
return remote:InvokeServer(...)
else
remote:FireServer(...)
end
end
-- Define the server-side handler
if isFunction then
self.server[key] = function(player, ...)
return value(player, ...)
end
remote.OnServerInvoke = self.server[key]
else
self.server[key] = function(player, ...)
value(player, ...)
end
remote.OnServerEvent:Connect(self.server[key])
end
else
error("Only functions can be assigned to client.")
end
end,
})
-- Prevent explicit creation of server values
setmetatable(self.server, {
__newindex = function(tbl, key, value)
error("Cannot explicitly create a server value! Define functions on 'client' instead.")
end,
__index = function(tbl, key)
-- Access to server functions should be direct
return self.server[key]
end,
})
return self
end
return NetworkModule
i want to use it to detect if i should pass the table or not, aka mimic the colon behavior, like this
setmetatable(Service.client, {
__newindex = function(t, key, val)
if typeof(val) ~= "function" then warn("only functions can be created on the client table!") return end
rawset(t, key, val)
local remote = Instance.new("RemoteFunction")
remote.Name = key
remote.Parent = fetchStorage
local isColon = true --placeholder
remote.OnServerInvoke = function(...)
if isColon then
return val(t, ...)
else
return val(...)
end
end
end,
})