Compare commits

..

38 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
EternityDev
44fa07df85 update .rbxm file 2024-05-11 09:31:23 +07:00
EternityDev
839a7af667 v1.0.10 2024-05-11 09:29:53 +07:00
EternityDev
acb08a385c improved ClientProcess 2024-05-10 16:35:09 +07:00
EternityDev
6acf92d913 improved ServerProcess 2024-05-10 16:15:39 +07:00
EternityDev
b4ee5dc1e3 fix serdes 2024-05-10 13:07:10 +07:00
EternityDev
598c30c147 v1.0.10-test 2024-05-04 12:49:50 +07:00
EternityDev
0fb349fe0f minor improvement 2024-04-13 09:00:52 +07:00
EternityDev
4cd0f3f2cf set memory 2024-04-12 19:15:22 +07:00
EternityDev
8fd9573b8a fixes 2024-04-09 15:49:06 +07:00
EternityDev
739e13537d minor improvement 2024-04-08 12:18:13 +07:00
EternityDev
46bbe5feb0 Merge branch 'master' of https://github.com/imezx/Warp 2024-04-06 11:22:44 +07:00
EternityDev
0bc8f56e11 change 2024-04-06 11:21:35 +07:00
EternityDev
69fc64d134
Merge pull request #10 from SpiralAPI/master
fixed rojo sync errors as well as safety checks
2024-04-04 20:56:17 +07:00
SpiralAPI
c8199d5744 fixed rojo sync errors as well as safety checks
for wally.toml
2024-04-04 09:49:14 -04:00
EternityDev
ef4b741f85
Merge pull request #8 from xArshy/v1.0.9
Removed ClientProcess.luau
2024-04-02 20:07:35 +07:00
xArshy
e7b4ae2f61 Redudant ClientProcess.luau, removes conflict 2024-04-02 17:06:18 +04:00
33 changed files with 849 additions and 697 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

4
.gitignore vendored
View file

@ -1,4 +1,6 @@
node_modules node_modules
docs/.vitepress/dist docs/.vitepress/dist
docs/.vitepress/cache docs/.vitepress/cache
wally.lock wally.lock
TestEZ
test.project.json

BIN
Warp.rbxm

Binary file not shown.

View file

@ -3,5 +3,5 @@
# To add a new tool, add an entry to this table. # To add a new tool, add an entry to this table.
[tools] [tools]
rojo = "rojo-rbx/rojo@7.4.0" rojo = "rojo-rbx/rojo@7.4.1"
wally = "UpliftGames/wally@0.3.2" wally = "UpliftGames/wally@0.3.2"

View file

