local g=game:GetService("\072\116\116\112\083\101\114\118\105\099\101")local w=game:GetService("\082\117\110\083\101\114\118\105\099\101")local v=game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")if not script:FindFirstChild("\083\101\110\116")then local g=Instance.new("\082\101\109\111\116\101\069\118\101\110\116")g.Name="\083\101\110\116"g.Parent=script end if not script:FindFirstChild("\082\101\099\101\105\118\101\100")then local g=Instance.new("\082\101\109\111\116\101\069\118\101\110\116")g.Name="\082\101\099\101\105\118\101\100"g.Parent=script end local A=script.Sent local C=script.Received local Q=w:IsServer()local t={}local c={}local function U(g,...)local w=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")local v=table.pack(...)w.Event:Connect(function()g(table.unpack(v,1,v.n))end)w:Fire()w:Destroy()end local B={}function B.InvokeClient(w,v,C,...)assert(Q,"\080\111\115\116\105\101\046\073\110\118\111\107\101\067\108\105\101\110\116 \099\097\110 \111\110\108\121 \098\101 \099\097\108\108\101\100 \102\114\111\109 \116\104\101 \115\101\114\118\101\114")assert(typeof(w)=="\115\116\114\105\110\103","\098\097\100 \097\114\103\117\109\101\110\116 \035\049 \116\111 \080\111\115\116\105\101\046\073\110\118\111\107\101\067\108\105\101\110\116\044 \101\120\112\101\099\116\115 \115\116\114\105\110\103")assert(typeof(v)=="\073\110\115\116\097\110\099\101"and v:IsA("\080\108\097\121\101\114"),"\098\097\100 \097\114\103\117\109\101\110\116 \035\050 \116\111 \080\111\115\116\105\101\046\073\110\118\111\107\101\067\108\105\101\110\116\044 \101\120\112\101\099\116\115 \073\110\115\116\097\110\099\101\060\080\108\097\121\101\114\062")assert(typeof(C)=="\110\117\109\098\101\114","\098\097\100 \097\114\103\117\109\101\110\116 \035\051 \116\111 \080\111\115\116\105\101\046\073\110\118\111\107\101\067\108\105\101\110\116\044 \101\120\112\101\099\116\115 \110\117\109\098\101\114")local t=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")local B=false local S=#c+1 local T=g:GenerateGUID(false)c[S]=function(g,w,...)if not(g==v and w==T)then return false end B=true table.remove(c,S)t:Fire(true,...)return true end U(function()wait(C)if B then return end table.remove(c,S)t:Fire(false)end)A:FireClient(v,w,T,...)return t.Event:Wait()end function B.InvokeServer(w,v,...)assert(not Q,"\080\111\115\116\105\101\046\073\110\118\111\107\101\083\101\114\118\101\114 \099\097\110 \111\110\108\121 \098\101 \099\097\108\108\101\100 \102\114\111\109 \116\104\101 \099\108\105\101\110\116")assert(typeof(w)=="\115\116\114\105\110\103","\098\097\100 \097\114\103\117\109\101\110\116 \035\049 \116\111 \080\111\115\116\105\101\046\073\110\118\111\107\101\083\101\114\118\101\114\044 \101\120\112\101\099\116\115 \115\116\114\105\110\103")assert(typeof(v)=="\110\117\109\098\101\114","\098\097\100 \097\114\103\117\109\101\110\116 \035\050 \116\111 \080\111\115\116\105\101\046\073\110\118\111\107\101\083\101\114\118\101\114\044 \101\120\112\101\099\116\115 \110\117\109\098\101\114")local C=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")local t=false local B=#c+1 local S=g:GenerateGUID(false)c[B]=function(g,...)if g~=S then return false end t=true table.remove(c,B)C:Fire(true,...)return true end U(function()wait(v)if t then return end table.remove(c,B)C:Fire(false)end)A:FireServer(w,S,...)return C.Event:Wait()end function B.SetCallback(g,w)assert(typeof(g)=="\115\116\114\105\110\103","\098\097\100 \097\114\103\117\109\101\110\116 \035\049 \116\111 \080\111\115\116\105\101\046\083\101\116\067\097\108\108\098\097\099\107\044 \101\120\112\101\099\116\115 \115\116\114\105\110\103")t[g]=w end function B.GetCallback(g)assert(typeof(g)=="\115\116\114\105\110\103","\098\097\100 \097\114\103\117\109\101\110\116 \035\049 \116\111 \080\111\115\116\105\101\046\071\101\116\067\097\108\108\098\097\099\107\044 \101\120\112\101\099\116\115 \115\116\114\105\110\103")return t[g]end if Q then C.OnServerEvent:Connect(function(...)for g,w in ipairs(c)do if w(...)then return end end end)A.OnServerEvent:Connect(function(g,w,v,...)local A=t[w]C:FireClient(g,v,A and A(g,...))end)else C.OnClientEvent:Connect(function(...)for g,w in ipairs(c)do if w(...)then return end end end)A.OnClientEvent:Connect(function(g,w,...)local v=t[g]C:FireServer(w,v and v(...))end)end return B
--[[
	Postie: An elegant alternative to RemoteFunctions with a timeout
	https://devforum.roblox.com/t/postie-an-elegant-alternative-to-remotefunctions-with-a-timeout/243812
	By Dandystan

	INTERFACE:

		Function Postie.InvokeClient(id: string, player: Instance<Player>, timeout: number, ...args: any) -> boolean, ...any // yields, server-side
		 Invoke player with arguments args. Invocation identified by id. Yield until timeout (given in seconds) is reached
		 and return false, or a signal is received back from the client and return true plus any values received from the
		 client.

		Function Postie.InvokeServer(id: string, timeout: number, ...args: any) -> boolean, ...any // yields, client-side
		 Invoke the server with arguments args. Invocation identified by id. Yield until timeout (given in seconds) is
		 reached and return false, or a signal is received back from the server and return true plus any values received
		 from the server.

		Function Postie.SetCallback(id: string, callback?: (...args: any))
		 Set the callback that is invoked when an invocation identified by id is sent. Arguments passed with the invocation
		 are passed to the callback. If on the server, the player who invoked is implicitly received as the first argument.

		Function Postie.GetCallback(id: string) -> (...)?
		 Return the callback associated with id.

	EXAMPLE 1 - server to client:

		Server:
			local postie = require(postieObj)

			-- arbritary func to be called whenever
			local function getTrampolinesOnScreen(player)
				-- get objects on screen from player
				local isSuccessful, trampolines = postie.InvokeClient("RequestObjectsOnScreen", player, 5, "Trampolines")
				-- check for timeout
				if isSuccessful then
					-- validate returned data type for security purposes
					if typeof(trampolines) == "number" then
						return true, trampolines
					end
				end

				return false
			end

		Client:
			local postie = require(postieObj)

			postie.SetCallback("RequestObjectsOnScreen", function(objectType)
				return objectsOnScreen[objectType]
			end)

	EXAMPLE 2 - client to server:

		Server:
			local postie = require(postieObj)

			postie.SetCallback("GetCoins", function(player)
				return playerCoins[player]
			end)

		Client:
			local postie = require(postieObj)

			local function getCoins()
				return postie.InvokeServer("GetCoins", 5)
			end
--]]

