diff --git a/lib/ClientEvent.luau b/lib/ClientEvent.luau deleted file mode 100644 index 1e61888..0000000 --- a/lib/ClientEvent.luau +++ /dev/null @@ -1,118 +0,0 @@ -local RunService = game:GetService("RunService") -local ReplicatedStorage = game:GetService("ReplicatedStorage") - -local Future = require(script.Parent.Parent.Future) -local Spawn = require(script.Parent.Parent.Spawn) - -local Identifier = require(script.Parent.Identifier) - -local Remote = ReplicatedStorage:WaitForChild("RedEvent") :: RemoteEvent -local ClientEvent = {} - -type Data = { [number]: any } -type EventMap = { [string]: { Data } } - -ClientEvent.ListenerMap = {} :: { [string]: (...any) -> () } -ClientEvent.OutgoingMap = {} :: { Fire: EventMap?, Call: EventMap? } -ClientEvent.ActiveCallMap = {} :: { [string]: thread } - -function ClientEvent.Start() - debug.setmemorycategory("Red.ClientEvent.Start") - - Remote.OnClientEvent:Connect(function(FireSection, CallSection) - debug.profilebegin("Red.ClientEvent.Start") - - if FireSection then - debug.profilebegin("Red.ClientEvent.Start.Fire") - - for EventId, CallList in FireSection do - local Callback = ClientEvent.ListenerMap[EventId] - - if Callback then - for _, Call in CallList do - Spawn(Callback, unpack(Call)) - end - end - end - - debug.profileend() - end - - if CallSection then - debug.profilebegin("Red.ClientEvent.Start.Call") - - for _, Call in CallSection do - local CallId = table.remove(Call, 1) :: any - - if ClientEvent.ActiveCallMap[CallId] then - coroutine.resume(ClientEvent.ActiveCallMap[CallId], unpack(Call)) - ClientEvent.ActiveCallMap[CallId] = nil - end - end - - debug.profileend() - end - - debug.profileend() - end) - - local Time = 0 - - RunService.Heartbeat:Connect(function(DeltaTime) - Time += DeltaTime - - if Time < 0.014 then - return - end - - Time = 0 - - debug.profilebegin("Red.ClientEvent.Outgoing") - - if ClientEvent.OutgoingMap.Call or ClientEvent.OutgoingMap.Fire then - Remote:FireServer(ClientEvent.OutgoingMap.Fire or {}, ClientEvent.OutgoingMap.Call or {}) - - table.clear(ClientEvent.OutgoingMap) - end - - debug.profileend() - end) -end - -function ClientEvent.Fire(EventId: string, Args: { any }) - if not ClientEvent.OutgoingMap.Fire then - ClientEvent.OutgoingMap.Fire = {} - end - - if not (ClientEvent.OutgoingMap.Fire :: any)[EventId] then - (ClientEvent.OutgoingMap.Fire :: any)[EventId] = {} - end - - table.insert((ClientEvent.OutgoingMap.Fire :: any)[EventId], Args) -end - -function ClientEvent.Call(EventId: string, Args: { any }) - return Future.new(function(EventId: string, Args: { any }) - local CallId = Identifier.Unique() - - if not ClientEvent.OutgoingMap.Call then - ClientEvent.OutgoingMap.Call = {} - end - - if not (ClientEvent.OutgoingMap.Call :: any)[EventId] then - (ClientEvent.OutgoingMap.Call :: any)[EventId] = {} - end - - table.insert(Args, 1, CallId) - table.insert((ClientEvent.OutgoingMap.Call :: any)[EventId], Args) - - ClientEvent.ActiveCallMap[CallId] = coroutine.running() - return coroutine.yield() - end, EventId, Args) -end - -function ClientEvent.Listen(EventId: string, Callback: (...any) -> ()) - ClientEvent.ListenerMap[EventId] = Callback -end - -return ClientEvent diff --git a/lib/Net/Client.luau b/lib/Net/Client.luau new file mode 100644 index 0000000..5d22c33 --- /dev/null +++ b/lib/Net/Client.luau @@ -0,0 +1,123 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local RunService = game:GetService("RunService") + +local ReliableRemote = ReplicatedStorage:WaitForChild("ReliableRedEvent") :: RemoteEvent +local UnreliableRemote = ReplicatedStorage:WaitForChild("UnreliableRedEvent") :: RemoteEvent + +local ListenerMap: { [string]: ({ any }) -> () } = {} +local CallMap: { [string]: thread } = {} +local Outgoing: { + Reliable: { [string]: { { any } } }, + Unreliable: { [string]: { { any } } }, + Call: { [string]: { { any } } }, +} = + { + Reliable = {}, + Unreliable = {}, + Call = {}, + } + +local function SendReliableEvent(EventId: string, CallList: { any }) + if not Outgoing.Reliable[EventId] then + Outgoing.Reliable[EventId] = {} + end + + table.insert(Outgoing.Reliable[EventId], CallList) +end + +local function SendUnreliableEvent(EventId: string, CallList: { any }) + if not Outgoing.Unreliable[EventId] then + Outgoing.Unreliable[EventId] = {} + end + + table.insert(Outgoing.Unreliable[EventId], CallList) +end + +local function SetListener(EventId: string, Callback: ({ any }) -> ()) + ListenerMap[EventId] = Callback +end + +local function CallAsync(EventId: string, CallList: { any }): { any } + if not Outgoing.Call[EventId] then + Outgoing.Call[EventId] = {} + end + + table.insert(Outgoing.Call[EventId], CallList) + CallMap[CallList[1]] = coroutine.running() + + return coroutine.yield() +end + +local function Start() + ReliableRemote.OnClientEvent:Connect(function(IncomingFireSection, IncomingCallSection) + if IncomingFireSection then + for EventId, CallList in IncomingFireSection do + local Listener = ListenerMap[EventId] + + if Listener then + for _, Call in CallList do + Listener(Call) + end + end + end + end + + if IncomingCallSection then + for CallId, CallList in IncomingCallSection do + local CallThread = CallMap[CallId] + + if CallThread then + coroutine.resume(CallThread, table.unpack(CallList)) + end + end + end + end) + + UnreliableRemote.OnClientEvent:Connect(function(IncomingFireSection, IncomingCallSection) + if IncomingFireSection then + for EventId, CallList in IncomingFireSection do + local Listener = ListenerMap[EventId] + + if Listener then + for _, Call in CallList do + Listener(Call) + end + end + end + end + + if IncomingCallSection then + for CallId, CallList in IncomingCallSection do + local CallThread = CallMap[CallId] + + if CallThread then + coroutine.resume(CallThread, table.unpack(CallList)) + CallMap[CallId] = nil + end + end + end + end) + + RunService.Heartbeat:Connect(function() + if next(Outgoing.Reliable) or next(Outgoing.Call) then + ReliableRemote:FireServer(Outgoing.Reliable, Outgoing.Call) + + Outgoing.Reliable = {} + Outgoing.Call = {} + end + + if next(Outgoing.Unreliable) then + UnreliableRemote:FireServer(Outgoing.Unreliable) + + Outgoing.Unreliable = {} + end + end) +end + +return { + SendReliableEvent = SendReliableEvent, + SendUnreliableEvent = SendUnreliableEvent, + SetListener = SetListener, + CallAsync = CallAsync, + Start = Start, +} diff --git a/lib/Net/Server.luau b/lib/Net/Server.luau new file mode 100644 index 0000000..d87fa7c --- /dev/null +++ b/lib/Net/Server.luau @@ -0,0 +1,144 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local RunService = game:GetService("RunService") + +local Guard = require(script.Parent.Parent.Parent.Guard) + +local ReliableRemote = ReplicatedStorage:WaitForChild("ReliableRedEvent") :: RemoteEvent +local UnreliableRemote = ReplicatedStorage:WaitForChild("UnreliableRedEvent") :: RemoteEvent + +local FireSectionCheck = Guard.Check(Guard.Map(Guard.String, Guard.List(Guard.Any))) +local CallSectionCheck = Guard.Check(Guard.Map(Guard.String, Guard.List(Guard.Any))) + +local ListenerMap: { [string]: (Player, { any }) -> () } = {} +local OutgoingMap: { + [Player]: { + Reliable: { [string]: { { any } } }, + Unreliable: { [string]: { { any } } }, + CallReturn: { [string]: { any } }, + }, +} = + {} + +local function SendReliableEvent(Player: Player, EventId: string, Args: { any }) + if not OutgoingMap[Player] then + OutgoingMap[Player] = { + Reliable = {}, + Unreliable = {}, + CallReturn = {}, + } + end + + if not OutgoingMap[Player].Reliable[EventId] then + OutgoingMap[Player].Reliable[EventId] = {} + end + + table.insert(OutgoingMap[Player].Reliable[EventId], Args) +end + +local function SendUnreliableEvent(Player: Player, EventId: string, Args: { any }) + if not OutgoingMap[Player] then + OutgoingMap[Player] = { + Reliable = {}, + Unreliable = {}, + CallReturn = {}, + } + end + + if not OutgoingMap[Player].Unreliable[EventId] then + OutgoingMap[Player].Unreliable[EventId] = {} + end + + table.insert(OutgoingMap[Player].Unreliable[EventId], Args) +end + +local function SetListener(EventId: string, Callback: (Player, { any }) -> ()) + ListenerMap[EventId] = Callback +end + +local function SendCallReturn(Player: Player, CallId: string, Args: { any }) + if not OutgoingMap[Player] then + OutgoingMap[Player] = { + Reliable = {}, + Unreliable = {}, + CallReturn = {}, + } + end + + OutgoingMap[Player].CallReturn[CallId] = Args +end + +local function Start() + ReliableRemote.OnServerEvent:Connect(function(Player, IncomingFireSection, IncomingCallSection) + local FireSectionValid, FireSection = FireSectionCheck(IncomingFireSection) + + if FireSectionValid then + for EventId, CallList in FireSection do + local Callback = ListenerMap[EventId] + + if Callback then + for _, Call in CallList do + Callback(Player, Call) + end + end + end + end + + local CallSectionValid, CallSection = CallSectionCheck(IncomingCallSection) + + if CallSectionValid then + for EventId, CallList in CallSection do + local Callback = ListenerMap[EventId] + + if Callback then + for _, Call in CallList do + Callback(Player, Call) + end + else + for _, Call in CallList do + local CallId = Call[1] + + SendCallReturn(Player, CallId, { false, "Event has no listener." }) + end + end + end + end + end) + + UnreliableRemote.OnServerEvent:Connect(function(Player, IncomingFireSection, IncomingCallSection) + local FireSectionValid, FireSection = FireSectionCheck(IncomingFireSection) + + if FireSectionValid then + for EventId, CallList in FireSection do + local Callback = ListenerMap[EventId] + + if Callback then + for _, Call in CallList do + Callback(Player, Call) + end + end + end + end + end) + + RunService.Heartbeat:Connect(function() + for Player, Outgoing in OutgoingMap do + if next(Outgoing.Reliable) or next(Outgoing.CallReturn) then + ReliableRemote:FireClient(Player, Outgoing.Reliable, Outgoing.CallReturn) + end + + if next(Outgoing.Unreliable) then + UnreliableRemote:FireClient(Player, Outgoing.Unreliable) + end + + OutgoingMap[Player] = nil + end + end) +end + +return { + SendReliableEvent = SendReliableEvent, + SendUnreliableEvent = SendUnreliableEvent, + SetListener = SetListener, + SendCallReturn = SendCallReturn, + Start = Start, +} diff --git a/lib/Net/init.luau b/lib/Net/init.luau new file mode 100644 index 0000000..db15b00 --- /dev/null +++ b/lib/Net/init.luau @@ -0,0 +1,4 @@ +return { + Server = require(script.Server), + Client = require(script.Client), +} diff --git a/lib/ServerEvent.luau b/lib/ServerEvent.luau deleted file mode 100644 index 0e61fb8..0000000 --- a/lib/ServerEvent.luau +++ /dev/null @@ -1,126 +0,0 @@ -local RunService = game:GetService("RunService") -local ReplicatedStorage = game:GetService("ReplicatedStorage") - -local Spawn = require(script.Parent.Parent.Spawn) -local Guard = require(script.Parent.Parent.Guard) - -local Remote = ReplicatedStorage:WaitForChild("RedEvent") :: RemoteEvent -local ServerEvent = {} - -type Data = { [number]: any } -type EventMap = { [string]: { Data } } - -ServerEvent.ListenerMap = {} :: { [string]: (Player, ...any) -> () } -ServerEvent.OutgoingMap = {} :: { [Player]: { Fire: EventMap?, Call: { Data }? } } - -local FireSectionCheck = Guard.Check(Guard.Map(Guard.String, Guard.List(Guard.Any))) -local CallSectionCheck = Guard.Check(Guard.Map(Guard.String, Guard.List(Guard.Any))) - -function ServerEvent.Start() - debug.setmemorycategory("Red.ServerEvent.Start") - - Remote.OnServerEvent:Connect(function(Player, UnknownFireSection, UnknownCallSection) - debug.profilebegin("Red.ServerEvent.Start") - - local FireSectionValid, FireSection = FireSectionCheck(UnknownFireSection) - - if FireSectionValid then - debug.profilebegin("Red.ServerEvent.Start.MultipleFire") - - for EventId, CallList in FireSection do - local Callback = ServerEvent.ListenerMap[EventId] - - if Callback then - for _, Call in CallList do - Spawn(Callback, Player, unpack(Call)) - end - end - end - - debug.profileend() - end - - local CallSectionValid, CallSection = CallSectionCheck(UnknownCallSection) - - if CallSectionValid then - debug.profilebegin("Red.ServerEvent.Start.Call") - - for EventId, CallList in CallSection do - local Callback = ServerEvent.ListenerMap[EventId] - - if Callback then - for _, Call in CallList do - Spawn(function() - local CallId = table.remove(Call, 1) :: any - local Result = { CallId, Callback(Player, unpack(Call)) } - - if not ServerEvent.OutgoingMap[Player] then - ServerEvent.OutgoingMap[Player] = {} - end - - if not ServerEvent.OutgoingMap[Player].Call then - ServerEvent.OutgoingMap[Player].Call = {} - end - - table.insert(ServerEvent.OutgoingMap[Player].Call :: any, Result) - end) - end - else - if not ServerEvent.OutgoingMap[Player] then - ServerEvent.OutgoingMap[Player] = {} - end - - if not ServerEvent.OutgoingMap[Player].Call then - ServerEvent.OutgoingMap[Player].Call = {} - end - - for _, Call in CallList do - local CallId = table.remove(Call, 1) :: any - table.insert( - ServerEvent.OutgoingMap[Player].Call :: any, - { CallId, false, "Event has no listener." } - ) - end - end - end - - debug.profileend() - end - - debug.profileend() - end) - - RunService.Heartbeat:Connect(function() - debug.profilebegin("Red.ServerEvent.Outgoing") - - for Player, Data in ServerEvent.OutgoingMap do - Remote:FireClient(Player, Data.Fire, Data.Call) - end - - table.clear(ServerEvent.OutgoingMap) - - debug.profileend() - end) -end - -function ServerEvent.Fire(Player: Player, EventId: string, Args: { any }) - if not ServerEvent.OutgoingMap[Player] then - ServerEvent.OutgoingMap[Player] = {} - end - - if not ServerEvent.OutgoingMap[Player].Fire then - ServerEvent.OutgoingMap[Player].Fire = {} - end - - if not (ServerEvent.OutgoingMap[Player].Fire :: any)[EventId] then - (ServerEvent.OutgoingMap[Player].Fire :: any)[EventId] = {} - end - - table.insert((ServerEvent.OutgoingMap[Player].Fire :: any)[EventId], Args) -end - -function ServerEvent.Listen(EventId: string, Callback: (Player, ...any) -> ...any) - ServerEvent.ListenerMap[EventId] = Callback -end - -return ServerEvent