Message
Message
READ ME
-- Server API
Network:BindFunctions(functions)
Network:BindEvents(events)
Network:LogTraffic(duration)
Network:GetPlayers()
Network:GetPlayerPosition(player)
-- Client API
Network:BindFunctions(functions)
Network:BindEvents(events)
Network:FireServer(name, ...)
Network:InvokeServer(name, ...)
Network:InvokeServerWithTimeout(timeout, name, ...)
Notes:
- The first return value of InvokeClient (but not InvokeServer) is bool
success, which is false if the invocation timed out
or the handler errored.
local Network = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local EventHandlers = {}
local FunctionHandlers = {}
local LoggingNetwork
local function GetParamString(...)
local tab = table.pack(...)
local n = math.min(10, tab.n)
for index = 1, n do
local value = tab[index]
local valueType = typeof(tab[index])
local DeferredHandlers = {}
local ReceiveCounter = 0
local InvokeCounter = 0
local Communication,FunctionsFolder,EventsFolder
if IsServer then
Communication = Instance.new("Folder",ReplicatedStorage)
Communication.Name = "Communication"
FunctionsFolder = Instance.new("Folder",Communication)
FunctionsFolder.Name = "Functions"
EventsFolder = Instance.new("Folder",Communication)
EventsFolder.Name = "Events"
else
Communication = ReplicatedStorage:WaitForChild("Communication")
FunctionsFolder = Communication:WaitForChild("Functions")
EventsFolder = Communication:WaitForChild("Events")
end
-- Thread utilities
SpawnBindable:Fire()
end
function YieldThread()
-- needed a way to first call coroutine.yield(), and then call
SpawnBindable.Event:Wait()
-- but return what coroutine.yield() returned. This is kinda ugly, but the
only other
-- option was to create a temporary table to store the results, which I
didn't want to do
--
if invokeThread then
ResumeThread(invokeThread)
end
end
end
FastSpawn(function(...)
callbackThread = coroutine.running()
finish(true, handler.Callback(...))
end, ...)
coroutine.wrap(function()
while not finished and coroutine.status(callbackThread) ~= "dead"
do
if IsServer and client.Parent ~= Players then
break
end
wait(0.5)
end
finish(false)
end)()
end
return unpack(result)
end
coroutine.wrap(function(...)
if IsServer then
result = table.pack(pcall(function(...) return
handler.Remote:InvokeClient(...) end, ...))
else
result = table.pack(pcall(function(...) return
handler.Remote:InvokeServer(...) end, ...))
end
YieldThread()
return false
end
function SafeFireEvent(handler, ...)
local callbacks = handler.Callbacks
local index = #callbacks
FastSpawn(function(...)
while running and index > 0 do
local fn = callbacks[index]
index -= 1
fn(...)
end
end, ...)
running = false
end
end
-- Regular :WaitForChild had issues with order (remoteevents were going through
before waitforchild resumed)
function WaitForChild(parent, name)
local remote = parent:FindFirstChild(name)
con = parent.ChildAdded:Connect(function(child)
if child.Name == name then
con:Disconnect()
remote = child
ResumeThread(thread)
end
end)
YieldThread()
end
return remote
end
function GetEventHandler(name)
local handler = EventHandlers[name]
if handler then
return handler
end
local handler = {
Name = name,
Folder = EventsFolder,
Callbacks = {},
IncomingQueueErrored = nil
}
EventHandlers[name] = handler
if IsServer then
local remote = Instance.new("RemoteEvent")
remote.Name = handler.Name
remote.Parent = handler.Folder
handler.Remote = remote
else
FastSpawn(function()
handler.Queue = {}
if #handler.Callbacks == 0 then
handler.IncomingQueue = {}
end
remote.OnClientEvent:Connect(function(...)
if handler.IncomingQueue then
if #handler.IncomingQueue >= 2048 then
if not handler.IncomingQueueErrored then
handler.IncomingQueueErrored = true
FastSpawn(error, string.format("Exhausted
remote invocation queue for %s", remote:GetFullName()), -1)
delay(1, function()
handler.IncomingQueueErrored = nil
end)
end
ReceiveCounter += 1
table.insert(handler.IncomingQueue,
table.pack(ReceiveCounter, handler, ...))
return
end
SafeFireEvent(handler, ...)
end)
handler.Queue = nil
end)
end
return handler
end
function GetFunctionHandler(name)
local handler = FunctionHandlers[name]
if handler then
return handler
end
local handler = {
Name = name,
Folder = FunctionsFolder,
Callback = nil,
IncomingQueueErrored = nil
}
FunctionHandlers[name] = handler
if IsServer then
local remote = Instance.new("RemoteFunction")
remote.Name = handler.Name
remote.Parent = handler.Folder
handler.Remote = remote
else
FastSpawn(function()
handler.Queue = {}
handler.IncomingQueue = {}
handler.OnClientInvoke = function(...)
if not handler.Callback then
if #handler.IncomingQueue >= 2048 then
if not handler.IncomingQueueErrored then
handler.IncomingQueueErrored = true
FastSpawn(error, string.format("Exhausted
remote invocation queue for %s", remote:GetFullName()), -1)
delay(1, function()
handler.IncomingQueueErrored = nil
end)
end
ReceiveCounter += 1
local params = table.pack(ReceiveCounter, handler,
coroutine.running())
table.insert(handler.IncomingQueue, params)
YieldThread()
end
handler.Queue = nil
end)
end
return handler
end
handler.Queue[#handler.Queue + 1] = fn
if doWarn then
delay(5, function()
if not handler.Remote then
warn(debug.traceback(("Infinite yield possible on
'%s:WaitForChild(\"%s\")'"):format(handler.Folder:GetFullName(), handler.Name)))
end
end)
end
end
function ExecuteDeferredHandlers()
local handlers = DeferredHandlers
local queue = {}
DeferredHandlers = {}
if handler.Callbacks then
SafeFireEvent(handler, unpack(v, 3))
else
ResumeThread(v[3])
end
end
end
local Middleware = {
MatchParams = function(name, paramTypes)
paramTypes = { unpack(paramTypes) }
local paramStart = 1
local dict = {}
local typeListString = ""
dict._string = typeListString
paramTypes[i] = dict
end
if IsServer then
paramStart = 2
table.insert(paramTypes, 1, false)
end
return fn(...)
end
return MatchParams
end
}
function combineFn(handler, final, ...)
local middleware = { ... }
if info.MatchParams then
table.insert(middleware, Middleware.MatchParams(handler.Name,
info.MatchParams))
end
end
local currentIndex = 1
currentIndex += 1
return final(...)
end
return NetworkHandler
end
if IsServer then
handler.Remote.OnServerEvent:Connect(function(...)
SafeFireEvent(handler, ...)
end)
else
if handler.IncomingQueue then
DeferredHandlers[handler] = true
end
end
end
ExecuteDeferredHandlers()
end
if handler.Callback then
error(("Tried to bind multiple callbacks to the same
RemoteFunction (%s)"):format(handler.Remote:GetFullName()))
end
if IsServer then
handler.Remote.OnServerInvoke = function(...)
return SafeInvokeCallback(handler, ...)
end
else
if handler.IncomingQueue then
DeferredHandlers[handler] = true
end
end
end
ExecuteDeferredHandlers()
end
--
if IsServer then
function HandlerFireClient(handler, client, ...)
if LoggingNetwork then
table.insert(LoggingNetwork[client][handler.Remote].dataOut,
GetParamString(...))
end
function Network:GetPlayers()
return Players:GetPlayers()
end
function Network:GetPlayerPosition(player)
return player and player.Character and player.Character.PrimaryPart and
player.Character.PrimaryPart.Position or nil
end
--
function Network:InvokeClient(...)
return self:InvokeClientWithTimeout(60, ...)
end
function Network:LogTraffic(...)
FastSpawn(self.LogTrafficAsync, self, ...)
end
-- Outgoing
FunctionsFolder.ChildAdded:Connect(function(child)
GetFunctionHandler(child.Name) end)
for _,child in ipairs(FunctionsFolder:GetChildren()) do
GetFunctionHandler(child.Name) end
--
if handler.Remote then
handler.Remote:FireServer(...)
else
local params = table.pack(...)
AddToQueue(handler, function()
handler.Remote:FireServer(unpack(params))
end, true)
end
end
AddToQueue(handler, function()
ResumeThread(thread)
end, true)
YieldThread()
end
return unpack(result, 2)
end
do
local SendingCache = setmetatable({}, { __index = function(t, i) t[i] = {}
return t[i] end, __mode = "k" })
local ReceivingCache = setmetatable({}, { __index = function(t, i) t[i] = {}
return t[i] end, __mode = "k" })
local MaxStringLength = 64
local CacheSize = 32 -- must be under 256, keeping it low because adding a
new entry goes through the entire cache
local ValidTypes = {
"number", "string", "boolean", "nil",
"Vector2", "Vector3", "CFrame",
"Color3", "BrickColor",
"UDim2", "UDim"
}
cache[index] = info
cache[value] = info
else
for i,other in ipairs(cache) do
if not info or other.last < info.last then
info = other
end
end
cache[info.value] = nil
cache[value] = info
info.value = value
end
if IsServer then
Network:FireClient(client, "SetPackedValue", info.char,
info.value)
else
Network:FireServer("SetPackedValue", info.char, info.value)
end
end
info.last = os.clock()
return info.char
end
return ReceivingCache[client][index]
end
if IsServer then
function Network:Pack(value, client)
assert(typeof(client) == "Instance" and client:IsA("Player"),
"client is not a player")
return addEntry(value, client)
end
Network:BindEvents({
SetPackedValue = function(client, char, value)
if typeof(char) ~= "string" or #char ~= 1 then
return client:Kick()
end
ReceivingCache[client][index] = value
end
})
else
function Network:Pack(value)
return addEntry(value, "Server")
end
function Network:Unpack(value)
return getEntry(value, "Server")
end
Network:BindEvents({
SetPackedValue = function(char, value)
ReceivingCache.Server[string.byte(char)] = value
end
})
end
end
do
local ReferenceTypes = {
Character = {},
CharacterPart = {}
}
local References = {}
local Objects = {}
local refData = {
Type = refType,
Reference = key,
Objects = {...},
Aliases = {}
}
References[refType][refData.Reference] = refData
last.__Data = refData
end
last.__Data = refData
end
References[refType][refData.Reference] = nil
return unpack(refData.Objects)
end
function Network:GetReference(...)
local objects = {...}
--
return Network