Compile Red library from corescripts/Libraries, update types to make Red FULLY TYPE SAFE!
This commit is contained in:
parent
d0c2b9d636
commit
7c4ee737f1
|
|
@ -0,0 +1,411 @@
|
|||
return function(IsServer: boolean)
|
||||
local RunService = game:GetService "RunService"
|
||||
|
||||
local RedEvent = require "../RedEvent"(IsServer)
|
||||
local Remote = RedEvent.Remote
|
||||
|
||||
local Serdes = require "./Serdes"(IsServer)
|
||||
|
||||
local Spawn = require "../Util/Spawn"
|
||||
local Promise = require "../Util/Promise"
|
||||
local Clock = require "../Util/Clock"
|
||||
|
||||
local Event = {}
|
||||
local nil_symbol = { __nil = true }
|
||||
|
||||
Event.Callbacks = {} :: { [string]: ((...any) -> ...any)? }
|
||||
Event.Outgoing = {} :: any
|
||||
|
||||
if not IsServer then
|
||||
Event.ActiveCalls = {}
|
||||
end
|
||||
|
||||
function Event.Listen()
|
||||
-- debug.setmemorycategory "Red.Listen"
|
||||
if not IsServer then
|
||||
Remote.OnClientEvent:connect(
|
||||
function(SingleFire, MultipleFire, IncomingCall)
|
||||
-- debug.profilebegin "Red.Listen.Incoming"
|
||||
|
||||
-- replace nil symbols with nil
|
||||
if SingleFire.__nil then
|
||||
SingleFire = nil
|
||||
end
|
||||
if MultipleFire.__nil then
|
||||
MultipleFire = nil
|
||||
end
|
||||
if IncomingCall.__nil then
|
||||
IncomingCall = nil
|
||||
end
|
||||
|
||||
if SingleFire then
|
||||
-- debug.profilebegin "Red.Listen.Incoming.SingleFire"
|
||||
|
||||
for EventId, Call in pairs(SingleFire) do
|
||||
local Callback = Event.Callbacks[EventId]
|
||||
|
||||
local c = 0
|
||||
repeat -- bruh
|
||||
RunService.Stepped:wait()
|
||||
Callback = Event.Callbacks[EventId]
|
||||
c += 1
|
||||
until Callback or c > 500 -- random
|
||||
|
||||
if Callback then
|
||||
if type(Call) == "table" then
|
||||
Spawn(Callback, unpack(Call))
|
||||
else
|
||||
Spawn(Callback, Call)
|
||||
end
|
||||
else
|
||||
print "[Red]: Callback not found!"
|
||||
end
|
||||
end
|
||||
|
||||
-- debug.profileend()
|
||||
end
|
||||
|
||||
if MultipleFire then
|
||||
-- debug.profilebegin "Red.Listen.Incoming.Fire"
|
||||
|
||||
for EventId, Calls in pairs(MultipleFire) do
|
||||
local Callback = Event.Callbacks[EventId]
|
||||
|
||||
if Callback then
|
||||
for _, Call in ipairs(Calls) do
|
||||
if type(Call) == "table" then
|
||||
Spawn(Callback, unpack(Call))
|
||||
else
|
||||
Spawn(Callback, Call)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- debug.profileend()
|
||||
end
|
||||
|
||||
if IncomingCall then
|
||||
-- debug.profilebegin "Red.Listen.Incoming.Call"
|
||||
|
||||
for _, Call in pairs(IncomingCall) do
|
||||
local CallId = table.remove(Call, 1)
|
||||
local Success = table.remove(Call, 1)
|
||||
|
||||
if Event.ActiveCalls[CallId] then
|
||||
if Success then
|
||||
Event.ActiveCalls[CallId].Resolve(
|
||||
unpack(Call)
|
||||
)
|
||||
else
|
||||
Event.ActiveCalls[CallId].Reject(
|
||||
unpack(Call)
|
||||
)
|
||||
end
|
||||
|
||||
Event.ActiveCalls[CallId] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- debug.profileend()
|
||||
end
|
||||
|
||||
-- debug.profileend()
|
||||
end
|
||||
)
|
||||
|
||||
Clock.new(1 / 60, function()
|
||||
-- debug.profilebegin "Red.Listen.Outgoing"
|
||||
|
||||
if not next(Event.Outgoing) then
|
||||
return
|
||||
end
|
||||
|
||||
local SingleFire = {}
|
||||
local SendSingleFire = false
|
||||
|
||||
if Event.Outgoing[1] then
|
||||
for EventId, Calls in pairs(Event.Outgoing[1]) do
|
||||
if #Calls == 1 then
|
||||
SingleFire[EventId] = Calls[1]
|
||||
Event.Outgoing[1][EventId] = nil
|
||||
|
||||
SendSingleFire = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- nils cannot be sent properly across remoteevents in 2013,
|
||||
-- so we have to use a symbol to represent nil
|
||||
local sf = nil_symbol
|
||||
if SendSingleFire then
|
||||
sf = SingleFire
|
||||
end
|
||||
|
||||
local eo1, eo2 = Event.Outgoing[1], Event.Outgoing[2]
|
||||
if eo1 == nil then
|
||||
eo1 = nil_symbol
|
||||
end
|
||||
if eo2 == nil then
|
||||
eo2 = nil_symbol
|
||||
end
|
||||
|
||||
Remote:FireServer(sf, eo1, eo2)
|
||||
|
||||
-- table.clear(Event.Outgoing)
|
||||
for i, _ in pairs(Event.Outgoing) do
|
||||
Event.Outgoing[i] = nil
|
||||
end
|
||||
|
||||
-- debug.profileend()
|
||||
end)
|
||||
else
|
||||
Remote.OnServerEvent:connect(
|
||||
function(Player, SingleFire, MultipleFire, IncomingCall)
|
||||
-- debug.profilebegin "Red.Listen.Incoming"
|
||||
|
||||
-- replace nil symbols with nil
|
||||
if SingleFire.__nil then
|
||||
SingleFire = nil
|
||||
end
|
||||
if MultipleFire.__nil then
|
||||
MultipleFire = nil
|
||||
end
|
||||
if IncomingCall.__nil then
|
||||
IncomingCall = nil
|
||||
end
|
||||
|
||||
if SingleFire then
|
||||
-- debug.profilebegin "Red.Listen.Incoming.SingleFire"
|
||||
|
||||
for EventId, Call in pairs(SingleFire) do
|
||||
local Callback = Event.Callbacks[EventId]
|
||||
|
||||
if Callback then
|
||||
if type(Call) == "table" then
|
||||
Spawn(Callback, Player, unpack(Call))
|
||||
else
|
||||
Spawn(Callback, Player, Call)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- debug.profileend()
|
||||
end
|
||||
|
||||
if MultipleFire then
|
||||
-- debug.profilebegin "Red.Listen.Incoming.MultipleFire"
|
||||
|
||||
for EventId, Calls in pairs(MultipleFire) do
|
||||
local Callback = Event.Callbacks[EventId]
|
||||
|
||||
if Callback then
|
||||
for _, Call in ipairs(Calls) do
|
||||
if type(Call) == "table" then
|
||||
Spawn(Callback, Player, unpack(Call))
|
||||
else
|
||||
Spawn(Callback, Player, Call)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- debug.profileend()
|
||||
end
|
||||
|
||||
if IncomingCall then
|
||||
-- debug.profilebegin "Red.Listen.Incoming.Call"
|
||||
|
||||
for EventId, Calls in pairs(IncomingCall) do
|
||||
if Event.Callbacks[EventId] then
|
||||
for _, Call in ipairs(Calls) do
|
||||
Spawn(function()
|
||||
local CallId = table.remove(Call, 1)
|
||||
local Result = {
|
||||
CallId,
|
||||
pcall(
|
||||
Event.Callbacks[EventId],
|
||||
Player,
|
||||
unpack(Call)
|
||||
),
|
||||
}
|
||||
|
||||
if Event.Outgoing[Player] == nil then
|
||||
Event.Outgoing[Player] = {}
|
||||
end
|
||||
|
||||
if Event.Outgoing[Player][2] == nil then
|
||||
Event.Outgoing[Player][2] = {}
|
||||
end
|
||||
|
||||
table.insert(
|
||||
Event.Outgoing[Player][2],
|
||||
Result
|
||||
)
|
||||
end)
|
||||
end
|
||||
else
|
||||
if Event.Outgoing[Player] == nil then
|
||||
Event.Outgoing[Player] = {}
|
||||
end
|
||||
|
||||
if Event.Outgoing[Player][2] == nil then
|
||||
Event.Outgoing[Player][2] = {}
|
||||
end
|
||||
|
||||
for _, Call in ipairs(Calls) do
|
||||
table.insert(Event.Outgoing[Player][2], {
|
||||
Call[1],
|
||||
false,
|
||||
"[Red]: Event not found",
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- debug.profileend()
|
||||
end
|
||||
|
||||
-- debug.profileend()
|
||||
end
|
||||
)
|
||||
|
||||
RunService.Heartbeat:connect(function()
|
||||
-- debug.profilebegin "Red.Listen.Outgoing"
|
||||
|
||||
for Player, Packets in pairs(Event.Outgoing) do
|
||||
local SingleCall = {}
|
||||
local SendSingleCall = false
|
||||
|
||||
if Packets[1] then
|
||||
for EventId, Calls in pairs(Packets[1]) do
|
||||
if #Calls == 1 then
|
||||
SingleCall[EventId] = Calls[1]
|
||||
Packets[1][EventId] = nil
|
||||
|
||||
SendSingleCall = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- nils cannot be sent properly across remoteevents in 2013,
|
||||
-- so we have to use a symbol to represent nil
|
||||
local sc = nil_symbol
|
||||
if SendSingleCall then
|
||||
sc = SingleCall
|
||||
end
|
||||
|
||||
local p1, p2 = Packets[1], Packets[2]
|
||||
|
||||
if p1 == nil then
|
||||
p1 = nil_symbol
|
||||
end
|
||||
if p2 == nil then
|
||||
p2 = nil_symbol
|
||||
end
|
||||
|
||||
Remote:FireClient(Player, sc, p1, p2)
|
||||
end
|
||||
|
||||
-- table.clear(Event.Outgoing)
|
||||
for i, _ in pairs(Event.Outgoing) do
|
||||
Event.Outgoing[i] = nil
|
||||
end
|
||||
|
||||
-- debug.profileend()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function Event.AddQueue(Queue: { any }, Call: { any })
|
||||
local Length = #Call
|
||||
|
||||
if Length == 1 then
|
||||
local Type = type(Call[1])
|
||||
|
||||
if Type ~= "table" then
|
||||
table.insert(Queue, Call[1])
|
||||
else
|
||||
table.insert(Queue, Call)
|
||||
end
|
||||
else
|
||||
table.insert(Queue, Call)
|
||||
end
|
||||
end
|
||||
|
||||
function Event.FireClient(Player: Player, EventName: string, ...)
|
||||
assert(IsServer, "Event.FireClient can only be called from the server")
|
||||
|
||||
local EventId = Serdes.IdentifierAsync(EventName)
|
||||
|
||||
if Event.Outgoing[Player] == nil then
|
||||
Event.Outgoing[Player] = {}
|
||||
end
|
||||
|
||||
if Event.Outgoing[Player][1] == nil then
|
||||
Event.Outgoing[Player][1] = {}
|
||||
end
|
||||
|
||||
if Event.Outgoing[Player][1][EventId] == nil then
|
||||
Event.Outgoing[Player][1][EventId] = {}
|
||||
end
|
||||
|
||||
Event.AddQueue(Event.Outgoing[Player][1][EventId], { ... })
|
||||
end
|
||||
|
||||
function Event.FireServer(EventName: string, ...)
|
||||
assert(
|
||||
not IsServer,
|
||||
"Event.FireServer can only be called on the client"
|
||||
)
|
||||
|
||||
local Args = { ... }
|
||||
|
||||
return Serdes.Identifier(EventName):Then(function(EventId)
|
||||
if Event.Outgoing[1] == nil then
|
||||
Event.Outgoing[1] = {}
|
||||
end
|
||||
|
||||
if Event.Outgoing[1][EventId] == nil then
|
||||
Event.Outgoing[1][EventId] = {}
|
||||
end
|
||||
|
||||
Event.AddQueue(Event.Outgoing[1][EventId], Args)
|
||||
end)
|
||||
end
|
||||
|
||||
function Event.Call(EventName: string, ...)
|
||||
assert(not IsServer, "Event.Call can only be called on the client")
|
||||
|
||||
local Args = { ... }
|
||||
|
||||
return Promise.new(function(Resolve, Reject)
|
||||
local CallId = Serdes.OneTime()
|
||||
local EventId = Serdes.IdentifierAsync(EventName)
|
||||
|
||||
if Event.Outgoing[2] == nil then
|
||||
Event.Outgoing[2] = {}
|
||||
end
|
||||
|
||||
if Event.Outgoing[2][EventId] == nil then
|
||||
Event.Outgoing[2][EventId] = {}
|
||||
end
|
||||
|
||||
table.insert(Args, 1, CallId)
|
||||
table.insert(Event.Outgoing[2][EventId], Args)
|
||||
|
||||
Event.ActiveCalls[CallId] = {
|
||||
Resolve = Resolve,
|
||||
Reject = Reject,
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
function Event.SetCallback(EventName: string, Callback: ((...any) -> any)?)
|
||||
return Serdes.Identifier(EventName):Then(function(EventId)
|
||||
Event.Callbacks[EventId] = Callback
|
||||
end)
|
||||
end
|
||||
|
||||
return Event
|
||||
end
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
return function(IsServer: boolean)
|
||||
local RedEvent = require "../RedEvent"(IsServer)
|
||||
local Event = RedEvent.Remote
|
||||
|
||||
local Promise = require "../Util/Promise"
|
||||
local Serdes = {}
|
||||
|
||||
Serdes.NextId = 1 -- StringValues think null bytes are empty strings
|
||||
Serdes.NextOT = 1
|
||||
|
||||
function Serdes.RegisterIdentifier(Name: string)
|
||||
assert(IsServer, "RegisterIdentifier can only be called on the server")
|
||||
|
||||
local Id = string.char(Serdes.NextId)
|
||||
Serdes.NextId += 1
|
||||
|
||||
local e = Event:FindFirstChild(Name)
|
||||
if e then
|
||||
e.Value = Id
|
||||
else
|
||||
e = Instance.new "StringValue"
|
||||
e.Name = Name
|
||||
e.Value = Id
|
||||
e.Parent = Event
|
||||
end
|
||||
|
||||
return Id
|
||||
end
|
||||
|
||||
function Serdes.Identifier(Name: string)
|
||||
if not IsServer then
|
||||
return Promise.new(function(Resolve)
|
||||
local e = Event:WaitForChild(Name)
|
||||
if e.Value ~= nil then
|
||||
Resolve(e.Value)
|
||||
else
|
||||
local Thread = Delay(5, function()
|
||||
print(
|
||||
"[Red.Serdes]: Retrieving identifier exceeded 5 seconds. Make sure '"
|
||||
.. Name
|
||||
.. "' is registered on the server."
|
||||
)
|
||||
end)
|
||||
|
||||
e.Changed:Once(function()
|
||||
coroutine.yield(Thread :: thread)
|
||||
|
||||
Resolve(e.Value)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
else
|
||||
local e = Event:FindFirstChild(Name)
|
||||
if e and e.Value then
|
||||
return Promise.Resolve(e.Value)
|
||||
end
|
||||
return Promise.Resolve(Serdes.RegisterIdentifier(Name))
|
||||
end
|
||||
end
|
||||
|
||||
function Serdes.IdentifierAsync(Name: string)
|
||||
return Serdes.Identifier(Name):Await()
|
||||
end
|
||||
|
||||
function Serdes.OneTime()
|
||||
Serdes.NextOT += 1
|
||||
|
||||
if Serdes.NextOT == 0xFFFF + 1 then
|
||||
Serdes.NextOT = 0
|
||||
end
|
||||
|
||||
return string.char(Serdes.NextOT)
|
||||
end
|
||||
|
||||
return Serdes
|
||||
end
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
return function(IsServer: boolean)
|
||||
local Players = game:GetService "Players"
|
||||
|
||||
local RedEvent = require "../RedEvent"(IsServer)
|
||||
local Remote = RedEvent.Remote
|
||||
local ClientFolder = RedEvent.ClientFolder
|
||||
|
||||
local Serdes = require "./Serdes"(IsServer)
|
||||
local Event = require "./Event"(IsServer)
|
||||
|
||||
local Server = {}
|
||||
Server.__index = Server
|
||||
|
||||
function Server.Server(Name: string)
|
||||
local self = setmetatable({}, Server)
|
||||
|
||||
self.Name = Name
|
||||
self.FolderInstance = nil :: Folder?
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
Server.new = Server.Server
|
||||
|
||||
function Server.Fire(self: Server, Player: Player, EventName: string, ...)
|
||||
Event.FireClient(Player, self.Name .. "_" .. EventName, ...)
|
||||
end
|
||||
|
||||
function Server.FireAll(self: Server, EventName: string, ...)
|
||||
for _, Player in ipairs(Players:GetPlayers()) do
|
||||
self:Fire(Player, EventName, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function Server.FireAllExcept(
|
||||
self: Server,
|
||||
Player: Player,
|
||||
EventName: string,
|
||||
...
|
||||
)
|
||||
for _, OtherPlayer in ipairs(Players:GetPlayers()) do
|
||||
if OtherPlayer ~= Player then
|
||||
self:Fire(OtherPlayer, EventName, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Server.FireList(
|
||||
self: Server,
|
||||
PlayerList: { Player },
|
||||
EventName: string,
|
||||
...
|
||||
)
|
||||
for _, Player in ipairs(PlayerList) do
|
||||
self:Fire(Player, EventName, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function Server.FireWithFilter(
|
||||
self: Server,
|
||||
Filter: (Player) -> boolean,
|
||||
EventName: string,
|
||||
...
|
||||
)
|
||||
for _, Player in ipairs(Players:GetPlayers()) do
|
||||
if Filter(Player) then
|
||||
self:Fire(Player, EventName, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Server.On(
|
||||
self: Server,
|
||||
EventName: string,
|
||||
Callback: ((Player, ...any) -> ...any)?
|
||||
)
|
||||
Event.SetCallback(self.Name .. "_" .. EventName, Callback)
|
||||
end
|
||||
|
||||
function Server.Folder(self: Server, Player: Player?)
|
||||
if Player then
|
||||
ClientFolder = (
|
||||
Player:FindFirstChild "PlayerGui" :: any
|
||||
).Red :: ScreenGui
|
||||
|
||||
if ClientFolder:FindFirstChild(self.Name) then
|
||||
return ClientFolder:FindFirstChild(self.Name) :: Model
|
||||
else
|
||||
local Folder = Instance.new "Model"
|
||||
Folder.Name = self.Name
|
||||
Folder.Parent = ClientFolder
|
||||
|
||||
return Folder :: Model
|
||||
end
|
||||
else
|
||||
if not self.FolderInstance then
|
||||
local Folder = Instance.new "Model"
|
||||
Folder.Name = self.Name
|
||||
Folder.Parent = Remote
|
||||
|
||||
self.FolderInstance = Folder
|
||||
end
|
||||
|
||||
return self.FolderInstance :: Model
|
||||
end
|
||||
end
|
||||
|
||||
export type Server = typeof(Server.Server "")
|
||||
|
||||
local Client = {}
|
||||
Client.__index = Client
|
||||
|
||||
function Client.Client(Name: string)
|
||||
local self = setmetatable({}, Client)
|
||||
|
||||
self.Name = Name
|
||||
self.FolderInstance = nil :: Folder?
|
||||
self.LocalFolderInstance = nil :: Folder?
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
Client.new = Client.Client
|
||||
|
||||
function Client.Fire(self: Client, EventName: string, ...)
|
||||
return Event.FireServer(self.Name .. "_" .. EventName, ...)
|
||||
end
|
||||
|
||||
function Client.Call(self: Client, EventName: string, ...)
|
||||
return Event.Call(self.Name .. "_" .. EventName, ...)
|
||||
end
|
||||
|
||||
function Client.On(
|
||||
self: Client,
|
||||
EventName: string,
|
||||
Callback: ((...any) -> ())?
|
||||
)
|
||||
return Event.SetCallback(self.Name .. "_" .. EventName, Callback)
|
||||
end
|
||||
|
||||
function Client.Folder(self: Client)
|
||||
if not self.FolderInstance then
|
||||
self.FolderInstance = Remote:WaitForChild(self.Name) :: Model
|
||||
end
|
||||
|
||||
return self.FolderInstance :: Model
|
||||
end
|
||||
|
||||
function Client.LocalFolder(self: Client)
|
||||
if not self.LocalFolderInstance then
|
||||
self.LocalFolderInstance =
|
||||
ClientFolder:WaitForChild(self.Name) :: Model
|
||||
end
|
||||
|
||||
return self.LocalFolderInstance :: Model
|
||||
end
|
||||
|
||||
export type Client = typeof(Client.Client "")
|
||||
|
||||
local Net = {}
|
||||
|
||||
Net.ServerNamespaceList = {}
|
||||
Net.ClientNamespaceList = {}
|
||||
|
||||
function Net.Server(Name: string, Definitions: { string }?): Server
|
||||
assert(IsServer, "Net.Server can only be used on the server")
|
||||
|
||||
if not Net.ServerNamespaceList[Name] then
|
||||
Net.ServerNamespaceList[Name] = Server.Server(Name)
|
||||
end
|
||||
|
||||
if Definitions then
|
||||
for _, Term in ipairs(Definitions) do
|
||||
Serdes.Identifier((Name :: string) .. "_" .. Term)
|
||||
end
|
||||
end
|
||||
|
||||
return Net.ServerNamespaceList[Name]
|
||||
end
|
||||
|
||||
function Net.Client(Name: string): Client
|
||||
assert(not IsServer, "Net.Client can only be used on the client")
|
||||
|
||||
if Net.ClientNamespaceList[Name] == nil then
|
||||
Net.ClientNamespaceList[Name] = Client.Client(Name)
|
||||
end
|
||||
|
||||
return Net.ClientNamespaceList[Name]
|
||||
end
|
||||
|
||||
function Net.Identifier(Name: string)
|
||||
return Serdes.Identifier(Name)
|
||||
end
|
||||
|
||||
Event.Listen()
|
||||
|
||||
return Net
|
||||
end
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
return function(IsServer: boolean)
|
||||
local ReplicatedStorage = game:GetService "ReplicatedStorage"
|
||||
local Players = game:GetService "Players"
|
||||
|
||||
local RedEvent = {}
|
||||
|
||||
local Remote, ClientFolder
|
||||
|
||||
if IsServer then
|
||||
Remote = ReplicatedStorage:FindFirstChild "RedEvent" :: RemoteEvent
|
||||
|
||||
if not Remote then -- prevent vile promise bugs
|
||||
Remote = Instance.new "RemoteEvent"
|
||||
Remote.Name = "RedEvent"
|
||||
Remote.Parent = ReplicatedStorage
|
||||
end
|
||||
|
||||
local function PlayerAdded(Player: Player)
|
||||
ClientFolder = Instance.new "ScreenGui"
|
||||
|
||||
-- ClientFolder.Enabled = false
|
||||
-- ClientFolder.ResetOnSpawn = false
|
||||
ClientFolder.Name = "Red"
|
||||
ClientFolder.Parent = Player:FindFirstChild "PlayerGui"
|
||||
end
|
||||
|
||||
Players.PlayerAdded:connect(PlayerAdded)
|
||||
|
||||
for _, Player in ipairs(Players:GetPlayers()) do
|
||||
PlayerAdded(Player)
|
||||
end
|
||||
else
|
||||
Remote = ReplicatedStorage:WaitForChild "RedEvent" :: RemoteEvent
|
||||
|
||||
ClientFolder = (
|
||||
Players.LocalPlayer:FindFirstChild "PlayerGui" :: PlayerGui
|
||||
):WaitForChild "Red" :: ScreenGui
|
||||
ClientFolder.Parent = nil
|
||||
end
|
||||
|
||||
RedEvent.Remote = Remote
|
||||
RedEvent.ClientFolder = ClientFolder
|
||||
|
||||
return RedEvent
|
||||
end
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
local Spawn = require "./Spawn"
|
||||
local typeof = require "../../../Modules/Polyfill/typeof"
|
||||
|
||||
type BinItem = Instance | RBXScriptConnection | () -> ...any
|
||||
|
||||
return function()
|
||||
local Bin: { BinItem } = {}
|
||||
|
||||
return function(Item: BinItem)
|
||||
table.insert(Bin, Item)
|
||||
end, function()
|
||||
for _, Item in ipairs(Bin) do
|
||||
if typeof(Item) == "Instance" then
|
||||
Item:Destroy()
|
||||
elseif typeof(Item) == "RBXScriptConnection" then
|
||||
Item:disconnect()
|
||||
elseif typeof(Item) == "function" then
|
||||
Spawn(Item)
|
||||
end
|
||||
end
|
||||
|
||||
-- table.clear(Bin)
|
||||
for i, _ in ipairs(Bin) do
|
||||
Bin[i] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
-- local ReplicatedStorage = game:GetService "ReplicatedStorage"
|
||||
local RunService = game:GetService "RunService"
|
||||
|
||||
local function MakeHeartbeatFunction(Clock: Clock)
|
||||
return function(Delta)
|
||||
Clock:Advance(Delta)
|
||||
end
|
||||
end
|
||||
|
||||
local Clock = {}
|
||||
Clock.__index = Clock
|
||||
|
||||
function Clock.Clock(Interval: number, Callback: () -> ())
|
||||
local self = setmetatable({}, Clock)
|
||||
|
||||
self.Interval = Interval
|
||||
self.Callback = Callback
|
||||
self.Delta = 0
|
||||
|
||||
self.Connection = RunService.Heartbeat:connect(MakeHeartbeatFunction(self))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
Clock.new = Clock.Clock
|
||||
|
||||
function Clock.Pause(self: Clock)
|
||||
if self.Connection then
|
||||
self.Connection:Disconnect()
|
||||
end
|
||||
end
|
||||
|
||||
function Clock.Resume(self: Clock)
|
||||
if self.Connection.Connected then
|
||||
return
|
||||
end
|
||||
|
||||
self.Connection = RunService.Heartbeat:connect(MakeHeartbeatFunction(self))
|
||||
end
|
||||
|
||||
function Clock.Advance(self: Clock, Delta: number)
|
||||
self.Delta += Delta
|
||||
|
||||
if self.Delta >= self.Interval * 10 then
|
||||
local Skipped = math.floor(self.Delta / self.Interval)
|
||||
self.Delta -= Skipped * self.Interval
|
||||
|
||||
-- if ReplicatedStorage:GetAttribute "RedDebug" then
|
||||
-- warn(
|
||||
-- "[Red.Clock]: Clock is falling behind! Skipped "
|
||||
-- .. Skipped
|
||||
-- .. " intervals."
|
||||
-- )
|
||||
-- end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if self.Delta >= self.Interval then
|
||||
self.Delta -= self.Interval
|
||||
self.Callback()
|
||||
end
|
||||
end
|
||||
|
||||
export type Clock = typeof(Clock.Clock(...))
|
||||
|
||||
return Clock
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
local CollectionService = game:GetService "CollectionService"
|
||||
|
||||
local Spawn = require "./Spawn"
|
||||
|
||||
return function<T...>(
|
||||
Tag: string,
|
||||
Start: (Instance) -> T...,
|
||||
Stop: (T...) -> ()
|
||||
): () -> ()
|
||||
local InstanceMap = {}
|
||||
|
||||
for _, Instance in ipairs(CollectionService:GetTagged(Tag)) do
|
||||
Spawn(function()
|
||||
InstanceMap[Instance] = { Start(Instance) }
|
||||
end)
|
||||
end
|
||||
|
||||
local AddConnection = CollectionService:GetInstanceAddedSignal(Tag)
|
||||
:connect(function(Instance)
|
||||
InstanceMap[Instance] = { Start(Instance) }
|
||||
end)
|
||||
|
||||
local RemoveConnection = CollectionService:GetInstanceRemovedSignal(Tag)
|
||||
:connect(function(Instance)
|
||||
local Value = InstanceMap[Instance]
|
||||
|
||||
if Value then
|
||||
InstanceMap[Instance] = nil
|
||||
Stop(unpack(Value))
|
||||
end
|
||||
end)
|
||||
|
||||
return function()
|
||||
AddConnection:Disconnect()
|
||||
RemoveConnection:Disconnect()
|
||||
|
||||
for Instance, Value in pairs(InstanceMap) do
|
||||
Spawn(Stop, unpack(Value))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
local RunService = game:GetService "RunService"
|
||||
|
||||
local Spawn = require "./Spawn"
|
||||
|
||||
local Promise = {}
|
||||
Promise.__index = Promise
|
||||
|
||||
function Promise.Promise(Callback: (
|
||||
Resolve: (...any) -> (),
|
||||
Reject: (...any) -> ()
|
||||
) -> ())
|
||||
local self = setmetatable({}, Promise)
|
||||
|
||||
self.Status = "Pending"
|
||||
self.OnResolve = {} :: { (...any) -> () }
|
||||
self.OnReject = {} :: { (...any) -> () }
|
||||
|
||||
self.Value = {} :: { any }
|
||||
|
||||
self.Thread = nil :: thread?
|
||||
self.Thread = coroutine.create(function()
|
||||
local ok, err = ypcall(Callback, function(...)
|
||||
self:_Resolve(...)
|
||||
end, function(...)
|
||||
self:_Reject(...)
|
||||
end)
|
||||
|
||||
if not ok then
|
||||
self:_Reject(err)
|
||||
end
|
||||
end)
|
||||
|
||||
coroutine.resume(self.Thread :: thread)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
Promise.new = Promise.Promise :: (
|
||||
Callback: (Resolve: (...any) -> (), Reject: (...any) -> ()) -> ()
|
||||
) -> Promise
|
||||
|
||||
function Promise.Resolve(...: any): Promise
|
||||
local self = setmetatable({}, Promise)
|
||||
|
||||
self.Status = "Resolved"
|
||||
self.OnResolve = {} :: { (...any) -> () }
|
||||
self.OnReject = {} :: { (...any) -> () }
|
||||
self.Value = { ... } :: { any }
|
||||
self.Thread = nil :: thread?
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Promise.Reject(...: any): Promise
|
||||
local self = setmetatable({}, Promise)
|
||||
|
||||
self.Status = "Rejected"
|
||||
self.OnResolve = {} :: { (...any) -> () }
|
||||
self.OnReject = {} :: { (...any) -> () }
|
||||
self.Value = { ... } :: { any }
|
||||
self.Thread = nil :: thread?
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Promise._Resolve(self: Promise, ...: any)
|
||||
assert(
|
||||
self.Status == "Pending",
|
||||
"Cannot resolve a promise that is not pending."
|
||||
)
|
||||
|
||||
self.Status = "Resolved"
|
||||
self.Value = { ... }
|
||||
|
||||
for _, Callback in ipairs(self.OnResolve) do
|
||||
Spawn(Callback, ...)
|
||||
end
|
||||
|
||||
-- task.defer(task.cancel, self.Thread :: thread)
|
||||
coroutine.resume(coroutine.create(function()
|
||||
coroutine.yield(self.Thread :: thread)
|
||||
end))
|
||||
end
|
||||
|
||||
function Promise._Reject(self: Promise, ...: any)
|
||||
assert(
|
||||
self.Status == "Pending",
|
||||
"Cannot reject a promise that is not pending."
|
||||
)
|
||||
|
||||
self.Status = "Rejected"
|
||||
self.Value = { ... }
|
||||
|
||||
for _, Callback in ipairs(self.OnReject) do
|
||||
Spawn(Callback, ...)
|
||||
end
|
||||
|
||||
-- task.defer(task.cancel, self.Thread :: thread)
|
||||
coroutine.resume(coroutine.create(function()
|
||||
coroutine.yield(self.Thread :: thread)
|
||||
end))
|
||||
end
|
||||
|
||||
function Promise.Then(
|
||||
self: Promise,
|
||||
OnResolve: ((...any) -> ...any)?,
|
||||
OnReject: ((...any) -> ...any)?
|
||||
): Promise
|
||||
return Promise.Promise(function(Resolve, Reject)
|
||||
local function PromiseResolutionProcedure(
|
||||
Value: Promise | any,
|
||||
...: any
|
||||
)
|
||||
if type(Value) == "table" and getmetatable(Value) == Promise then
|
||||
if Value.Status == "Pending" then
|
||||
table.insert(Value.OnResolve, Resolve)
|
||||
table.insert(Value.OnReject, Reject)
|
||||
elseif Value.Status == "Resolved" then
|
||||
Resolve(Value.Value)
|
||||
elseif Value.Status == "Rejected" then
|
||||
Reject(Value.Value)
|
||||
end
|
||||
else
|
||||
Resolve(Value, ...)
|
||||
end
|
||||
end
|
||||
|
||||
if self.Status == "Pending" then
|
||||
if OnResolve then
|
||||
table.insert(self.OnResolve, function(...)
|
||||
PromiseResolutionProcedure(OnResolve(...))
|
||||
end)
|
||||
else
|
||||
table.insert(self.OnResolve, PromiseResolutionProcedure)
|
||||
end
|
||||
|
||||
if OnReject then
|
||||
table.insert(self.OnReject, function(...)
|
||||
PromiseResolutionProcedure(OnReject(...))
|
||||
end)
|
||||
else
|
||||
table.insert(self.OnReject, Reject)
|
||||
end
|
||||
elseif self.Status == "Resolved" then
|
||||
if OnResolve then
|
||||
PromiseResolutionProcedure(OnResolve(unpack(self.Value)))
|
||||
else
|
||||
Resolve(unpack(self.Value))
|
||||
end
|
||||
elseif self.Status == "Rejected" then
|
||||
if OnReject then
|
||||
PromiseResolutionProcedure(OnReject(unpack(self.Value)))
|
||||
else
|
||||
Reject(unpack(self.Value))
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Promise.Catch(self: Promise, OnReject: (...any) -> ())
|
||||
return self:Then(nil, OnReject)
|
||||
end
|
||||
|
||||
function Promise.Finally(self: Promise, Finally: () -> ())
|
||||
return self:Then(function(...)
|
||||
Finally()
|
||||
return ...
|
||||
end, function(Error)
|
||||
Finally()
|
||||
error(Error)
|
||||
end)
|
||||
end
|
||||
|
||||
function Promise.Await(self: Promise): ...any
|
||||
if self.Status == "Resolved" then
|
||||
return unpack(self.Value)
|
||||
elseif self.Status == "Rejected" then
|
||||
return error(unpack(self.Value))
|
||||
end
|
||||
|
||||
local c = 0
|
||||
repeat -- bruh
|
||||
RunService.Stepped:wait()
|
||||
c += 1
|
||||
until self.Status ~= "Pending" or c > 500 -- random
|
||||
|
||||
local Current = coroutine.running()
|
||||
|
||||
local function Resume()
|
||||
coroutine.resume(Current)
|
||||
end
|
||||
|
||||
table.insert(self.OnResolve, Resume)
|
||||
table.insert(self.OnReject, Resume)
|
||||
|
||||
coroutine.yield()
|
||||
|
||||
if self.Status == "Resolved" then
|
||||
return unpack(self.Value)
|
||||
end
|
||||
return error(unpack(self.Value))
|
||||
end
|
||||
|
||||
export type Promise = typeof(Promise.Promise(...))
|
||||
|
||||
return Promise
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
return function<T>(Limit: number, Interval: number)
|
||||
assert(Limit > 0, "Limit must be greater than 0")
|
||||
|
||||
local CountMap = {} :: { [T]: number }
|
||||
local CountKeyless = 0
|
||||
|
||||
return function(Key: T?)
|
||||
if Key then
|
||||
local Count = CountMap[Key]
|
||||
|
||||
if Count == nil then
|
||||
Count = 0
|
||||
|
||||
Delay(Interval, function()
|
||||
CountMap[Key] = nil
|
||||
end)
|
||||
end
|
||||
|
||||
if Count >= Limit then
|
||||
return false
|
||||
end
|
||||
|
||||
CountMap[Key] = Count + 1
|
||||
else
|
||||
if CountKeyless == 0 then
|
||||
Delay(Interval, function()
|
||||
CountKeyless = 0
|
||||
end)
|
||||
end
|
||||
|
||||
if CountKeyless >= Limit then
|
||||
return false
|
||||
end
|
||||
|
||||
CountKeyless += 1
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
local Promise = require "./Promise"
|
||||
local Spawn = require "./Spawn"
|
||||
|
||||
type SignalNode<T...> = {
|
||||
Next: SignalNode<T...>?,
|
||||
Callback: (T...) -> (),
|
||||
}
|
||||
|
||||
export type Signal<T...> = {
|
||||
Root: SignalNode<T...>?,
|
||||
|
||||
Connect: (self: Signal<T...>, Callback: (T...) -> ()) -> () -> (),
|
||||
Wait: (self: Signal<T...>) -> Promise.Promise,
|
||||
Fire: (self: Signal<T...>, T...) -> (),
|
||||
DisconnectAll: (self: Signal<T...>) -> (),
|
||||
}
|
||||
|
||||
local Signal = {}
|
||||
Signal.__index = Signal
|
||||
|
||||
function Signal.new<T...>(): Signal<T...>
|
||||
return setmetatable({
|
||||
Root = nil,
|
||||
}, Signal) :: any
|
||||
end
|
||||
|
||||
function Signal.Connect<T...>(self: Signal<T...>, Callback: (T...) -> ()): () -> ()
|
||||
local Node = {
|
||||
Next = self.Root,
|
||||
Callback = Callback,
|
||||
}
|
||||
|
||||
self.Root = Node
|
||||
|
||||
return function()
|
||||
if self.Root == Node then
|
||||
self.Root = Node.Next
|
||||
else
|
||||
local Current = self.Root
|
||||
while Current do
|
||||
if Current.Next == Node then
|
||||
Current.Next = Node.Next
|
||||
break
|
||||
end
|
||||
Current = Current.Next
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Signal.Wait<T...>(self: Signal<T...>): Promise.Promise
|
||||
return Promise.new(function(Resolve)
|
||||
local Disconnect
|
||||
|
||||
Disconnect = self:Connect(function(...)
|
||||
Disconnect()
|
||||
Resolve(... :: any)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
function Signal.Fire<T...>(self: Signal<T...>, ...: T...)
|
||||
local Current = self.Root
|
||||
while Current do
|
||||
Spawn(Current.Callback, ...)
|
||||
Current = Current.Next
|
||||
end
|
||||
end
|
||||
|
||||
function Signal.DisconnectAll<T...>(self: Signal<T...>)
|
||||
self.Root = nil
|
||||
end
|
||||
|
||||
return Signal
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
local FreeThread: thread? = nil
|
||||
|
||||
local function FunctionPasser(fn, ...)
|
||||
local AquiredThread = FreeThread
|
||||
FreeThread = nil
|
||||
fn(...)
|
||||
FreeThread = AquiredThread
|
||||
end
|
||||
|
||||
local function Yielder()
|
||||
while true do
|
||||
FunctionPasser(coroutine.yield())
|
||||
end
|
||||
end
|
||||
|
||||
return function<T...>(fn: (T...) -> (), ...: T...)
|
||||
if not FreeThread then
|
||||
FreeThread = coroutine.create(Yielder)
|
||||
coroutine.resume(FreeThread :: any)
|
||||
end
|
||||
|
||||
coroutine.resume(FreeThread :: thread, fn, ...)
|
||||
end
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
local function Red(_, Script: LuaSourceContainer)
|
||||
local _SERVER
|
||||
|
||||
if Script:IsA "LocalScript" then
|
||||
_SERVER = false
|
||||
elseif Script:IsA "Script" then
|
||||
_SERVER = true
|
||||
else
|
||||
error("Argument must be the script itself", 2)
|
||||
end
|
||||
|
||||
local Net = require "./Net"(_SERVER)
|
||||
|
||||
return {
|
||||
Server = Net.Server,
|
||||
Client = Net.Client,
|
||||
|
||||
Collection = require "./Util/Collection",
|
||||
Ratelimit = require "./Util/Ratelimit",
|
||||
Promise = require "./Util/Promise",
|
||||
Signal = require "./Util/Signal",
|
||||
Clock = require "./Util/Clock",
|
||||
Spawn = require "./Util/Spawn",
|
||||
Bin = require "./Util/Bin",
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
Help = function()
|
||||
return "See https://redblox.dev/ for more information."
|
||||
end,
|
||||
Load = Red,
|
||||
}
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
-- A basic polyfill for the typeof function
|
||||
|
||||
return function(value)
|
||||
local basicType = type(value)
|
||||
|
||||
if
|
||||
basicType == "nil"
|
||||
or basicType == "boolean"
|
||||
or basicType == "number"
|
||||
or basicType == "string"
|
||||
or basicType == "function"
|
||||
or basicType == "thread"
|
||||
or basicType == "table"
|
||||
then
|
||||
return basicType
|
||||
end
|
||||
|
||||
-- Will short-circuit
|
||||
--[[
|
||||
{
|
||||
name of type to check,
|
||||
{ list of required properties },
|
||||
}
|
||||
]]
|
||||
local tests = {
|
||||
{
|
||||
"Instance",
|
||||
{ "ClassName" },
|
||||
},
|
||||
{
|
||||
"EnumItem",
|
||||
{ "EnumType", "Name", "Value" },
|
||||
},
|
||||
{
|
||||
"Enum",
|
||||
{ "GetEnumItems" },
|
||||
},
|
||||
{
|
||||
"Enums",
|
||||
{ "MembershipType" }, -- lmao
|
||||
},
|
||||
{
|
||||
"RBXScriptSignal",
|
||||
{
|
||||
"connect",
|
||||
-- "connected",
|
||||
-- "connectFirst",
|
||||
-- "connectLast",
|
||||
"wait",
|
||||
},
|
||||
},
|
||||
{
|
||||
"RBXScriptConnection",
|
||||
{
|
||||
"connected",
|
||||
"disconnect",
|
||||
},
|
||||
},
|
||||
{
|
||||
"TweenInfo",
|
||||
{
|
||||
"EasingDirection",
|
||||
-- "Time",
|
||||
-- "DelayTime",
|
||||
"RepeatCount",
|
||||
"EasingStyle",
|
||||
-- "Reverses",
|
||||
},
|
||||
},
|
||||
{
|
||||
"CFrame",
|
||||
{
|
||||
"p",
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"lookVector",
|
||||
},
|
||||
},
|
||||
{
|
||||
"Vector3",
|
||||
{
|
||||
"Lerp",
|
||||
-- "Cross",
|
||||
-- "Dot",
|
||||
"unit",
|
||||
"magnitude",
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
},
|
||||
},
|
||||
{
|
||||
"Vector3int16",
|
||||
{ "z", "x", "y" },
|
||||
},
|
||||
{
|
||||
"Vector2",
|
||||
{ "unit", "magnitude", "x", "y" },
|
||||
},
|
||||
{
|
||||
"Vector2int16",
|
||||
{ "x", "y" },
|
||||
},
|
||||
{
|
||||
"Region3",
|
||||
{ "CFrame", "Size" },
|
||||
},
|
||||
{
|
||||
"Region3int16",
|
||||
{ "Min", "Max" },
|
||||
},
|
||||
{
|
||||
"Ray",
|
||||
{
|
||||
"Origin",
|
||||
"Direction",
|
||||
"Unit",
|
||||
"ClosestPoint",
|
||||
"Distance",
|
||||
},
|
||||
},
|
||||
{
|
||||
"UDim",
|
||||
{ "Scale", "Offset" },
|
||||
},
|
||||
{
|
||||
"Axes",
|
||||
{ "Z", "X", "Y" },
|
||||
},
|
||||
{
|
||||
"UDim2",
|
||||
{ "X", "Y" },
|
||||
},
|
||||
{
|
||||
"BrickColor",
|
||||
{
|
||||
"Number",
|
||||
"Name",
|
||||
"Color",
|
||||
"r",
|
||||
"g",
|
||||
"b",
|
||||
},
|
||||
},
|
||||
{
|
||||
"Color3",
|
||||
{ "r", "g", "b" },
|
||||
},
|
||||
{
|
||||
"Faces",
|
||||
{
|
||||
"Right",
|
||||
"Top",
|
||||
"Back",
|
||||
-- "Left",
|
||||
-- "Bottom",
|
||||
-- "Front",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, v in ipairs(tests) do
|
||||
local t, test = v[1], v[2]
|
||||
|
||||
local ok, result = pcall(function()
|
||||
for _, prop in ipairs(test) do
|
||||
if value[prop] == nil then
|
||||
return false
|
||||
end
|
||||
-- Cannot throw if the property does not exist,
|
||||
-- as userdatas may allow nil indexing
|
||||
end
|
||||
return true
|
||||
end)
|
||||
|
||||
if ok and result then
|
||||
return t
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -5,6 +5,7 @@ done
|
|||
|
||||
echo "Processing libraries..."
|
||||
darklua process -c dense.json5 ./corescripts/Libraries/Fusion/init.luau ./corescripts/processed/10000001.lua
|
||||
darklua process -c bundle.json5 ./corescripts/Libraries/Red/init.luau ./corescripts/processed/10000002.lua
|
||||
|
||||
echo "Processing other corescripts..."
|
||||
for file in ./corescripts/luau/[a-z]*.luau; do
|
||||
|
|
|
|||
484
defs.d.lua
484
defs.d.lua
File diff suppressed because one or more lines are too long
1334
luau/10000002.luau
1334
luau/10000002.luau
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue