Compare commits

..

15 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
27 changed files with 564 additions and 586 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

@ -33,14 +33,12 @@ function side() {
text: 'Feature', text: 'Feature',
items: [ items: [
{ text: 'Rate Limit', link: '/api/1.0/ratelimit' }, { text: 'Rate Limit', link: '/api/1.0/ratelimit' },
{ text: 'Middleware', link: '/api/1.0/middleware' },
] ]
}, },
{ {
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

@ -1,64 +0,0 @@
# Middleware <Badge type="tip" text="feature" />
::: code-group
```lua [Server]
local Event1 = Warp.Server("Remote1")
local storeC = Event1:Connect(function(player: Player, arg1: string, arg2: number, arg3: boolean)
print(player, arg1, arg2, arg3)
end):middleware(function(player: Player, arg1: string, arg2: number, arg3: boolean)
assert(type(player) == "userdata" and player:IsA("Player"), "player must be a Player.")
assert(typeof(arg1) == "string", "arg1 must be a string.")
assert(typeof(arg2) == "number", "arg2 must be a number.")
assert(typeof(arg3) == "boolean", "arg3 must be a boolean.")
end)
print(storeC:key())
task.delay(15, function()
Event1:Disconnect(storeC:key())
end)
for _=1,5 do
print("send incorrect values")
Event1:Fires(true, 1e9, "hello world!")
task.wait(0.5)
end
for _=1,5 do
print("send correct values")
Event1:Fires(true, "hello world!", 1e9)
task.wait(0.5)
end
```
```lua [Client]
local Event1 = Warp.Client("Remote1")
local storeC = Event1:Connect(function(arg1: boolean, arg2: string, arg3: number)
print(arg1, arg2, arg3)
end):middleware(function(arg1: boolean, arg2: string, arg3: number)
assert(typeof(arg1) == "boolean", "arg1 must be a boolean.")
assert(typeof(arg2) == "string", "arg2 must be a string.")
assert(typeof(arg3) == "number", "arg3 must be a number.")
end)
print(storeC:key())
task.delay(15, function()
Event1:Disconnect(storeC:key())
end)
for _=1,5 do
print("send incorrect values")
Event1:Fires("hello world!", false, 1e9)
task.wait(0.5)
end
for _=1,5 do
print("send correct values")
Event1:Fires("hello world!", 1e9, false)
task.wait(0.5)
end
```
:::

View file

@ -8,7 +8,7 @@
::: code-group ::: code-group
```toml [wally.toml] ```toml [wally.toml]
[dependencies] [dependencies]
warp = "imezx/warp@1.0.12" 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,18 +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 } = {} local registeredIdentifier: { [string]: boolean } = {}
local queueIn: {
[string]: {any}
} = {}
local queueInRequest: { local queueInRequest: {
[number]: { [number]: {
[string]: { [string]: {
@ -38,9 +34,6 @@ local queueOutRequest: {
} }
} }
} = {} } = {}
local logger: {
[string]: boolean
} = {}
queueInRequest[1] = {} queueInRequest[1] = {}
queueInRequest[2] = {} queueInRequest[2] = {}
@ -82,12 +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 table.find(registeredIdentifier, Identifier) then if not registeredIdentifier[Identifier] then
table.insert(registeredIdentifier, Identifier) registeredIdentifier[Identifier] = true
if conf.logging then
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
@ -116,50 +106,40 @@ 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")
local clock_limit = 1/60
local past_clock = os.clock()
RunService.PostSimulation:Connect(function() RunService.PostSimulation:Connect(function()
if (os.clock()-past_clock) >= (clock_limit - 0.006) then -- less potential to skip frames
past_clock = os.clock()
-- Unreliable -- 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
@ -168,9 +148,8 @@ function ClientProcess.start()
for Identifier: string, data: any in clientQueue do for Identifier: string, data: any in clientQueue do
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
@ -180,23 +159,16 @@ function ClientProcess.start()
for Identifier: string, requestsData in queueOutRequest[1] do for Identifier: string, requestsData in queueOutRequest[1] do
if #requestsData == 0 then continue end if #requestsData == 0 then continue end
RequestEvent:FireServer(Buffer.revert(Identifier), "\1", requestsData) 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 queueOutRequest[1][Identifier] = nil
end end
-- Sent returning invokes -- Sent returning invokes
for Identifier: string, toReturnDatas in queueOutRequest[2] do for Identifier: string, toReturnDatas in queueOutRequest[2] do
if #toReturnDatas == 0 then continue end if #toReturnDatas == 0 then continue end
RequestEvent:FireServer(Buffer.revert(Identifier), "\0", toReturnDatas) 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 queueOutRequest[2][Identifier] = nil
end end
end
for _, Identifier: string in registeredIdentifier do 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
@ -212,18 +184,6 @@ function ClientProcess.start()
local callback = clientCallback[Identifier] or nil local callback = clientCallback[Identifier] or nil
if not callback then continue end if not callback then continue end
if queueIn[Identifier] then
for _, packedDatas: any in queueIn[Identifier] do
if #packedDatas == 0 then continue end
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
-- Return Invoke -- Return Invoke
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
@ -270,15 +230,16 @@ function ClientProcess.start()
end end
end 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
table.insert(queueIn[Identifier], data) local callback = clientCallback[Identifier :: string]
if logger[Identifier] then if not callback then return end
task.defer(Logger.write, Identifier, `state: in -> net -> {#data} data.`) for _, fn: any in callback do
Spawn(fn, table.unpack(read))
end end
end end
ReliableEvent.OnClientEvent:Connect(onClientNetworkReceive) ReliableEvent.OnClientEvent:Connect(onClientNetworkReceive)
@ -297,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

@ -13,30 +13,23 @@ 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)
local Middleware = require(Util.Middleware)
function Client.new(Identifier: string, conf: Type.ClientConf?) 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.middleware = {}
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
@ -45,39 +38,23 @@ function Client:Invoke(timeout: number, ...: any): any
return ClientProcess.insertRequest(self.id, timeout, ...) return ClientProcess.insertRequest(self.id, timeout, ...)
end end
function Client:Connect(callback: (args: any) -> ()) function Client:Connect(callback: (args: any) -> ()): string
local key = tostring(Key()) local key = tostring(Key())
local _middleware = Middleware(key)
table.insert(self.fn, key) table.insert(self.fn, key)
self.middleware[key] = _middleware
self.IsConnected = #self.fn > 0 self.IsConnected = #self.fn > 0
ClientProcess.addCallback(self.id, key, function(...) ClientProcess.addCallback(self.id, key, callback)
if _middleware.bridge(...) then return key
return callback(...)
end
return nil
end)
return _middleware
end end
function Client:Once(callback: (args: any) -> ()) function Client:Once(callback: (args: any) -> ()): string
local key = tostring(Key()) local key = tostring(Key())
local _middleware = Middleware(key)
table.insert(self.fn, key) table.insert(self.fn, key)
self.middleware[key] = _middleware
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)
if _middleware.bridge(...) then task.spawn(callback, ...)
return callback(...)
end
return nil
end) end)
return _middleware return key
end end
function Client:Wait() function Client:Wait()
@ -99,14 +76,14 @@ function Client:Disconnect(key: string)
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
if self.middleware[key] then
self.middleware[key]:destroy()
self.middleware[key] = nil
end
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

