Compare commits

...

22 commits

Author SHA1 Message Date
EternityDev
0b1304f4c5 v1.0.14 2024-12-02 14:37:46 +07:00
EternityDev
d8526c7e25 pre v1.0.14 2024-12-01 21:10:56 +07:00
EternityDev
d065bc2e50 oops 2024-11-30 23:55:07 +07:00
EternityDev
925df47d4c improvements 2024-11-30 23:49:37 +07:00
EternityDev
f3b674b377 rewrite buffer instance type serialization for fix issues 2024-11-23 19:51:14 +07:00
EternityDev
677d3fa675 rewrite buffer instance type serialization for fix issues 2024-11-23 19:50:06 +07:00
EternityDev
91862a65fe add more type support to buffer 2024-11-21 13:57:53 +07:00
EternityDev
8ba9540550 update .rbxm 2024-11-21 07:11:47 +07:00
EternityDev
1d67954ef9 fix & improved array type 2024-11-21 06:59:55 +07:00
EternityDev
43c4a1594f fixes to v1.0.13 2024-11-20 10:25:00 +07:00
EternityDev
22996c9357 v1.0.13 2024-11-19 18:24:46 +07:00
EternityDev
a377788f22 patch oudated deploy.yml 2024-10-05 18:41:32 +07:00
EternityDev
3354324c5b patch outdated deploy.yml 2024-10-05 18:40:12 +07:00
EternityDev
20b97eeb54 v1.0.13 .rbxm 2024-09-27 11:14:33 +07:00
EternityDev
dbed984eea v1.0.13 2024-09-27 11:10:12 +07:00
EternityDev
064075fbd9 v1.0.12 2024-05-29 16:58:20 +07:00
EternityDev
77de85b6b8 update: .rbxm file 2024-05-25 22:39:07 +07:00
EternityDev
10de54608a Fix: server invoke 2024-05-25 22:33:58 +07:00
EternityDev
5b2e36b7bb
Merge pull request #23 from lhkzh/patch-2
Fix bug: unpack server invoke params error
2024-05-25 22:31:14 +07:00
EternityDev
eba9f79655 Fix: Destroying a Signal 2024-05-24 20:52:00 +07:00
lhkzh
7309840005
Update init.luau
fix bug: unpack server invoke params error
2024-05-24 17:43:22 +08:00
EternityDev
aa693aee4f v1.0.11 2024-05-19 13:17:07 +07:00
29 changed files with 697 additions and 468 deletions

View file

@ -18,16 +18,16 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }} url: ${{ steps.deployment.outputs.page_url }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: npm cache: npm
- name: Setup Pages - name: Setup Pages
uses: actions/configure-pages@v3 uses: actions/configure-pages@v4
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Build - name: Build
@ -35,9 +35,9 @@ jobs:
npm run docs:build npm run docs:build
touch docs/.vitepress/dist touch docs/.vitepress/dist
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@v2 uses: actions/upload-pages-artifact@v3
with: with:
path: docs/.vitepress/dist path: docs/.vitepress/dist
- name: Deploy - name: Deploy
id: deployment id: deployment
uses: actions/deploy-pages@v2 uses: actions/deploy-pages@v4

BIN
Warp.rbxm

Binary file not shown.

View file

@ -39,7 +39,6 @@ function side() {
text: 'Utilities', text: 'Utilities',
items: [ items: [
{ text: 'Signal', link: '/api/1.0/signal' }, { text: 'Signal', link: '/api/1.0/signal' },
{ text: 'Buffer', link: '/api/1.0/buffer' },
] ]
}, },
] ]

View file

@ -1,3 +0,0 @@
# Buffer <Badge type="tip" text="utilities" />
A Additional buffer.

View file

@ -13,8 +13,10 @@ When creating a event on Server, you can add second argument (optional) as table
-- Server -- Server
-- Let's make the event have ratelimit with max 50 entrance for 2 seconds. -- Let's make the event have ratelimit with max 50 entrance for 2 seconds.
local Remote = Warp.Server("Remote1", { local Remote = Warp.Server("Remote1", {
rateLimit = {
maxEntrance = 50, -- maximum 50 fires. maxEntrance = 50, -- maximum 50 fires.
interval = 2, -- 2 seconds interval = 2, -- 2 seconds
}
}) })
-- Now the Event RateLimit is configured, and ready to use. -- Now the Event RateLimit is configured, and ready to use.
-- No need anything to adds on client side. -- No need anything to adds on client side.

View file

@ -36,12 +36,16 @@ Create new Warp events with array.
```lua [Example] ```lua [Example]
local Events = Warp.fromServerArray({ local Events = Warp.fromServerArray({
["Remote1"] = { ["Remote1"] = {
rateLimit = {
maxEntrance: 50, maxEntrance: 50,
interval: 1, interval: 1,
}
}, -- with rateLimit configuration }, -- with rateLimit configuration
"Remote2", -- without rateLimit configuration "Remote2", -- without rateLimit configuration
["Remote3"] = { ["Remote3"] = {
rateLimit = {
maxEntrance: 10, maxEntrance: 10,
}
}, -- with rateLimit configuration }, -- with rateLimit configuration
}) })

View file

@ -102,7 +102,23 @@ Signal1:DisconnectAll()
## `:Fire` ## `:Fire`
Fire the signal. Fire the signal (Immediate)
::: code-group
```lua [Variable]
(
...: any
)
```
```lua [Example]
Signal1:Fire("Hello World!")
```
:::
## `:DeferFire`
Fire the signal (Deferred)
::: code-group ::: code-group
```lua [Variable] ```lua [Variable]
@ -122,7 +138,7 @@ This uses `pcall`, which means it never error (safe-mode, sacrificed debugging),
## `:FireTo` ## `:FireTo`
Fire to other signal, this also use `:Fire`. Fire to other signal, this uses `:Fire`.
::: code-group ::: code-group
```lua [Variable] ```lua [Variable]

View file

@ -68,3 +68,4 @@ Pong:Destroy()
-- Yay Done! -- Yay Done!
``` ```
:::

View file

@ -8,7 +8,7 @@
::: code-group ::: code-group
```toml [wally.toml] ```toml [wally.toml]
[dependencies] [dependencies]
warp = "imezx/warp@1.0.5" warp = "imezx/warp@1.0.13"
``` ```
3. Run `wally install` in command. 3. Run `wally install` in command.

View file

@ -1,34 +0,0 @@
--!strict
--!native
--!optimize 2
local Logger = {}
local Logs: {
[string]: {
[string]: string
}
} = {}
local logging: {
[string]: boolean
} = {}
local now = tick()
function Logger.write(Identifier: string, text: string, log: boolean?)
if not Logs[Identifier] then
Logs[Identifier] = {}
end
if log ~= nil then
logging[Identifier] = log
end
now = tick()
Logs[Identifier][tostring(now)] = text
if logging[Identifier] then
print(`[{now}] ->`, text)
end
end
function Logger.read(Identifier: string)
return Logs[Identifier]
end
return Logger

View file