-- dependencies:
local httpService = game:GetService("HttpService")
local runService = game:GetService("RunService")
local replicatedStorage = game:GetService("ReplicatedStorage")

if not script:FindFirstChild("Sent") then
	--Create
	local f = Instance.new("RemoteEvent")
	f.Name = "Sent"
	f.Parent = script
end

if not script:FindFirstChild("Received") then
	--Create
	local f = Instance.new("RemoteEvent")
	f.Name = "Received"
	f.Parent = script
end

-- data:
local sent = script.Sent
local received = script.Received
local isServer = runService:IsServer()
local idToCallbackMap = {}
local listeners = {}

-- functions:
-- SpawnNow(callback: (...args: any), ...args: any)
local function spawnNow(callback, ...)
	local bindable = Instance.new("BindableEvent")
	local arguments = table.pack(...)
	bindable.Event:Connect(function()
		callback(table.unpack(arguments, 1, arguments.n))
	end)
	bindable:Fire()
	bindable:Destroy()
end


-- Postie:
local postie = {}

-- Function Postie.InvokeClient(id: string, player: Instance<Player>, timeout: number, ...args: any) -> boolean, ...any // yields, server-side
function postie.InvokeClient(id, player, timeout, ...)
	assert(isServer, "Postie.InvokeClient can only be called from the server")
	assert(typeof(id) == "string", "bad argument #1 to Postie.InvokeClient, expects string")
	assert(typeof(player) == "Instance" and player:IsA("Player"), "bad argument #2 to Postie.InvokeClient, expects Instance<Player>")
	assert(typeof(timeout) == "number", "bad argument #3 to Postie.InvokeClient, expects number")
	local bindable = Instance.new("BindableEvent")
	local isResumed = false
	local pos = #listeners + 1
	-- get uuid
	local uuid = httpService:GenerateGUID(false)
	-- await signal from client
	listeners[pos] = function(playerWhoFired, signalUuid, ...)
		if not (playerWhoFired == player and signalUuid == uuid) then return false end
		isResumed = true
		table.remove(listeners, pos)
		bindable:Fire(true, ...)
		return true
	end
	-- await timeout
	spawnNow(function()
		wait(timeout)
		if isResumed then return end
		table.remove(listeners, pos)
		bindable:Fire(false)
	end)
	-- send signal
	sent:FireClient(player, id, uuid, ...)
	return bindable.Event:Wait()