@ -13,16 +13,14 @@ 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)
local Middleware = require(Util.Middleware)
function Server.new(Identifier: string, conf: Type.ServerConf?) 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.middleware = {}
self._conf = table.freeze(conf or {}) self._conf = table.freeze(conf or {})
self.IsConnected = false self.IsConnected = false
@ -32,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
@ -65,39 +58,23 @@ 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
function Server:Connect(callback: (plyer: Player, args: any) -> ()) function Server:Connect(callback: (plyer: Player, args: any) -> ()): string
local key = tostring(Key()) local key = tostring(Key())
local _middleware = Middleware(key)
table.insert(self.fn, key) table.insert(self.fn, key)
self.middleware[key] = _middleware ServerProcess.addCallback(self.id, key, callback)
self.IsConnected = #self.fn > 0 self.IsConnected = #self.fn > 0
ServerProcess.addCallback(self.id, key, function(...) return key
if _middleware.bridge(...) then
return callback(...)
end
return nil
end)
return _middleware
end end
function Server:Once(callback: (plyer: Player, args: any) -> ()) function Server:Once(callback: (plyer: Player, args: any) -> ()): string
local key = tostring(Key()) local key = tostring(Key())
local _middleware = Middleware(key)
table.insert(self.fn, key) table.insert(self.fn, key)
self.middleware[key] = _middleware
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)
if _middleware.bridge(...) then task.spawn(callback, player, ...)
return callback(...)
end
return nil
end) end)
return _middleware return key
end end
function Server:Wait() function Server:Wait()
@ -119,15 +96,15 @@ function Server:Disconnect(key: string): boolean
ServerProcess.removeCallback(self.id, key) ServerProcess.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
if self.middleware[key] then
self.middleware[key]:destroy()
self.middleware[key] = nil
end
return table.find(self.fn, key) == nil return table.find(self.fn, key) == nil
end 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,13 +49,14 @@ 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] = {}
@ -78,9 +67,6 @@ local function initializeEachPlayer(player: Player)
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
queueIn[Identifier][player] = {}
end
if not queueOutRequest[1][Identifier] then if not queueOutRequest[1][Identifier] then
queueOutRequest[1][Identifier] = {} queueOutRequest[1][Identifier] = {}
end end
@ -99,6 +85,29 @@ 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
@ -140,14 +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
@ -161,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
@ -183,44 +186,36 @@ 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")
local clock_limit = 1/60
local past_clock = os.clock()
RunService.PostSimulation:Connect(function() RunService.PostSimulation:Connect(function()
if (os.clock()-past_clock) >= (clock_limit - 0.006) then -- less potential to skip frames
past_clock = os.clock()
-- Unreliable -- 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
@ -230,7 +225,9 @@ function ServerProcess.start()
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, content: any in contents do
if #content > 0 and queueOut[player] then if #content > 0 and queueOut[player] then
ReliableEvent:FireClient(player, Buffer.revert(Identifier), content) for _, unpacked in content do
ReliableEvent:FireClient(player, Buffer.revert(Identifier), Buffer.write(unpacked))
end
end end
serverQueue[Identifier][player] = nil serverQueue[Identifier][player] = nil
end end
@ -241,9 +238,6 @@ function ServerProcess.start()
for player: Player, requestsData: any in contents 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
@ -254,17 +248,13 @@ function ServerProcess.start()
for player: Player, toReturnDatas: any in contents 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 queueOutRequest[2][Identifier] = nil
end end
end
for _, Identifier: string in registeredIdentifier do for Identifier: string in registeredIdentifier do
if serverRequestQueue[Identifier] then if serverRequestQueue[Identifier] then
for player, content in serverRequestQueue[Identifier] do for player, content in serverRequestQueue[Identifier] do
if #content == 0 then serverRequestQueue[Identifier][player] = nil continue end if #content == 0 then serverRequestQueue[Identifier][player] = nil continue end
@ -285,21 +275,6 @@ function ServerProcess.start()
local callback = serverCallback[Identifier] or nil local callback = serverCallback[Identifier] or nil
if not callback then continue end if not callback then continue end
-- Unreliable & Reliable
for player, content in queueIn[Identifier] do
if not callback then break end
for _, incoming in content do
if not callback then break end
if #incoming == 0 then continue end
for _, fn: any in callback do
for i=1,#incoming do
Spawn(fn, player, table.unpack(incoming[i] or {}))
end
end
end
queueIn[Identifier][player] = nil
end
-- Return Invoke -- Return Invoke
for player, content in queueInRequest[1][Identifier] do for player, content in queueInRequest[1][Identifier] do
if not callback then break end if not callback then break end
@ -352,22 +327,17 @@ function ServerProcess.start()
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)
@ -391,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,19 +38,17 @@ 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 self[key] = nil
end end
function Signal:DisconnectAll(): () function Signal:DisconnectAll(): ()
for _, handle in self do
handle:Disconnect()
end
table.clear(self) table.clear(self)
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)
@ -88,12 +86,6 @@ end
function Signal:Destroy(): () function Signal:Destroy(): ()
self:DisconnectAll() self:DisconnectAll()
for idx: string, signal in Signals do
if self :: any == signal then
Signals[idx] = nil
break
end
end
setmetatable(self, nil) setmetatable(self, nil)
end end

View file

@ -4,19 +4,12 @@ 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 = { export type Middleware = {
@ -34,7 +27,6 @@ export type Client = {
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 = {
@ -47,7 +39,6 @@ export type Server = {
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,46 +0,0 @@
--!strict
--!native
--!optimize 2
local Middleware = {}
Middleware.__index = Middleware
local function wrap(middleware: (...any) -> (...any)): (...any) -> boolean
return function(...): boolean
local obj: any = { ... }
local s, r = pcall(function()
return middleware(table.unpack(obj))
end)
if not s and r then
warn(r)
r = nil
table.clear(obj)
obj = nil
end
return s
end
end
function Middleware.new(key: string)
return setmetatable({
root = key,
bridge = function(...: any?): any?
return true
end,
}, Middleware)
end
function Middleware:middleware(middleware: (...any) -> (...any))
self.bridge = wrap(middleware)
return self
end
function Middleware:key(): string
return self.root
end
function Middleware:destroy()
table.clear(self)
setmetatable(self, nil)
end
return Middleware.new :: typeof(Middleware.new)

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.12 -- 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.12" 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]