-- Compiled with roblox-ts v3.0.0 local TS = _G[script] local _services = TS.import(script, TS.getModule(script, "@rbxts", "services")) local Players = _services.Players local ReplicatedStorage = _services.ReplicatedStorage local RunService = _services.RunService local Destroyable = TS.import(script, TS.getModule(script, "@rbxts", "destroyable").out).default local Object = TS.import(script, TS.getModule(script, "@rbxts", "object-utils")) local createSerializer = TS.import(script, TS.getModule(script, "@rbxts", "serio").out).default local repr = TS.import(script, TS.getModule(script, "@rbxts", "repr").out) local _middleware = TS.import(script, script.Parent, "middleware") local DropRequest = _middleware.DropRequest local MiddlewareProvider = _middleware.MiddlewareProvider local IS_LUNE = string.sub(_VERSION, 1, 4) == "Lune" if setLuneContext == nil then setLuneContext = function() end end setLuneContext("both") local noServerListen = "[@rbxts/tether]: Cannot listen to server message from client" local noClientListen = "[@rbxts/tether]: Cannot listen to client message from server" local metaGenerationFailed = "[@rbxts/tether]: Failed to generate message metadata - make sure you have the Flamework transformer and are using Flamework macro-friendly types in your schemas" local guardFailed = function(message, data) return `[@rbxts/tether]: Type validation guard failed for message '{message}' - check your sent data\nSent data: {repr(data)}` end local defaultMesssageEmitterOptions = { batchRemotes = true, batchRate = 1 / 24, } local sendMessage do local name = "sendMessage" local existing = ReplicatedStorage:FindFirstChild(name) local remote = (existing or Instance.new("RemoteEvent", ReplicatedStorage)) if existing == nil then remote.Name = name end sendMessage = remote end local sendUnreliableMessage do local name = "unreliableMessage" local existing = ReplicatedStorage:FindFirstChild(name) local remote = (existing or Instance.new("UnreliableRemoteEvent", ReplicatedStorage)) if existing == nil then remote.Name = name end sendUnreliableMessage = remote end local MessageEmitter do local super = Destroyable MessageEmitter = setmetatable({}, { __tostring = function() return "MessageEmitter" end, __index = super, }) MessageEmitter.__index = MessageEmitter function MessageEmitter.new(...) local self = setmetatable({}, MessageEmitter) return self:constructor(...) or self end function MessageEmitter:constructor(options) if options == nil then options = defaultMesssageEmitterOptions end super.constructor(self) self.options = options self.middleware = MiddlewareProvider.new() self.guards = {} self.serializers = {} self.clientCallbacks = {} self.clientFunctions = {} self.serverCallbacks = {} self.serverFunctions = {} self.serverQueue = {} self.clientBroadcastQueue = {} self.clientQueue = {} self.server = { on = function(message, callback) if RunService:IsClient() then error(noServerListen) end return self:on(message, callback, self.serverCallbacks) end, once = function(message, callback) if RunService:IsClient() then error(noServerListen) end return self:once(message, callback, self.serverCallbacks) end, emit = function(message, data, unreliable) if unreliable == nil then unreliable = false end if RunService:IsServer() then error("[@rbxts/tether]: Cannot emit message to server from server") end task.spawn(function() local _binding = self:runServerMiddlewares(message, data) local dropRequest = _binding[1] local newData = _binding[2] if dropRequest then return nil end local _serverQueue = self.serverQueue local _arg0 = { message, newData, unreliable } table.insert(_serverQueue, _arg0) if not self.options.batchRemotes then self:update() end end) end, invoke = TS.async(function(message, returnMessage, data, unreliable) if unreliable == nil then unreliable = false end if RunService:IsServer() then error("[@rbxts/tether]: Cannot invoke server function from server") end local _clientFunctions = self.clientFunctions local _returnMessage = returnMessage if not (_clientFunctions[_returnMessage] ~= nil) then local _clientFunctions_1 = self.clientFunctions local _returnMessage_1 = returnMessage _clientFunctions_1[_returnMessage_1] = {} end local _clientFunctions_1 = self.clientFunctions local _returnMessage_1 = returnMessage local functions = _clientFunctions_1[_returnMessage_1] local returnValue functions[function(data) returnValue = data return returnValue end] = true self.server.emit(message, data, unreliable) while returnValue == nil do RunService.Heartbeat:Wait() end return returnValue end), setCallback = function(message, returnMessage, callback) if RunService:IsClient() then error(noServerListen) end return self.server.on(message, function(player, data) local returnValue = callback(player, data) self.client.emit(player, returnMessage, returnValue) end) end, } self.client = { on = function(message, callback) if RunService:IsServer() then error(noClientListen) end return self:on(message, callback, self.clientCallbacks) end, once = function(message, callback) if RunService:IsServer() then error(noClientListen) end return self:once(message, callback, self.clientCallbacks) end, emit = function(player, message, data, unreliable) if unreliable == nil then unreliable = false end if RunService:IsClient() then error("[@rbxts/tether]: Cannot emit message to client from client") end task.spawn(function() local _binding = self:runClientMiddlewares(message, data) local dropRequest = _binding[1] local newData = _binding[2] if dropRequest then return nil end local _clientQueue = self.clientQueue local _arg0 = { player, message, newData, unreliable } table.insert(_clientQueue, _arg0) if not self.options.batchRemotes then self:update() end end) end, emitExcept = function(player, message, data, unreliable) if unreliable == nil then unreliable = false end local shouldSendTo = function(p) local _player = player local _result if typeof(_player) == "Instance" then _result = p ~= player else local _player_1 = player local _p = p _result = not (table.find(_player_1, _p) ~= nil) end return _result end local _client = self.client local _exp = Players:GetPlayers() -- ▼ ReadonlyArray.filter ▼ local _newValue = {} local _length = 0 for _k, _v in _exp do if shouldSendTo(_v, _k - 1, _exp) == true then _length += 1 _newValue[_length] = _v end end -- ▲ ReadonlyArray.filter ▲ _client.emit(_newValue, message, data, unreliable) end, emitAll = function(message, data, unreliable) if unreliable == nil then unreliable = false end if RunService:IsClient() then error("[@rbxts/tether]: Cannot emit message to all clients from client") end task.spawn(function() local _binding = self:runClientMiddlewares(message, data) local dropRequest = _binding[1] local newData = _binding[2] if dropRequest then return nil end local _clientBroadcastQueue = self.clientBroadcastQueue local _arg0 = { message, newData, unreliable } table.insert(_clientBroadcastQueue, _arg0) if not self.options.batchRemotes then self:update() end end) end, invoke = TS.async(function(message, returnMessage, player, data, unreliable) if unreliable == nil then unreliable = false end if RunService:IsClient() then error("[@rbxts/tether]: Cannot invoke client function from client") end local _serverFunctions = self.serverFunctions local _returnMessage = returnMessage if not (_serverFunctions[_returnMessage] ~= nil) then local _serverFunctions_1 = self.serverFunctions local _returnMessage_1 = returnMessage _serverFunctions_1[_returnMessage_1] = {} end local _serverFunctions_1 = self.serverFunctions local _returnMessage_1 = returnMessage local functions = _serverFunctions_1[_returnMessage_1] local returnValue functions[function(data) returnValue = data return returnValue end] = true self.client.emit(player, message, data, unreliable) while returnValue == nil do RunService.Heartbeat:Wait() end return returnValue end), setCallback = function(message, returnMessage, callback) if RunService:IsServer() then error(noClientListen) end return self.client.on(message, function(data) local returnValue = callback(data) self.server.emit(returnMessage, returnValue) end) end, } self.trash:add(function() self.clientCallbacks = {} self.serverCallbacks = {} self.clientFunctions = {} self.clientCallbacks = {} self.serializers = {} setmetatable(self, nil) end) end function MessageEmitter:create(options, meta) local emitter = MessageEmitter.new(Object.assign({}, defaultMesssageEmitterOptions, options)) if meta == nil then warn(metaGenerationFailed) return emitter:initialize() end for kind, _binding in pairs(meta) do local guard = _binding.guard local serializerMetadata = _binding.serializerMetadata local numberKind = tonumber(kind) emitter.guards[numberKind] = guard if serializerMetadata == nil then continue end emitter:addSerializer(numberKind, serializerMetadata) end return emitter:initialize() end function MessageEmitter:initialize() setLuneContext("client") if RunService:IsClient() then self.trash:add(sendMessage.OnClientEvent:Connect(function(...) local serializedPacket = { ... } return self:onRemoteFire(false, serializedPacket) end)) self.trash:add(sendUnreliableMessage.OnClientEvent:Connect(function(...) local serializedPacket = { ... } return self:onRemoteFire(false, serializedPacket) end)) end setLuneContext("server") if RunService:IsServer() then self.trash:add(sendMessage.OnServerEvent:Connect(function(player, ...) local serializedPacket = { ... } return self:onRemoteFire(true, serializedPacket, player) end)) self.trash:add(sendUnreliableMessage.OnServerEvent:Connect(function(player, ...) local serializedPacket = { ... } return self:onRemoteFire(true, serializedPacket, player) end)) end local elapsed = 0 local _binding = self.options local batchRemotes = _binding.batchRemotes local batchRate = _binding.batchRate if not batchRemotes then return self end self.trash:add(RunService.Heartbeat:Connect(function(dt) elapsed += dt if elapsed < batchRate then return nil end elapsed -= batchRate self:update() end)) return self end function MessageEmitter:update() local getPacket = function(info) return info.packet end if RunService:IsClient() then if #self.serverQueue == 0 then return nil end local _exp = self.serverQueue -- ▼ ReadonlyArray.map ▼ local _newValue = table.create(#_exp) local _callback = function(_param) local message = _param[1] local data = _param[2] local unreliable = _param[3] local packet = self:getPacket(message, data) return { packet = packet, unreliable = unreliable, } end for _k, _v in _exp do _newValue[_k] = _callback(_v, _k - 1, _exp) end -- ▲ ReadonlyArray.map ▲ local serverPacketInfos = _newValue -- ▼ ReadonlyArray.filter ▼ local _newValue_1 = {} local _callback_1 = function(info) return info.unreliable end local _length = 0 for _k, _v in serverPacketInfos do if _callback_1(_v, _k - 1, serverPacketInfos) == true then _length += 1 _newValue_1[_length] = _v end end -- ▲ ReadonlyArray.filter ▲ -- ▼ ReadonlyArray.map ▼ local _newValue_2 = table.create(#_newValue_1) for _k, _v in _newValue_1 do _newValue_2[_k] = getPacket(_v, _k - 1, _newValue_1) end -- ▲ ReadonlyArray.map ▲ local unreliableServerPackets = _newValue_2 -- ▼ ReadonlyArray.filter ▼ local _newValue_3 = {} local _callback_2 = function(info) return not info.unreliable end local _length_1 = 0 for _k, _v in serverPacketInfos do if _callback_2(_v, _k - 1, serverPacketInfos) == true then _length_1 += 1 _newValue_3[_length_1] = _v end end -- ▲ ReadonlyArray.filter ▲ -- ▼ ReadonlyArray.map ▼ local _newValue_4 = table.create(#_newValue_3) for _k, _v in _newValue_3 do _newValue_4[_k] = getPacket(_v, _k - 1, _newValue_3) end -- ▲ ReadonlyArray.map ▲ local serverPackets = _newValue_4 if not (#unreliableServerPackets == 0) then sendUnreliableMessage:FireServer(unpack(unreliableServerPackets)) end if not (#serverPackets == 0) then sendMessage:FireServer(unpack(serverPackets)) end self.serverQueue = {} return nil end local clientPackets = {} local addClientPacket = function(player, packetInfo) local _player = player local _condition = clientPackets[_player] if _condition == nil then _condition = {} end local packetList = _condition local _packetInfo = packetInfo table.insert(packetList, _packetInfo) local _player_1 = player clientPackets[_player_1] = packetList end for _, _binding in self.clientQueue do local player = _binding[1] local message = _binding[2] local data = _binding[3] local unreliable = _binding[4] local packet = self:getPacket(message, data) local info = { packet = packet, unreliable = unreliable, } if typeof(player) == "Instance" then addClientPacket(player, info) else for _1, p in player do addClientPacket(p, info) end end end if not (#self.clientBroadcastQueue == 0) then local _exp = self.clientBroadcastQueue -- ▼ ReadonlyArray.map ▼ local _newValue = table.create(#_exp) local _callback = function(_param) local message = _param[1] local data = _param[2] local unreliable = _param[3] local packet = self:getPacket(message, data) return { packet = packet, unreliable = unreliable, } end for _k, _v in _exp do _newValue[_k] = _callback(_v, _k - 1, _exp) end -- ▲ ReadonlyArray.map ▲ local clientBroadcastPackets = _newValue -- ▼ ReadonlyArray.filter ▼ local _newValue_1 = {} local _callback_1 = function(info) return info.unreliable end local _length = 0 for _k, _v in clientBroadcastPackets do if _callback_1(_v, _k - 1, clientBroadcastPackets) == true then _length += 1 _newValue_1[_length] = _v end end -- ▲ ReadonlyArray.filter ▲ -- ▼ ReadonlyArray.map ▼ local _newValue_2 = table.create(#_newValue_1) for _k, _v in _newValue_1 do _newValue_2[_k] = getPacket(_v, _k - 1, _newValue_1) end -- ▲ ReadonlyArray.map ▲ local unreliableBroadcastPackets = _newValue_2 -- ▼ ReadonlyArray.filter ▼ local _newValue_3 = {} local _callback_2 = function(info) return not info.unreliable end local _length_1 = 0 for _k, _v in clientBroadcastPackets do if _callback_2(_v, _k - 1, clientBroadcastPackets) == true then _length_1 += 1 _newValue_3[_length_1] = _v end end -- ▲ ReadonlyArray.filter ▲ -- ▼ ReadonlyArray.map ▼ local _newValue_4 = table.create(#_newValue_3) for _k, _v in _newValue_3 do _newValue_4[_k] = getPacket(_v, _k - 1, _newValue_3) end -- ▲ ReadonlyArray.map ▲ local broadcastPackets = _newValue_4 if not (#unreliableBroadcastPackets == 0) then sendUnreliableMessage:FireAllClients(unpack(unreliableBroadcastPackets)) end if not (#broadcastPackets == 0) then sendMessage:FireAllClients(unpack(broadcastPackets)) end self.clientBroadcastQueue = {} end if not (#self.clientQueue == 0) then for player, packetInfo in clientPackets do if #packetInfo == 0 then continue end if #packetInfo == 0 then continue end -- ▼ ReadonlyArray.filter ▼ local _newValue = {} local _callback = function(info) return info.unreliable end local _length = 0 for _k, _v in packetInfo do if _callback(_v, _k - 1, packetInfo) == true then _length += 1 _newValue[_length] = _v end end -- ▲ ReadonlyArray.filter ▲ -- ▼ ReadonlyArray.map ▼ local _newValue_1 = table.create(#_newValue) for _k, _v in _newValue do _newValue_1[_k] = getPacket(_v, _k - 1, _newValue) end -- ▲ ReadonlyArray.map ▲ local unreliablePackets = _newValue_1 -- ▼ ReadonlyArray.filter ▼ local _newValue_2 = {} local _callback_1 = function(info) return not info.unreliable end local _length_1 = 0 for _k, _v in packetInfo do if _callback_1(_v, _k - 1, packetInfo) == true then _length_1 += 1 _newValue_2[_length_1] = _v end end -- ▲ ReadonlyArray.filter ▲ -- ▼ ReadonlyArray.map ▼ local _newValue_3 = table.create(#_newValue_2) for _k, _v in _newValue_2 do _newValue_3[_k] = getPacket(_v, _k - 1, _newValue_2) end -- ▲ ReadonlyArray.map ▲ local packets = _newValue_3 if not (#unreliablePackets == 0) then sendUnreliableMessage:FireClient(player, unpack(unreliablePackets)) end if not (#packets == 0) then sendMessage:FireClient(player, unpack(packets)) end end self.clientQueue = {} end end function MessageEmitter:runClientMiddlewares(message, data, player) if not self:validateData(message, data) then return { true, data } end local players = player or Players:GetPlayers() local ctx = { message = message, data = data, getRawData = function() return self:getPacket(message, data) end, } for _, globalMiddleware in self.middleware:getClientGlobal() do local result = globalMiddleware(players, ctx) if not self:validateData(message, ctx.data, "Invalid data after global client middleware") then return { false, ctx.data } end if result == DropRequest then self.middleware:notifyRequestDropped(message, "Global client middleware") return { true, ctx.data } end end for _, middleware in self.middleware:getClient(message) do local result = middleware(players, ctx) if not self:validateData(message, ctx.data, "Invalid data after client middleware") then return { false, ctx.data } end if result == DropRequest then self.middleware:notifyRequestDropped(message, "Client middleware") return { true, ctx.data } end end if not self:validateData(message, ctx.data) then return { true, ctx.data } end return { false, ctx.data } end function MessageEmitter:runServerMiddlewares(message, data) if not self:validateData(message, data) then return { true, data } end local ctx = { message = message, data = data, getRawData = function() return self:getPacket(message, data) end, } for _, globalMiddleware in self.middleware:getServerGlobal() do if not self:validateData(message, ctx.data, "Invalid data after global server middleware") then return { false, ctx.data } end local result = globalMiddleware(ctx) if result == DropRequest then self.middleware:notifyRequestDropped(message, "Global server middleware") return { true, ctx.data } end end for _, middleware in self.middleware:getServer(message) do if not self:validateData(message, ctx.data, "Invalid data after server middleware") then return { false, ctx.data } end local result = middleware(ctx) if result == DropRequest then self.middleware:notifyRequestDropped(message, "Server middleware") return { true, ctx.data } end end if not self:validateData(message, ctx.data) then return { true, ctx.data } end return { false, ctx.data } end function MessageEmitter:validateData(message, data, requestDropReason) if requestDropReason == nil then requestDropReason = "Invalid data" end local _guards = self.guards local _message = message local guard = _guards[_message] local guardPassed = guard(data) if not guardPassed then warn(guardFailed(message, data)) self.middleware:notifyRequestDropped(message, requestDropReason) end return guardPassed end function MessageEmitter:onRemoteFire(isServer, serializedPackets, player) for _, packet in serializedPackets do if buffer.len(packet.messageBuf) > 1 then return warn("[@rbxts/tether]: Rejected packet because message buffer was larger than one byte") end local message = buffer.readu8(packet.messageBuf, 0) self:executeEventCallbacks(isServer, message, packet, player) self:executeFunctions(isServer, message, packet) end end function MessageEmitter:executeFunctions(isServer, message, serializedPacket) local functionsMap = if isServer then self.serverFunctions else self.clientFunctions local _message = message local functions = functionsMap[_message] if functions == nil then return nil end local packet = self:deserializeAndValidate(message, serializedPacket) for callback in functions do callback(packet) end end function MessageEmitter:executeEventCallbacks(isServer, message, serializedPacket, player) local callbacksMap = if isServer then self.serverCallbacks else self.clientCallbacks local _message = message local callbacks = callbacksMap[_message] if callbacks == nil then return nil end local packet = self:deserializeAndValidate(message, serializedPacket) for callback in callbacks do if isServer then callback(player, packet) else callback(packet) end end end function MessageEmitter:deserializeAndValidate(message, serializedPacket) local serializer = self:getSerializer(message) local _packet = serializer if _packet ~= nil then _packet = _packet.deserialize(serializedPacket) end local packet = _packet self:validateData(message, packet) return packet end function MessageEmitter:once(message, callback, callbacksMap) local destructor destructor = self:on(message, function(player, data) destructor() callback(player, data) end, callbacksMap) return destructor end function MessageEmitter:on(message, callback, callbacksMap) local _callbacksMap = callbacksMap local _message = message if not (_callbacksMap[_message] ~= nil) then local _callbacksMap_1 = callbacksMap local _message_1 = message _callbacksMap_1[_message_1] = {} end local _callbacksMap_1 = callbacksMap local _message_1 = message local callbacks = _callbacksMap_1[_message_1] local _callback = callback callbacks[_callback] = true local _callbacksMap_2 = callbacksMap local _message_2 = message _callbacksMap_2[_message_2] = callbacks return function() local _callback_1 = callback -- ▼ Set.delete ▼ local _valueExisted = callbacks[_callback_1] ~= nil callbacks[_callback_1] = nil -- ▲ Set.delete ▲ return _valueExisted end end function MessageEmitter:getPacket(message, data) local serializer = self:getSerializer(message) local messageBuf = buffer.create(1) buffer.writeu8(messageBuf, 0, message) if serializer == nil then return { messageBuf = messageBuf, buf = buffer.create(0), blobs = {}, } end local _object = { messageBuf = messageBuf, } for _k, _v in serializer.serialize(data) do _object[_k] = _v end return _object end function MessageEmitter:addSerializer(message, meta) self.serializers[message] = self:createMessageSerializer(meta) end function MessageEmitter:createMessageSerializer(meta) return createSerializer(meta) end function MessageEmitter:getSerializer(message) return self.serializers[message] end end return { MessageEmitter = MessageEmitter, }