I’m looking for a review on my API module, which is part of a framework I’m developing. For purposes of this module, an API is just defined as a folder full of remote/bindable events/functions (hereinafter “mediators” because I don’t want to type that out every time) all used by one system.
This module intends to simplify handling mediators. It works by using metatables to abstract methods and, in result, cut out a lot of the typing for the scripter and get the script closer to plain english.
Knowing my luck, it’s not unlikely that I’ve failed to take into account some serious perfomance issue or something, so I am inviting other scripters to have a look at the system’s guts (under “Framework Code”) and tell me what I’ve done wrong.
Vanilla LuaU vs. My Framework Comparison
These two bits of code perform the exact same function:
-- Vanilla LuaU
local API = SomePath
API.OnSomeEvent.OnServerEvent:Connect(function(Player)
API.OnOtherEvent:FireClient(Player)
end)
API.SomeFunction.OnServerInvoke = function(Player)
return SomeBindableFunction:Invoke(Blah)
end
-- Avid API Module
local API = APIsModule.SomeAPIName
API.OnSomeEvent(function(Player) -- Using _call metamethod
API.OnOtherEvent.Fire(Player) -- No need to specify client or server, handles automatically.
end)
API.SomeFunction.Hook(function(Player)
return API.SomeBindableFunction(Blah)
end
Framework Code
--[[
Avid API Module
kiloe2 11/19/2022
MEDIATOR (n.) - BindableEvent, BindableFunction, RemoteEvent, RemoteFunction.
Term exists solely for the purposes of variable naming.
]]--
-- Variables
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local ReplicatedAPIs = ReplicatedStorage.APIs
local ServerAPIs
local IsServer = RunService:IsServer()
local MediatorMetatables = {}
if IsServer then ServerAPIs = ServerStorage.APIs end
-- Mediator metatables
MediatorMetatables.BindableEvent = {
__call = function(Mediator, Callback) -- Mediator will be a table with the mediator inside of it.
return Mediator[1].Event:Connect(Callback)
end,
__index = function(Mediator, Index)
assert(Index == "Fire", "\""..Index.."\" is not a method of mediator \""..Mediator[1].Name.."\"!")
return function(...)
Mediator[1]:Fire(...)
end
end
}
MediatorMetatables.BindableFunction = {
__call = function(Mediator, ...)
return Mediator[1]:Invoke(...)
end,
__index = function(Mediator, Index)
assert(Index == "Hook", "\""..Index.."\" is not a method of mediator \""..Mediator[1].Name.."\"!")
return function(Callback)
Mediator[1].OnInvoke = Callback
end
end
}
if IsServer then
MediatorMetatables.RemoteEvent = {
__call = function(Mediator, Callback)
return Mediator[1].OnServerEvent:Connect(Callback)
end,
__index = function(Mediator, Index)
assert(Index == "Fire", "\""..Index.."\" is not a method of mediator \""..Mediator[1].Name.."\"!")
return function(...)
Mediator[1]:FireClient(...)
end
end,
}
MediatorMetatables.RemoteFunction = {
__call = function(Mediator, ...)
error("Woah! You tried to invoke the client with "..Mediator[1].Name.."\". Restructure your code, please!")
end,
__index = function(Mediator, Index)
assert(Index == "Hook", "\""..Index.."\" is not a method of mediator \""..Mediator[1].Name.."\"!")
return function(Callback)
Mediator[1].OnServerInvoke = Callback
end
end
}
else
MediatorMetatables.RemoteEvent = {
__call = function(Mediator, Callback)
return Mediator[1].OnClientEvent:Connect(Callback)
end,
__index = function(Mediator, Index)
assert(Index == "Fire", "\""..Index.."\" is not a method of mediator \""..Mediator[1].Name.."\"!")
return function(...)
Mediator[1]:FireServer(...)
end
end,
}
MediatorMetatables.RemoteFunction = {
__call = function(Mediator, ...)
return Mediator[1]:InvokeServer(...)
end,
__index = function(Mediator, Index)
assert(Index == "Hook", "\""..Index.."\" is not a method of mediator \""..Mediator[1].Name.."\"!")
error("Woah! You tried to hook on the client with "..Mediator[1].Name.."\". Restructure your code, please!")
end
}
end
-- Functions
local function GetAPI(APIFolder, Name) -- APIFolder will be a table because GetAPI is a metamethod.
local API = APIFolder[1]:FindFirstChild(Name)
assert(API, "API \""..Name.."\" not found!")
return setmetatable({}, {__index = function(Table, Index)
local Mediator = API:FindFirstChild(Index)
assert(Mediator, "Mediator \""..Index.."\" does not exist under API \""..Name.."\"!")
local Metatable = MediatorMetatables[Mediator.ClassName]
assert(Metatable, "Mediator \""..Index.."\" is a "..Mediator.ClassName.." and not a valid mediator!")
return setmetatable({Mediator}, Metatable)
end})
end
-- Module
return {
Replicated = setmetatable({ReplicatedAPIs}, {__index = GetAPI}),
Server = setmetatable({ServerAPIs}, {__index = GetAPI})
}