@ -1,12 +1,6 @@
{ {
"name": "Warp", "name": "Warp",
"tree": { "tree": {
"$className": "DataModel", "$path": "src"
"ReplicatedStorage": {
"$className": "ReplicatedStorage",
"Warp": {
"$path": "src"
}
}
} }
} }

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", {
maxEntrance = 50, -- maximum 50 fires. rateLimit = {
interval = 2, -- 2 seconds maxEntrance = 50, -- maximum 50 fires.
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"] = {
maxEntrance: 50, rateLimit = {
interval: 1, maxEntrance: 50,
interval: 1,
}
}, -- with rateLimit configuration }, -- with rateLimit configuration
"Remote2", -- without rateLimit configuration "Remote2", -- without rateLimit configuration
["Remote3"] = { ["Remote3"] = {
maxEntrance: 10, rateLimit = {
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

@ -67,4 +67,5 @@ Pong:Disconnect(connection1)
Pong:Destroy() 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,216 +0,0 @@
--!native
--!strict
--!optimize 2
local ClientProcess = {}
local RunService = game:GetService("RunService")
local Util = script.Parent.Parent.Util
local Type = require(script.Parent.Parent.Type)
local Event = require(script.Parent.Parent.Event)
local Spawn = require(Util.Spawn)
local Key = require(Util.Key)
local RateLimit = require(Util.RateLimit)
local Buffer = require(Util.Buffer)
local clientRatelimit: Type.StoredRatelimit = {}
local clientQueue: Type.QueueMap = {}
local unreliableClientQueue: Type.QueueMap = {}
local clientCallback: Type.CallbackMap = {}
local clientRequestQueue: Type.QueueMap = {}
local queueIn: {
[string]: {any}
} = {}
local queueInRequest: {
[number]: {
[string]: {
any
}
}
} = {}
local queueOutRequest: {
[number]: {
[string]: {
any
}
}
} = {}
local incoming_cache: {
[string]: {
any
}
} = {}
queueInRequest[1] = {}
queueInRequest[2] = {}
queueOutRequest[1] = {}
queueOutRequest[2] = {}
local ReliableEvent = Event.Reliable
local UnreliableEvent = Event.Unreliable
local RequestEvent = Event.Request
function ClientProcess.insertQueue(Identifier: string, reliable: boolean, ...: any)
if not reliable then
table.insert(unreliableClientQueue[Identifier], { ... })
return
end
table.insert(clientQueue[Identifier], { ... })
end
function ClientProcess.insertRequest(Identifier: string, timeout: number, ...: any)
local yieldThread: thread, start = coroutine.running(), os.clock()
local cancel = task.delay(timeout, function()
task.spawn(yieldThread, nil)
end)
table.insert(clientRequestQueue[Identifier], { tostring(Key()), function(...: any)
if (os.clock() - start) > timeout then return end
task.cancel(cancel)
task.spawn(yieldThread, ...)
end :: any, { ... } :: any })
return coroutine.yield()
end
function ClientProcess.add(Identifier: string, originId: string)
if not clientQueue[Identifier] then
clientRatelimit[Identifier] = RateLimit.create(originId)
clientQueue[Identifier] = {}
unreliableClientQueue[Identifier] = {}
clientRequestQueue[Identifier] = {}
clientCallback[Identifier] = {}
queueOutRequest[1][Identifier] = {}
queueOutRequest[2][Identifier] = {}
queueInRequest[1][Identifier] = {}
queueInRequest[2][Identifier] = {}
queueIn[Identifier] = {}
end
end
function ClientProcess.addCallback(Identifier: string, key: string, callback)
clientCallback[Identifier][key] = callback
end
function ClientProcess.removeCallback(Identifier: string, key: string)
clientCallback[Identifier][key] = nil
end
function ClientProcess.start()
RunService.PostSimulation:Connect(function()
for Identifier: string, data: any in unreliableClientQueue do
if #data == 0 then continue end
if clientRatelimit[Identifier](#data) then
UnreliableEvent:FireServer(Buffer.revert(Identifier), data)
end
table.clear(data)
end
for Identifier: string, data: any in clientQueue do
local callback = clientCallback[Identifier] or nil
if #data > 0 then
if clientRatelimit[Identifier](#data) then
ReliableEvent:FireServer(Buffer.revert(Identifier), data)
end
table.clear(data)
end
if #clientRequestQueue[Identifier] > 0 then
for _, requestData in clientRequestQueue[Identifier] do
if not requestData[3] then continue end
table.insert(queueOutRequest[1][Identifier], { requestData[1], requestData[3] })
table.remove(requestData, #requestData)
end
end
if incoming_cache[Identifier] then
for _, packet in incoming_cache[Identifier] do
if not queueIn[Identifier] then continue end
table.insert(queueIn[Identifier], table.clone(packet))
table.clear(incoming_cache[Identifier])
end
end
if callback then
if #queueIn[Identifier] > 0 then
for _, packedDatas: any in queueIn[Identifier] do
if #packedDatas == 0 then continue end
for _, fn: any in callback do
for i=1,math.min(1e3, #packedDatas) do
Spawn(fn, table.unpack(packedDatas[i] or {}))
end
end
end
table.clear(queueIn[Identifier])
end
if #queueInRequest[1][Identifier] > 0 then
for idx, packetDatas: any in queueInRequest[1][Identifier] do
if #packetDatas == 0 then continue end
for _, fn: any in callback do
for i=1,math.min(1e3, #packetDatas) do
local packetData = packetDatas[i]
if not packetData then continue end
Spawn(function()
local requestReturn = { fn(table.unpack(packetData[2])) }
table.insert(queueOutRequest[2][Identifier], { packetData[1], requestReturn })
end)
end
end
end
table.clear(queueInRequest[1][Identifier])
end
if #queueInRequest[2][Identifier] > 0 then
for _, packetDatas: any in queueInRequest[2][Identifier] do
for _, packetData in packetDatas do
if #packetData == 1 then continue end
for y=1, math.min(1e3, #clientRequestQueue[Identifier]) do
local clientRequest = clientRequestQueue[Identifier][y]
if not clientRequest then continue end
if clientRequest[1] == packetData[1] then
Spawn(clientRequest[2], table.unpack(packetData[2]))
table.remove(clientRequestQueue[Identifier], y)
break
end
end
end
end
table.clear(queueInRequest[2][Identifier])
end
end
end
for Identifier: string, requestsData in queueOutRequest[1] do
if #requestsData == 0 then continue end
RequestEvent:FireServer(Buffer.revert(Identifier), "\1", requestsData)
table.clear(queueOutRequest[1][Identifier])
end
for Identifier: string, requestsData in queueOutRequest[2] do
if #requestsData == 0 then continue end
RequestEvent:FireServer(Buffer.revert(Identifier), "\0", requestsData)
table.clear(queueOutRequest[2][Identifier])
end
end)
local function onClientNetworkReceive(Identifier: any, data: any)
if not Identifier or not data then return end
Identifier = Buffer.convert(Identifier)
if not queueIn[Identifier] then
queueIn[Identifier] = {}
end
if not clientCallback[Identifier] then
if not incoming_cache[Identifier] then
incoming_cache[Identifier] = {}
end
table.insert(incoming_cache[Identifier], data)
return
end
table.insert(queueIn[Identifier], data)
end
ReliableEvent.OnClientEvent:Connect(onClientNetworkReceive)
UnreliableEvent.OnClientEvent:Connect(onClientNetworkReceive)
RequestEvent.OnClientEvent:Connect(function(Identifier: any, action: string, returnDatas)
if not Identifier or not returnDatas then return end
Identifier = Buffer.convert(Identifier)
if action == "\1" then
table.insert(queueInRequest[1][Identifier], returnDatas)
else
table.insert(queueInRequest[2][Identifier], returnDatas)
end
end)
end
return ClientProcess

View file

@ -1,32 +0,0 @@
--!strict
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] = {}
@ -57,13 +46,22 @@ local RequestEvent = Event.Request
function ClientProcess.insertQueue(Identifier: string, reliable: boolean, ...: any) function ClientProcess.insertQueue(Identifier: string, reliable: boolean, ...: any)
if not reliable then if not reliable then
if not unreliableClientQueue[Identifier] then
unreliableClientQueue[Identifier] = {}
end
table.insert(unreliableClientQueue[Identifier], { ... }) table.insert(unreliableClientQueue[Identifier], { ... })
return return
end end
if not clientQueue[Identifier] then
clientQueue[Identifier] = {}
end
table.insert(clientQueue[Identifier], { ... }) table.insert(clientQueue[Identifier], { ... })
end end
function ClientProcess.insertRequest(Identifier: string, timeout: number, ...: any) function ClientProcess.insertRequest(Identifier: string, timeout: number, ...: any)
if not clientRequestQueue[Identifier] then
clientRequestQueue[Identifier] = {}
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)
@ -76,115 +74,147 @@ function ClientProcess.insertRequest(Identifier: string, timeout: number, ...: a
return coroutine.yield() return coroutine.yield()
end end
function ClientProcess.add(Identifier: any, originId: string) function ClientProcess.add(Identifier: any, originId: string, conf: Type.ClientConf)
if not clientQueue[Identifier] then if not registeredIdentifier[Identifier] then
clientRatelimit[Identifier] = RateLimit.create(originId) registeredIdentifier[Identifier] = true
clientQueue[Identifier] = {}
unreliableClientQueue[Identifier] = {} if not clientRatelimit[Identifier] then
clientRequestQueue[Identifier] = {} clientRatelimit[Identifier] = RateLimit.create(originId)
clientCallback[Identifier] = {} end
if not clientQueue[Identifier] then
queueOutRequest[1][Identifier] = {} clientQueue[Identifier] = {}
queueOutRequest[2][Identifier] = {} end
queueInRequest[1][Identifier] = {} if not unreliableClientQueue[Identifier] then
queueInRequest[2][Identifier] = {} unreliableClientQueue[Identifier] = {}
queueIn[Identifier] = {} end
if not clientRequestQueue[Identifier] then
clientRequestQueue[Identifier] = {}
end
if not clientCallback[Identifier] then
clientCallback[Identifier] = {}
end
if not queueOutRequest[1][Identifier] then
queueOutRequest[1][Identifier] = {}
end
if not queueOutRequest[2][Identifier] then
queueOutRequest[2][Identifier] = {}
end
if not queueInRequest[1][Identifier] then
queueInRequest[1][Identifier] = {}
end
if not queueInRequest[2][Identifier] then
queueInRequest[2][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")
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
table.clear(data) 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
table.clear(data) clientQueue[Identifier] = nil
end end
if #clientRequestQueue[Identifier] > 0 then 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
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
if not queueOutRequest[1][Identifier] then
queueOutRequest[1][Identifier] = {}
end
table.insert(queueOutRequest[1][Identifier], { requestData[1], requestData[3] }) table.insert(queueOutRequest[1][Identifier], { requestData[1], requestData[3] })
table.remove(requestData, #requestData) requestData[3] = nil
end end
end end
if incoming_cache[Identifier] then
for _, packet in incoming_cache[Identifier] do -- Unreliable & Reliable
if not queueIn[Identifier] then continue end local callback = clientCallback[Identifier] or nil
table.insert(queueIn[Identifier], table.clone(packet)) if not callback then continue end
table.clear(incoming_cache[Identifier])
-- Return Invoke
if queueInRequest[1][Identifier] then
for _, packetDatas: any in queueInRequest[1][Identifier] do
if #packetDatas == 0 then continue end
for _, fn: any in callback do
for i=1,#packetDatas do
if not packetDatas[i] then continue end
local packetData1 = packetDatas[i][1]
local packetData2 = packetDatas[i][2]
Spawn(function()
local requestReturn = { fn(table.unpack(packetData2)) }
if not queueOutRequest[2][Identifier] then
queueOutRequest[2][Identifier] = {}
end
table.insert(queueOutRequest[2][Identifier], { packetData1, requestReturn })
packetData1 = nil
packetData2 = nil
end)
end
end
end end
queueInRequest[1][Identifier] = nil
end end
if callback then
if #queueIn[Identifier] > 0 then -- Call to Invoke
for _, packedDatas: any in queueIn[Identifier] do if queueInRequest[2][Identifier] then
if #packedDatas == 0 then continue end if clientRequestQueue[Identifier] then
for _, fn: any in callback do
for i=1,math.min(1e3, #packedDatas) do
Spawn(fn, table.unpack(packedDatas[i] or {}))
end
end
end
table.clear(queueIn[Identifier])
end
if #queueInRequest[1][Identifier] > 0 then
for idx, packetDatas: any in queueInRequest[1][Identifier] do
if #packetDatas == 0 then continue end
for _, fn: any in callback do
for i=1,math.min(1e3, #packetDatas) do
local packetData = packetDatas[i]
if not packetData then continue end
Spawn(function()
local requestReturn = { fn(table.unpack(packetData[2])) }
table.insert(queueOutRequest[2][Identifier], { packetData[1], requestReturn })
end)
end
end
end
table.clear(queueInRequest[1][Identifier])
end
if #queueInRequest[2][Identifier] > 0 then
for _, packetDatas: any in queueInRequest[2][Identifier] do for _, packetDatas: any in queueInRequest[2][Identifier] do
for _, packetData in packetDatas do for _, packetData in packetDatas do
if #packetData == 1 then continue end if #packetData == 1 then continue end
for y=1, math.min(1e3, #clientRequestQueue[Identifier]) do for y=1,#clientRequestQueue[Identifier] do
local clientRequest = clientRequestQueue[Identifier][y] local clientRequest = clientRequestQueue[Identifier][y]
if not clientRequest then continue end if not clientRequest then continue end
if clientRequest[1] == packetData[1] then if clientRequest[1] == packetData[1] then
@ -195,44 +225,22 @@ function ClientProcess.start()
end end
end end
end end
table.clear(queueInRequest[2][Identifier])
end end
queueInRequest[2][Identifier] = nil
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
table.clear(queueOutRequest[1][Identifier])
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
table.clear(queueOutRequest[2][Identifier])
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)
if not read then return end
local callback = clientCallback[Identifier :: string]
if not callback then return end
for _, fn: any in callback do
Spawn(fn, table.unpack(read))
end end
if logger[Identifier] then
task.defer(Logger.write, Identifier, `state: in -> net -> {#data} data.`)
end
if not clientCallback[Identifier] then
if not incoming_cache[Identifier] then
incoming_cache[Identifier] = {}
end
table.insert(incoming_cache[Identifier], data)
return
end
table.insert(queueIn[Identifier], data)
end end
ReliableEvent.OnClientEvent:Connect(onClientNetworkReceive) ReliableEvent.OnClientEvent:Connect(onClientNetworkReceive)
UnreliableEvent.OnClientEvent:Connect(onClientNetworkReceive) UnreliableEvent.OnClientEvent:Connect(onClientNetworkReceive)
@ -240,13 +248,16 @@ function ClientProcess.start()
if not Identifier or not data then return end if not Identifier or not data then return end
Identifier = Buffer.convert(Identifier) Identifier = Buffer.convert(Identifier)
if action == "\1" then if action == "\1" then
if not queueInRequest[1][Identifier] then
queueInRequest[1][Identifier] = {}
end
table.insert(queueInRequest[1][Identifier], data) table.insert(queueInRequest[1][Identifier], data)
else else
if not queueInRequest[2][Identifier] then
queueInRequest[2][Identifier] = {}
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

@ -7,29 +7,27 @@ Client.__index = Client
local Players = game:GetService("Players") local Players = game:GetService("Players")
local Util = script.Parent.Parent.Util local Util = script.Parent.Parent.Util
local Type = require(script.Parent.Parent.Type)
local ClientProcess = require(script.Parent.ClientProcess) local ClientProcess = require(script.Parent.ClientProcess)
local Assert = require(Util.Assert) local Assert = require(Util.Assert)
local Key = require(Util.Key) local Key = require(Util.Key)
local Serdes = require(Util.Serdes) local Serdes = require(Util.Serdes)
local Buffer = require(Util.Buffer) local Buffer = require(Util.Buffer)
function Client.new(Identifier: string, yieldWait: number?) 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, 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.IsConnected = false self.IsConnected = false
ClientProcess.add(self.id, Identifier)
self._buffer:remove()
return self
end
function Client:Logging(store: boolean, opt: boolean) ClientProcess.add(self.id, Identifier, conf or { yieldWait = 10 })
ClientProcess.logger(self.id, store, opt) self._buffer:remove()
return function()
return ClientProcess.getlogs(self.id) return self
end
end end
function Client:Fire(reliable: boolean,...: any) function Client:Fire(reliable: boolean,...: any)
@ -52,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)
@ -73,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,4 +1,5 @@
--!strict --!strict
--!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

@ -14,23 +14,20 @@ local Key = require(Util.Key)
local Serdes = require(Util.Serdes) local Serdes = require(Util.Serdes)
local Buffer = require(Util.Buffer) local Buffer = require(Util.Buffer)
function Server.new(Identifier: string, rateLimit: Type.rateLimitArg?) 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.IsConnected = false self.IsConnected = false
ServerProcess.add(self.id, Identifier, rateLimit or { maxEntrance = 200, interval = 2 })
self._buffer:remove()
return self
end
function Server:Logging(store: boolean, opt: boolean) ServerProcess.add(self.id, Identifier, conf or { rateLimit = { maxEntrance = 200, interval = 2 } })
ServerProcess.logger(self.id, store, opt) self._buffer:remove()
return function()
return ServerProcess.getlogs(self.id) return self
end
end end
function Server:Fire(reliable: boolean, player: Player, ...: any) function Server:Fire(reliable: boolean, player: Player, ...: any)
@ -50,6 +47,13 @@ function Server:FireExcept(reliable: boolean, except: { Player }, ...: any)
end end
end end
function Server:FireIn(reliable: boolean, range: number, from: Vector3, data: { any }, except: { Player }?)
for _, player: Player in ipairs(Players:GetPlayers()) do
if (except and table.find(except, player)) or not player.Character or not player.Character.PrimaryPart or (player.Character.PrimaryPart.Position - from).Magnitude < range then continue end
ServerProcess.insertQueue(self.id, reliable, player, table.unpack(data))
end
end
function Server:Invoke(timeout: number, player: Player, ...: any): any function Server:Invoke(timeout: number, player: Player, ...: any): any
return ServerProcess.insertRequest(self.id, timeout, player, ...) return ServerProcess.insertRequest(self.id, timeout, player, ...)
end end
@ -66,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
@ -97,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,32 +0,0 @@
--!strict
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,23 +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]: boolean } = {}
local queueOut: { local queueOut: {
[Player]: { [Player]: {
[string]: {any}, [string]: {any},
} }
} = {} } = {}
local queueIn: {
[string]: {
[Player]: {any},
}
} = {}
local queueInRequest: { local queueInRequest: {
[number]: { [number]: {
[string]: { [string]: {
@ -44,9 +39,6 @@ local queueOutRequest: {
} }
} }
} = {} } = {}
local logger: {
[string]: boolean
} = {}
queueInRequest[1] = {} queueInRequest[1] = {}
queueInRequest[2] = {} queueInRequest[2] = {}
@ -57,21 +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 serverQueue 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] = {}
@ -85,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
@ -101,8 +130,11 @@ 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 if not serverRequestQueue[Identifier] then
serverQueue[Identifier][player] = {} serverRequestQueue[Identifier] = {}
end
if not serverRequestQueue[Identifier][player] then
serverRequestQueue[Identifier][player] = {}
end 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()
@ -116,168 +148,196 @@ function ServerProcess.insertRequest(Identifier: string, timeout: number, player
return coroutine.yield() return coroutine.yield()
end end
function ServerProcess.add(Identifier: string, originId: string, ratelimit: Type.rateLimitArg) function ServerProcess.add(Identifier: string, originId: string, conf: Type.ServerConf)
if not serverQueue[Identifier] then if not registeredIdentifier[Identifier] then
RateLimit.create(originId, ratelimit.maxEntrance or 200, ratelimit.interval or 2) registeredIdentifier[Identifier] = true
serverQueue[Identifier] = {}
unreliableServerQueue[Identifier] = {} RateLimit.create(originId, conf.rateLimit and conf.rateLimit.maxEntrance or 200, conf.rateLimit and conf.rateLimit.interval or 2)
serverCallback[Identifier] = {}
serverRequestQueue[Identifier] = {} if not serverQueue[Identifier] then
serverQueue[Identifier] = {}
queueIn[Identifier] = {} end
queueInRequest[1][Identifier] = {} if not serverRequestQueue[Identifier] then
queueInRequest[2][Identifier] = {} serverRequestQueue[Identifier] = {}
queueOutRequest[1][Identifier] = {} end
queueOutRequest[2][Identifier] = {} if not serverCallback[Identifier] then
serverCallback[Identifier] = {}
end
if not unreliableServerQueue[Identifier] then
unreliableServerQueue[Identifier] = {}
end
if not queueInRequest[1][Identifier] then
queueInRequest[1][Identifier] = {}
end
if not queueInRequest[2][Identifier] then
queueInRequest[2][Identifier] = {}
end
if not queueOutRequest[1][Identifier] then
queueOutRequest[1][Identifier] = {}
end
if not queueOutRequest[2][Identifier] then
queueOutRequest[2][Identifier] = {}
end
for _, player: Player in ipairs(Players:GetPlayers()) do for _, player: Player in ipairs(Players:GetPlayers()) do
task.spawn(initializeEachPlayer, player) task.spawn(initializeEachPlayer, player)
end end
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")
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, data: any in players do for player: Player, content: any in players do
if #data == 0 then continue end if #content == 0 then continue end
UnreliableEvent:FireClient(player, Buffer.revert(Identifier), data) 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 -> {#data} data.`)
end end
table.clear(data) unreliableServerQueue[Identifier][player] = nil
end end
unreliableServerQueue[Identifier] = nil
end end
for _, player: Player in ipairs(Players:GetPlayers()) do -- Reliable
if not queueOut[player] then continue end for Identifier: string, contents: { [Player]: { any } } in serverQueue do
for Identifier: string, data: any in queueOut[player] do for player, content: any in contents do
if #data == 0 then continue end if #content > 0 and queueOut[player] then
ReliableEvent:FireClient(player, Buffer.revert(Identifier), data) for _, unpacked in content do
if logger[Identifier] then ReliableEvent:FireClient(player, Buffer.revert(Identifier), Buffer.write(unpacked))
task.defer(Logger.write, Identifier, `state: out -> reliable -> {#data} data.`) end
end end
table.clear(data) serverQueue[Identifier][player] = nil
end end
serverQueue[Identifier] = nil
end end
for Identifier: string, players in serverQueue do -- Sent new invokes
local callback = serverCallback[Identifier] or nil for Identifier: string, contents in queueOutRequest[1] do
for player: Player, data in players do for player: Player, requestsData: any in contents do
if #data > 0 and queueOut[player] then if #requestsData > 0 then
queueOut[player][Identifier] = table.clone(data) RequestEvent:FireClient(player, Buffer.revert(Identifier), "\1", requestsData)
table.clear(data)
end end
if #serverRequestQueue[Identifier][player] > 0 then queueOutRequest[1][Identifier][player] = nil
for _, requestData in serverRequestQueue[Identifier][player] do end
queueOutRequest[1][Identifier] = nil
end
-- Sent returning invokes
for Identifier: string, contents in queueOutRequest[2] do
for player: Player, toReturnDatas: any in contents do
if #toReturnDatas > 0 then
RequestEvent:FireClient(player, Buffer.revert(Identifier), "\0", toReturnDatas)
end
queueOutRequest[2][Identifier][player] = nil
end
queueOutRequest[2][Identifier] = nil
end
for Identifier: string in registeredIdentifier 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
queueOutRequest[1][Identifier][player] = {}
end
table.insert(queueOutRequest[1][Identifier][player], { requestData[1], requestData[3] }) table.insert(queueOutRequest[1][Identifier][player], { requestData[1], requestData[3] })
table.remove(requestData, #requestData) requestData[3] = nil
end end
end end
if callback then end
if #queueIn[Identifier][player] > 0 then
for _, packedDatas: any in queueIn[Identifier][player] do local callback = serverCallback[Identifier] or nil
if #packedDatas == 0 then continue end if not callback then continue end
for _, fn: any in callback do
for i=1,math.min(1e3, #packedDatas) do -- Return Invoke
Spawn(fn, player, table.unpack(packedDatas[i] or {})) for player, content in queueInRequest[1][Identifier] do
if not callback then break end
for _, packetDatas in content do
if not callback then break end
if #packetDatas == 0 then continue end
for _, fn: any in callback do
for i=1,#packetDatas do
if not packetDatas[i] then continue end
local packetData1 = packetDatas[i][1]
local packetData2 = packetDatas[i][2]
Spawn(function()
local requestReturn = { fn(player, table.unpack(packetData2)) }
if not queueOutRequest[2][Identifier] then
queueOutRequest[2][Identifier] = {}
end end
end if not queueOutRequest[2][Identifier][player] then
end queueOutRequest[2][Identifier][player] = {}
table.clear(queueIn[Identifier][player])
end
if #queueInRequest[1][Identifier][player] > 0 then
for idx, packetDatas: any in queueInRequest[1][Identifier][player] do
if #packetDatas == 0 then continue end
for _, fn: any in callback do
for i=1,math.min(1e3, #packetDatas) do
local packetData = packetDatas[i]
if not packetData then continue end
Spawn(function()
local requestReturn = { fn(player, table.unpack(packetData[2])) }
table.insert(queueOutRequest[2][Identifier][player], { packetData[1], requestReturn })
end)
end end
end table.insert(queueOutRequest[2][Identifier][player], { packetData1, requestReturn })
packetData1 = nil
packetData2 = nil
end)
end end
table.clear(queueInRequest[1][Identifier][player])
end
if #queueInRequest[2][Identifier][player] > 0 then
for _, packetDatas: any in queueInRequest[2][Identifier][player] do
for idx, packetData in packetDatas do
if #packetData == 1 then continue end
for y=1, math.min(1e3, #serverRequestQueue[Identifier][player]) do
local serverRequest = serverRequestQueue[Identifier][player][y]
if not serverRequest then continue end
if serverRequest[1] == packetData[1] then
Spawn(serverRequest[2], table.unpack(packetData[2]))
table.remove(packetDatas, idx)
table.remove(serverRequestQueue[Identifier][player], y)
break
end
end
end
end
table.clear(queueInRequest[2][Identifier][player])
end
for player: Player, requestsData: any in queueOutRequest[1][Identifier] do
if #requestsData == 0 then continue end
RequestEvent:FireClient(player, Buffer.revert(Identifier), "\1", requestsData)
if logger[Identifier] then
task.defer(Logger.write, Identifier, `state: out -> request -> {#requestsData} data.`)
end
table.clear(requestsData)
end
for player: Player, toReturnDatas: any in queueOutRequest[2][Identifier] do
if #toReturnDatas == 0 then continue end
RequestEvent:FireClient(player, Buffer.revert(Identifier), "\0", toReturnDatas)
if logger[Identifier] then
task.defer(Logger.write, Identifier, `state: out -> return request -> {#toReturnDatas} data.`)
end
table.clear(toReturnDatas)
end end
end end
queueInRequest[1][Identifier][player] = nil
end
-- 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
if not callback then break end
if #packetData == 1 then continue end
local data = serverRequestQueue[Identifier][player]
for i=1,#data do
local serverRequest = data[i]
if not serverRequest then continue end
if serverRequest[1] == packetData[1] then
Spawn(serverRequest[2], table.unpack(packetData[2]))
table.remove(data, i)
break
end
end
end
end
queueInRequest[2][Identifier][player] = nil
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)
@ -286,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
@ -296,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

@ -1,32 +1,44 @@
--!strict --!strict
export type rateLimitArg = { type rateLimitArg = {
maxEntrance: number?, maxEntrance: number?,
interval: number?, interval: number?,
} }
export type ServerConf = {
rateLimit: rateLimitArg?,
}
export type ClientConf = {
yieldWait: number?,
}
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) -> (),
Destroy: (self: Client) -> (),
Wait: (self: Client) -> number, Wait: (self: Client) -> number,
Logging: (self: Client, store: boolean, opt: boolean) -> (), Destroy: (self: Client) -> (),
} }
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) -> (),
Destroy: (self: Server) -> (),
Wait: (self: Server) -> number, Wait: (self: Server) -> number,
Logging: (self: Server, store: boolean, opt: boolean) -> (), Destroy: (self: Server) -> (),
} }
export type Signal = { export type Signal = {

View file

@ -1,4 +1,6 @@
--!strict --!strict
return function(condition: (any), errorMessage: string?): () --!native
if not (condition) then error(`Warp: {errorMessage}`, 2) end --!optimize 2
return function(condition: (any), errorMessage: string, level: number?): ()
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,65 +56,192 @@ 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
DedicatedBuffer.alloc(self, 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
DedicatedBuffer.alloc(self, 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
DedicatedBuffer.alloc(self, 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
DedicatedBuffer.alloc(self, 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
DedicatedBuffer.alloc(self, 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
DedicatedBuffer.alloc(self, 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
DedicatedBuffer.alloc(self, 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
DedicatedBuffer.alloc(self, 8) self:alloc(alloc or 8)
writef64(self.buffer, self.point, val) writef64(self.buffer, self.point, val)
end end
function DedicatedBuffer.wstring(self: any, val: string) function DedicatedBuffer.wstring(self: any, val: string)
if not val then return end if not val then return end
DedicatedBuffer.alloc(self, #val) self:alloc(#val)
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,4 +1,5 @@
--!strict --!strict
--!optimize 2
return function(): number? return function(): number?
return tonumber(string.sub(tostring(Random.new():NextNumber()), 3, 7)) -- 4 digits return tonumber(string.sub(tostring(Random.new():NextNumber()), 3, 8)) -- 6 digits
end end

View file

@ -1,23 +1,54 @@
--!strict --!strict
--!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
@ -26,9 +57,19 @@ function RateLimit.create(Identifier: string, entrance: number?, interval: numbe
entrances = 0 entrances = 0
end) end)
end end
entrances += incoming and incoming or 1 entrances += incoming or 1
return (entrances <= entrance :: number) return (entrances <= entrance :: number)
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,30 +1,50 @@
--!strict --!strict
--!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
else else
local retreived = false local yieldThread: thread = coroutine.running()
task.delay(timeout or 10, function() local cancel = task.delay(timeout or 10, function() -- yield cancelation (timerout)
if retreived then return end task.spawn(yieldThread, nil)
retreived = true error(`Serdes: {Identifier} is taking too long to retrieve, seems like it's not replicated on server.`, 2)
error(`Serdes: {Identifier} is taking too long to retrieve, seems like not replicated on server.`, 2)
end) end)
while (not retreived) and (not Event:GetAttribute(Identifier)) do task.spawn(function()
task.wait(0.5) while coroutine.status(cancel) ~= "dead" and task.wait(0.04) do -- let it loop for yields! 1/24
end if Event:GetAttribute(Identifier) then
retreived = true task.cancel(cancel)
task.spawn(yieldThread, Event:GetAttribute(Identifier))
break
end
end
end)
return coroutine.yield() -- yield
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

@ -1,4 +1,5 @@
--!strict --!strict
--!native
--!optimize 2 --!optimize 2
local Index = {} local Index = {}
@ -20,39 +21,43 @@ else
require(Client.ClientProcess).start() require(Client.ClientProcess).start()
end end
function Index.Server(Identifier: string, rateLimit: Type.rateLimitArg?): Type.Server function Index.Server(Identifier: string, conf: Type.ServerConf?): Type.Server
Assert(IsServer, `[Warp]: Calling .Server({Identifier}) on client side (expected server side)`) Assert(IsServer, `[Warp]: Calling .Server({Identifier}) on client side (expected server side)`)
Assert(typeof(Identifier) == "string", `[Warp]: Identifier must be a string type, got {typeof(Identifier)}`) Assert(typeof(Identifier) == "string", `[Warp]: Identifier must be a string type, got {typeof(Identifier)}`)
return require(Server.Index)(Identifier, rateLimit) :: Type.Server return require(Server.Index)(Identifier, conf) :: Type.Server
end end
function Index.Client(Identifier: string, yieldWait: number?): Type.Client function Index.Client(Identifier: string, conf: Type.ClientConf?): Type.Client
Assert(not IsServer, `[Warp]: Calling .Client({Identifier}) on server side (expected client side)`) Assert(not IsServer, `[Warp]: Calling .Client({Identifier}) on server side (expected client side)`)
Assert(typeof(Identifier) == "string", `[Warp]: Identifier must be a string type, got {typeof(Identifier)}`) Assert(typeof(Identifier) == "string", `[Warp]: Identifier must be a string type, got {typeof(Identifier)}`)
return require(Client.Index)(Identifier, yieldWait) :: 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") 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 table.freeze(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 _, identifier: string in arrays do for param1, param2: string | Type.ClientConf in arrays do
copy[identifier] = Index.Client(identifier) if typeof(param2) == "table" then
copy[param1] = Index.Client(param1, param2)
else
copy[param2] = Index.Client(param2)
end
end end
return table.freeze(copy) :: typeof(copy) return copy
end end
function Index.Signal(Identifier: string) function Index.Signal(Identifier: string)
@ -65,11 +70,11 @@ function Index.fromSignalArray(arrays: { any })
for _, identifier: string in arrays do for _, identifier: string in arrays do
copy[identifier] = Index.Signal(identifier) copy[identifier] = Index.Signal(identifier)
end end
return table.freeze(copy) :: typeof(copy) return copy :: typeof(copy)
end end
function Index.buffer() function Index.buffer()
return Buffer.new() return Buffer.new()
end end
return table.freeze(Index) :: typeof(Index) return table.freeze(Index) :: typeof(Index)

View file

@ -1,5 +1,5 @@
-- Warp Library (@Eternity_Devs) -- Warp Library (@Eternity_Devs)
-- version 1.0.9 -- 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,14 +1,10 @@
[package] [package]
name = "imezx/warp" name = "imezx/warp"
version = "1.0.9" 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 = [ exclude = ["node_modules", "docs", ".github", "*.rbxl", "*.rbxmx", "*.rbxml", "*.rbxm", "TestEZ", "test.project.json"]
"node_modules",
"docs",
".github",
]
description = "A very-fast & powerful networking library for Roblox." description = "A very-fast & powerful networking library for Roblox."
[dependencies] [dependencies]