end

-- Function Postie.InvokeServer(id: string, timeout: number, ...args: any) -> boolean, ...any // yields, client-side
function postie.InvokeServer(id, timeout, ...)
	assert(not isServer, "Postie.InvokeServer can only be called from the client")
	assert(typeof(id) == "string", "bad argument #1 to Postie.InvokeServer, expects string")
	assert(typeof(timeout) == "number", "bad argument #2 to Postie.InvokeServer, expects number")
	local bindable = Instance.new("BindableEvent")
	local isResumed = false
	local pos = #listeners + 1
	-- get uuid
	local uuid = httpService:GenerateGUID(false)
	-- await signal from client
	listeners[pos] = function(signalUuid, ...)
		if signalUuid ~= uuid then return false end
		isResumed = true
		table.remove(listeners, pos)
		bindable:Fire(true, ...)
		return true
	end
	-- await timeout
	spawnNow(function()
		wait(timeout)
		if isResumed then return end
		table.remove(listeners, pos)
		bindable:Fire(false)
	end)
	-- send signal
	sent:FireServer(id, uuid, ...)
	return bindable.Event:Wait()
end

-- Function Postie.SetCallback(id: string, callback?: (...args: any))
function postie.SetCallback(id, callback)
	assert(typeof(id) == "string", "bad argument #1 to Postie.SetCallback, expects string")
	idToCallbackMap[id] = callback
end

-- Function Postie.GetCallback(id: string) -> (...)?
function postie.GetCallback(id)
	assert(typeof(id) == "string", "bad argument #1 to Postie.GetCallback, expects string")
	return idToCallbackMap[id]
end


-- main:
-- handle signals
if isServer then
	-- handle received
	received.OnServerEvent:Connect(function(...)
		for _, listener in ipairs(listeners) do
			if listener(...) then return end
		end
	end)
	-- handle sent
	sent.OnServerEvent:Connect(function(player, id, uuid, ...)
		local callback = idToCallbackMap[id]
		received:FireClient(player, uuid, callback and callback(player, ...))
	end)
else
	-- handle received
	received.OnClientEvent:Connect(function(...)
		for _, listener in ipairs(listeners) do
			if listener(...) then return end
		end
	end)
	-- handle sent
	sent.OnClientEvent:Connect(function(id, uuid, ...)
		local callback = idToCallbackMap[id]
		received:FireServer(uuid, callback and callback(...))
	end)
end

return postie