@ -12,17 +12,14 @@ local Spawn = require(Util.Spawn)
local Key = require(Util.Key) local Key = require(Util.Key)
local RateLimit = require(Util.RateLimit) local RateLimit = require(Util.RateLimit)
local Buffer = require(Util.Buffer) local Buffer = require(Util.Buffer)
local Logger = require(script.Logger)
local clientRatelimit: Type.StoredRatelimit = {} local clientRatelimit: Type.StoredRatelimit = {}
local clientQueue: Type.QueueMap = {} local clientQueue: Type.QueueMap = {}
local unreliableClientQueue: Type.QueueMap = {} local unreliableClientQueue: Type.QueueMap = {}
local clientCallback: Type.CallbackMap = {} local clientCallback: Type.CallbackMap = {}
local clientRequestQueue: Type.QueueMap = {} local clientRequestQueue: Type.QueueMap = {}
local registeredIdentifier: { [string]: boolean } = {}
local queueIn: {
[string]: {any}
} = {}
local queueInRequest: { local queueInRequest: {
[number]: { [number]: {
[string]: { [string]: {
@ -37,14 +34,6 @@ local queueOutRequest: {
} }
} }
} = {} } = {}
local incoming_cache: {
[string]: {
any
}
} = {}
local logger: {
[string]: boolean
} = {}
queueInRequest[1] = {} queueInRequest[1] = {}
queueInRequest[2] = {} queueInRequest[2] = {}
@ -86,10 +75,9 @@ function ClientProcess.insertRequest(Identifier: string, timeout: number, ...: a
end end
function ClientProcess.add(Identifier: any, originId: string, conf: Type.ClientConf) function ClientProcess.add(Identifier: any, originId: string, conf: Type.ClientConf)
if not clientQueue[Identifier] then if not registeredIdentifier[Identifier] then
if conf.logging then registeredIdentifier[Identifier] = true
ClientProcess.logger(Identifier, conf.logging.store, conf.logging.opt)
end
if not clientRatelimit[Identifier] then if not clientRatelimit[Identifier] then
clientRatelimit[Identifier] = RateLimit.create(originId) clientRatelimit[Identifier] = RateLimit.create(originId)
end end
@ -118,59 +106,69 @@ function ClientProcess.add(Identifier: any, originId: string, conf: Type.ClientC
if not queueInRequest[2][Identifier] then if not queueInRequest[2][Identifier] then
queueInRequest[2][Identifier] = {} queueInRequest[2][Identifier] = {}
end end
if not queueIn[Identifier] then
queueIn[Identifier] = {}
end
end end
end end
function ClientProcess.logger(Identifier: string, store: boolean, log: boolean) function ClientProcess.remove(Identifier: string)
logger[Identifier] = store if not registeredIdentifier[Identifier] then return end
Logger.write(Identifier, `state: change -> {log == true and "enabled" or "disabled"} logger.`, log) registeredIdentifier[Identifier] = nil
end clientQueue[Identifier] = nil
unreliableClientQueue[Identifier] = nil
function ClientProcess.getlogs(Identifier: string) clientRequestQueue[Identifier] = nil
return Logger.read(Identifier) clientCallback[Identifier] = nil
clientRatelimit[Identifier] = nil
queueOutRequest[1][Identifier] = nil
queueOutRequest[2][Identifier] = nil
queueInRequest[1][Identifier] = nil
queueInRequest[2][Identifier] = nil
end end
function ClientProcess.addCallback(Identifier: string, key: string, callback) function ClientProcess.addCallback(Identifier: string, key: string, callback)
clientCallback[Identifier][key] = callback clientCallback[Identifier][key] = callback
if logger[Identifier] then
task.defer(Logger.write, Identifier, `state: change -> new callback added.`)
end
end end
function ClientProcess.removeCallback(Identifier: string, key: string) function ClientProcess.removeCallback(Identifier: string, key: string)
clientCallback[Identifier][key] = nil clientCallback[Identifier][key] = nil
if logger[Identifier] then
task.defer(Logger.write, Identifier, `state: change -> removed a callback.`)
end
end end
function ClientProcess.start() function ClientProcess.start()
debug.setmemorycategory("Warp") debug.setmemorycategory("Warp")
RunService.PostSimulation:Connect(function() RunService.PostSimulation:Connect(function()
-- Unreliable
for Identifier: string, data: any in unreliableClientQueue do for Identifier: string, data: any in unreliableClientQueue do
if #data == 0 then continue end if #data == 0 then continue end
if clientRatelimit[Identifier](#data) then if clientRatelimit[Identifier](#data) then
UnreliableEvent:FireServer(Buffer.revert(Identifier), data) for _, unpacked in data do
if logger[Identifier] then UnreliableEvent:FireServer(Buffer.revert(Identifier), Buffer.write(unpacked))
task.defer(Logger.write, Identifier, `state: out -> unreliable -> {#data} data.`)
end end
end end
unreliableClientQueue[Identifier] = nil unreliableClientQueue[Identifier] = nil
end end
-- Reliable
for Identifier: string, data: any in clientQueue do for Identifier: string, data: any in clientQueue do
local callback = clientCallback[Identifier] or nil
if #data > 0 then if #data > 0 then
if clientRatelimit[Identifier](#data) then if clientRatelimit[Identifier](#data) then
ReliableEvent:FireServer(Buffer.revert(Identifier), data) for _, unpacked in data do
if logger[Identifier] then ReliableEvent:FireServer(Buffer.revert(Identifier), Buffer.write(unpacked))
task.defer(Logger.write, Identifier, `state: out -> reliable -> {#data} data.`)
end end
end end
clientQueue[Identifier] = nil clientQueue[Identifier] = nil
end end
end
-- Sent new invokes
for Identifier: string, requestsData in queueOutRequest[1] do
if #requestsData == 0 then continue end
RequestEvent:FireServer(Buffer.revert(Identifier), "\1", requestsData)
queueOutRequest[1][Identifier] = nil
end
-- Sent returning invokes
for Identifier: string, toReturnDatas in queueOutRequest[2] do
if #toReturnDatas == 0 then continue end
RequestEvent:FireServer(Buffer.revert(Identifier), "\0", toReturnDatas)
queueOutRequest[2][Identifier] = nil
end
for Identifier: string in registeredIdentifier do
if clientRequestQueue[Identifier] then if clientRequestQueue[Identifier] then
for _, requestData in clientRequestQueue[Identifier] do for _, requestData in clientRequestQueue[Identifier] do
if not requestData[3] then continue end if not requestData[3] then continue end
@ -178,51 +176,39 @@ function ClientProcess.start()
queueOutRequest[1][Identifier] = {} queueOutRequest[1][Identifier] = {}
end end
table.insert(queueOutRequest[1][Identifier], { requestData[1], requestData[3] }) table.insert(queueOutRequest[1][Identifier], { requestData[1], requestData[3] })
end requestData[3] = nil
clientRequestQueue[Identifier] = nil
end
if callback then
if incoming_cache[Identifier] then
for _, packet in incoming_cache[Identifier] do
if #packet == 0 then continue end
for _, fn: any in callback do
for i=1,#packet do
Spawn(fn, table.unpack(packet[i] or {}))
end end
end end
end
incoming_cache[Identifier] = nil -- Unreliable & Reliable
end local callback = clientCallback[Identifier] or nil
if queueIn[Identifier] then if not callback then continue end
for _, packedDatas: any in queueIn[Identifier] do
if #packedDatas == 0 then continue end -- Return Invoke
for _, fn: any in callback do
for i=1,#packedDatas do
Spawn(fn, table.unpack(packedDatas[i] or {}))
end
end
end
queueIn[Identifier] = nil
end
if queueInRequest[1][Identifier] then if queueInRequest[1][Identifier] then
for _, packetDatas: any in queueInRequest[1][Identifier] do for _, packetDatas: any in queueInRequest[1][Identifier] do
if #packetDatas == 0 then continue end if #packetDatas == 0 then continue end
for _, fn: any in callback do for _, fn: any in callback do
for i=1,#packetDatas do for i=1,#packetDatas do
local packetData = packetDatas[i] if not packetDatas[i] then continue end
if not packetData then continue end local packetData1 = packetDatas[i][1]
local packetData2 = packetDatas[i][2]
Spawn(function() Spawn(function()
local requestReturn = { fn(table.unpack(packetData[2])) } local requestReturn = { fn(table.unpack(packetData2)) }
if not queueOutRequest[2][Identifier] then if not queueOutRequest[2][Identifier] then
queueOutRequest[2][Identifier] = {} queueOutRequest[2][Identifier] = {}
end end
table.insert(queueOutRequest[2][Identifier], { packetData[1], requestReturn }) table.insert(queueOutRequest[2][Identifier], { packetData1, requestReturn })
packetData1 = nil
packetData2 = nil
end) end)
end end
end end
end end
queueInRequest[1][Identifier] = nil queueInRequest[1][Identifier] = nil
end end
-- Call to Invoke
if queueInRequest[2][Identifier] then if queueInRequest[2][Identifier] then
if clientRequestQueue[Identifier] then if clientRequestQueue[Identifier] then
for _, packetDatas: any in queueInRequest[2][Identifier] do for _, packetDatas: any in queueInRequest[2][Identifier] do
@ -243,43 +229,17 @@ function ClientProcess.start()
queueInRequest[2][Identifier] = nil queueInRequest[2][Identifier] = nil
end end
end end
end
for Identifier: string, requestsData in queueOutRequest[1] do
if #requestsData == 0 then continue end
RequestEvent:FireServer(Buffer.revert(Identifier), "\1", requestsData)
if logger[Identifier] then
task.defer(Logger.write, Identifier, `state: out -> request -> {#requestsData} data.`)
end
queueOutRequest[1][Identifier] = nil
end
for Identifier: string, toReturnDatas in queueOutRequest[2] do
if #toReturnDatas == 0 then continue end
RequestEvent:FireServer(Buffer.revert(Identifier), "\0", toReturnDatas)
if logger[Identifier] then
task.defer(Logger.write, Identifier, `state: out -> return request -> {#toReturnDatas} data.`)
end
queueOutRequest[2][Identifier] = nil
end
end) end)
local function onClientNetworkReceive(Identifier: any, data: any) local function onClientNetworkReceive(Identifier: buffer | string, data: buffer, ref: { any }?)
if not Identifier or not data then return end if not Identifier or typeof(Identifier) ~= "buffer" or not data or typeof(data) ~= "buffer" then return end
Identifier = Buffer.convert(Identifier) Identifier = Buffer.convert(Identifier)
if not queueIn[Identifier] then if not registeredIdentifier[Identifier :: string] then return end
queueIn[Identifier] = {} local read = Buffer.read(data, ref)
end if not read then return end
if not clientCallback[Identifier] then local callback = clientCallback[Identifier :: string]
if not incoming_cache[Identifier] then if not callback then return end
incoming_cache[Identifier] = {} for _, fn: any in callback do
end Spawn(fn, table.unpack(read))
table.insert(incoming_cache[Identifier], data)
if logger[Identifier] then
task.defer(Logger.write, Identifier, `state: cache -> net -> {#data} data.`)
end
return
end
table.insert(queueIn[Identifier], data)
if logger[Identifier] then
task.defer(Logger.write, Identifier, `state: in -> net -> {#data} data.`)
end end
end end
ReliableEvent.OnClientEvent:Connect(onClientNetworkReceive) ReliableEvent.OnClientEvent:Connect(onClientNetworkReceive)
@ -298,9 +258,6 @@ function ClientProcess.start()
end end
table.insert(queueInRequest[2][Identifier], data) table.insert(queueInRequest[2][Identifier], data)
end end
if logger[Identifier] then
task.defer(Logger.write, Identifier, `state: in -> request -> {#data} data.`)
end
end) end)
end end

View file

@ -18,23 +18,18 @@ function Client.new(Identifier: string, conf: Type.ClientConf?)
local self = setmetatable({}, Client) local self = setmetatable({}, Client)
self._buffer = Buffer.new() self._buffer = Buffer.new()
self._buffer:wu8(Serdes(Identifier, conf and conf.yieldWait)) self._buffer:wu8(Serdes.increment(Identifier, conf and conf.yieldWait))
self.id = Buffer.convert(self._buffer:build()) self.id = Buffer.convert(self._buffer:build())
self.fn = {} self.fn = {}
self._conf = table.freeze(conf or {}) self._conf = table.freeze(conf or {})
self.IsConnected = false self.IsConnected = false
ClientProcess.add(self.id, Identifier, conf or { yieldWait = 10, logging = { store = false, opt = false } }) ClientProcess.add(self.id, Identifier, conf or { yieldWait = 10 })
self._buffer:remove() self._buffer:remove()
return self return self
end end
function Client:logs()
Assert(self._conf.logging, "[Client]: Event is not configured with logging.")
return ClientProcess.getlogs(self.id)
end
function Client:Fire(reliable: boolean,...: any) function Client:Fire(reliable: boolean,...: any)
ClientProcess.insertQueue(self.id, reliable, ...) ClientProcess.insertQueue(self.id, reliable, ...)
end end
@ -55,7 +50,7 @@ function Client:Once(callback: (args: any) -> ()): string
local key = tostring(Key()) local key = tostring(Key())
table.insert(self.fn, key) table.insert(self.fn, key)
self.IsConnected = #self.fn > 0 self.IsConnected = #self.fn > 0
ClientProcess.addCallback(self.id, key, function(...) ClientProcess.addCallback(self.id, key, function(...: any?)
self:Disconnect(key) self:Disconnect(key)
task.spawn(callback, ...) task.spawn(callback, ...)
end) end)
@ -76,16 +71,19 @@ function Client:DisconnectAll()
end end
end end
function Client:Disconnect(key: string): boolean function Client:Disconnect(key: string)
Assert(typeof(key) == "string", "Key must be a string type.") Assert(typeof(key) == "string", "Key must be a string type.")
ClientProcess.removeCallback(self.id, key) ClientProcess.removeCallback(self.id, key)
table.remove(self.fn, table.find(self.fn, key)) table.remove(self.fn, table.find(self.fn, key))
self.IsConnected = #self.fn > 0 self.IsConnected = #self.fn > 0
return table.find(self.fn, key) == nil
end end
function Client:Destroy() function Client:Destroy()
self:DisconnectAll() self:DisconnectAll()
self._buffer:remove()
ClientProcess.remove(self.id)
Serdes.decrement()
table.clear(self)
setmetatable(self, nil) setmetatable(self, nil)
end end

View file

@ -1,5 +1,4 @@
--!strict --!strict
--!native
--!optimize 2 --!optimize 2
local RunService = game:GetService("RunService") local RunService = game:GetService("RunService")
local Type = require(script.Parent.Type) local Type = require(script.Parent.Type)

View file

@ -18,7 +18,7 @@ function Server.new(Identifier: string, conf: Type.ServerConf?)
local self = setmetatable({}, Server) local self = setmetatable({}, Server)
self._buffer = Buffer.new() self._buffer = Buffer.new()
self._buffer:wu8(Serdes(Identifier)) self._buffer:wu8(Serdes.increment(Identifier))
self.id = Buffer.convert(self._buffer:build()) self.id = Buffer.convert(self._buffer:build())
self.fn = {} self.fn = {}
self._conf = table.freeze(conf or {}) self._conf = table.freeze(conf or {})
@ -30,11 +30,6 @@ function Server.new(Identifier: string, conf: Type.ServerConf?)
return self return self
end end
function Server:logs()
Assert(self._conf.logging, "[Server]: Event is not configured with logging.")
return ServerProcess.getlogs(self.id)
end
function Server:Fire(reliable: boolean, player: Player, ...: any) function Server:Fire(reliable: boolean, player: Player, ...: any)
ServerProcess.insertQueue(self.id, reliable, player, ...) ServerProcess.insertQueue(self.id, reliable, player, ...)
end end
@ -75,9 +70,9 @@ function Server:Once(callback: (plyer: Player, args: any) -> ()): string
local key = tostring(Key()) local key = tostring(Key())
table.insert(self.fn, key) table.insert(self.fn, key)
self.IsConnected = #self.fn > 0 self.IsConnected = #self.fn > 0
ServerProcess.addCallback(self.id, key, function(...) ServerProcess.addCallback(self.id, key, function(player: Player, ...: any?)
self:Disconnect(key) self:Disconnect(key)
task.spawn(callback, ...) task.spawn(callback, player, ...)
end) end)
return key return key
end end
@ -106,6 +101,10 @@ end
function Server:Destroy() function Server:Destroy()
self:DisconnectAll() self:DisconnectAll()
self._buffer:remove()
ServerProcess.remove(self.id)
Serdes.decrement()
table.clear(self)
setmetatable(self, nil) setmetatable(self, nil)
end end

View file

@ -1,34 +0,0 @@
--!strict
--!native
--!optimize 2
local Logger = {}
local Logs: {
[string]: {
[string]: string
}
} = {}
local logging: {
[string]: boolean
} = {}
local now = tick()
function Logger.write(Identifier: string, text: string, log: boolean?)
if not Logs[Identifier] then
Logs[Identifier] = {}
end
if log ~= nil then
logging[Identifier] = log
end
now = tick()
Logs[Identifier][tostring(now)] = text
if logging[Identifier] then
print(`[{now}] ->`, text)
end
end
function Logger.read(Identifier: string)
return Logs[Identifier]
end
return Logger

View file

@ -13,24 +13,18 @@ local Spawn = require(Util.Spawn)
local Key = require(Util.Key) local Key = require(Util.Key)
local RateLimit = require(Util.RateLimit) local RateLimit = require(Util.RateLimit)
local Buffer = require(Util.Buffer) local Buffer = require(Util.Buffer)
local Logger = require(script.Logger)
local serverQueue: Type.QueueMap = {} local serverQueue: Type.QueueMap = {}
local unreliableServerQueue: Type.QueueMap = {} local unreliableServerQueue: Type.QueueMap = {}
local serverCallback: Type.CallbackMap = {} local serverCallback: Type.CallbackMap = {}
local serverRequestQueue: Type.QueueMap = {} local serverRequestQueue: Type.QueueMap = {}
local registeredIdentifier: { string } = {} local registeredIdentifier: { [string]: boolean } = {}
local queueOut: { local queueOut: {
[Player]: { [Player]: {
[string]: {any}, [string]: {any},
} }
} = {} } = {}
local queueIn: {
[string]: {
[Player]: {any},
}
} = {}
local queueInRequest: { local queueInRequest: {
[number]: { [number]: {
[string]: { [string]: {
@ -45,12 +39,6 @@ local queueOutRequest: {
} }
} }
} = {} } = {}
local logger: {
[string]: boolean
} = {}
local players: {
Player
} = {}
queueInRequest[1] = {} queueInRequest[1] = {}
queueInRequest[2] = {} queueInRequest[2] = {}
@ -61,22 +49,29 @@ local ReliableEvent = Event.Reliable
local UnreliableEvent = Event.Unreliable local UnreliableEvent = Event.Unreliable
local RequestEvent = Event.Request local RequestEvent = Event.Request
RateLimit.Protect()
local function initializeEachPlayer(player: Player) local function initializeEachPlayer(player: Player)
if not player then return end if not player then return end
if not queueOut[player] then if not queueOut[player] then
queueOut[player] = {} queueOut[player] = {}
end end
for Identifier: string in registeredIdentifier do
for _, Identifier: string in registeredIdentifier do
if not player then break end if not player then break end
if not queueOut[player][Identifier] then if not queueOut[player][Identifier] then
queueOut[player][Identifier] = {} queueOut[player][Identifier] = {}
end end
if not serverRequestQueue[Identifier] then
serverRequestQueue[Identifier] = {}
end
if not serverRequestQueue[Identifier][player] then if not serverRequestQueue[Identifier][player] then
serverRequestQueue[Identifier][player] = {} serverRequestQueue[Identifier][player] = {}
end end
if not queueIn[Identifier][player] then if not queueOutRequest[1][Identifier] then
queueIn[Identifier][player] = {} queueOutRequest[1][Identifier] = {}
end
if not queueOutRequest[2][Identifier] then
queueOutRequest[2][Identifier] = {}
end end
if not queueInRequest[1][Identifier][player] then if not queueInRequest[1][Identifier][player] then
queueInRequest[1][Identifier][player] = {} queueInRequest[1][Identifier][player] = {}
@ -90,15 +85,44 @@ local function initializeEachPlayer(player: Player)
end end
Players.PlayerAdded:Connect(initializeEachPlayer) Players.PlayerAdded:Connect(initializeEachPlayer)
Players.PlayerRemoving:Connect(function(player: Player)
if not player then return end
if queueOut[player] then
queueOut[player] = nil
end
for _, map in { serverQueue, unreliableServerQueue, serverRequestQueue } do
for Identifier: string in map do
map[Identifier][player] = nil
end
end
for i=1,2 do
for Identifier: string in queueInRequest[i] do
if queueInRequest[i][Identifier][player] then
queueInRequest[i][Identifier][player] = nil
end
end
for Identifier: string in queueOutRequest[i] do
if queueOutRequest[i][Identifier][player] then
queueOutRequest[i][Identifier][player] = nil
end
end
end
end)
function ServerProcess.insertQueue(Identifier: string, reliable: boolean, player: Player, ...: any) function ServerProcess.insertQueue(Identifier: string, reliable: boolean, player: Player, ...: any)
if not reliable then if not reliable then
if not unreliableServerQueue[Identifier] then
unreliableServerQueue[Identifier] = {}
end
if not unreliableServerQueue[Identifier][player] then if not unreliableServerQueue[Identifier][player] then
unreliableServerQueue[Identifier][player] = {} unreliableServerQueue[Identifier][player] = {}
end end
table.insert(unreliableServerQueue[Identifier][player], { ... }) table.insert(unreliableServerQueue[Identifier][player], { ... })
return return
end end
if not serverQueue[Identifier] then
serverQueue[Identifier] = {}
end
if not serverQueue[Identifier][player] then if not serverQueue[Identifier][player] then
serverQueue[Identifier][player] = {} serverQueue[Identifier][player] = {}
end end
@ -106,12 +130,12 @@ function ServerProcess.insertQueue(Identifier: string, reliable: boolean, player
end end
function ServerProcess.insertRequest(Identifier: string, timeout: number, player: Player, ...: any) function ServerProcess.insertRequest(Identifier: string, timeout: number, player: Player, ...: any)
if not serverQueue[Identifier][player] then
serverQueue[Identifier][player] = {}
end
if not serverRequestQueue[Identifier] then if not serverRequestQueue[Identifier] then
serverRequestQueue[Identifier] = {} serverRequestQueue[Identifier] = {}
end end
if not serverRequestQueue[Identifier][player] then
serverRequestQueue[Identifier][player] = {}
end
local yieldThread: thread, start = coroutine.running(), os.clock() local yieldThread: thread, start = coroutine.running(), os.clock()
local cancel = task.delay(timeout, function() local cancel = task.delay(timeout, function()
task.spawn(yieldThread, nil) task.spawn(yieldThread, nil)
@ -125,12 +149,11 @@ function ServerProcess.insertRequest(Identifier: string, timeout: number, player
end end
function ServerProcess.add(Identifier: string, originId: string, conf: Type.ServerConf) function ServerProcess.add(Identifier: string, originId: string, conf: Type.ServerConf)
if not table.find(registeredIdentifier, Identifier) then if not registeredIdentifier[Identifier] then
table.insert(registeredIdentifier, Identifier) registeredIdentifier[Identifier] = true
RateLimit.create(originId, conf.rateLimit and conf.rateLimit.maxEntrance or 200, conf.rateLimit and conf.rateLimit.interval or 2) RateLimit.create(originId, conf.rateLimit and conf.rateLimit.maxEntrance or 200, conf.rateLimit and conf.rateLimit.interval or 2)
if conf.logging then
ServerProcess.logger(Identifier, conf.logging.store, conf.logging.opt)
end
if not serverQueue[Identifier] then if not serverQueue[Identifier] then
serverQueue[Identifier] = {} serverQueue[Identifier] = {}
end end
@ -144,9 +167,6 @@ function ServerProcess.add(Identifier: string, originId: string, conf: Type.Serv
unreliableServerQueue[Identifier] = {} unreliableServerQueue[Identifier] = {}
end end
if not queueIn[Identifier] then
queueIn[Identifier] = {}
end
if not queueInRequest[1][Identifier] then if not queueInRequest[1][Identifier] then
queueInRequest[1][Identifier] = {} queueInRequest[1][Identifier] = {}
end end
@ -166,124 +186,130 @@ function ServerProcess.add(Identifier: string, originId: string, conf: Type.Serv
end end
end end
function ServerProcess.logger(Identifier: string, store: boolean, log: boolean) function ServerProcess.remove(Identifier: string)
logger[Identifier] = store if not registeredIdentifier[Identifier] then return end
Logger.write(Identifier, `state: change -> {log == true and "enabled" or "disabled"} logger.`, log) registeredIdentifier[Identifier] = nil
end serverQueue[Identifier] = nil
serverRequestQueue[Identifier] = nil
function ServerProcess.getlogs(Identifier: string) serverCallback[Identifier] = nil
return Logger.read(Identifier) unreliableServerQueue[Identifier] = nil
queueInRequest[1][Identifier] = nil
queueInRequest[2][Identifier] = nil
queueOutRequest[1][Identifier] = nil
queueOutRequest[2][Identifier] = nil
end end
function ServerProcess.addCallback(Identifier: string, key: string, callback) function ServerProcess.addCallback(Identifier: string, key: string, callback)
serverCallback[Identifier][key] = callback serverCallback[Identifier][key] = callback
if logger[Identifier] then
task.defer(Logger.write, Identifier, `state: change -> new callback added.`)
end
end end
function ServerProcess.removeCallback(Identifier: string, key: string) function ServerProcess.removeCallback(Identifier: string, key: string)
serverCallback[Identifier][key] = nil serverCallback[Identifier][key] = nil
if logger[Identifier] then
task.defer(Logger.write, Identifier, `state: change -> removed a callback.`)
end
end end
function ServerProcess.start() function ServerProcess.start()
debug.setmemorycategory("Warp") debug.setmemorycategory("Warp")
RunService.PostSimulation:Connect(function() RunService.PostSimulation:Connect(function()
-- Unreliable
for Identifier: string, players in unreliableServerQueue do for Identifier: string, players in unreliableServerQueue do
for player: Player, content: any in players do for player: Player, content: any in players do
if #content == 0 then continue end if #content == 0 then continue end
UnreliableEvent:FireClient(player, Buffer.revert(Identifier), content) for _, unpacked in content do
if logger[Identifier] then UnreliableEvent:FireClient(player, Buffer.revert(Identifier), Buffer.write(unpacked))
task.defer(Logger.write, Identifier, `state: out -> unreliable -> {#content} data.`)
end end
unreliableServerQueue[Identifier][player] = nil unreliableServerQueue[Identifier][player] = nil
end end
unreliableServerQueue[Identifier] = nil
end end
-- Reliable
for Identifier: string, contents: { [Player]: { any } } in serverQueue do for Identifier: string, contents: { [Player]: { any } } in serverQueue do
for player, content: any in contents do
for player: Player, requestsData: any in queueOutRequest[1][Identifier] do if #content > 0 and queueOut[player] then
for _, unpacked in content do
ReliableEvent:FireClient(player, Buffer.revert(Identifier), Buffer.write(unpacked))
end
end
serverQueue[Identifier][player] = nil
end
serverQueue[Identifier] = nil
end
-- Sent new invokes
for Identifier: string, contents in queueOutRequest[1] do
for player: Player, requestsData: any in contents do
if #requestsData > 0 then if #requestsData > 0 then
RequestEvent:FireClient(player, Buffer.revert(Identifier), "\1", requestsData) RequestEvent:FireClient(player, Buffer.revert(Identifier), "\1", requestsData)
if logger[Identifier] then
task.defer(Logger.write, Identifier, `state: out -> request -> {#requestsData} data.`)
end
end end
queueOutRequest[1][Identifier][player] = nil queueOutRequest[1][Identifier][player] = nil
end end
queueOutRequest[1][Identifier] = nil
for player: Player, toReturnDatas: any in queueOutRequest[2][Identifier] do end
-- Sent returning invokes
for Identifier: string, contents in queueOutRequest[2] do
for player: Player, toReturnDatas: any in contents do
if #toReturnDatas > 0 then if #toReturnDatas > 0 then
RequestEvent:FireClient(player, Buffer.revert(Identifier), "\0", toReturnDatas) RequestEvent:FireClient(player, Buffer.revert(Identifier), "\0", toReturnDatas)
if logger[Identifier] then
task.defer(Logger.write, Identifier, `state: out -> return request -> {#toReturnDatas} data.`)
end
end end
queueOutRequest[2][Identifier][player] = nil queueOutRequest[2][Identifier][player] = nil
end end
queueOutRequest[2][Identifier] = nil
local callback = serverCallback[Identifier] or nil
for player, content: any in contents do
if #content > 0 and queueOut[player] then
ReliableEvent:FireClient(player, Buffer.revert(Identifier), content)
end end
serverQueue[Identifier][player] = nil
if serverRequestQueue[Identifier][player] then for Identifier: string in registeredIdentifier do
for _, requestData in serverRequestQueue[Identifier][player] do if serverRequestQueue[Identifier] then
for player, content in serverRequestQueue[Identifier] do
if #content == 0 then serverRequestQueue[Identifier][player] = nil continue end
for _, requestData in content do
if not requestData[3] then continue end if not requestData[3] then continue end
if not queueOutRequest[1][Identifier] then
queueOutRequest[1][Identifier] = {}
end
if not queueOutRequest[1][Identifier][player] then if not queueOutRequest[1][Identifier][player] then
queueOutRequest[1][Identifier][player] = {} queueOutRequest[1][Identifier][player] = {}
end end
table.insert(queueOutRequest[1][Identifier][player], { requestData[1], requestData[3] }) table.insert(queueOutRequest[1][Identifier][player], { requestData[1], requestData[3] })
requestData[3] = nil
end
end end
serverRequestQueue[Identifier][player] = nil
end end
if callback then local callback = serverCallback[Identifier] or nil
local requestIn1: any = queueInRequest[1][Identifier][player] if not callback then continue end
local requestIn2: any = queueInRequest[2][Identifier][player]
local incoming: any = queueIn[Identifier][player]
if incoming then -- Return Invoke
for _, packedDatas: any in incoming do for player, content in queueInRequest[1][Identifier] do
if #packedDatas == 0 then continue end if not callback then break end
for _, fn: any in callback do for _, packetDatas in content do
for i=1,#packedDatas do if not callback then break end
Spawn(fn, player, table.unpack(packedDatas[i] or {}))
end
end
end
incoming = nil
queueIn[Identifier][player] = nil
end
if requestIn1 then
for _, packetDatas: any in requestIn1 do
if #packetDatas == 0 then continue end if #packetDatas == 0 then continue end
for _, fn: any in callback do for _, fn: any in callback do
for i=1,#packetDatas do for i=1,#packetDatas do
local packetData = packetDatas[i] if not packetDatas[i] then continue end
if not packetData then continue end local packetData1 = packetDatas[i][1]
local packetData2 = packetDatas[i][2]
Spawn(function() Spawn(function()
local requestReturn = { fn(player, table.unpack(packetData[2])) } local requestReturn = { fn(player, table.unpack(packetData2)) }
local state = queueOutRequest[2][Identifier][player] if not queueOutRequest[2][Identifier] then
queueOutRequest[2][Identifier] = {}
end
if not queueOutRequest[2][Identifier][player] then if not queueOutRequest[2][Identifier][player] then
queueOutRequest[2][Identifier][player] = {} queueOutRequest[2][Identifier][player] = {}
end end
table.insert(queueOutRequest[2][Identifier][player], { packetData[1], requestReturn }) table.insert(queueOutRequest[2][Identifier][player], { packetData1, requestReturn })
packetData1 = nil
packetData2 = nil
end) end)
end end
end end
end end
requestIn1 = nil
queueInRequest[1][Identifier][player] = nil queueInRequest[1][Identifier][player] = nil
end end
if requestIn2 then
for _, packetDatas: any in requestIn2 do -- Call to Invoke
for player, content in queueInRequest[2][Identifier] do
if not callback then break end
for _, packetDatas in content do
for _, packetData in packetDatas do for _, packetData in packetDatas do
if not callback then break end
if #packetData == 1 then continue end if #packetData == 1 then continue end
local data = serverRequestQueue[Identifier][player] local data = serverRequestQueue[Identifier][player]
for i=1,#data do for i=1,#data do
@ -297,29 +323,21 @@ function ServerProcess.start()
end end
end end
end end
requestIn2 = nil
queueInRequest[2][Identifier][player] = nil queueInRequest[2][Identifier][player] = nil
end end
end end
end
end
end) end)
local function onServerNetworkReceive(player: Player, Identifier: any, data: any) local function onServerNetworkReceive(player: Player, Identifier: buffer | string, data: buffer, ref: { any }?)
if not Identifier or not data then return end if not Identifier or typeof(Identifier) ~= "buffer" or not data or typeof(data) ~= "buffer" then return end
Identifier = Buffer.convert(Identifier) Identifier = Buffer.convert(Identifier :: buffer)
if not serverQueue[Identifier] then if not registeredIdentifier[Identifier :: string] then return end
serverQueue[Identifier] = {} local read = Buffer.read(data, ref)
if not read then return end
local callback = serverCallback[Identifier :: string]
if not callback then return end
for _, fn: any in callback do
Spawn(fn, player, table.unpack(read))
end end
if not serverQueue[Identifier][player] then
serverQueue[Identifier][player] = {}
end
if not queueIn[Identifier][player] then
queueIn[Identifier][player] = {}
end
if logger[Identifier] then
task.defer(Logger.write, Identifier, `state: in -> net -> {#data} data.`)
end
table.insert(queueIn[Identifier][player], data)
end end
ReliableEvent.OnServerEvent:Connect(onServerNetworkReceive) ReliableEvent.OnServerEvent:Connect(onServerNetworkReceive)
UnreliableEvent.OnServerEvent:Connect(onServerNetworkReceive) UnreliableEvent.OnServerEvent:Connect(onServerNetworkReceive)
@ -328,8 +346,13 @@ function ServerProcess.start()
Identifier = Buffer.convert(Identifier) Identifier = Buffer.convert(Identifier)
if not queueInRequest[1][Identifier][player] then if not queueInRequest[1][Identifier][player] then
queueInRequest[1][Identifier][player] = {} queueInRequest[1][Identifier][player] = {}
end
if not queueInRequest[2][Identifier][player] then
queueInRequest[2][Identifier][player] = {} queueInRequest[2][Identifier][player] = {}
end end
if not serverQueue[Identifier] then
serverQueue[Identifier] = {}
end
if not serverQueue[Identifier][player] then if not serverQueue[Identifier][player] then
serverQueue[Identifier][player] = {} serverQueue[Identifier][player] = {}
end end
@ -338,9 +361,6 @@ function ServerProcess.start()
else else
table.insert(queueInRequest[2][Identifier][player], data) table.insert(queueInRequest[2][Identifier][player], data)
end end
if logger[Identifier] then
task.defer(Logger.write, Identifier, `state: in -> request -> {#data} data.`)
end
end) end)
end end

View file

@ -12,6 +12,7 @@ function Dedicated.new(signal: any, handler: (...any) -> ())
end end
function Dedicated:Disconnect() function Dedicated:Disconnect()
table.clear(self)
setmetatable(self, nil) setmetatable(self, nil)
end end

View file

@ -22,15 +22,15 @@ function Signal.new(Identifier: string)
return Signals[Identifier] return Signals[Identifier]
end end
function Signal:Connect(fn: (...any) -> ()): string function Signal:Connect(fn: (...any) -> (), optKey: string?): string
local key = tostring(Key()) local key: typeof(Signal) = optKey or tostring(Key()) :: any
self[key] = DedicatedSignal(self, fn) self[key] = DedicatedSignal(self, fn)
return key return key :: any
end end
function Signal:Once(fn: (...any) -> ()): string function Signal:Once(fn: (...any) -> ()): string
local key: string local key: string
key = self:Connect(function(...) key = self:Connect(function(...: any)
self:Disconnect(key) self:Disconnect(key)
task.spawn(fn, ...) task.spawn(fn, ...)
end) end)
@ -38,7 +38,9 @@ function Signal:Once(fn: (...any) -> ()): string
end end
function Signal:Disconnect(key: string) function Signal:Disconnect(key: string)
if not self[key] then return end
self[key]:Disconnect() self[key]:Disconnect()
self[key] = nil
end end
function Signal:DisconnectAll(): () function Signal:DisconnectAll(): ()
@ -46,13 +48,19 @@ function Signal:DisconnectAll(): ()
end end
function Signal:Wait(): number function Signal:Wait(): number
local thread, t = coroutine.running(), os.clock() local t, thread = os.clock(), coroutine.running()
self:Once(function() self:Once(function()
task.spawn(thread, os.clock()-t) task.spawn(thread, os.clock()-t)
end) end)
return coroutine.yield() return coroutine.yield()
end end
function Signal:DeferFire(...: any): ()
for _, handle in self do
task.defer(handle.fn, ...)
end
end
function Signal:Fire(...: any): () function Signal:Fire(...: any): ()
for _, handle in self do for _, handle in self do
task.spawn(handle.fn, ...) task.spawn(handle.fn, ...)

View file

@ -4,44 +4,41 @@ type rateLimitArg = {
interval: number?, interval: number?,
} }
type logging = {
store: boolean,
opt: boolean,
}
export type ServerConf = { export type ServerConf = {
rateLimit: rateLimitArg?, rateLimit: rateLimitArg?,
logging: logging?,
} }
export type ClientConf = { export type ClientConf = {
yieldWait: number?, yieldWait: number?,
logging: logging?, }
export type Middleware = {
middleware: (self: Middleware, middleware: (...any) -> (...any)) -> (),
key: (self: Middleware) -> string,
destroy: (self: Middleware) -> (),
} }
export type Client = { export type Client = {
Fire: (self: Client, reliable: boolean, ...any) -> (), Fire: (self: Client, reliable: boolean, ...any) -> (),
Invoke: (self: Client, timeout: number, ...any) -> any, Invoke: (self: Client, timeout: number, ...any) -> any,
Connect: (self: Client, callback: (...any) -> ()) -> string, Connect: (self: Client, callback: (...any) -> ()) -> Middleware,
Once: (self: Client, callback: (player: Player, ...any) -> ()) -> string, Once: (self: Client, callback: (player: Player, ...any) -> ()) -> Middleware,
Disconnect: (self: Client, key: string) -> (), Disconnect: (self: Client, key: string) -> (),
DisconnectAll: (self: Client) -> (), DisconnectAll: (self: Client) -> (),
Wait: (self: Client) -> number, Wait: (self: Client) -> number,
Destroy: (self: Client) -> (), Destroy: (self: Client) -> (),
logs: (self: Client) -> any,
} }
export type Server = { export type Server = {
Fire: (self: Server, reliable: boolean, player: Player, ...any) -> (), Fire: (self: Server, reliable: boolean, player: Player, ...any) -> (),
Fires: (self: Server, reliable: boolean, ...any) -> (), Fires: (self: Server, reliable: boolean, ...any) -> (),
Invoke: (self: Server, timeout: number, player: Player, ...any) -> any, Invoke: (self: Server, timeout: number, player: Player, ...any) -> any,
Connect: (self: Server, callback: (player: Player, ...any) -> ()) -> string, Connect: (self: Server, callback: (player: Player, ...any) -> ()) -> Middleware,
Once: (self: Server, callback: (player: Player, ...any) -> ()) -> string, Once: (self: Server, callback: (player: Player, ...any) -> ()) -> Middleware,
Disconnect: (self: Server, key: string) -> (), Disconnect: (self: Server, key: string) -> (),
DisconnectAll: (self: Server) -> (), DisconnectAll: (self: Server) -> (),
Wait: (self: Server) -> number, Wait: (self: Server) -> number,
Destroy: (self: Server) -> (), Destroy: (self: Server) -> (),
logs: (self: Server) -> any,
} }
export type Signal = { export type Signal = {

View file

@ -1,6 +1,6 @@
--!strict --!strict
--!native --!native
--!optimize 2 --!optimize 2
return function(condition: (any), errorMessage: string?): () return function(condition: (any), errorMessage: string, level: number?): ()
if not (condition) then error(`Warp: {errorMessage}`, 2) end if not (condition) then error(`Warp: {errorMessage}`, level or 2) end
end end

View file

@ -16,7 +16,7 @@ local writef32 = buffer.writef32
local writef64 = buffer.writef64 local writef64 = buffer.writef64
local writestring = buffer.writestring local writestring = buffer.writestring
local default = { local default: { [string]: number } = {
point = 0, point = 0,
next = 0, next = 0,
size = 128, size = 128,
@ -35,17 +35,16 @@ function DedicatedBuffer.alloc(self: any, byte: number)
local size: number = self.size local size: number = self.size
local b: buffer = self.buffer local b: buffer = self.buffer
while self.next + byte >= size do
while self.point + byte >= size do size = math.floor(size * 1.25) -- +25% increase
size = math.floor(size * 1.5)
end end
local newBuffer: buffer = create(size) local newBuffer: buffer = create(size)
copy(newBuffer, 0, b) copy(newBuffer, 0, b)
b = newBuffer b = newBuffer
self.point = self.next self.point = self.next
self.buffer = b
self.next += byte self.next += byte
end end
@ -57,51 +56,62 @@ function DedicatedBuffer.build(self: any): buffer
return build return build
end end
function DedicatedBuffer.wi8(self: any, val: number) function DedicatedBuffer.buildAndRemove(self: any): (buffer, (any)?)
local p: number = self.next > self.point and self.next or self.point
local build: buffer = create(p)
local ref = #self.ref > 0 and table.clone(self.ref) or nil
copy(build, 0, self.buffer, 0, p)
self:remove()
return build, ref
end
function DedicatedBuffer.wi8(self: any, val: number, alloc: number?)
if not val then return end if not val then return end
self:alloc(1) self:alloc(alloc or 1)
writei8(self.buffer, self.point, val) writei8(self.buffer, self.point, val)
end end
function DedicatedBuffer.wi16(self: any, val: number) function DedicatedBuffer.wi16(self: any, val: number, alloc: number?)
if not val then return end if not val then return end
self:alloc(2) self:alloc(alloc or 2)
writei16(self.buffer, self.point, val) writei16(self.buffer, self.point, val)
end end
function DedicatedBuffer.wi32(self: any, val: number) function DedicatedBuffer.wi32(self: any, val: number, alloc: number?)
if not val then return end if not val then return end
self:alloc(4) self:alloc(alloc or 4)
writei32(self.buffer, self.point, val) writei32(self.buffer, self.point, val)
end end
function DedicatedBuffer.wu8(self: any, val: number) function DedicatedBuffer.wu8(self: any, val: number, alloc: number?)
if not val then return end if not val then return end
self:alloc(1) self:alloc(alloc or 1)
writeu8(self.buffer, self.point, val) writeu8(self.buffer, self.point, val)
end end
function DedicatedBuffer.wu16(self: any, val: number) function DedicatedBuffer.wu16(self: any, val: number, alloc: number?)
if not val then return end if not val then return end
self:alloc(2) self:alloc(alloc or 2)
writeu16(self.buffer, self.point, val) writeu16(self.buffer, self.point, val)
end end
function DedicatedBuffer.wu32(self: any, val: number) function DedicatedBuffer.wu32(self: any, val: number, alloc: number?)
if not val then return end if not val then return end
self:alloc(4) self:alloc(alloc or 4)
writeu32(self.buffer, self.point, val) writeu32(self.buffer, self.point, val)
end end
function DedicatedBuffer.wf32(self: any, val: number) function DedicatedBuffer.wf32(self: any, val: number, alloc: number?)
if not val then return end if not val then return end
self:alloc(4) self:alloc(alloc or 4)
writef32(self.buffer, self.point, val) writef32(self.buffer, self.point, val)
end end
function DedicatedBuffer.wf64(self: any, val: number) function DedicatedBuffer.wf64(self: any, val: number, alloc: number?)
if not val then return end if not val then return end
self:alloc(8) self:alloc(alloc or 8)
writef64(self.buffer, self.point, val) writef64(self.buffer, self.point, val)
end end
@ -111,11 +121,127 @@ function DedicatedBuffer.wstring(self: any, val: string)
writestring(self.buffer, self.point, val) writestring(self.buffer, self.point, val)
end end
function DedicatedBuffer.wType(self: any, ref: number)
writeu8(self.buffer, self.point, ref)
self.point += 1
end
function DedicatedBuffer.wRef(self: any, value: any, alloc: number?)
if not value then return end
self:alloc(alloc or 1)
table.insert(self.ref, value)
local index = #self.ref
writeu8(self.buffer, self.point, index)
self.point += 1
end
function DedicatedBuffer.pack(self: any, data: {any})
if typeof(data) == "nil" then
self:wi8(0)
elseif typeof(data) == "Instance" then
self:wi8(-1) -- Instance marker
self:wRef(data)
elseif typeof(data) == "table" then
--local isArray = (next(data) ~= nil and #data > 0) and true or false
local isArray = true
local count = 0
for k in data do
count += 1
if typeof(k) ~= "number" or math.floor(k) ~= k then
isArray = false
end
end
if isArray then
self:wi8(-2) -- array marker
self:wu16(count) -- use 32-bit length
for _, v in data do
self:pack(v)
end
else
self:wi8(-3) -- dictionary marker
self:wu16(count) -- number of key-value pairs
for k, v in data do
self:pack(k) -- pack the key
self:pack(v) -- pack the value
end
end
elseif typeof(data) == "EnumItem" then
self:wi8(-4)
self:wi8(#`{data.EnumType}`)
self:wstring(`{data.EnumType}`)
self:wu8(data.Value)
elseif typeof(data) == "BrickColor" then
self:wi8(-5)
self:wi16(data.Number)
elseif typeof(data) == "Enum" then
self:wi8(-6)
self:wi8(#`{data}`)
self:wstring(`{data}`)
elseif typeof(data) == "number" then
if math.floor(data) == data then -- Integer
if data >= 0 and data <= 255 then
self:wi8(1) -- u8 marker
self:wu8(data)
elseif data >= -32768 and data <= 32767 then
self:wi8(2) -- i16 marker
self:wi16(data)
elseif data >= -2147483647 and data <= 2147483647 then
self:wi8(3) -- i32 marker
self:wi32(data)
else
self:wi8(4) -- f64 marker
self:wf64(data)
end
else
self:wi8(4) -- f64 marker
self:wf64(data)
end
elseif typeof(data) == "boolean" then
self:wi8(5) -- boolean marker
self:wu8(data and 1 or 0)
elseif typeof(data) == "string" then
local length = #data
if length <= 255 then
self:wi8(6)
self:wu8(length)
elseif length <= 65535 then
self:wi8(7)
self:wu16(length)
else
self:wi8(8)
self:wi32(length)
end
self:wstring(data)
elseif typeof(data) == "Vector3" then
self:wi8(9) -- Vector3 marker
self:wf32(data.X)
self:wf32(data.Y)
self:wf32(data.Z)
elseif typeof(data) == "Vector2" then
self:wi8(10) -- Vector2 marker
self:wf32(data.X)
self:wf32(data.Y)
elseif typeof(data) == "CFrame" then
self:wi8(11) -- CFrame marker
for _, v in {data:GetComponents()} do
self:wf32(v)
end
elseif typeof(data) == "Color3" then
self:wi8(12) -- Color3 marker
self:wu8(data.R * 255)
self:wu8(data.G * 255)
self:wu8(data.B * 255)
else
warn(`Unsupported data type: {typeof(data)} value: {data}`)
end
end
function DedicatedBuffer.flush(self: any) function DedicatedBuffer.flush(self: any)
self.point = default.point self.point = default.point
self.next = default.next self.next = default.next
self.size = default.size self.size = default.size
self.buffer = create(default.bufferSize) self.buffer = create(default.bufferSize)
table.clear(self.ref)
end end
function DedicatedBuffer.new() function DedicatedBuffer.new()
@ -123,13 +249,17 @@ function DedicatedBuffer.new()
point = default.point, point = default.point,
next = default.next, next = default.next,
size = default.size, size = default.size,
buffer = create(default.bufferSize) buffer = create(default.bufferSize),
ref = {},
}, DedicatedBuffer) }, DedicatedBuffer)
end end
function DedicatedBuffer.remove(self: any) function DedicatedBuffer.remove(self: any)
self:flush() self:flush()
table.clear(self)
setmetatable(self, nil) setmetatable(self, nil)
end end
export type DedicatedType = typeof(DedicatedBuffer.new())
return DedicatedBuffer.new :: typeof(DedicatedBuffer.new) return DedicatedBuffer.new :: typeof(DedicatedBuffer.new)

View file

@ -8,8 +8,115 @@ local Dedicated = require(script.Dedicated)
local tostring = buffer.tostring local tostring = buffer.tostring
local fromstring = buffer.fromstring local fromstring = buffer.fromstring
local readu8 = buffer.readu8
local readi8 = buffer.readi8
local readu16 = buffer.readu16
local readi16 = buffer.readi16
local readi32 = buffer.readi32
local readf32 = buffer.readf32
local readf64 = buffer.readf64
local readstring = buffer.readstring
local len = buffer.len
function Buffer.new() local function readValue(b: buffer, position: number, ref: { any }?): (any, number)
local typeByte = readi8(b, position)
position += 1
if typeByte == 0 then -- nil
return nil, position
elseif typeByte == -1 then -- Instance
if not ref or #ref == 0 then
return nil, position + 1
end
local value = ref[readu8(b, position)]
if typeof(value) == "Instance" then
return value, position + 1
end
return nil, position + 1
elseif typeByte == -2 then -- array
local length = readu16(b, position)
position += 2
local array = {}
for _ = 1, length do
local value
value, position = readValue(b, position, ref)
table.insert(array, value)
end
return array, position
elseif typeByte == -3 then -- dictionary
local length = readu16(b, position)
position += 2
local dict = {}
for _ = 1, length do
local key, value
key, position = readValue(b, position, ref)
value, position = readValue(b, position, ref)
dict[key] = value
end
return dict, position
elseif typeByte == -4 then -- EnumItem
local length = readi8(b, position)
local value = readstring(b, position + 1, length)
local value2 = readu8(b, position + 1 + length)
return Enum[value]:FromValue(value2), position + 2 + length
elseif typeByte == -5 then -- BrickColor
local value = readi16(b, position)
return BrickColor.new(value), position + 2
elseif typeByte == -6 then -- Enum
local length = readi8(b, position)
local value = readstring(b, position + 1, length)
return Enum[value], position + 1 + length
elseif typeByte == 1 then -- int u8
local value = readu8(b, position)
return value, position + 1
elseif typeByte == 2 then -- int i16
local value = readi16(b, position)
return value, position + 2
elseif typeByte == 3 then -- int i32
local value = readi32(b, position)
return value, position + 4
elseif typeByte == 4 then -- f64
local value = readf64(b, position)
return value, position + 8
elseif typeByte == 5 then -- boolean
local value = readu8(b, position) == 1
return value, position + 1
elseif typeByte == 6 then -- string u8
local length = readu8(b, position)
local value = readstring(b, position + 1, length)
return value, position + length + 1
elseif typeByte == 7 then -- string u16
local length = readu16(b, position)
local value = readstring(b, position + 2, length)
return value, position + length + 2
elseif typeByte == 8 then -- string i32
local length = readi32(b, position)
local value = readstring(b, position + 4, length)
return value, position + length + 4
elseif typeByte == 9 then -- Vector3
local x = readf32(b, position)
local y = readf32(b, position + 4)
local z = readf32(b, position + 8)
return Vector3.new(x, y, z), position + 12
elseif typeByte == 10 then -- Vector2
local x = readf32(b, position)
local y = readf32(b, position + 8)
return Vector2.new(x, y), position + 8
elseif typeByte == 11 then -- CFrame
local components = {}
for i = 1, 12 do
table.insert(components, readf32(b, position + (i - 1) * 4))
end
return CFrame.new(unpack(components)), position + 48
elseif typeByte == 12 then -- Color3
local r = readu8(b, position)
local g = readu8(b, position + 1)
local b = readu8(b, position + 2)
return Color3.fromRGB(r, g, b), position + 3
end
error(`Unsupported type marker: {typeByte}`)
end
function Buffer.new(): Dedicated.DedicatedType
return Dedicated() return Dedicated()
end end
@ -21,4 +128,22 @@ function Buffer.revert(s: string): buffer
return fromstring(s) return fromstring(s)
end end
function Buffer.write(data: { any }): (buffer, (any)?)
local newBuffer = Dedicated()
newBuffer:pack(data)
return newBuffer:buildAndRemove()
end
function Buffer.read(b: buffer, ref: { any }?): any?
local position = 0
local result = {}
while position < len(b) do
local value
value, position = readValue(b, position, ref)
table.insert(result, value)
end
ref = nil
return table.unpack(result)
end
return Buffer :: typeof(Buffer) return Buffer :: typeof(Buffer)

View file

@ -1,6 +1,5 @@
--!strict --!strict
--!native
--!optimize 2 --!optimize 2
return function(): number? return function(): number?
return tonumber(string.sub(tostring(Random.new():NextNumber()), 3, 6)) -- 4 digits return tonumber(string.sub(tostring(Random.new():NextNumber()), 3, 8)) -- 6 digits
end end

View file

@ -1,25 +1,54 @@
--!strict --!strict
--!native
--!optimize 2 --!optimize 2
local RateLimit = {} local RateLimit = {}
local RunService = game:GetService("RunService") local RunService = game:GetService("RunService")
local Assert = require(script.Parent.Assert) local Assert = require(script.Parent.Assert)
local Event = require(script.Parent.Parent.Event).Reliable local Events = require(script.Parent.Parent.Event)
local Reliable, Unreliable, Request = Events.Reliable, Events.Unreliable, Events.Request
local Signal = require(script.Parent.Parent.Signal)("Warp_OnSpamSignal")
local map, activity, meta = {}, {}, {}
setmetatable(meta , {
__index = map,
__newindex = function(self, key, value)
if not activity[key] then
activity[key] = os.clock()
end
if (os.clock()-activity[key]) >= 1 then
activity[key] = os.clock()
map[key] = 1
return
end
if value >= 1e2 then -- 100
Signal:Fire(key)
return
end
map[key] = value
end,
})
local function onReceived(player: Player)
if not meta[player] then
meta[player] = 1
return
end
meta[player] += 1
end
function RateLimit.create(Identifier: string, entrance: number?, interval: number?) function RateLimit.create(Identifier: string, entrance: number?, interval: number?)
Assert(typeof(Identifier) == "string", "Identifier must a string type.") Assert(typeof(Identifier) == "string", "Identifier must a string type.")
if RunService:IsServer() then if RunService:IsServer() then
Assert(typeof(entrance) == "number", "entrance must a number type.") Assert(typeof(entrance) == "number", "entrance must a number type.")
Assert(entrance :: number > 0, "entrance must above 0.") Assert(entrance :: number > 0, "entrance must above 0.")
Event:SetAttribute(Identifier.."_ent", entrance) Reliable:SetAttribute(Identifier.."_ent", entrance)
Event:SetAttribute(Identifier.."_int", interval) Reliable:SetAttribute(Identifier.."_int", interval)
else else
while (not Event:GetAttribute(Identifier.."_ent")) or (not Event:GetAttribute(Identifier.."_int")) do while (not Reliable:GetAttribute(Identifier.."_ent")) or (not Reliable:GetAttribute(Identifier.."_int")) do
task.wait(0.5) task.wait(0.1)
end end
entrance = tonumber(Event:GetAttribute(Identifier.."_ent")) entrance = tonumber(Reliable:GetAttribute(Identifier.."_ent"))
interval = tonumber(Event:GetAttribute(Identifier.."_int")) interval = tonumber(Reliable:GetAttribute(Identifier.."_int"))
end end
local entrances: number = 0 local entrances: number = 0
return function(incoming: number?): boolean return function(incoming: number?): boolean
@ -33,4 +62,14 @@ function RateLimit.create(Identifier: string, entrance: number?, interval: numbe
end end
end end
function RateLimit.Protect()
if not RunService:IsServer() or Reliable:GetAttribute("Protected") or Unreliable:GetAttribute("Protected") or Request:GetAttribute("Protected") then return end
Reliable:SetAttribute("Protected", true)
Unreliable:SetAttribute("Protected", true)
Request:SetAttribute("Protected", true)
Reliable.OnServerEvent:Connect(onReceived)
Unreliable.OnServerEvent:Connect(onReceived)
Request.OnServerEvent:Connect(onReceived)
end
return RateLimit :: typeof(RateLimit) return RateLimit :: typeof(RateLimit)

View file

@ -1,18 +1,19 @@
--!strict --!strict
--!native
--!optimize 2 --!optimize 2
local SerDes = {}
local RunService = game:GetService("RunService") local RunService = game:GetService("RunService")
local SerInt = 0 local SerInt = 0
local Event = require(script.Parent.Parent.Event).Reliable local Event = require(script.Parent.Parent.Event).Reliable
local Assert = require(script.Parent.Assert) local Assert = require(script.Parent.Assert)
return function(Identifier: string, timeout: number?): number function SerDes.increment(Identifier: string, timeout: number?): number
Assert(typeof(Identifier) == "string", "Identifier must be a string type.") Assert(typeof(Identifier) == "string", "Identifier must be a string type.")
Assert(SerInt < 255, "reached max 255 identifiers.")
if RunService:IsServer() then if RunService:IsServer() then
Assert(SerInt < 255, "reached max 255 identifiers.")
if not Event:GetAttribute(Identifier) then if not Event:GetAttribute(Identifier) then
SerInt += 1 SerInt += 1
Event:SetAttribute(`{SerInt}`, Identifier)
Event:SetAttribute(Identifier, SerInt) Event:SetAttribute(Identifier, SerInt)
--Event:SetAttribute(Identifier, string.pack("I1", SerInt)) -- I1 -> 255 max, I2 -> ~ 6.5e4 max. (SerInt), removed/disabled for buffer migration. --Event:SetAttribute(Identifier, string.pack("I1", SerInt)) -- I1 -> 255 max, I2 -> ~ 6.5e4 max. (SerInt), removed/disabled for buffer migration.
end end
@ -20,11 +21,11 @@ return function(Identifier: string, timeout: number?): number
local yieldThread: thread = coroutine.running() local yieldThread: thread = coroutine.running()
local cancel = task.delay(timeout or 10, function() -- yield cancelation (timerout) local cancel = task.delay(timeout or 10, function() -- yield cancelation (timerout)
task.spawn(yieldThread, nil) task.spawn(yieldThread, nil)
error(`Serdes: {Identifier} is taking too long to retrieve, seems like not replicated on server.`, 2) error(`Serdes: {Identifier} is taking too long to retrieve, seems like it's not replicated on server.`, 2)
end) end)
task.spawn(function() task.spawn(function()
while coroutine.status(cancel) ~= "dead" and task.wait(0.5) do -- let it loop for yields! while coroutine.status(cancel) ~= "dead" and task.wait(0.04) do -- let it loop for yields! 1/24
if (Event:GetAttribute(Identifier)) then if Event:GetAttribute(Identifier) then
task.cancel(cancel) task.cancel(cancel)
task.spawn(yieldThread, Event:GetAttribute(Identifier)) task.spawn(yieldThread, Event:GetAttribute(Identifier))
break break
@ -35,3 +36,15 @@ return function(Identifier: string, timeout: number?): number
end end
return Event:GetAttribute(Identifier) return Event:GetAttribute(Identifier)
end end
function SerDes.decrement()
if not RunService:IsServer() or SerInt <= 0 then return end
local Identifier = Event:GetAttribute(`{SerInt}`)
if not Identifier then return end
Event:SetAttribute(`{Identifier}`, nil)
Event:SetAttribute(`{SerInt}`, nil)
SerInt -= 1
Identifier = nil
end
return SerDes :: typeof(SerDes)

View file

@ -1,30 +1,21 @@
--!native --!native
--!strict --!strict
--!optimize 2 --!optimize 2
local thread: thread? = nil local thread: thread?
local function passer(fn, ...): () local function passer<T...>(func: (T...) -> (), ...: T...): ()
local hold = thread local HoldThread: thread = thread :: thread
thread = nil thread = nil
fn(...) func(...)
thread = hold thread = HoldThread
end end
local function yield(): never local function newThread(): ()
while true do thread = coroutine.running()
passer(coroutine.yield()) while true do passer(coroutine.yield()) end
end
end end
if not thread then return function<T...>(func: (T...) -> (), ...: T...): ()
thread = coroutine.create(yield) if not thread then task.spawn(newThread) end
coroutine.resume(thread :: any, thread) task.spawn(thread :: thread, func, ...)
end
return function(fn: (...any) -> (...any?), ...: any): ()
if not thread then
thread = coroutine.create(yield)
coroutine.resume(thread :: any, thread)
end
task.spawn(thread :: thread, fn, ...)
end end

View file

@ -32,32 +32,32 @@ function Index.Client(Identifier: string, conf: Type.ClientConf?): Type.Client
return require(Client.Index)(Identifier, conf) :: Type.Client return require(Client.Index)(Identifier, conf) :: Type.Client
end end
function Index.fromServerArray(arrays: { any }): Type.fromServerArray function Index.fromServerArray(arrays: { string } | { [string]: Type.ServerConf }): Type.fromServerArray
Assert(IsServer, `[Warp]: Calling .fromServerArray({arrays}) on client side (expected server side)`) Assert(IsServer, `[Warp]: Calling .fromServerArray({arrays}) on client side (expected server side)`)
Assert(typeof(arrays) == "table", "[Warp]: Array must be a table type, got {typeof(arrays)}") Assert(typeof(arrays) == "table", "[Warp]: Array must be a table type, got {typeof(arrays)}")
local copy = {} local copy: { [string]: Type.Server } = {}
for param1: any, param2: any in arrays do for param1, param2: string | Type.ServerConf in arrays do
if typeof(param2) == "table" then if typeof(param2) == "table" then
copy[param1] = Index.Server(param1, param2) copy[param1] = Index.Server(param1, param2)
else else
copy[param2] = Index.Server(param2) copy[param2] = Index.Server(param2)
end end
end end
return copy :: typeof(copy) return copy
end end
function Index.fromClientArray(arrays: { any }): Type.fromClientArray function Index.fromClientArray(arrays: { string } | { [string]: Type.ClientConf }): Type.fromClientArray
Assert(not IsServer, `[Warp]: Calling .fromClientArray({arrays}) on server side (expected client side)`) Assert(not IsServer, `[Warp]: Calling .fromClientArray({arrays}) on server side (expected client side)`)
Assert(typeof(arrays) == "table", `[Warp]: Array must be a table type, got {typeof(arrays)}`) Assert(typeof(arrays) == "table", `[Warp]: Array must be a table type, got {typeof(arrays)}`)
local copy = {} local copy = {}
for param1: any, param2: any in arrays do for param1, param2: string | Type.ClientConf in arrays do
if typeof(param2) == "table" then if typeof(param2) == "table" then
copy[param1] = Index.Client(param1, param2) copy[param1] = Index.Client(param1, param2)
else else
copy[param2] = Index.Client(param2) copy[param2] = Index.Client(param2)
end end
end end
return copy :: typeof(copy) return copy
end end
function Index.Signal(Identifier: string) function Index.Signal(Identifier: string)

View file

@ -1,5 +1,5 @@
-- Warp Library (@Eternity_Devs) -- Warp Library (@Eternity_Devs)
-- version 1.0.10 -- version 1.0.14
--!strict --!strict
--!native --!native
--!optimize 2 --!optimize 2
@ -11,6 +11,8 @@ return {
fromServerArray = Index.fromServerArray, fromServerArray = Index.fromServerArray,
fromClientArray = Index.fromClientArray, fromClientArray = Index.fromClientArray,
OnSpamSignal = Index.OnSpamSignal,
Signal = Index.Signal, Signal = Index.Signal,
fromSignalArray = Index.fromSignalArray, fromSignalArray = Index.fromSignalArray,

View file

@ -1,10 +1,10 @@
[package] [package]
name = "imezx/warp" name = "imezx/warp"
version = "1.0.10" version = "1.0.14"
registry = "https://github.com/UpliftGames/wally-index" registry = "https://github.com/UpliftGames/wally-index"
realm = "shared" realm = "shared"
license = "MIT" license = "MIT"
exclude = ["node_modules", "docs", ".github", "*.rbxl", "*.rbxmx", "*.rbxml", "TestEZ", "test.project.json"] exclude = ["node_modules", "docs", ".github", "*.rbxl", "*.rbxmx", "*.rbxml", "*.rbxm", "TestEZ", "test.project.json"]
description = "A very-fast & powerful networking library for Roblox." description = "A very-fast & powerful networking library for Roblox."
[dependencies] [dependencies]