mirror of
https://github.com/imezx/Warp.git
synced 2026-03-18 00:44:16 +00:00
rewrite: phase 1
This commit is contained in:
parent
0b1304f4c5
commit
3d075cd966
21 changed files with 1360 additions and 1708 deletions
1
sourcemap.json
Normal file
1
sourcemap.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"name":"Warp","className":"ModuleScript","filePaths":["src/init.luau","default.project.json"],"children":[{"name":"Client","className":"ModuleScript","filePaths":["src/Client/init.luau"]},{"name":"Server","className":"ModuleScript","filePaths":["src/Server/init.luau"]},{"name":"Buffer","className":"ModuleScript","filePaths":["src/Buffer.luau"]},{"name":"Thread","className":"ModuleScript","filePaths":["src/Thread.luau"]}]}
|
||||
964
src/Buffer.luau
Normal file
964
src/Buffer.luau
Normal file
|
|
@ -0,0 +1,964 @@
|
|||
--!native
|
||||
--!optimize 2
|
||||
--@EternityDev
|
||||
|
||||
-- 0x00-0x7F: + fixint (0-127) - single byte
|
||||
-- 0x80-0x9F: - fixint (-32 to -1) - single byte
|
||||
local T_NIL = 0xA0
|
||||
local T_FALSE = 0xA1
|
||||
local T_TRUE = 0xA2
|
||||
local T_U8 = 0xA3
|
||||
local T_U16 = 0xA4
|
||||
local T_U32 = 0xA5
|
||||
local T_I8 = 0xA6
|
||||
local T_I16 = 0xA7
|
||||
local T_I32 = 0xA8
|
||||
local T_F32 = 0xA9
|
||||
local T_F64 = 0xAA
|
||||
|
||||
-- str: 0xB0-0xBF = inline len 0-15
|
||||
local T_STR_BASE = 0xB0
|
||||
local T_STR8 = 0xC0
|
||||
local T_STR16 = 0xC1
|
||||
local T_STRVAR = 0xC2
|
||||
|
||||
-- arrays: 0xC4-0xCB = inline len 0-7
|
||||
local T_ARR_BASE = 0xC4
|
||||
local T_ARR8 = 0xCC
|
||||
local T_ARR16 = 0xCD
|
||||
local T_ARRVAR = 0xCE
|
||||
|
||||
-- maps: 0xD0-0xD7 = inline len 0-7
|
||||
local T_MAP_BASE = 0xD0
|
||||
local T_MAP8 = 0xD8
|
||||
local T_MAP16 = 0xD9
|
||||
local T_MAPVAR = 0xDA
|
||||
|
||||
-- Typed arrays (homogeneous, no per-element tags)
|
||||
local T_TYPED_ARR = 0xDB
|
||||
|
||||
local T_VEC3 = 0xE0 -- f32 precision (12 bytes)
|
||||
local T_VEC3_F16 = 0xE1 -- f16 precision (6 bytes)
|
||||
local T_VEC2 = 0xE2 -- f32 precision (8 bytes)
|
||||
local T_VEC2_F16 = 0xE3 -- f16 precision (4 bytes)
|
||||
local T_CFRAME = 0xE4 -- f32 pos + f32 orient (24 bytes)
|
||||
local T_CFRAME_F16 = 0xE5 -- f16 pos + f16 orient (12 bytes)
|
||||
local T_COLOR3 = 0xE6 -- RGB bytes (3 bytes)
|
||||
local T_COLOR3_F = 0xE7 -- RGB floats (12 bytes)
|
||||
local T_BRICKCOLOR = 0xE8
|
||||
local T_INSTANCE = 0xE9
|
||||
local T_ENUMITEM = 0xEA
|
||||
local T_ENUM = 0xEB
|
||||
local T_UDIM2 = 0xEC
|
||||
local T_UDIM = 0xED
|
||||
local T_RECT = 0xEE
|
||||
local T_NUMBERRANGE = 0xEF
|
||||
local T_RAY = 0xF0
|
||||
local T_COLSEQ = 0xF2 -- ColorSequence
|
||||
local T_NUMSEQ = 0xF3 -- NumberSequence
|
||||
|
||||
local function f32ToF16(value: number): number
|
||||
if value ~= value then return 0x7E00 end
|
||||
if value == math.huge then return 0x7C00 end
|
||||
if value == -math.huge then return 0xFC00 end
|
||||
if value == 0 then return 0 end
|
||||
|
||||
local sign = 0
|
||||
if value < 0 then
|
||||
sign = 0x8000
|
||||
value = -value
|
||||
end
|
||||
|
||||
local mantissa, exponent = math.frexp(value)
|
||||
exponent += 14
|
||||
|
||||
if exponent <= 0 then
|
||||
if exponent < -10 then return sign end
|
||||
mantissa = math.floor(mantissa * bit32.lshift(1, 10 + exponent) + 0.5)
|
||||
return bit32.bor(sign, mantissa)
|
||||
elseif exponent >= 31 then
|
||||
return bit32.bor(sign, 0x7C00)
|
||||
end
|
||||
|
||||
mantissa = math.floor((mantissa * 2 - 1) * 1024 + 0.5)
|
||||
if mantissa == 1024 then
|
||||
mantissa = 0
|
||||
exponent += 1
|
||||
if exponent >= 31 then return bit32.bor(sign, 0x7C00) end
|
||||
end
|
||||
return bit32.bor(sign, bit32.lshift(exponent, 10), mantissa)
|
||||
end
|
||||
|
||||
local function f16ToF32(bits: number): number
|
||||
local sign = bit32.band(bits, 0x8000)
|
||||
local exponent = bit32.band(bit32.rshift(bits, 10), 0x1F)
|
||||
local mantissa = bit32.band(bits, 0x03FF)
|
||||
|
||||
local value: number
|
||||
if exponent == 0 then
|
||||
value = mantissa == 0 and 0 or math.ldexp(mantissa / 1024, -14)
|
||||
elseif exponent == 31 then
|
||||
value = mantissa == 0 and math.huge or 0/0
|
||||
else
|
||||
value = math.ldexp(1 + mantissa / 1024, exponent - 15)
|
||||
end
|
||||
|
||||
return sign ~= 0 and -value or value
|
||||
end
|
||||
|
||||
local function varUIntSize(value: number): number
|
||||
if value < 0x80 then return 1 end
|
||||
if value < 0x4000 then return 2 end
|
||||
if value < 0x200000 then return 3 end
|
||||
if value < 0x10000000 then return 4 end
|
||||
return 5
|
||||
end
|
||||
|
||||
local function writeVarUInt(buf: buffer, offset: number, value: number): number
|
||||
while value >= 0x80 do
|
||||
buffer.writeu8(buf, offset, bit32.bor(bit32.band(value, 0x7F), 0x80))
|
||||
offset += 1
|
||||
value = bit32.rshift(value, 7)
|
||||
end
|
||||
buffer.writeu8(buf, offset, value)
|
||||
return offset + 1
|
||||
end
|
||||
|
||||
local function readVarUInt(buf: buffer, offset: number): (number, number)
|
||||
local value = 0
|
||||
local shift = 0
|
||||
repeat
|
||||
local byte = buffer.readu8(buf, offset)
|
||||
offset += 1
|
||||
value = bit32.bor(value, bit32.lshift(bit32.band(byte, 0x7F), shift))
|
||||
shift += 7
|
||||
until byte < 0x80
|
||||
return value, offset
|
||||
end
|
||||
|
||||
export type Writer = {
|
||||
buf: buffer,
|
||||
cursor: number,
|
||||
capacity: number,
|
||||
refs: {any},
|
||||
}
|
||||
|
||||
local DEFAULT_CAPACITY = 128
|
||||
|
||||
local function createWriter(capacity: number?): Writer
|
||||
local cap = capacity or DEFAULT_CAPACITY
|
||||
return {
|
||||
buf = buffer.create(cap),
|
||||
cursor = 0,
|
||||
capacity = cap,
|
||||
refs = {},
|
||||
}
|
||||
end
|
||||
|
||||
local function ensureSpace(w: Writer, bytes: number)
|
||||
local needed = w.cursor + bytes
|
||||
if needed <= w.capacity then return end
|
||||
|
||||
local newCap = w.capacity
|
||||
while newCap < needed do
|
||||
newCap *= 2
|
||||
end
|
||||
|
||||
local newBuf = buffer.create(newCap)
|
||||
buffer.copy(newBuf, 0, w.buf, 0, w.cursor)
|
||||
w.buf = newBuf
|
||||
w.capacity = newCap
|
||||
end
|
||||
|
||||
local function wByte(w: Writer, v: number)
|
||||
ensureSpace(w, 1)
|
||||
buffer.writeu8(w.buf, w.cursor, v)
|
||||
w.cursor += 1
|
||||
end
|
||||
|
||||
local function wU16(w: Writer, v: number)
|
||||
ensureSpace(w, 2)
|
||||
buffer.writei16(w.buf, w.cursor, v)
|
||||
w.cursor += 2
|
||||
end
|
||||
|
||||
local function wI16(w: Writer, v: number)
|
||||
ensureSpace(w, 2)
|
||||
buffer.writei16(w.buf, w.cursor, v)
|
||||
w.cursor += 2
|
||||
end
|
||||
|
||||
local function wU32(w: Writer, v: number)
|
||||
ensureSpace(w, 4)
|
||||
buffer.writei32(w.buf, w.cursor, v)
|
||||
w.cursor += 4
|
||||
end
|
||||
|
||||
local function wI32(w: Writer, v: number)
|
||||
ensureSpace(w, 4)
|
||||
buffer.writei32(w.buf, w.cursor, v)
|
||||
w.cursor += 4
|
||||
end
|
||||
|
||||
local function wF32(w: Writer, v: number)
|
||||
ensureSpace(w, 4)
|
||||
buffer.writef32(w.buf, w.cursor, v)
|
||||
w.cursor += 4
|
||||
end
|
||||
|
||||
local function wF64(w: Writer, v: number)
|
||||
ensureSpace(w, 8)
|
||||
buffer.writef64(w.buf, w.cursor, v)
|
||||
w.cursor += 8
|
||||
end
|
||||
|
||||
local function wF16(w: Writer, v: number)
|
||||
ensureSpace(w, 2)
|
||||
buffer.writei16(w.buf, w.cursor, f32ToF16(v))
|
||||
w.cursor += 2
|
||||
end
|
||||
|
||||
local function wVarUInt(w: Writer, v: number)
|
||||
ensureSpace(w, varUIntSize(v))
|
||||
w.cursor = writeVarUInt(w.buf, w.cursor, v)
|
||||
end
|
||||
|
||||
local function wString(w: Writer, s: string)
|
||||
local n = #s
|
||||
ensureSpace(w, n)
|
||||
buffer.writestring(w.buf, w.cursor, s)
|
||||
w.cursor += n
|
||||
end
|
||||
|
||||
local packValue: (w: Writer, v: any) -> ()
|
||||
|
||||
local function packNumber(w: Writer, n: number)
|
||||
if n ~= n then
|
||||
wByte(w, T_F64)
|
||||
wF64(w, n)
|
||||
return
|
||||
end
|
||||
|
||||
local isInt = n == math.floor(n)
|
||||
|
||||
if isInt then
|
||||
-- + fixint: 0-127 in single byte
|
||||
if n >= 0 and n <= 127 then
|
||||
wByte(w, n)
|
||||
return
|
||||
end
|
||||
|
||||
-- - fixint: -32 to -1 in single byte
|
||||
if n >= -32 and n < 0 then
|
||||
wByte(w, bit32.bor(0x80, n + 32))
|
||||
return
|
||||
end
|
||||
|
||||
-- larger ints
|
||||
if n >= 0 then
|
||||
if n <= 255 then
|
||||
wByte(w, T_U8)
|
||||
wByte(w, n)
|
||||
elseif n <= 65535 then
|
||||
wByte(w, T_U16)
|
||||
wU16(w, n)
|
||||
elseif n <= 4294967295 then
|
||||
wByte(w, T_U32)
|
||||
wU32(w, n)
|
||||
else
|
||||
wByte(w, T_F64)
|
||||
wF64(w, n)
|
||||
end
|
||||
else
|
||||
if n >= -128 then
|
||||
wByte(w, T_I8)
|
||||
ensureSpace(w, 1)
|
||||
buffer.writei8(w.buf, w.cursor, n)
|
||||
w.cursor += 1
|
||||
elseif n >= -32768 then
|
||||
wByte(w, T_I16)
|
||||
wI16(w, n)
|
||||
elseif n >= -2147483648 then
|
||||
wByte(w, T_I32)
|
||||
wI32(w, n)
|
||||
else
|
||||
wByte(w, T_F64)
|
||||
wF64(w, n)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- float: try f32 first
|
||||
local testBuf = buffer.create(4)
|
||||
buffer.writef32(testBuf, 0, n)
|
||||
local f32Val = buffer.readf32(testBuf, 0)
|
||||
|
||||
if f32Val == n or math.abs(n - f32Val) < math.abs(n) * 1e-6 then
|
||||
wByte(w, T_F32)
|
||||
wF32(w, n)
|
||||
else
|
||||
wByte(w, T_F64)
|
||||
wF64(w, n)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function packString(w: Writer, s: string)
|
||||
local n = #s
|
||||
|
||||
if n <= 15 then
|
||||
wByte(w, T_STR_BASE + n)
|
||||
elseif n <= 255 then
|
||||
wByte(w, T_STR8)
|
||||
wByte(w, n)
|
||||
elseif n <= 65535 then
|
||||
wByte(w, T_STR16)
|
||||
wU16(w, n)
|
||||
else
|
||||
wByte(w, T_STRVAR)
|
||||
wVarUInt(w, n)
|
||||
end
|
||||
wString(w, s)
|
||||
end
|
||||
|
||||
-- is the table a homogeneous number array
|
||||
local function analyzeNumberArray(t: {any}): (boolean, string?, number)
|
||||
local count = #t
|
||||
if count == 0 then return false, nil, 0 end
|
||||
|
||||
local minVal, maxVal = math.huge, -math.huge
|
||||
local allInt = true
|
||||
|
||||
for i = 1, count do
|
||||
local v = t[i]
|
||||
if type(v) ~= "number" then return false, nil, count end
|
||||
if v ~= math.floor(v) then allInt = false end
|
||||
if v < minVal then minVal = v end
|
||||
if v > maxVal then maxVal = v end
|
||||
end
|
||||
|
||||
if not allInt then
|
||||
return true, "f32", count
|
||||
elseif minVal >= 0 and maxVal <= 255 then
|
||||
return true, "u8", count
|
||||
elseif minVal >= -128 and maxVal <= 127 then
|
||||
return true, "i8", count
|
||||
elseif minVal >= 0 and maxVal <= 65535 then
|
||||
return true, "u16", count
|
||||
elseif minVal >= -32768 and maxVal <= 32767 then
|
||||
return true, "i16", count
|
||||
elseif minVal >= 0 and maxVal <= 4294967295 then
|
||||
return true, "u32", count
|
||||
elseif minVal >= -2147483648 and maxVal <= 2147483647 then
|
||||
return true, "i32", count
|
||||
end
|
||||
return true, "f64", count
|
||||
end
|
||||
|
||||
local TYPED_CODES = { u8 = 1, i8 = 2, u16 = 3, i16 = 4, u32 = 5, i32 = 6, f32 = 7, f64 = 8 }
|
||||
local TYPED_SIZES = { u8 = 1, i8 = 1, u16 = 2, i16 = 2, u32 = 4, i32 = 4, f32 = 4, f64 = 8 }
|
||||
local TYPED_WRITERS = {
|
||||
u8 = buffer.writeu8, i8 = buffer.writei8, u16 = buffer.writeu16, i16 = buffer.writei16,
|
||||
u32 = buffer.writeu32, i32 = buffer.writei32, f32 = buffer.writef32, f64 = buffer.writef64,
|
||||
}
|
||||
|
||||
local function packTable(w: Writer, t: {[any]: any})
|
||||
local count = 0
|
||||
local maxIdx = 0
|
||||
local isArray = true
|
||||
|
||||
for k in t do
|
||||
count += 1
|
||||
if type(k) == "number" and k > 0 and k == math.floor(k) then
|
||||
if k > maxIdx then maxIdx = k end
|
||||
else
|
||||
isArray = false
|
||||
end
|
||||
end
|
||||
|
||||
isArray = isArray and maxIdx == count
|
||||
|
||||
if isArray then
|
||||
-- typed array optimization (worth it for 4+ elements)
|
||||
local isHomogeneous, dtype, arrCount = analyzeNumberArray(t)
|
||||
if isHomogeneous and dtype and arrCount >= 4 then
|
||||
local code = TYPED_CODES[dtype]
|
||||
local size = TYPED_SIZES[dtype]
|
||||
local writer = TYPED_WRITERS[dtype]
|
||||
|
||||
wByte(w, T_TYPED_ARR)
|
||||
wByte(w, code)
|
||||
wVarUInt(w, arrCount)
|
||||
ensureSpace(w, arrCount * size)
|
||||
|
||||
for i = 1, arrCount do
|
||||
writer(w.buf, w.cursor, t[i])
|
||||
w.cursor += size
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- regular array
|
||||
if count <= 7 then
|
||||
wByte(w, T_ARR_BASE + count)
|
||||
elseif count <= 255 then
|
||||
wByte(w, T_ARR8)
|
||||
wByte(w, count)
|
||||
elseif count <= 65535 then
|
||||
wByte(w, T_ARR16)
|
||||
wU16(w, count)
|
||||
else
|
||||
wByte(w, T_ARRVAR)
|
||||
wVarUInt(w, count)
|
||||
end
|
||||
|
||||
for i = 1, count do
|
||||
packValue(w, t[i])
|
||||
end
|
||||
else
|
||||
-- map
|
||||
if count <= 7 then
|
||||
wByte(w, T_MAP_BASE + count)
|
||||
elseif count <= 255 then
|
||||
wByte(w, T_MAP8)
|
||||
wByte(w, count)
|
||||
elseif count <= 65535 then
|
||||
wByte(w, T_MAP16)
|
||||
wU16(w, count)
|
||||
else
|
||||
wByte(w, T_MAPVAR)
|
||||
wVarUInt(w, count)
|
||||
end
|
||||
|
||||
for k, v in t do
|
||||
packValue(w, k)
|
||||
packValue(w, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function packVector3(w: Writer, v: Vector3)
|
||||
local x, y, z = v.X, v.Y, v.Z
|
||||
local maxComp = math.max(math.abs(x), math.abs(y), math.abs(z))
|
||||
|
||||
if maxComp < 65000 and maxComp > 0.00006 then
|
||||
wByte(w, T_VEC3_F16)
|
||||
wF16(w, x)
|
||||
wF16(w, y)
|
||||
wF16(w, z)
|
||||
else
|
||||
wByte(w, T_VEC3)
|
||||
wF32(w, x)
|
||||
wF32(w, y)
|
||||
wF32(w, z)
|
||||
end
|
||||
end
|
||||
|
||||
local function packVector2(w: Writer, v: Vector2)
|
||||
local x, y = v.X, v.Y
|
||||
local maxComp = math.max(math.abs(x), math.abs(y))
|
||||
|
||||
if maxComp < 65000 and maxComp > 0.00006 then
|
||||
wByte(w, T_VEC2_F16)
|
||||
wF16(w, x)
|
||||
wF16(w, y)
|
||||
else
|
||||
wByte(w, T_VEC2)
|
||||
wF32(w, x)
|
||||
wF32(w, y)
|
||||
end
|
||||
end
|
||||
|
||||
local function packCFrame(w: Writer, cf: CFrame)
|
||||
local pos = cf.Position
|
||||
local rx, ry, rz = cf:ToOrientation()
|
||||
|
||||
local px, py, pz = pos.X, pos.Y, pos.Z
|
||||
local maxPos = math.max(math.abs(px), math.abs(py), math.abs(pz))
|
||||
|
||||
-- f16
|
||||
if maxPos < 65000 and (maxPos > 0.00006 or maxPos == 0) then
|
||||
wByte(w, T_CFRAME_F16)
|
||||
wF16(w, px)
|
||||
wF16(w, py)
|
||||
wF16(w, pz)
|
||||
wF16(w, rx)
|
||||
wF16(w, ry)
|
||||
wF16(w, rz)
|
||||
else
|
||||
wByte(w, T_CFRAME)
|
||||
wF32(w, px)
|
||||
wF32(w, py)
|
||||
wF32(w, pz)
|
||||
wF32(w, rx)
|
||||
wF32(w, ry)
|
||||
wF32(w, rz)
|
||||
end
|
||||
end
|
||||
|
||||
local function packColor3(w: Writer, c: Color3)
|
||||
local r, g, b = c.R, c.G, c.B
|
||||
|
||||
if r > 1 or g > 1 or b > 1 then
|
||||
wByte(w, T_COLOR3_F)
|
||||
wF32(w, r)
|
||||
wF32(w, g)
|
||||
wF32(w, b)
|
||||
else
|
||||
wByte(w, T_COLOR3)
|
||||
wByte(w, math.clamp(math.round(r * 255), 0, 255))
|
||||
wByte(w, math.clamp(math.round(g * 255), 0, 255))
|
||||
wByte(w, math.clamp(math.round(b * 255), 0, 255))
|
||||
end
|
||||
end
|
||||
|
||||
packValue = function(w: Writer, v: any): ()
|
||||
local t: string = typeof(v)
|
||||
|
||||
if t == "nil" then
|
||||
wByte(w, T_NIL)
|
||||
elseif t == "boolean" then
|
||||
wByte(w, v and T_TRUE or T_FALSE)
|
||||
elseif t == "number" then
|
||||
packNumber(w, v)
|
||||
elseif t == "string" then
|
||||
packString(w, v)
|
||||
elseif t == "table" then
|
||||
packTable(w, v)
|
||||
elseif t == "Vector3" then
|
||||
packVector3(w, v)
|
||||
elseif t == "Vector2" then
|
||||
packVector2(w, v)
|
||||
elseif t == "CFrame" then
|
||||
packCFrame(w, v)
|
||||
elseif t == "Color3" then
|
||||
packColor3(w, v)
|
||||
elseif t == "Instance" then
|
||||
wByte(w, T_INSTANCE)
|
||||
table.insert(w.refs, v)
|
||||
wVarUInt(w, #w.refs)
|
||||
elseif t == "EnumItem" then
|
||||
wByte(w, T_ENUMITEM)
|
||||
local enumName = `{v.EnumType}`
|
||||
wVarUInt(w, #enumName)
|
||||
wString(w, enumName)
|
||||
wU16(w, v.Value)
|
||||
elseif t == "BrickColor" then
|
||||
wByte(w, T_BRICKCOLOR)
|
||||
wU16(w, v.Number)
|
||||
elseif t == "Enum" then
|
||||
wByte(w, T_ENUM)
|
||||
local name = v.Name
|
||||
wVarUInt(w, #name)
|
||||
wString(w, name)
|
||||
elseif t == "UDim2" then
|
||||
local X, Y = v.X, v.Y
|
||||
wByte(w, T_UDIM2)
|
||||
wF32(w, X.Scale)
|
||||
wI32(w, X.Offset)
|
||||
wF32(w, Y.Scale)
|
||||
wI32(w, Y.Offset)
|
||||
elseif t == "UDim" then
|
||||
wByte(w, T_UDIM)
|
||||
wF32(w, v.Scale)
|
||||
wI32(w, v.Offset)
|
||||
elseif t == "Rect" then
|
||||
local Min, Max = v.Min, v.Max
|
||||
wByte(w, T_RECT)
|
||||
wF32(w, Min.X)
|
||||
wF32(w, Min.Y)
|
||||
wF32(w, Max.X)
|
||||
wF32(w, Max.Y)
|
||||
elseif t == "NumberRange" then
|
||||
wByte(w, T_NUMBERRANGE)
|
||||
wF32(w, v.Min)
|
||||
wF32(w, v.Max)
|
||||
elseif t == "Ray" then
|
||||
local Origin, Direction = v.Origin, v.Direction
|
||||
wByte(w, T_RAY)
|
||||
wF32(w, Origin.X)
|
||||
wF32(w, Origin.Y)
|
||||
wF32(w, Origin.Z)
|
||||
wF32(w, Direction.X)
|
||||
wF32(w, Direction.Y)
|
||||
wF32(w, Direction.Z)
|
||||
elseif t == "ColorSequence" then
|
||||
local keypoints = v.Keypoints
|
||||
wByte(w, T_COLSEQ)
|
||||
wByte(w, #keypoints)
|
||||
for _, kp in keypoints do
|
||||
wF32(w, kp.Time)
|
||||
local c = kp.Value
|
||||
wByte(w, math.clamp(math.round(c.R * 255), 0, 255))
|
||||
wByte(w, math.clamp(math.round(c.G * 255), 0, 255))
|
||||
wByte(w, math.clamp(math.round(c.B * 255), 0, 255))
|
||||
end
|
||||
elseif t == "NumberSequence" then
|
||||
local keypoints = v.Keypoints
|
||||
wByte(w, T_NUMSEQ)
|
||||
wByte(w, #keypoints)
|
||||
for _, kp in keypoints do
|
||||
wF32(w, kp.Time)
|
||||
wF32(w, kp.Value)
|
||||
wF32(w, kp.Envelope)
|
||||
end
|
||||
else
|
||||
error(`Unsupported type: {t}`)
|
||||
end
|
||||
end
|
||||
|
||||
local TYPED_READERS = {
|
||||
[1] = function(b, o) return buffer.readu8(b, o), o + 1 end,
|
||||
[2] = function(b, o) return buffer.readi8(b, o), o + 1 end,
|
||||
[3] = function(b, o) return buffer.readu16(b, o), o + 2 end,
|
||||
[4] = function(b, o) return buffer.readi16(b, o), o + 2 end,
|
||||
[5] = function(b, o) return buffer.readu32(b, o), o + 4 end,
|
||||
[6] = function(b, o) return buffer.readi32(b, o), o + 4 end,
|
||||
[7] = function(b, o) return buffer.readf32(b, o), o + 4 end,
|
||||
[8] = function(b, o) return buffer.readf64(b, o), o + 8 end,
|
||||
}
|
||||
|
||||
local function readF16(b: buffer, o: number): number
|
||||
return f16ToF32(buffer.readu16(b, o))
|
||||
end
|
||||
|
||||
local unpackValue: (buf: buffer, pos: number, refs: {any}?) -> (any, number)
|
||||
|
||||
unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
|
||||
local t = buffer.readu8(buf, pos)
|
||||
pos += 1
|
||||
|
||||
-- + fixint (0-127)
|
||||
if t <= 0x7F then
|
||||
return t, pos
|
||||
end
|
||||
|
||||
-- - fixint (-32 to -1)
|
||||
if t >= 0x80 and t <= 0x9F then
|
||||
return t - 0x80 - 32, pos
|
||||
end
|
||||
|
||||
if t == T_NIL then return nil, pos end
|
||||
if t == T_FALSE then return false, pos end
|
||||
if t == T_TRUE then return true, pos end
|
||||
|
||||
if t == T_U8 then return buffer.readu8(buf, pos), pos + 1 end
|
||||
if t == T_U16 then return buffer.readu16(buf, pos), pos + 2 end
|
||||
if t == T_U32 then return buffer.readu32(buf, pos), pos + 4 end
|
||||
if t == T_I8 then return buffer.readi8(buf, pos), pos + 1 end
|
||||
if t == T_I16 then return buffer.readi16(buf, pos), pos + 2 end
|
||||
if t == T_I32 then return buffer.readi32(buf, pos), pos + 4 end
|
||||
if t == T_F32 then return buffer.readf32(buf, pos), pos + 4 end
|
||||
if t == T_F64 then return buffer.readf64(buf, pos), pos + 8 end
|
||||
|
||||
-- inline str len (0-15)
|
||||
if t >= T_STR_BASE and t <= T_STR_BASE + 15 then
|
||||
local n = t - T_STR_BASE
|
||||
return buffer.readstring(buf, pos, n), pos + n
|
||||
end
|
||||
|
||||
if t == T_STR8 then
|
||||
local n = buffer.readu8(buf, pos)
|
||||
return buffer.readstring(buf, pos + 1, n), pos + 1 + n
|
||||
end
|
||||
if t == T_STR16 then
|
||||
local n = buffer.readu16(buf, pos)
|
||||
return buffer.readstring(buf, pos + 2, n), pos + 2 + n
|
||||
end
|
||||
if t == T_STRVAR then
|
||||
local n; n, pos = readVarUInt(buf, pos)
|
||||
return buffer.readstring(buf, pos, n), pos + n
|
||||
end
|
||||
|
||||
-- inline array len (0-7)
|
||||
if t >= T_ARR_BASE and t <= T_ARR_BASE + 7 then
|
||||
local count = t - T_ARR_BASE
|
||||
local arr = table.create(count)
|
||||
for i = 1, count do
|
||||
arr[i], pos = unpackValue(buf, pos, refs)
|
||||
end
|
||||
return arr, pos
|
||||
end
|
||||
|
||||
if t == T_ARR8 then
|
||||
local count = buffer.readu8(buf, pos); pos += 1
|
||||
local arr = table.create(count)
|
||||
for i = 1, count do
|
||||
arr[i], pos = unpackValue(buf, pos, refs)
|
||||
end
|
||||
return arr, pos
|
||||
end
|
||||
if t == T_ARR16 then
|
||||
local count = buffer.readu16(buf, pos); pos += 2
|
||||
local arr = table.create(count)
|
||||
for i = 1, count do
|
||||
arr[i], pos = unpackValue(buf, pos, refs)
|
||||
end
|
||||
return arr, pos
|
||||
end
|
||||
if t == T_ARRVAR then
|
||||
local count; count, pos = readVarUInt(buf, pos)
|
||||
local arr = table.create(count)
|
||||
for i = 1, count do
|
||||
arr[i], pos = unpackValue(buf, pos, refs)
|
||||
end
|
||||
return arr, pos
|
||||
end
|
||||
|
||||
-- inline map len (0-7)
|
||||
if t >= T_MAP_BASE and t <= T_MAP_BASE + 7 then
|
||||
local count = t - T_MAP_BASE
|
||||
local map = {}
|
||||
for _ = 1, count do
|
||||
local k, v
|
||||
k, pos = unpackValue(buf, pos, refs)
|
||||
v, pos = unpackValue(buf, pos, refs)
|
||||
map[k] = v
|
||||
end
|
||||
return map, pos
|
||||
end
|
||||
|
||||
if t == T_MAP8 then
|
||||
local count = buffer.readu8(buf, pos); pos += 1
|
||||
local map = {}
|
||||
for _ = 1, count do
|
||||
local k, v
|
||||
k, pos = unpackValue(buf, pos, refs)
|
||||
v, pos = unpackValue(buf, pos, refs)
|
||||
map[k] = v
|
||||
end
|
||||
return map, pos
|
||||
end
|
||||
if t == T_MAP16 then
|
||||
local count = buffer.readu16(buf, pos); pos += 2
|
||||
local map = {}
|
||||
for _ = 1, count do
|
||||
local k, v
|
||||
k, pos = unpackValue(buf, pos, refs)
|
||||
v, pos = unpackValue(buf, pos, refs)
|
||||
map[k] = v
|
||||
end
|
||||
return map, pos
|
||||
end
|
||||
if t == T_MAPVAR then
|
||||
local count; count, pos = readVarUInt(buf, pos)
|
||||
local map = {}
|
||||
for _ = 1, count do
|
||||
local k, v
|
||||
k, pos = unpackValue(buf, pos, refs)
|
||||
v, pos = unpackValue(buf, pos, refs)
|
||||
map[k] = v
|
||||
end
|
||||
return map, pos
|
||||
end
|
||||
|
||||
-- typed array
|
||||
if t == T_TYPED_ARR then
|
||||
local subtype = buffer.readu8(buf, pos); pos += 1
|
||||
local count; count, pos = readVarUInt(buf, pos)
|
||||
local reader = TYPED_READERS[subtype]
|
||||
local arr = table.create(count)
|
||||
for i = 1, count do
|
||||
arr[i], pos = reader(buf, pos)
|
||||
end
|
||||
return arr, pos
|
||||
end
|
||||
|
||||
-- Vector3
|
||||
if t == T_VEC3 then
|
||||
local x = buffer.readf32(buf, pos)
|
||||
local y = buffer.readf32(buf, pos + 4)
|
||||
local z = buffer.readf32(buf, pos + 8)
|
||||
return Vector3.new(x, y, z), pos + 12
|
||||
end
|
||||
if t == T_VEC3_F16 then
|
||||
local x = readF16(buf, pos)
|
||||
local y = readF16(buf, pos + 2)
|
||||
local z = readF16(buf, pos + 4)
|
||||
return Vector3.new(x, y, z), pos + 6
|
||||
end
|
||||
|
||||
-- Vector2
|
||||
if t == T_VEC2 then
|
||||
local x = buffer.readf32(buf, pos)
|
||||
local y = buffer.readf32(buf, pos + 4)
|
||||
return Vector2.new(x, y), pos + 8
|
||||
end
|
||||
if t == T_VEC2_F16 then
|
||||
local x = readF16(buf, pos)
|
||||
local y = readF16(buf, pos + 2)
|
||||
return Vector2.new(x, y), pos + 4
|
||||
end
|
||||
|
||||
-- CFrame
|
||||
if t == T_CFRAME then
|
||||
local px = buffer.readf32(buf, pos)
|
||||
local py = buffer.readf32(buf, pos + 4)
|
||||
local pz = buffer.readf32(buf, pos + 8)
|
||||
local rx = buffer.readf32(buf, pos + 12)
|
||||
local ry = buffer.readf32(buf, pos + 16)
|
||||
local rz = buffer.readf32(buf, pos + 20)
|
||||
return CFrame.new(px, py, pz) * CFrame.fromOrientation(rx, ry, rz), pos + 24
|
||||
end
|
||||
if t == T_CFRAME_F16 then
|
||||
local px = readF16(buf, pos)
|
||||
local py = readF16(buf, pos + 2)
|
||||
local pz = readF16(buf, pos + 4)
|
||||
local rx = readF16(buf, pos + 6)
|
||||
local ry = readF16(buf, pos + 8)
|
||||
local rz = readF16(buf, pos + 10)
|
||||
return CFrame.new(px, py, pz) * CFrame.fromOrientation(rx, ry, rz), pos + 12
|
||||
end
|
||||
|
||||
-- Color3
|
||||
if t == T_COLOR3 then
|
||||
local r = buffer.readu8(buf, pos)
|
||||
local g = buffer.readu8(buf, pos + 1)
|
||||
local b = buffer.readu8(buf, pos + 2)
|
||||
return Color3.fromRGB(r, g, b), pos + 3
|
||||
end
|
||||
if t == T_COLOR3_F then
|
||||
local r = buffer.readf32(buf, pos)
|
||||
local g = buffer.readf32(buf, pos + 4)
|
||||
local b = buffer.readf32(buf, pos + 8)
|
||||
return Color3.new(r, g, b), pos + 12
|
||||
end
|
||||
|
||||
if t == T_BRICKCOLOR then
|
||||
return BrickColor.new(buffer.readu16(buf, pos)), pos + 2
|
||||
end
|
||||
|
||||
if t == T_INSTANCE then
|
||||
local idx; idx, pos = readVarUInt(buf, pos)
|
||||
return refs and refs[idx] or nil, pos
|
||||
end
|
||||
|
||||
if t == T_ENUMITEM then
|
||||
local nameLen; nameLen, pos = readVarUInt(buf, pos)
|
||||
local enumName = buffer.readstring(buf, pos, nameLen)
|
||||
pos += nameLen
|
||||
local val = buffer.readu16(buf, pos)
|
||||
return (Enum :: any)[enumName]:FromValue(val), pos + 2
|
||||
end
|
||||
|
||||
if t == T_ENUM then
|
||||
local nameLen; nameLen, pos = readVarUInt(buf, pos)
|
||||
local enumName = buffer.readstring(buf, pos, nameLen)
|
||||
return (Enum :: any)[enumName], pos + nameLen
|
||||
end
|
||||
|
||||
if t == T_UDIM2 then
|
||||
local xs = buffer.readf32(buf, pos)
|
||||
local xo = buffer.readi32(buf, pos + 4)
|
||||
local ys = buffer.readf32(buf, pos + 8)
|
||||
local yo = buffer.readi32(buf, pos + 12)
|
||||
return UDim2.new(xs, xo, ys, yo), pos + 16
|
||||
end
|
||||
|
||||
if t == T_UDIM then
|
||||
local s = buffer.readf32(buf, pos)
|
||||
local o = buffer.readi32(buf, pos + 4)
|
||||
return UDim.new(s, o), pos + 8
|
||||
end
|
||||
|
||||
if t == T_RECT then
|
||||
return Rect.new(
|
||||
buffer.readf32(buf, pos), buffer.readf32(buf, pos + 4),
|
||||
buffer.readf32(buf, pos + 8), buffer.readf32(buf, pos + 12)
|
||||
), pos + 16
|
||||
end
|
||||
|
||||
if t == T_NUMBERRANGE then
|
||||
return NumberRange.new(buffer.readf32(buf, pos), buffer.readf32(buf, pos + 4)), pos + 8
|
||||
end
|
||||
|
||||
if t == T_RAY then
|
||||
return Ray.new(
|
||||
Vector3.new(buffer.readf32(buf, pos), buffer.readf32(buf, pos + 4), buffer.readf32(buf, pos + 8)),
|
||||
Vector3.new(buffer.readf32(buf, pos + 12), buffer.readf32(buf, pos + 16), buffer.readf32(buf, pos + 20))
|
||||
), pos + 24
|
||||
end
|
||||
|
||||
if t == T_COLSEQ then
|
||||
local count = buffer.readu8(buf, pos); pos += 1
|
||||
local keypoints = table.create(count)
|
||||
for i = 1, count do
|
||||
local time = buffer.readf32(buf, pos)
|
||||
local r = buffer.readu8(buf, pos + 4)
|
||||
local g = buffer.readu8(buf, pos + 5)
|
||||
local b = buffer.readu8(buf, pos + 6)
|
||||
keypoints[i] = ColorSequenceKeypoint.new(time, Color3.fromRGB(r, g, b))
|
||||
pos += 7
|
||||
end
|
||||
return ColorSequence.new(keypoints), pos
|
||||
end
|
||||
|
||||
if t == T_NUMSEQ then
|
||||
local count = buffer.readu8(buf, pos); pos += 1
|
||||
local keypoints = table.create(count)
|
||||
for i = 1, count do
|
||||
local time = buffer.readf32(buf, pos)
|
||||
local value = buffer.readf32(buf, pos + 4)
|
||||
local envelope = buffer.readf32(buf, pos + 8)
|
||||
keypoints[i] = NumberSequenceKeypoint.new(time, value, envelope)
|
||||
pos += 12
|
||||
end
|
||||
return NumberSequence.new(keypoints), pos
|
||||
end
|
||||
|
||||
error(`(serdes) unknown type: {t}`)
|
||||
end
|
||||
|
||||
local function build(w: Writer): buffer
|
||||
local result = buffer.create(w.cursor)
|
||||
buffer.copy(result, 0, w.buf, 0, w.cursor)
|
||||
return result
|
||||
end
|
||||
|
||||
local function buildWithRefs(w: Writer): (buffer, {any}?)
|
||||
local result = buffer.create(w.cursor)
|
||||
buffer.copy(result, 0, w.buf, 0, w.cursor)
|
||||
return result, #w.refs > 0 and table.clone(w.refs) or nil
|
||||
end
|
||||
|
||||
local function reset(w: Writer)
|
||||
w.cursor = 0
|
||||
table.clear(w.refs)
|
||||
end
|
||||
|
||||
local BufferSerdes = {}
|
||||
|
||||
function BufferSerdes.write(data: any): (buffer, {any}?)
|
||||
local w = createWriter()
|
||||
packValue(w, data)
|
||||
return buildWithRefs(w)
|
||||
end
|
||||
|
||||
function BufferSerdes.read(buf: buffer, refs: {any}?): any
|
||||
return (unpackValue(buf, 0, refs))
|
||||
end
|
||||
|
||||
function BufferSerdes.readAll(buf: buffer, refs: {any}?): {any}
|
||||
local bufLen = buffer.len(buf)
|
||||
local pos = 0
|
||||
local results = {}
|
||||
while pos < bufLen do
|
||||
local value
|
||||
value, pos = unpackValue(buf, pos, refs)
|
||||
table.insert(results, value)
|
||||
end
|
||||
return results
|
||||
end
|
||||
|
||||
BufferSerdes.createWriter = createWriter
|
||||
BufferSerdes.pack = packValue
|
||||
BufferSerdes.build = build
|
||||
BufferSerdes.buildWithRefs = buildWithRefs
|
||||
BufferSerdes.reset = reset
|
||||
|
||||
BufferSerdes.varUIntSize = varUIntSize
|
||||
BufferSerdes.writeVarUInt = writeVarUInt
|
||||
BufferSerdes.readVarUInt = readVarUInt
|
||||
BufferSerdes.f32ToF16 = f32ToF16
|
||||
BufferSerdes.f16ToF32 = f16ToF32
|
||||
|
||||
return BufferSerdes :: typeof(BufferSerdes)
|
||||
169
src/Client/init.luau
Normal file
169
src/Client/init.luau
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
--!optimize 2
|
||||
--!strict
|
||||
--@EternityDev
|
||||
local Client = {}
|
||||
|
||||
local RunService = game:GetService("RunService")
|
||||
local Thread = require("./Thread")
|
||||
local Buffer = require("./Buffer")
|
||||
local Event: RemoteEvent = script.Parent:WaitForChild("Event")
|
||||
local Function: RemoteFunction = script.Parent:WaitForChild("Function")
|
||||
local deltaT: number, cycle: number = 0, 1 / 61
|
||||
local writer: Buffer.Writer = Buffer.createWriter()
|
||||
|
||||
type Connection = {
|
||||
Connected: boolean,
|
||||
Disconnect: (self: Connection) -> (),
|
||||
}
|
||||
type Event = {
|
||||
remote: string,
|
||||
fn: (Player, ...any?) -> (...any?),
|
||||
}
|
||||
|
||||
local queueEvent: { { any } } = {}
|
||||
local eventListeners: { Event } = {}
|
||||
|
||||
local pendingInvokes: { [string]: thread } = {}
|
||||
local invokeHandlers: { [string]: (...any?) -> ...any? } = {}
|
||||
local invokeId = 0
|
||||
|
||||
Client.Connect = function(remoteName: string, fn: (Player, ...any?) -> (...any?)): Connection
|
||||
local detail = {
|
||||
remote = remoteName,
|
||||
fn = fn,
|
||||
}
|
||||
table.insert(eventListeners, detail)
|
||||
return {
|
||||
Connected = true,
|
||||
Disconnect = function(self: Connection)
|
||||
if not self.Connected then return end
|
||||
self.Connected = false
|
||||
local idx = table.find(eventListeners, detail)
|
||||
if idx then
|
||||
table.remove(eventListeners, idx)
|
||||
end
|
||||
end,
|
||||
} :: Connection
|
||||
end
|
||||
|
||||
Client.Once = function(remoteName: string, fn: (...any?) -> ()): Connection
|
||||
local connection
|
||||
connection = Client.Connect(remoteName, function(...: any?)
|
||||
if connection then
|
||||
connection:Disconnect()
|
||||
end
|
||||
fn(...)
|
||||
end)
|
||||
return connection
|
||||
end
|
||||
|
||||
Client.Wait = function(remoteName: string): (number, ...any?)
|
||||
local thread, t = coroutine.running(), os.clock()
|
||||
Client.Once(remoteName, function(...: any?)
|
||||
task.spawn(thread, os.clock()-t, ...)
|
||||
end)
|
||||
return coroutine.yield()
|
||||
end
|
||||
|
||||
Client.DisconnectAll = function(remoteName: string)
|
||||
for idx = #eventListeners, 1, -1 do
|
||||
if eventListeners[idx].remote == remoteName then
|
||||
table.remove(eventListeners, idx)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Client.Destroy = function(remoteName: string)
|
||||
Client.DisconnectAll(remoteName)
|
||||
end
|
||||
|
||||
Client.Fire = function(remoteName: string, ...: any?)
|
||||
table.insert(queueEvent, {
|
||||
remoteName,
|
||||
{ ... } :: any
|
||||
})
|
||||
end
|
||||
|
||||
Client.Invoke = function(remoteName: string, timeout: number?, ...: any?): ...any?
|
||||
invokeId += 1
|
||||
local invokeId, thread = `{invokeId}`, coroutine.running()
|
||||
|
||||
pendingInvokes[invokeId] = thread
|
||||
task.delay(timeout or 2, function()
|
||||
local pending = pendingInvokes[invokeId]
|
||||
if not pending then
|
||||
return
|
||||
end
|
||||
task.spawn(pending, nil)
|
||||
pendingInvokes[invokeId] = nil
|
||||
end)
|
||||
|
||||
table.insert(queueEvent, {
|
||||
"\0",
|
||||
{ remoteName, invokeId, { ... } :: any } :: any
|
||||
})
|
||||
return coroutine.yield()
|
||||
end
|
||||
|
||||
if RunService:IsClient() then
|
||||
Event.OnClientEvent:Connect(function(b: buffer, ref: { Instance? })
|
||||
if type(b) ~= "buffer" then return end
|
||||
local contents = Buffer.read(b, ref)
|
||||
for _, content in contents do
|
||||
local remote = content[1]
|
||||
local content = content[2]
|
||||
if remote == "\1" then
|
||||
local invokeId = content[1]
|
||||
local results = content[2]
|
||||
local pending = pendingInvokes[invokeId]
|
||||
if pending then
|
||||
task.spawn(pending :: any, table.unpack(results))
|
||||
pendingInvokes[invokeId] = nil
|
||||
end
|
||||
continue
|
||||
end
|
||||
if #eventListeners == 0 then continue end
|
||||
if remote == "\0" then
|
||||
local remoteName = content[1]
|
||||
local invokeId = content[2]
|
||||
local args = content[3]
|
||||
for _, connection in eventListeners do
|
||||
if connection.remote == remoteName then
|
||||
Thread(function()
|
||||
local results = { connection.fn(table.unpack(args)) }
|
||||
table.insert(queueEvent, {
|
||||
"\1",
|
||||
{ invokeId, results } :: any
|
||||
})
|
||||
end)
|
||||
break
|
||||
end
|
||||
end
|
||||
continue
|
||||
end
|
||||
for _, connection in eventListeners do
|
||||
if connection.remote ~= remote then continue end
|
||||
Thread(connection.fn, table.unpack(content))
|
||||
end
|
||||
end
|
||||
end)
|
||||
RunService.PostSimulation:Connect(function(d: number)
|
||||
deltaT += d
|
||||
if deltaT < cycle then return end
|
||||
deltaT = 0
|
||||
if #queueEvent == 0 then return end
|
||||
Buffer.pack(writer, queueEvent)
|
||||
do
|
||||
local buf, ref = Buffer.buildWithRefs(writer)
|
||||
Buffer.reset(writer)
|
||||
if not ref or #ref == 0 then
|
||||
Event:FireServer(buf)
|
||||
else
|
||||
Event:FireServer(buf, ref)
|
||||
end
|
||||
end
|
||||
table.clear(queueEvent)
|
||||
end)
|
||||
end
|
||||
|
||||
return Client :: typeof(Client)
|
||||
|
|
@ -1,264 +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 registeredIdentifier: { [string]: boolean } = {}
|
||||
|
||||
local queueInRequest: {
|
||||
[number]: {
|
||||
[string]: {
|
||||
any
|
||||
}
|
||||
}
|
||||
} = {}
|
||||
local queueOutRequest: {
|
||||
[number]: {
|
||||
[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
|
||||
if not unreliableClientQueue[Identifier] then
|
||||
unreliableClientQueue[Identifier] = {}
|
||||
end
|
||||
table.insert(unreliableClientQueue[Identifier], { ... })
|
||||
return
|
||||
end
|
||||
if not clientQueue[Identifier] then
|
||||
clientQueue[Identifier] = {}
|
||||
end
|
||||
table.insert(clientQueue[Identifier], { ... })
|
||||
end
|
||||
|
||||
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 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: any, originId: string, conf: Type.ClientConf)
|
||||
if not registeredIdentifier[Identifier] then
|
||||
registeredIdentifier[Identifier] = true
|
||||
|
||||
if not clientRatelimit[Identifier] then
|
||||
clientRatelimit[Identifier] = RateLimit.create(originId)
|
||||
end
|
||||
if not clientQueue[Identifier] then
|
||||
clientQueue[Identifier] = {}
|
||||
end
|
||||
if not unreliableClientQueue[Identifier] then
|
||||
unreliableClientQueue[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
|
||||
|
||||
function ClientProcess.remove(Identifier: string)
|
||||
if not registeredIdentifier[Identifier] then return end
|
||||
registeredIdentifier[Identifier] = nil
|
||||
clientQueue[Identifier] = nil
|
||||
unreliableClientQueue[Identifier] = nil
|
||||
clientRequestQueue[Identifier] = nil
|
||||
clientCallback[Identifier] = nil
|
||||
clientRatelimit[Identifier] = nil
|
||||
queueOutRequest[1][Identifier] = nil
|
||||
queueOutRequest[2][Identifier] = nil
|
||||
queueInRequest[1][Identifier] = nil
|
||||
queueInRequest[2][Identifier] = nil
|
||||
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()
|
||||
debug.setmemorycategory("Warp")
|
||||
RunService.PostSimulation:Connect(function()
|
||||
-- Unreliable
|
||||
for Identifier: string, data: any in unreliableClientQueue do
|
||||
if #data == 0 then continue end
|
||||
if clientRatelimit[Identifier](#data) then
|
||||
for _, unpacked in data do
|
||||
UnreliableEvent:FireServer(Buffer.revert(Identifier), Buffer.write(unpacked))
|
||||
end
|
||||
end
|
||||
unreliableClientQueue[Identifier] = nil
|
||||
end
|
||||
-- Reliable
|
||||
for Identifier: string, data: any in clientQueue do
|
||||
if #data > 0 then
|
||||
if clientRatelimit[Identifier](#data) then
|
||||
for _, unpacked in data do
|
||||
ReliableEvent:FireServer(Buffer.revert(Identifier), Buffer.write(unpacked))
|
||||
end
|
||||
end
|
||||
clientQueue[Identifier] = nil
|
||||
end
|
||||
end
|
||||
-- Sent new invokes
|
||||
for Identifier: string, requestsData in queueOutRequest[1] do
|
||||
if #requestsData == 0 then continue end
|
||||
RequestEvent:FireServer(Buffer.revert(Identifier), "\1", requestsData)
|
||||
queueOutRequest[1][Identifier] = nil
|
||||
end
|
||||
-- Sent returning invokes
|
||||
for Identifier: string, toReturnDatas in queueOutRequest[2] do
|
||||
if #toReturnDatas == 0 then continue end
|
||||
RequestEvent:FireServer(Buffer.revert(Identifier), "\0", toReturnDatas)
|
||||
queueOutRequest[2][Identifier] = nil
|
||||
end
|
||||
|
||||
for Identifier: string in registeredIdentifier do
|
||||
if clientRequestQueue[Identifier] then
|
||||
for _, requestData in clientRequestQueue[Identifier] do
|
||||
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] })
|
||||
requestData[3] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Unreliable & Reliable
|
||||
local callback = clientCallback[Identifier] or nil
|
||||
if not callback then continue end
|
||||
|
||||
-- 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
|
||||
queueInRequest[1][Identifier] = nil
|
||||
end
|
||||
|
||||
-- Call to Invoke
|
||||
if queueInRequest[2][Identifier] then
|
||||
if clientRequestQueue[Identifier] then
|
||||
for _, packetDatas: any in queueInRequest[2][Identifier] do
|
||||
for _, packetData in packetDatas do
|
||||
if #packetData == 1 then continue end
|
||||
for y=1,#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
|
||||
end
|
||||
queueInRequest[2][Identifier] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
local function onClientNetworkReceive(Identifier: buffer | string, data: buffer, ref: { any }?)
|
||||
if not Identifier or typeof(Identifier) ~= "buffer" or not data or typeof(data) ~= "buffer" then return end
|
||||
Identifier = Buffer.convert(Identifier)
|
||||
if not registeredIdentifier[Identifier :: string] then return end
|
||||
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
|
||||
ReliableEvent.OnClientEvent:Connect(onClientNetworkReceive)
|
||||
UnreliableEvent.OnClientEvent:Connect(onClientNetworkReceive)
|
||||
RequestEvent.OnClientEvent:Connect(function(Identifier: any, action: string, data)
|
||||
if not Identifier or not data then return end
|
||||
Identifier = Buffer.convert(Identifier)
|
||||
if action == "\1" then
|
||||
if not queueInRequest[1][Identifier] then
|
||||
queueInRequest[1][Identifier] = {}
|
||||
end
|
||||
table.insert(queueInRequest[1][Identifier], data)
|
||||
else
|
||||
if not queueInRequest[2][Identifier] then
|
||||
queueInRequest[2][Identifier] = {}
|
||||
end
|
||||
table.insert(queueInRequest[2][Identifier], data)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
return ClientProcess
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
--!strict
|
||||
--!native
|
||||
--!optimize 2
|
||||
local Client = {}
|
||||
Client.__index = Client
|
||||
|
||||
local Players = game:GetService("Players")
|
||||
local Util = script.Parent.Parent.Util
|
||||
|
||||
local Type = require(script.Parent.Parent.Type)
|
||||
local ClientProcess = require(script.Parent.ClientProcess)
|
||||
local Assert = require(Util.Assert)
|
||||
local Key = require(Util.Key)
|
||||
local Serdes = require(Util.Serdes)
|
||||
local Buffer = require(Util.Buffer)
|
||||
|
||||
function Client.new(Identifier: string, conf: Type.ClientConf?)
|
||||
local self = setmetatable({}, Client)
|
||||
|
||||
self._buffer = Buffer.new()
|
||||
self._buffer:wu8(Serdes.increment(Identifier, conf and conf.yieldWait))
|
||||
self.id = Buffer.convert(self._buffer:build())
|
||||
self.fn = {}
|
||||
self._conf = table.freeze(conf or {})
|
||||
self.IsConnected = false
|
||||
|
||||
ClientProcess.add(self.id, Identifier, conf or { yieldWait = 10 })
|
||||
self._buffer:remove()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Client:Fire(reliable: boolean,...: any)
|
||||
ClientProcess.insertQueue(self.id, reliable, ...)
|
||||
end
|
||||
|
||||
function Client:Invoke(timeout: number, ...: any): any
|
||||
return ClientProcess.insertRequest(self.id, timeout, ...)
|
||||
end
|
||||
|
||||
function Client:Connect(callback: (args: any) -> ()): string
|
||||
local key = tostring(Key())
|
||||
table.insert(self.fn, key)
|
||||
self.IsConnected = #self.fn > 0
|
||||
ClientProcess.addCallback(self.id, key, callback)
|
||||
return key
|
||||
end
|
||||
|
||||
function Client:Once(callback: (args: any) -> ()): string
|
||||
local key = tostring(Key())
|
||||
table.insert(self.fn, key)
|
||||
self.IsConnected = #self.fn > 0
|
||||
ClientProcess.addCallback(self.id, key, function(...: any?)
|
||||
self:Disconnect(key)
|
||||
task.spawn(callback, ...)
|
||||
end)
|
||||
return key
|
||||
end
|
||||
|
||||
function Client:Wait()
|
||||
local thread: thread, t = coroutine.running(), os.clock()
|
||||
self:Once(function()
|
||||
task.spawn(thread, os.clock()-t)
|
||||
end)
|
||||
return coroutine.yield()
|
||||
end
|
||||
|
||||
function Client:DisconnectAll()
|
||||
for _, key: string in self.fn do
|
||||
self:Disconnect(key)
|
||||
end
|
||||
end
|
||||
|
||||
function Client:Disconnect(key: string)
|
||||
Assert(typeof(key) == "string", "Key must be a string type.")
|
||||
ClientProcess.removeCallback(self.id, key)
|
||||
table.remove(self.fn, table.find(self.fn, key))
|
||||
self.IsConnected = #self.fn > 0
|
||||
end
|
||||
|
||||
function Client:Destroy()
|
||||
self:DisconnectAll()
|
||||
self._buffer:remove()
|
||||
ClientProcess.remove(self.id)
|
||||
Serdes.decrement()
|
||||
table.clear(self)
|
||||
setmetatable(self, nil)
|
||||
end
|
||||
|
||||
return Client.new
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
--!strict
|
||||
--!optimize 2
|
||||
local RunService = game:GetService("RunService")
|
||||
local Type = require(script.Parent.Type)
|
||||
|
||||
if RunService:IsServer() then
|
||||
if not script:FindFirstChild("Reliable") then
|
||||
Instance.new("RemoteEvent", script).Name = "Reliable"
|
||||
end
|
||||
if not script:FindFirstChild("Unreliable") then
|
||||
Instance.new("UnreliableRemoteEvent", script).Name = "Unreliable"
|
||||
end
|
||||
if not script:FindFirstChild("Request") then
|
||||
Instance.new("RemoteEvent", script).Name = "Request"
|
||||
end
|
||||
elseif not script:FindFirstChild("Reliable") or not script:FindFirstChild("Unreliable") or not script:FindFirstChild("Request") then
|
||||
repeat task.wait() until script:FindFirstChild("Reliable") and script:FindFirstChild("Unreliable") and script:FindFirstChild("Request")
|
||||
end
|
||||
|
||||
return {
|
||||
Reliable = script.Reliable,
|
||||
Unreliable = script.Unreliable,
|
||||
Request = script.Request
|
||||
} :: Type.Event
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
--!strict
|
||||
--!native
|
||||
--!optimize 2
|
||||
local Server = {}
|
||||
Server.__index = Server
|
||||
|
||||
local Players = game:GetService("Players")
|
||||
local Util = script.Parent.Parent.Util
|
||||
|
||||
local Type = require(script.Parent.Parent.Type)
|
||||
local ServerProcess = require(script.Parent.ServerProcess)
|
||||
local Assert = require(Util.Assert)
|
||||
local Key = require(Util.Key)
|
||||
local Serdes = require(Util.Serdes)
|
||||
local Buffer = require(Util.Buffer)
|
||||
|
||||
function Server.new(Identifier: string, conf: Type.ServerConf?)
|
||||
local self = setmetatable({}, Server)
|
||||
|
||||
self._buffer = Buffer.new()
|
||||
self._buffer:wu8(Serdes.increment(Identifier))
|
||||
self.id = Buffer.convert(self._buffer:build())
|
||||
self.fn = {}
|
||||
self._conf = table.freeze(conf or {})
|
||||
self.IsConnected = false
|
||||
|
||||
ServerProcess.add(self.id, Identifier, conf or { rateLimit = { maxEntrance = 200, interval = 2 } })
|
||||
self._buffer:remove()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Server:Fire(reliable: boolean, player: Player, ...: any)
|
||||
ServerProcess.insertQueue(self.id, reliable, player, ...)
|
||||
end
|
||||
|
||||
function Server:Fires(reliable: boolean, ...: any)
|
||||
for _, player: Player in ipairs(Players:GetPlayers()) do
|
||||
ServerProcess.insertQueue(self.id, reliable, player, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function Server:FireExcept(reliable: boolean, except: { Player }, ...: any)
|
||||
for _, player: Player in ipairs(Players:GetPlayers()) do
|
||||
if table.find(except, player) then continue end
|
||||
ServerProcess.insertQueue(self.id, reliable, player, ...)
|
||||
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
|
||||
return ServerProcess.insertRequest(self.id, timeout, player, ...)
|
||||
end
|
||||
|
||||
function Server:Connect(callback: (plyer: Player, args: any) -> ()): string
|
||||
local key = tostring(Key())
|
||||
table.insert(self.fn, key)
|
||||
ServerProcess.addCallback(self.id, key, callback)
|
||||
self.IsConnected = #self.fn > 0
|
||||
return key
|
||||
end
|
||||
|
||||
function Server:Once(callback: (plyer: Player, args: any) -> ()): string
|
||||
local key = tostring(Key())
|
||||
table.insert(self.fn, key)
|
||||
self.IsConnected = #self.fn > 0
|
||||
ServerProcess.addCallback(self.id, key, function(player: Player, ...: any?)
|
||||
self:Disconnect(key)
|
||||
task.spawn(callback, player, ...)
|
||||
end)
|
||||
return key
|
||||
end
|
||||
|
||||
function Server:Wait()
|
||||
local thread: thread, t = coroutine.running(), os.clock()
|
||||
self:Once(function()
|
||||
task.spawn(thread, os.clock()-t)
|
||||
end)
|
||||
return coroutine.yield()
|
||||
end
|
||||
|
||||
function Server:DisconnectAll()
|
||||
for _, key: string in self.fn do
|
||||
self:Disconnect(key)
|
||||
end
|
||||
end
|
||||
|
||||
function Server:Disconnect(key: string): boolean
|
||||
Assert(typeof(key) == "string", "Key must be a string type.")
|
||||
ServerProcess.removeCallback(self.id, key)
|
||||
table.remove(self.fn, table.find(self.fn, key))
|
||||
self.IsConnected = #self.fn > 0
|
||||
return table.find(self.fn, key) == nil
|
||||
end
|
||||
|
||||
function Server:Destroy()
|
||||
self:DisconnectAll()
|
||||
self._buffer:remove()
|
||||
ServerProcess.remove(self.id)
|
||||
Serdes.decrement()
|
||||
table.clear(self)
|
||||
setmetatable(self, nil)
|
||||
end
|
||||
|
||||
return Server.new
|
||||
|
|
@ -1,371 +0,0 @@
|
|||
--!native
|
||||
--!strict
|
||||
--!optimize 2
|
||||
local ServerProcess = {}
|
||||
|
||||
local RunService = game:GetService("RunService")
|
||||
local Players = game:GetService("Players")
|
||||
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 serverQueue: Type.QueueMap = {}
|
||||
local unreliableServerQueue: Type.QueueMap = {}
|
||||
local serverCallback: Type.CallbackMap = {}
|
||||
local serverRequestQueue: Type.QueueMap = {}
|
||||
local registeredIdentifier: { [string]: boolean } = {}
|
||||
|
||||
local queueOut: {
|
||||
[Player]: {
|
||||
[string]: {any},
|
||||
}
|
||||
} = {}
|
||||
local queueInRequest: {
|
||||
[number]: {
|
||||
[string]: {
|
||||
[Player]: {any}
|
||||
}
|
||||
}
|
||||
} = {}
|
||||
local queueOutRequest: {
|
||||
[number]: {
|
||||
[string]: {
|
||||
[Player]: {any}
|
||||
}
|
||||
}
|
||||
} = {}
|
||||
|
||||
queueInRequest[1] = {}
|
||||
queueInRequest[2] = {}
|
||||
queueOutRequest[1] = {}
|
||||
queueOutRequest[2] = {}
|
||||
|
||||
local ReliableEvent = Event.Reliable
|
||||
local UnreliableEvent = Event.Unreliable
|
||||
local RequestEvent = Event.Request
|
||||
|
||||
RateLimit.Protect()
|
||||
|
||||
local function initializeEachPlayer(player: Player)
|
||||
if not player then return end
|
||||
if not queueOut[player] then
|
||||
queueOut[player] = {}
|
||||
end
|
||||
for Identifier: string in registeredIdentifier do
|
||||
if not player then break end
|
||||
if not queueOut[player][Identifier] then
|
||||
queueOut[player][Identifier] = {}
|
||||
end
|
||||
if not serverRequestQueue[Identifier] then
|
||||
serverRequestQueue[Identifier] = {}
|
||||
end
|
||||
if not serverRequestQueue[Identifier][player] then
|
||||
serverRequestQueue[Identifier][player] = {}
|
||||
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][player] then
|
||||
queueInRequest[1][Identifier][player] = {}
|
||||
queueInRequest[2][Identifier][player] = {}
|
||||
end
|
||||
if not queueOutRequest[1][Identifier][player] then
|
||||
queueOutRequest[1][Identifier][player] = {}
|
||||
queueOutRequest[2][Identifier][player] = {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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)
|
||||
if not reliable then
|
||||
if not unreliableServerQueue[Identifier] then
|
||||
unreliableServerQueue[Identifier] = {}
|
||||
end
|
||||
if not unreliableServerQueue[Identifier][player] then
|
||||
unreliableServerQueue[Identifier][player] = {}
|
||||
end
|
||||
table.insert(unreliableServerQueue[Identifier][player], { ... })
|
||||
return
|
||||
end
|
||||
if not serverQueue[Identifier] then
|
||||
serverQueue[Identifier] = {}
|
||||
end
|
||||
if not serverQueue[Identifier][player] then
|
||||
serverQueue[Identifier][player] = {}
|
||||
end
|
||||
table.insert(serverQueue[Identifier][player], { ... })
|
||||
end
|
||||
|
||||
function ServerProcess.insertRequest(Identifier: string, timeout: number, player: Player, ...: any)
|
||||
if not serverRequestQueue[Identifier] then
|
||||
serverRequestQueue[Identifier] = {}
|
||||
end
|
||||
if not serverRequestQueue[Identifier][player] then
|
||||
serverRequestQueue[Identifier][player] = {}
|
||||
end
|
||||
local yieldThread: thread, start = coroutine.running(), os.clock()
|
||||
local cancel = task.delay(timeout, function()
|
||||
task.spawn(yieldThread, nil)
|
||||
end)
|
||||
table.insert(serverRequestQueue[Identifier][player], { 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 ServerProcess.add(Identifier: string, originId: string, conf: Type.ServerConf)
|
||||
if not registeredIdentifier[Identifier] then
|
||||
registeredIdentifier[Identifier] = true
|
||||
|
||||
RateLimit.create(originId, conf.rateLimit and conf.rateLimit.maxEntrance or 200, conf.rateLimit and conf.rateLimit.interval or 2)
|
||||
|
||||
if not serverQueue[Identifier] then
|
||||
serverQueue[Identifier] = {}
|
||||
end
|
||||
if not serverRequestQueue[Identifier] then
|
||||
serverRequestQueue[Identifier] = {}
|
||||
end
|
||||
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
|
||||
task.spawn(initializeEachPlayer, player)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ServerProcess.remove(Identifier: string)
|
||||
if not registeredIdentifier[Identifier] then return end
|
||||
registeredIdentifier[Identifier] = nil
|
||||
serverQueue[Identifier] = nil
|
||||
serverRequestQueue[Identifier] = nil
|
||||
serverCallback[Identifier] = nil
|
||||
unreliableServerQueue[Identifier] = nil
|
||||
queueInRequest[1][Identifier] = nil
|
||||
queueInRequest[2][Identifier] = nil
|
||||
queueOutRequest[1][Identifier] = nil
|
||||
queueOutRequest[2][Identifier] = nil
|
||||
end
|
||||
|
||||
function ServerProcess.addCallback(Identifier: string, key: string, callback)
|
||||
serverCallback[Identifier][key] = callback
|
||||
end
|
||||
|
||||
function ServerProcess.removeCallback(Identifier: string, key: string)
|
||||
serverCallback[Identifier][key] = nil
|
||||
end
|
||||
|
||||
function ServerProcess.start()
|
||||
debug.setmemorycategory("Warp")
|
||||
RunService.PostSimulation:Connect(function()
|
||||
-- Unreliable
|
||||
for Identifier: string, players in unreliableServerQueue do
|
||||
for player: Player, content: any in players do
|
||||
if #content == 0 then continue end
|
||||
for _, unpacked in content do
|
||||
UnreliableEvent:FireClient(player, Buffer.revert(Identifier), Buffer.write(unpacked))
|
||||
end
|
||||
unreliableServerQueue[Identifier][player] = nil
|
||||
end
|
||||
unreliableServerQueue[Identifier] = nil
|
||||
end
|
||||
-- Reliable
|
||||
for Identifier: string, contents: { [Player]: { any } } in serverQueue do
|
||||
for player, content: any in contents do
|
||||
if #content > 0 and queueOut[player] then
|
||||
for _, unpacked in content do
|
||||
ReliableEvent:FireClient(player, Buffer.revert(Identifier), Buffer.write(unpacked))
|
||||
end
|
||||
end
|
||||
serverQueue[Identifier][player] = nil
|
||||
end
|
||||
serverQueue[Identifier] = nil
|
||||
end
|
||||
-- Sent new invokes
|
||||
for Identifier: string, contents in queueOutRequest[1] do
|
||||
for player: Player, requestsData: any in contents do
|
||||
if #requestsData > 0 then
|
||||
RequestEvent:FireClient(player, Buffer.revert(Identifier), "\1", requestsData)
|
||||
end
|
||||
queueOutRequest[1][Identifier][player] = nil
|
||||
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 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] })
|
||||
requestData[3] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local callback = serverCallback[Identifier] or nil
|
||||
if not callback then continue end
|
||||
|
||||
-- Return Invoke
|
||||
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
|
||||
if not queueOutRequest[2][Identifier][player] then
|
||||
queueOutRequest[2][Identifier][player] = {}
|
||||
end
|
||||
table.insert(queueOutRequest[2][Identifier][player], { packetData1, requestReturn })
|
||||
packetData1 = nil
|
||||
packetData2 = nil
|
||||
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)
|
||||
local function onServerNetworkReceive(player: Player, Identifier: buffer | string, data: buffer, ref: { any }?)
|
||||
if not Identifier or typeof(Identifier) ~= "buffer" or not data or typeof(data) ~= "buffer" then return end
|
||||
Identifier = Buffer.convert(Identifier :: buffer)
|
||||
if not registeredIdentifier[Identifier :: string] then return end
|
||||
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
|
||||
ReliableEvent.OnServerEvent:Connect(onServerNetworkReceive)
|
||||
UnreliableEvent.OnServerEvent:Connect(onServerNetworkReceive)
|
||||
RequestEvent.OnServerEvent:Connect(function(player: Player, Identifier: any, action: string, data: any)
|
||||
if not Identifier or not data then return end
|
||||
Identifier = Buffer.convert(Identifier)
|
||||
if not queueInRequest[1][Identifier][player] then
|
||||
queueInRequest[1][Identifier][player] = {}
|
||||
end
|
||||
if not queueInRequest[2][Identifier][player] then
|
||||
queueInRequest[2][Identifier][player] = {}
|
||||
end
|
||||
if not serverQueue[Identifier] then
|
||||
serverQueue[Identifier] = {}
|
||||
end
|
||||
if not serverQueue[Identifier][player] then
|
||||
serverQueue[Identifier][player] = {}
|
||||
end
|
||||
if action == "\1" then
|
||||
table.insert(queueInRequest[1][Identifier][player], data)
|
||||
else
|
||||
table.insert(queueInRequest[2][Identifier][player], data)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
for _, player: Player in ipairs(Players:GetPlayers()) do
|
||||
task.spawn(initializeEachPlayer, player)
|
||||
end
|
||||
|
||||
return ServerProcess
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
--!strict
|
||||
--!native
|
||||
--!optimize 2
|
||||
local Dedicated = {}
|
||||
Dedicated.__index = Dedicated
|
||||
|
||||
function Dedicated.new(signal: any, handler: (...any) -> ())
|
||||
return setmetatable({
|
||||
fn = handler,
|
||||
root = signal,
|
||||
}, Dedicated)
|
||||
end
|
||||
|
||||
function Dedicated:Disconnect()
|
||||
table.clear(self)
|
||||
setmetatable(self, nil)
|
||||
end
|
||||
|
||||
return Dedicated.new :: typeof(Dedicated.new)
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
--!strict
|
||||
--!native
|
||||
--!optimize 2
|
||||
local Signal = {}
|
||||
Signal.__index = Signal
|
||||
|
||||
local DedicatedSignal = require(script.Dedicated)
|
||||
|
||||
local Util = script.Parent.Util
|
||||
local Key = require(Util.Key)
|
||||
local Assert = require(Util.Assert)
|
||||
|
||||
local Signals = {}
|
||||
|
||||
function Signal.new(Identifier: string)
|
||||
Assert(typeof(Identifier) == "string", `[Signal]: Identifier must be a string type, got {typeof(Identifier)}`)
|
||||
if not Signals[Identifier] then
|
||||
local signal = setmetatable({}, Signal)
|
||||
Signals[Identifier] = signal
|
||||
return signal
|
||||
end
|
||||
return Signals[Identifier]
|
||||
end
|
||||
|
||||
function Signal:Connect(fn: (...any) -> (), optKey: string?): string
|
||||
local key: typeof(Signal) = optKey or tostring(Key()) :: any
|
||||
self[key] = DedicatedSignal(self, fn)
|
||||
return key :: any
|
||||
end
|
||||
|
||||
function Signal:Once(fn: (...any) -> ()): string
|
||||
local key: string
|
||||
key = self:Connect(function(...: any)
|
||||
self:Disconnect(key)
|
||||
task.spawn(fn, ...)
|
||||
end)
|
||||
return key
|
||||
end
|
||||
|
||||
function Signal:Disconnect(key: string)
|
||||
if not self[key] then return end
|
||||
self[key]:Disconnect()
|
||||
self[key] = nil
|
||||
end
|
||||
|
||||
function Signal:DisconnectAll(): ()
|
||||
table.clear(self)
|
||||
end
|
||||
|
||||
function Signal:Wait(): number
|
||||
local t, thread = os.clock(), coroutine.running()
|
||||
self:Once(function()
|
||||
task.spawn(thread, os.clock()-t)
|
||||
end)
|
||||
return coroutine.yield()
|
||||
end
|
||||
|
||||
function Signal:DeferFire(...: any): ()
|
||||
for _, handle in self do
|
||||
task.defer(handle.fn, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function Signal:Fire(...: any): ()
|
||||
for _, handle in self do
|
||||
task.spawn(handle.fn, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function Signal:FireTo(signal: string, ...: any): ()
|
||||
local to = Signals[signal]
|
||||
if not to then return end
|
||||
Signal.Fire(to, ...)
|
||||
end
|
||||
|
||||
function Signal:Invoke(key: string, ...: any): ()
|
||||
local to = self[key]
|
||||
if not to then return end
|
||||
return to.fn(...)
|
||||
end
|
||||
|
||||
function Signal:InvokeTo(signal: string, key: string, ...: any): ()
|
||||
if not Signals[signal] then return end
|
||||
return Signal.Invoke(Signals[signal], key, ...)
|
||||
end
|
||||
|
||||
function Signal:Destroy(): ()
|
||||
self:DisconnectAll()
|
||||
setmetatable(self, nil)
|
||||
end
|
||||
|
||||
return Signal.new :: typeof(Signal.new)
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
--!strict
|
||||
type rateLimitArg = {
|
||||
maxEntrance: 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 = {
|
||||
Fire: (self: Client, reliable: boolean, ...any) -> (),
|
||||
Invoke: (self: Client, timeout: number, ...any) -> any,
|
||||
Connect: (self: Client, callback: (...any) -> ()) -> Middleware,
|
||||
Once: (self: Client, callback: (player: Player, ...any) -> ()) -> Middleware,
|
||||
Disconnect: (self: Client, key: string) -> (),
|
||||
DisconnectAll: (self: Client) -> (),
|
||||
Wait: (self: Client) -> number,
|
||||
Destroy: (self: Client) -> (),
|
||||
}
|
||||
|
||||
export type Server = {
|
||||
Fire: (self: Server, reliable: boolean, player: Player, ...any) -> (),
|
||||
Fires: (self: Server, reliable: boolean, ...any) -> (),
|
||||
Invoke: (self: Server, timeout: number, player: Player, ...any) -> any,
|
||||
Connect: (self: Server, callback: (player: Player, ...any) -> ()) -> Middleware,
|
||||
Once: (self: Server, callback: (player: Player, ...any) -> ()) -> Middleware,
|
||||
Disconnect: (self: Server, key: string) -> (),
|
||||
DisconnectAll: (self: Server) -> (),
|
||||
Wait: (self: Server) -> number,
|
||||
Destroy: (self: Server) -> (),
|
||||
}
|
||||
|
||||
export type Signal = {
|
||||
Fire: (self: Signal, ...any) -> (),
|
||||
FireTo: (self: Signal, Identifier: string, ...any) -> (),
|
||||
Invoke: (self: Signal, ...any) -> (...any?),
|
||||
InvokeTo: (self: Signal, Identifier: string, ...any) -> (...any?),
|
||||
Connect: (self: Signal, callback: (...any) -> ()) -> string,
|
||||
Once: (self: Signal, callback: (...any) -> ()) -> string,
|
||||
Disconnect: (self: Signal, key: string) -> (),
|
||||
DisconnectAll: (self: Signal) -> (),
|
||||
Wait: (self: Signal) -> number,
|
||||
Destroy: (self: Signal) -> (),
|
||||
}
|
||||
|
||||
export type Event = {
|
||||
Reliable: RemoteEvent,
|
||||
Unreliable: UnreliableRemoteEvent,
|
||||
Request: RemoteEvent,
|
||||
}
|
||||
|
||||
export type QueueMap = {
|
||||
[string]: {
|
||||
[any]: any,
|
||||
}
|
||||
}
|
||||
|
||||
export type CallbackMap = {
|
||||
[string]: (any),
|
||||
}
|
||||
|
||||
export type StoredRatelimit = {
|
||||
[string]: any
|
||||
}
|
||||
|
||||
export type fromServerArray = {
|
||||
[string]: Server
|
||||
}
|
||||
|
||||
export type fromClientArray = {
|
||||
[string]: Client
|
||||
}
|
||||
|
||||
export type fromSignalArray = {
|
||||
[string]: Signal
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
--!strict
|
||||
--!native
|
||||
--!optimize 2
|
||||
return function(condition: (any), errorMessage: string, level: number?): ()
|
||||
if not (condition) then error(`Warp: {errorMessage}`, level or 2) end
|
||||
end
|
||||
|
|
@ -1,265 +0,0 @@
|
|||
--!strict
|
||||
--!native
|
||||
--!optimize 2
|
||||
local DedicatedBuffer = {}
|
||||
DedicatedBuffer.__index = DedicatedBuffer
|
||||
|
||||
local create = buffer.create
|
||||
local copy = buffer.copy
|
||||
local writei8 = buffer.writei8
|
||||
local writei16 = buffer.writei16
|
||||
local writei32 = buffer.writei32
|
||||
local writeu8 = buffer.writeu8
|
||||
local writeu16 = buffer.writeu16
|
||||
local writeu32 = buffer.writeu32
|
||||
local writef32 = buffer.writef32
|
||||
local writef64 = buffer.writef64
|
||||
local writestring = buffer.writestring
|
||||
|
||||
local default: { [string]: number } = {
|
||||
point = 0,
|
||||
next = 0,
|
||||
size = 128,
|
||||
bufferSize = 128,
|
||||
}
|
||||
|
||||
function DedicatedBuffer.copy(self: any, offset: number, b: buffer?, src: buffer?, srcOffset: number?, count: number?)
|
||||
if not b then
|
||||
copy(create(count or default.size), offset, src or self.buffer, srcOffset, count)
|
||||
else
|
||||
copy(b, offset, src or self.buffer, srcOffset, count)
|
||||
end
|
||||
end
|
||||
|
||||
function DedicatedBuffer.alloc(self: any, byte: number)
|
||||
local size: number = self.size
|
||||
local b: buffer = self.buffer
|
||||
|
||||
while self.next + byte >= size do
|
||||
size = math.floor(size * 1.25) -- +25% increase
|
||||
end
|
||||
local newBuffer: buffer = create(size)
|
||||
copy(newBuffer, 0, b)
|
||||
|
||||
b = newBuffer
|
||||
|
||||
self.point = self.next
|
||||
self.buffer = b
|
||||
self.next += byte
|
||||
end
|
||||
|
||||
function DedicatedBuffer.build(self: any): buffer
|
||||
local p: number = self.next > self.point and self.next or self.point
|
||||
local build: buffer = create(p)
|
||||
|
||||
copy(build, 0, self.buffer, 0, p)
|
||||
return build
|
||||
end
|
||||
|
||||
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
|
||||
self:alloc(alloc or 1)
|
||||
writei8(self.buffer, self.point, val)
|
||||
end
|
||||
|
||||
function DedicatedBuffer.wi16(self: any, val: number, alloc: number?)
|
||||
if not val then return end
|
||||
self:alloc(alloc or 2)
|
||||
writei16(self.buffer, self.point, val)
|
||||
end
|
||||
|
||||
function DedicatedBuffer.wi32(self: any, val: number, alloc: number?)
|
||||
if not val then return end
|
||||
self:alloc(alloc or 4)
|
||||
writei32(self.buffer, self.point, val)
|
||||
end
|
||||
|
||||
function DedicatedBuffer.wu8(self: any, val: number, alloc: number?)
|
||||
if not val then return end
|
||||
self:alloc(alloc or 1)
|
||||
writeu8(self.buffer, self.point, val)
|
||||
end
|
||||
|
||||
function DedicatedBuffer.wu16(self: any, val: number, alloc: number?)
|
||||
if not val then return end
|
||||
self:alloc(alloc or 2)
|
||||
writeu16(self.buffer, self.point, val)
|
||||
end
|
||||
|
||||
function DedicatedBuffer.wu32(self: any, val: number, alloc: number?)
|
||||
if not val then return end
|
||||
self:alloc(alloc or 4)
|
||||
writeu32(self.buffer, self.point, val)
|
||||
end
|
||||
|
||||
function DedicatedBuffer.wf32(self: any, val: number, alloc: number?)
|
||||
if not val then return end
|
||||
self:alloc(alloc or 4)
|
||||
writef32(self.buffer, self.point, val)
|
||||
end
|
||||
|
||||
function DedicatedBuffer.wf64(self: any, val: number, alloc: number?)
|
||||
if not val then return end
|
||||
self:alloc(alloc or 8)
|
||||
writef64(self.buffer, self.point, val)
|
||||
end
|
||||
|
||||
function DedicatedBuffer.wstring(self: any, val: string)
|
||||
if not val then return end
|
||||
self:alloc(#val)
|
||||
writestring(self.buffer, self.point, val)
|
||||
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)
|
||||
self.point = default.point
|
||||
self.next = default.next
|
||||
self.size = default.size
|
||||
self.buffer = create(default.bufferSize)
|
||||
table.clear(self.ref)
|
||||
end
|
||||
|
||||
function DedicatedBuffer.new()
|
||||
return setmetatable({
|
||||
point = default.point,
|
||||
next = default.next,
|
||||
size = default.size,
|
||||
buffer = create(default.bufferSize),
|
||||
ref = {},
|
||||
}, DedicatedBuffer)
|
||||
end
|
||||
|
||||
function DedicatedBuffer.remove(self: any)
|
||||
self:flush()
|
||||
table.clear(self)
|
||||
setmetatable(self, nil)
|
||||
end
|
||||
|
||||
export type DedicatedType = typeof(DedicatedBuffer.new())
|
||||
|
||||
return DedicatedBuffer.new :: typeof(DedicatedBuffer.new)
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
--!strict
|
||||
--!native
|
||||
--!optimize 2
|
||||
local Buffer = {}
|
||||
Buffer.__index = Buffer
|
||||
|
||||
local Dedicated = require(script.Dedicated)
|
||||
|
||||
local tostring = buffer.tostring
|
||||
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
|
||||
|
||||
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()
|
||||
end
|
||||
|
||||
function Buffer.convert(b: buffer): string
|
||||
return tostring(b)
|
||||
end
|
||||
|
||||
function Buffer.revert(s: string): buffer
|
||||
return fromstring(s)
|
||||
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)
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
--!strict
|
||||
--!optimize 2
|
||||
return function(): number?
|
||||
return tonumber(string.sub(tostring(Random.new():NextNumber()), 3, 8)) -- 6 digits
|
||||
end
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
--!strict
|
||||
--!optimize 2
|
||||
local RateLimit = {}
|
||||
|
||||
local RunService = game:GetService("RunService")
|
||||
local Assert = require(script.Parent.Assert)
|
||||
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?)
|
||||
Assert(typeof(Identifier) == "string", "Identifier must a string type.")
|
||||
if RunService:IsServer() then
|
||||
Assert(typeof(entrance) == "number", "entrance must a number type.")
|
||||
Assert(entrance :: number > 0, "entrance must above 0.")
|
||||
Reliable:SetAttribute(Identifier.."_ent", entrance)
|
||||
Reliable:SetAttribute(Identifier.."_int", interval)
|
||||
else
|
||||
while (not Reliable:GetAttribute(Identifier.."_ent")) or (not Reliable:GetAttribute(Identifier.."_int")) do
|
||||
task.wait(0.1)
|
||||
end
|
||||
entrance = tonumber(Reliable:GetAttribute(Identifier.."_ent"))
|
||||
interval = tonumber(Reliable:GetAttribute(Identifier.."_int"))
|
||||
end
|
||||
local entrances: number = 0
|
||||
return function(incoming: number?): boolean
|
||||
if entrances == 0 then
|
||||
task.delay(interval, function()
|
||||
entrances = 0
|
||||
end)
|
||||
end
|
||||
entrances += incoming or 1
|
||||
return (entrances <= entrance :: number)
|
||||
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)
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
--!strict
|
||||
--!optimize 2
|
||||
local SerDes = {}
|
||||
local RunService = game:GetService("RunService")
|
||||
local SerInt = 0
|
||||
|
||||
local Event = require(script.Parent.Parent.Event).Reliable
|
||||
local Assert = require(script.Parent.Assert)
|
||||
|
||||
function SerDes.increment(Identifier: string, timeout: number?): number
|
||||
Assert(typeof(Identifier) == "string", "Identifier must be a string type.")
|
||||
if RunService:IsServer() then
|
||||
Assert(SerInt < 255, "reached max 255 identifiers.")
|
||||
if not Event:GetAttribute(Identifier) then
|
||||
SerInt += 1
|
||||
Event:SetAttribute(`{SerInt}`, Identifier)
|
||||
Event:SetAttribute(Identifier, SerInt)
|
||||
--Event:SetAttribute(Identifier, string.pack("I1", SerInt)) -- I1 -> 255 max, I2 -> ~ 6.5e4 max. (SerInt), removed/disabled for buffer migration.
|
||||
end
|
||||
else
|
||||
local yieldThread: thread = coroutine.running()
|
||||
local cancel = task.delay(timeout or 10, function() -- yield cancelation (timerout)
|
||||
task.spawn(yieldThread, nil)
|
||||
error(`Serdes: {Identifier} is taking too long to retrieve, seems like it's not replicated on server.`, 2)
|
||||
end)
|
||||
task.spawn(function()
|
||||
while coroutine.status(cancel) ~= "dead" and task.wait(0.04) do -- let it loop for yields! 1/24
|
||||
if Event:GetAttribute(Identifier) then
|
||||
task.cancel(cancel)
|
||||
task.spawn(yieldThread, Event:GetAttribute(Identifier))
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
return coroutine.yield() -- yield
|
||||
end
|
||||
return Event:GetAttribute(Identifier)
|
||||
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)
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
--!strict
|
||||
--!native
|
||||
--!optimize 2
|
||||
local Index = {}
|
||||
|
||||
local RunService = game:GetService("RunService")
|
||||
local IsServer = RunService:IsServer()
|
||||
|
||||
local Util = script.Util
|
||||
local Server = script.Server
|
||||
local Client = script.Client
|
||||
|
||||
local Type = require(script.Type)
|
||||
local Assert = require(Util.Assert)
|
||||
local Signal = require(script.Signal)
|
||||
local Buffer = require(Util.Buffer)
|
||||
|
||||
if IsServer then
|
||||
require(Server.ServerProcess).start()
|
||||
else
|
||||
require(Client.ClientProcess).start()
|
||||
end
|
||||
|
||||
function Index.Server(Identifier: string, conf: Type.ServerConf?): Type.Server
|
||||
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)}`)
|
||||
return require(Server.Index)(Identifier, conf) :: Type.Server
|
||||
end
|
||||
function Index.Client(Identifier: string, conf: Type.ClientConf?): Type.Client
|
||||
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)}`)
|
||||
return require(Client.Index)(Identifier, conf) :: Type.Client
|
||||
end
|
||||
|
||||
function Index.fromServerArray(arrays: { string } | { [string]: Type.ServerConf }): Type.fromServerArray
|
||||
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)}")
|
||||
local copy: { [string]: Type.Server } = {}
|
||||
for param1, param2: string | Type.ServerConf in arrays do
|
||||
if typeof(param2) == "table" then
|
||||
copy[param1] = Index.Server(param1, param2)
|
||||
else
|
||||
copy[param2] = Index.Server(param2)
|
||||
end
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
function Index.fromClientArray(arrays: { string } | { [string]: Type.ClientConf }): Type.fromClientArray
|
||||
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)}`)
|
||||
local copy = {}
|
||||
for param1, param2: string | Type.ClientConf in arrays do
|
||||
if typeof(param2) == "table" then
|
||||
copy[param1] = Index.Client(param1, param2)
|
||||
else
|
||||
copy[param2] = Index.Client(param2)
|
||||
end
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
function Index.Signal(Identifier: string)
|
||||
return Signal(Identifier)
|
||||
end
|
||||
|
||||
function Index.fromSignalArray(arrays: { any })
|
||||
Assert(typeof(arrays) == "table", `[Warp]: Array must be a table type, got {typeof(arrays)}`)
|
||||
local copy = {}
|
||||
for _, identifier: string in arrays do
|
||||
copy[identifier] = Index.Signal(identifier)
|
||||
end
|
||||
return copy :: typeof(copy)
|
||||
end
|
||||
|
||||
function Index.buffer()
|
||||
return Buffer.new()
|
||||
end
|
||||
|
||||
return table.freeze(Index) :: typeof(Index)
|
||||
203
src/Server/init.luau
Normal file
203
src/Server/init.luau
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
--!optimize 2
|
||||
--!strict
|
||||
--@EternityDev
|
||||
local Server = {}
|
||||
|
||||
local Players = game:GetService("Players")
|
||||
local RunService = game:GetService("RunService")
|
||||
local Thread = require("./Thread")
|
||||
local Buffer = require("./Buffer")
|
||||
local Event: RemoteEvent = script.Parent:WaitForChild("Event")
|
||||
local Function: RemoteFunction = script.Parent:WaitForChild("Function")
|
||||
local deltaT: number, cycle: number = 0, 1 / 61
|
||||
local writer: Buffer.Writer = Buffer.createWriter()
|
||||
|
||||
type Connection = {
|
||||
Connected: boolean,
|
||||
Disconnect: (self: Connection) -> (),
|
||||
}
|
||||
type Event = {
|
||||
remote: string,
|
||||
fn: (Player, ...any?) -> (...any?),
|
||||
}
|
||||
|
||||
local queueEvent: {
|
||||
[Player]: { { any } },
|
||||
} = {}
|
||||
local eventListeners: { Event } = {}
|
||||
local players_ready: { Player } = {}
|
||||
|
||||
local pendingInvokes: { [string]: thread } = {}
|
||||
local invokeHandlers: { [string]: (...any?) -> ...any? } = {}
|
||||
local invokeId = 0
|
||||
|
||||
Server.Connect = function(remoteName: string, fn: (Player, ...any?) -> (...any?)): Connection
|
||||
local detail = {
|
||||
remote = remoteName,
|
||||
fn = fn,
|
||||
}
|
||||
table.insert(eventListeners, detail)
|
||||
return {
|
||||
Connected = true,
|
||||
Disconnect = function(self: Connection)
|
||||
if not self.Connected then return end
|
||||
self.Connected = false
|
||||
local idx = table.find(eventListeners, detail)
|
||||
if idx then
|
||||
table.remove(eventListeners, idx)
|
||||
end
|
||||
end,
|
||||
} :: Connection
|
||||
end
|
||||
|
||||
Server.Once = function(remoteName: string, fn: (...any?) -> ()): Connection
|
||||
local connection
|
||||
connection = Server.Connect(remoteName, function(...: any?)
|
||||
if connection then
|
||||
connection:Disconnect()
|
||||
end
|
||||
fn(...)
|
||||
end)
|
||||
return connection
|
||||
end
|
||||
|
||||
Server.Wait = function(remoteName: string): (number, ...any?)
|
||||
local thread, t = coroutine.running(), os.clock()
|
||||
Server.Once(remoteName, function(...: any?)
|
||||
task.spawn(thread, os.clock()-t, ...)
|
||||
end)
|
||||
return coroutine.yield()
|
||||
end
|
||||
|
||||
Server.DisconnectAll = function(remoteName: string)
|
||||
for idx = #eventListeners, 1, -1 do
|
||||
if eventListeners[idx].remote == remoteName then
|
||||
table.remove(eventListeners, idx)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Server.Destroy = function(remoteName: string)
|
||||
Server.DisconnectAll(remoteName)
|
||||
end
|
||||
|
||||
Server.Fire = function(remoteName: string, reliable: boolean, player: Player, ...: any?)
|
||||
if not queueEvent[player] then
|
||||
queueEvent[player] = {} :: any
|
||||
end
|
||||
table.insert(queueEvent[player], {
|
||||
remoteName,
|
||||
{ ... } :: any
|
||||
})
|
||||
end
|
||||
|
||||
Server.Fires = function(remoteName: string, reliable: boolean, ...: any?)
|
||||
for _, player: Player in players_ready do
|
||||
Server.Fire(remoteName, reliable, player, ...)
|
||||
end
|
||||
end
|
||||
|
||||
Server.Invoke = function(remoteName: string, player: Player, timeout: number?, ...: any?): ...any?
|
||||
invokeId += 1
|
||||
local invokeId, thread = `{invokeId}`, coroutine.running()
|
||||
|
||||
pendingInvokes[invokeId] = thread
|
||||
task.delay(timeout or 2, function()
|
||||
local pending = pendingInvokes[invokeId]
|
||||
if not pending then
|
||||
return
|
||||
end
|
||||
task.spawn(pending, nil)
|
||||
pendingInvokes[invokeId] = nil
|
||||
end)
|
||||
|
||||
table.insert(queueEvent[player], {
|
||||
"\0",
|
||||
{ remoteName, invokeId, { ... } :: any } :: any
|
||||
})
|
||||
return coroutine.yield()
|
||||
end
|
||||
|
||||
if RunService:IsServer() then
|
||||
Event.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance? })
|
||||
if type(b) ~= "buffer" then return end
|
||||
local contents = Buffer.read(b, ref)
|
||||
for _, content in contents do
|
||||
local remote = content[1]
|
||||
local content = content[2]
|
||||
if remote == "\1" then
|
||||
local invokeId = content[1]
|
||||
local results = content[2]
|
||||
local pending = pendingInvokes[invokeId]
|
||||
if pending then
|
||||
task.spawn(pending :: any, table.unpack(results))
|
||||
pendingInvokes[invokeId] = nil
|
||||
end
|
||||
continue
|
||||
end
|
||||
if #eventListeners == 0 then continue end
|
||||
if remote == "\0" then
|
||||
local remoteName = content[1]
|
||||
local invokeId = content[2]
|
||||
local args = content[3]
|
||||
for _, connection in eventListeners do
|
||||
if connection.remote == remoteName then
|
||||
Thread(function()
|
||||
local results = { connection.fn(table.unpack(args)) }
|
||||
table.insert(queueEvent[player], {
|
||||
"\1",
|
||||
{ invokeId, results } :: any
|
||||
})
|
||||
end)
|
||||
break
|
||||
end
|
||||
end
|
||||
continue
|
||||
end
|
||||
for _, connection in eventListeners do
|
||||
if connection.remote ~= remote then continue end
|
||||
Thread(connection.fn, player, table.unpack(content))
|
||||
end
|
||||
end
|
||||
end)
|
||||
RunService.PostSimulation:Connect(function(d: number)
|
||||
deltaT += d
|
||||
if deltaT < cycle then return end
|
||||
deltaT = 0
|
||||
for player: Player, content in queueEvent do
|
||||
if #content == 0 or player.Parent ~= Players then continue end
|
||||
Buffer.pack(writer, content)
|
||||
do
|
||||
local buf, ref = Buffer.buildWithRefs(writer)
|
||||
Buffer.reset(writer)
|
||||
if not ref or #ref == 0 then
|
||||
Event:FireClient(player, buf)
|
||||
else
|
||||
Event:FireClient(player, buf, ref)
|
||||
end
|
||||
end
|
||||
table.clear(queueEvent[player])
|
||||
end
|
||||
end)
|
||||
local function onAdded(player: Player)
|
||||
if not table.find(players_ready, player) then
|
||||
table.insert(players_ready, player)
|
||||
end
|
||||
if not queueEvent[player] then
|
||||
queueEvent[player] = {} :: any
|
||||
end
|
||||
end
|
||||
Players.PlayerAdded:Connect(onAdded)
|
||||
Players.PlayerRemoving:Connect(function(player: Player)
|
||||
table.remove(players_ready, table.find(players_ready, player))
|
||||
if queueEvent[player] then
|
||||
table.clear(queueEvent[player])
|
||||
queueEvent[player] = nil
|
||||
end
|
||||
end)
|
||||
for _, player: Player in ipairs(Players:GetPlayers()) do
|
||||
onAdded(player)
|
||||
end
|
||||
end
|
||||
|
||||
return Server :: typeof(Server)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
--!native
|
||||
--!strict
|
||||
--!optimize 2
|
||||
--@EternityDev
|
||||
local thread: thread?
|
||||
|
||||
local function passer<T...>(func: (T...) -> (), ...: T...): ()
|
||||
|
|
@ -18,4 +18,4 @@ end
|
|||
return function<T...>(func: (T...) -> (), ...: T...): ()
|
||||
if not thread then task.spawn(newThread) end
|
||||
task.spawn(thread :: thread, func, ...)
|
||||
end
|
||||
end
|
||||
|
|
@ -1,20 +1,25 @@
|
|||
-- Warp Library (@Eternity_Devs)
|
||||
-- version 1.0.14
|
||||
--!strict
|
||||
--!native
|
||||
--!optimize 2
|
||||
local Index = require(script.Index)
|
||||
--@EternityDev
|
||||
local Remote = {}
|
||||
|
||||
return {
|
||||
Server = Index.Server,
|
||||
Client = Index.Client,
|
||||
fromServerArray = Index.fromServerArray,
|
||||
fromClientArray = Index.fromClientArray,
|
||||
|
||||
OnSpamSignal = Index.OnSpamSignal,
|
||||
if game.RunService:IsServer() then
|
||||
if not script:FindFirstChild("Event") then
|
||||
Instance.new("RemoteEvent", script).Name = "Event"
|
||||
end
|
||||
if not script:FindFirstChild("Function") then
|
||||
Instance.new("RemoteFunction", script).Name = "Function"
|
||||
end
|
||||
end
|
||||
|
||||
Signal = Index.Signal,
|
||||
fromSignalArray = Index.fromSignalArray,
|
||||
local Client = require("@self/Client")
|
||||
local Server = require("@self/Server")
|
||||
|
||||
buffer = Index.buffer,
|
||||
}
|
||||
Remote.Client = function()
|
||||
return Client
|
||||
end
|
||||
|
||||
Remote.Server = function()
|
||||
return Server
|
||||
end
|
||||
|
||||
return Remote :: typeof(Remote)
|
||||
|
|
|
|||
Loading…
Reference in a new issue