mirror of
https://github.com/imezx/Warp.git
synced 2026-03-18 00:44:16 +00:00
rewrite(phase2): schema-defined buffer
This commit is contained in:
parent
4be184815c
commit
391be2dbeb
5 changed files with 376 additions and 27 deletions
|
|
@ -1 +1 @@
|
||||||
{"name":"Warp","className":"ModuleScript","filePaths":["src\\init.luau","default.project.json"],"children":[{"name":"Buffer","className":"ModuleScript","filePaths":["src\\Buffer.luau"]},{"name":"Client","className":"ModuleScript","filePaths":["src\\Client\\init.luau"]},{"name":"Server","className":"ModuleScript","filePaths":["src\\Server\\init.luau"]},{"name":"Thread","className":"ModuleScript","filePaths":["src\\Thread.luau"]}]}
|
{"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":"Thread","className":"ModuleScript","filePaths":["src\\Thread.luau"]},{"name":"Buffer","className":"ModuleScript","filePaths":["src\\Buffer\\init.luau"]}]}
|
||||||
|
|
@ -947,8 +947,315 @@ local function reset(w: Writer)
|
||||||
table.clear(w.refs)
|
table.clear(w.refs)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local Schema = {}
|
||||||
|
export type SchemaType = { type: string, [any]: any }
|
||||||
|
|
||||||
|
Schema.u8 = { type = "u8" }
|
||||||
|
Schema.i8 = { type = "i8" }
|
||||||
|
Schema.u16 = { type = "u16" }
|
||||||
|
Schema.i16 = { type = "i16" }
|
||||||
|
Schema.u32 = { type = "u32" }
|
||||||
|
Schema.i32 = { type = "i32" }
|
||||||
|
Schema.f32 = { type = "f32" }
|
||||||
|
Schema.f64 = { type = "f64" }
|
||||||
|
Schema.f16 = { type = "f16" }
|
||||||
|
Schema.boolean = { type = "boolean" }
|
||||||
|
Schema.vector3 = { type = "vector3" }
|
||||||
|
Schema.vector2 = { type = "vector2" }
|
||||||
|
Schema.cframe = { type = "cframe" }
|
||||||
|
Schema.color3 = { type = "color3" }
|
||||||
|
Schema.instance = { type = "instance" }
|
||||||
|
Schema.string = { type = "string" }
|
||||||
|
|
||||||
|
function Schema.optional(item: SchemaType): SchemaType
|
||||||
|
return { type = "optional", item = item }
|
||||||
|
end
|
||||||
|
|
||||||
|
function Schema.array(item: SchemaType): SchemaType
|
||||||
|
return { type = "array", item = item }
|
||||||
|
end
|
||||||
|
|
||||||
|
function Schema.map(key: SchemaType, value: SchemaType): SchemaType
|
||||||
|
return { type = "map", key = key, value = value }
|
||||||
|
end
|
||||||
|
|
||||||
|
function Schema.struct(fields: {[string]: SchemaType}): SchemaType
|
||||||
|
local orderedFields = {}
|
||||||
|
for k, v in fields do
|
||||||
|
table.insert(orderedFields, { key = k, schema = v })
|
||||||
|
end
|
||||||
|
table.sort(orderedFields, function(a, b) return a.key < b.key end)
|
||||||
|
return { type = "struct", fields = orderedFields }
|
||||||
|
end
|
||||||
|
|
||||||
|
local function compilePacker(s: SchemaType): (Writer, any) -> ()
|
||||||
|
if s.type == "u8" then return wByte end
|
||||||
|
if s.type == "i8" then return function(w, v) ensureSpace(w, 1) buffer.writei8(w.buf, w.cursor, v) w.cursor += 1 end end
|
||||||
|
if s.type == "u16" then return wU16 end
|
||||||
|
if s.type == "i16" then return wI16 end
|
||||||
|
if s.type == "u32" then return wU32 end
|
||||||
|
if s.type == "i32" then return wI32 end
|
||||||
|
if s.type == "f32" then return wF32 end
|
||||||
|
if s.type == "f64" then return wF64 end
|
||||||
|
if s.type == "f16" then return wF16 end
|
||||||
|
if s.type == "boolean" then return function(w, v) wByte(w, v and 1 or 0) end end
|
||||||
|
if s.type == "string" then return function(w, v) local len = #v wVarUInt(w, len) wString(w, v) end end
|
||||||
|
|
||||||
|
if s.type == "vector3" then return function(w, v) wF16(w, f32ToF16(v.X)) wF16(w, f32ToF16(v.Y)) wF16(w, f32ToF16(v.Z)) end end
|
||||||
|
if s.type == "vector2" then return function(w, v) wF16(w, f32ToF16(v.X)) wF16(w, f32ToF16(v.Y)) end end
|
||||||
|
|
||||||
|
if s.type == "cframe" then
|
||||||
|
return function(w, v)
|
||||||
|
local pos = v.Position
|
||||||
|
local rx, ry, rz = v:ToOrientation()
|
||||||
|
wF16(w, f32ToF16(pos.X)) wF16(w, f32ToF16(pos.Y)) wF16(w, f32ToF16(pos.Z))
|
||||||
|
wF16(w, f32ToF16(rx)) wF16(w, f32ToF16(ry)) wF16(w, f32ToF16(rz))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if s.type == "color3" then
|
||||||
|
return function(w, v)
|
||||||
|
wByte(w, math.clamp(math.round(v.R * 255), 0, 255))
|
||||||
|
wByte(w, math.clamp(math.round(v.G * 255), 0, 255))
|
||||||
|
wByte(w, math.clamp(math.round(v.B * 255), 0, 255))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if s.type == "instance" then return function(w, v) table.insert(w.refs, v) wVarUInt(w, #w.refs) end end
|
||||||
|
|
||||||
|
if s.type == "struct" then
|
||||||
|
local fields = {}
|
||||||
|
for _, field in s.fields do
|
||||||
|
table.insert(fields, { key = field.key, packer = compilePacker(field.schema) })
|
||||||
|
end
|
||||||
|
return function(w, v)
|
||||||
|
if type(v) ~= "table" then error(`Expected table for struct, got {typeof(v)}`) end
|
||||||
|
for _, f in fields do
|
||||||
|
local val = v[f.key]
|
||||||
|
if val == nil then error(`Schema Error: Missing required field '{f.key}'`) end
|
||||||
|
f.packer(w, val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if s.type == "array" then
|
||||||
|
local itemPacker = compilePacker(s.item)
|
||||||
|
return function(w, v)
|
||||||
|
if type(v) ~= "table" then error(`Expected table for array, got {typeof(v)}`) end
|
||||||
|
local len = #v
|
||||||
|
wVarUInt(w, len)
|
||||||
|
for i = 1, len do
|
||||||
|
if v[i] == nil then error(`Schema Error: Array item at index {i} is nil`) end
|
||||||
|
itemPacker(w, v[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if s.type == "map" then
|
||||||
|
local keyPacker = compilePacker(s.key)
|
||||||
|
local valPacker = compilePacker(s.value)
|
||||||
|
return function(w, v)
|
||||||
|
local count = 0
|
||||||
|
for _ in v do count += 1 end
|
||||||
|
wVarUInt(w, count)
|
||||||
|
for k, val in v do
|
||||||
|
keyPacker(w, k)
|
||||||
|
valPacker(w, val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if s.type == "optional" then
|
||||||
|
local itemPacker = compilePacker(s.item)
|
||||||
|
return function(w, v)
|
||||||
|
if v == nil then
|
||||||
|
wByte(w, 0)
|
||||||
|
else
|
||||||
|
wByte(w, 1)
|
||||||
|
itemPacker(w, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return function() end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function compileReader(s: SchemaType): (buffer, number, {any}?) -> (any, number)
|
||||||
|
if s.type == "u8" then return function(b, c) return buffer.readu8(b, c), c + 1 end end
|
||||||
|
if s.type == "i8" then return function(b, c) return buffer.readi8(b, c), c + 1 end end
|
||||||
|
if s.type == "u16" then return function(b, c) return buffer.readu16(b, c), c + 2 end end
|
||||||
|
if s.type == "i16" then return function(b, c) return buffer.readi16(b, c), c + 2 end end
|
||||||
|
if s.type == "u32" then return function(b, c) return buffer.readu32(b, c), c + 4 end end
|
||||||
|
if s.type == "i32" then return function(b, c) return buffer.readi32(b, c), c + 4 end end
|
||||||
|
if s.type == "f32" then return function(b, c) return buffer.readf32(b, c), c + 4 end end
|
||||||
|
if s.type == "f64" then return function(b, c) return buffer.readf64(b, c), c + 8 end end
|
||||||
|
if s.type == "f16" then return function(b, c) return f16ToF32(buffer.readu16(b, c)), c + 2 end end
|
||||||
|
if s.type == "boolean" then return function(b, c) return buffer.readu8(b, c) ~= 0, c + 1 end end
|
||||||
|
if s.type == "string" then
|
||||||
|
return function(b, c)
|
||||||
|
local len; len, c = readVarUInt(b, c)
|
||||||
|
return buffer.readstring(b, c, len), c + len
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if s.type == "vector3" then
|
||||||
|
return function(b, c)
|
||||||
|
local x = f16ToF32(buffer.readu16(b, c))
|
||||||
|
local y = f16ToF32(buffer.readu16(b, c + 2))
|
||||||
|
local z = f16ToF32(buffer.readu16(b, c + 4))
|
||||||
|
return Vector3.new(x, y, z), c + 6
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if s.type == "vector2" then
|
||||||
|
return function(b, c)
|
||||||
|
local x = f16ToF32(buffer.readu16(b, c))
|
||||||
|
local y = f16ToF32(buffer.readu16(b, c + 2))
|
||||||
|
return Vector2.new(x, y), c + 4
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if s.type == "color3" then
|
||||||
|
return function(b, c)
|
||||||
|
local r = buffer.readu8(b, c)
|
||||||
|
local g = buffer.readu8(b, c + 1)
|
||||||
|
local bVal = buffer.readu8(b, c + 2)
|
||||||
|
return Color3.fromRGB(r, g, bVal), c + 3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if s.type == "cframe" then
|
||||||
|
return function(b, c)
|
||||||
|
local px = f16ToF32(buffer.readu16(b, c))
|
||||||
|
local py = f16ToF32(buffer.readu16(b, c + 2))
|
||||||
|
local pz = f16ToF32(buffer.readu16(b, c + 4))
|
||||||
|
local rx = f16ToF32(buffer.readu16(b, c + 6))
|
||||||
|
local ry = f16ToF32(buffer.readu16(b, c + 8))
|
||||||
|
local rz = f16ToF32(buffer.readu16(b, c + 10))
|
||||||
|
return CFrame.new(px, py, pz) * CFrame.fromOrientation(rx, ry, rz), c + 12
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if s.type == "instance" then
|
||||||
|
return function(b, c, refs)
|
||||||
|
local idx; idx, c = readVarUInt(b, c)
|
||||||
|
return refs and refs[idx] or nil, c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if s.type == "struct" then
|
||||||
|
local fields = {}
|
||||||
|
for _, field in s.fields do
|
||||||
|
table.insert(fields, { key = field.key, reader = compileReader(field.schema) })
|
||||||
|
end
|
||||||
|
return function(b, c, refs)
|
||||||
|
local obj = {}
|
||||||
|
for _, f in fields do
|
||||||
|
obj[f.key], c = f.reader(b, c, refs)
|
||||||
|
end
|
||||||
|
return obj, c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if s.type == "array" then
|
||||||
|
local itemReader = compileReader(s.item)
|
||||||
|
return function(b, c, refs)
|
||||||
|
local len; len, c = readVarUInt(b, c)
|
||||||
|
local arr = table.create(len)
|
||||||
|
for i = 1, len do
|
||||||
|
arr[i], c = itemReader(b, c, refs)
|
||||||
|
end
|
||||||
|
return arr, c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if s.type == "map" then
|
||||||
|
local keyReader = compileReader(s.key)
|
||||||
|
local valReader = compileReader(s.value)
|
||||||
|
return function(b, c, refs)
|
||||||
|
local count; count, c = readVarUInt(b, c)
|
||||||
|
local map = {}
|
||||||
|
for _ = 1, count do
|
||||||
|
local k, val
|
||||||
|
k, c = keyReader(b, c, refs)
|
||||||
|
val, c = valReader(b, c, refs)
|
||||||
|
map[k] = val
|
||||||
|
end
|
||||||
|
return map, c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if s.type == "optional" then
|
||||||
|
local itemReader = compileReader(s.item)
|
||||||
|
return function(b, c, refs)
|
||||||
|
local exists = buffer.readu8(b, c) ~= 0
|
||||||
|
c += 1
|
||||||
|
if exists then
|
||||||
|
return itemReader(b, c, refs)
|
||||||
|
else
|
||||||
|
return nil, c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(_, c) return nil, c end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function packStrict(w: Writer, s: SchemaType, v: any)
|
||||||
|
local packer = compilePacker(s)
|
||||||
|
packer(w, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function readStrict(buf: buffer, cursor: number, s: SchemaType, refs: { any }?): (any, number)
|
||||||
|
local reader = compileReader(s)
|
||||||
|
return reader(buf, cursor, refs)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function writeEvents(w: Writer, events: {{any}}, schemas: {[string]: SchemaType})
|
||||||
|
local count = #events
|
||||||
|
wVarUInt(w, count)
|
||||||
|
for _, event in events do
|
||||||
|
local remote = event[1]
|
||||||
|
local args = event[2]
|
||||||
|
packValue(w, remote)
|
||||||
|
local schema = schemas[remote]
|
||||||
|
if schema then
|
||||||
|
packStrict(w, schema, args[1])
|
||||||
|
else
|
||||||
|
packValue(w, args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function readEvents(buf: buffer, refs: {any}?, schemas: {[string]: SchemaType}): {{any}}
|
||||||
|
local pos, count = 0
|
||||||
|
count, pos = readVarUInt(buf, pos)
|
||||||
|
local events = table.create(count)
|
||||||
|
for i = 1, count do
|
||||||
|
local remote
|
||||||
|
remote, pos = unpackValue(buf, pos, refs)
|
||||||
|
local args
|
||||||
|
local schema = schemas[remote]
|
||||||
|
if schema then
|
||||||
|
local val
|
||||||
|
val, pos = readStrict(buf, pos, schema, refs)
|
||||||
|
args = {val}
|
||||||
|
else
|
||||||
|
args, pos = unpackValue(buf, pos, refs)
|
||||||
|
end
|
||||||
|
events[i] = {remote, args}
|
||||||
|
end
|
||||||
|
return events
|
||||||
|
end
|
||||||
|
|
||||||
local BufferSerdes = {}
|
local BufferSerdes = {}
|
||||||
|
|
||||||
|
BufferSerdes.writeEvents = writeEvents
|
||||||
|
BufferSerdes.readEvents = readEvents
|
||||||
|
BufferSerdes.Schema = Schema
|
||||||
|
BufferSerdes.compilePacker = compilePacker
|
||||||
|
BufferSerdes.compileReader = compileReader
|
||||||
|
BufferSerdes.packStrict = packStrict
|
||||||
|
BufferSerdes.readStrict = readStrict
|
||||||
|
|
||||||
function BufferSerdes.write(data: any): (buffer, {any}?)
|
function BufferSerdes.write(data: any): (buffer, {any}?)
|
||||||
local w = createWriter()
|
local w = createWriter()
|
||||||
packValue(w, data)
|
packValue(w, data)
|
||||||
|
|
@ -983,4 +1290,8 @@ BufferSerdes.readVarUInt = readVarUInt
|
||||||
BufferSerdes.f32ToF16 = f32ToF16
|
BufferSerdes.f32ToF16 = f32ToF16
|
||||||
BufferSerdes.f16ToF32 = f16ToF32
|
BufferSerdes.f16ToF32 = f16ToF32
|
||||||
|
|
||||||
return BufferSerdes :: typeof(BufferSerdes)
|
BufferSerdes.readTagged = unpackValue
|
||||||
|
BufferSerdes.packTagged = packValue
|
||||||
|
BufferSerdes.unpack = unpackValue
|
||||||
|
|
||||||
|
return BufferSerdes :: typeof(BufferSerdes)
|
||||||
|
|
@ -22,11 +22,15 @@ type Event = {
|
||||||
|
|
||||||
local queueEvent: { { any } } = {}
|
local queueEvent: { { any } } = {}
|
||||||
local eventListeners: { Event } = {}
|
local eventListeners: { Event } = {}
|
||||||
|
local eventSchemas: { [string]: Buffer.SchemaType } = {}
|
||||||
|
|
||||||
local pendingInvokes: { [string]: thread } = {}
|
local pendingInvokes: { [string]: thread } = {}
|
||||||
local invokeHandlers: { [string]: (...any?) -> ...any? } = {}
|
|
||||||
local invokeId = 0
|
local invokeId = 0
|
||||||
|
|
||||||
|
Client.useSchema = function(remoteName: string, schema: Buffer.SchemaType)
|
||||||
|
eventSchemas[remoteName] = schema
|
||||||
|
end
|
||||||
|
|
||||||
Client.Connect = function(remoteName: string, fn: (Player, ...any?) -> (...any?)): Connection
|
Client.Connect = function(remoteName: string, fn: (Player, ...any?) -> (...any?)): Connection
|
||||||
local detail = {
|
local detail = {
|
||||||
remote = remoteName,
|
remote = remoteName,
|
||||||
|
|
@ -86,21 +90,21 @@ end
|
||||||
|
|
||||||
Client.Invoke = function(remoteName: string, timeout: number?, ...: any?): ...any?
|
Client.Invoke = function(remoteName: string, timeout: number?, ...: any?): ...any?
|
||||||
invokeId += 1
|
invokeId += 1
|
||||||
local invokeId, thread = `{invokeId}`, coroutine.running()
|
local id, thread = `{invokeId}`, coroutine.running()
|
||||||
|
|
||||||
pendingInvokes[invokeId] = thread
|
pendingInvokes[id] = thread
|
||||||
task.delay(timeout or 2, function()
|
task.delay(timeout or 2, function()
|
||||||
local pending = pendingInvokes[invokeId]
|
local pending = pendingInvokes[id]
|
||||||
if not pending then
|
if not pending then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
task.spawn(pending, nil)
|
task.spawn(pending, nil)
|
||||||
pendingInvokes[invokeId] = nil
|
pendingInvokes[id] = nil
|
||||||
end)
|
end)
|
||||||
|
|
||||||
table.insert(queueEvent, {
|
table.insert(queueEvent, {
|
||||||
"\0",
|
"\0",
|
||||||
{ remoteName, invokeId, { ... } :: any } :: any
|
{ remoteName, id, { ... } :: any } :: any
|
||||||
})
|
})
|
||||||
return coroutine.yield()
|
return coroutine.yield()
|
||||||
end
|
end
|
||||||
|
|
@ -113,19 +117,19 @@ if RunService:IsClient() then
|
||||||
local remote = content[1]
|
local remote = content[1]
|
||||||
local content = content[2]
|
local content = content[2]
|
||||||
if remote == "\1" then
|
if remote == "\1" then
|
||||||
local invokeId = content[1]
|
local id = content[1]
|
||||||
local results = content[2]
|
local results = content[2]
|
||||||
local pending = pendingInvokes[invokeId]
|
local pending = pendingInvokes[id]
|
||||||
if pending then
|
if pending then
|
||||||
task.spawn(pending :: any, table.unpack(results))
|
task.spawn(pending :: any, table.unpack(results))
|
||||||
pendingInvokes[invokeId] = nil
|
pendingInvokes[id] = nil
|
||||||
end
|
end
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
if #eventListeners == 0 then continue end
|
if #eventListeners == 0 then continue end
|
||||||
if remote == "\0" then
|
if remote == "\0" then
|
||||||
local remoteName = content[1]
|
local remoteName = content[1]
|
||||||
local invokeId = content[2]
|
local id = content[2]
|
||||||
local args = content[3]
|
local args = content[3]
|
||||||
for _, connection in eventListeners do
|
for _, connection in eventListeners do
|
||||||
if connection.remote == remoteName then
|
if connection.remote == remoteName then
|
||||||
|
|
@ -133,7 +137,7 @@ if RunService:IsClient() then
|
||||||
local results = { connection.fn(table.unpack(args)) }
|
local results = { connection.fn(table.unpack(args)) }
|
||||||
table.insert(queueEvent, {
|
table.insert(queueEvent, {
|
||||||
"\1",
|
"\1",
|
||||||
{ invokeId, results } :: any
|
{ id, results } :: any
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
break
|
break
|
||||||
|
|
@ -152,7 +156,7 @@ if RunService:IsClient() then
|
||||||
if deltaT < cycle then return end
|
if deltaT < cycle then return end
|
||||||
deltaT = 0
|
deltaT = 0
|
||||||
if #queueEvent == 0 then return end
|
if #queueEvent == 0 then return end
|
||||||
Buffer.pack(writer, queueEvent)
|
Buffer.writeEvents(writer, queueEvent, eventSchemas)
|
||||||
do
|
do
|
||||||
local buf, ref = Buffer.buildWithRefs(writer)
|
local buf, ref = Buffer.buildWithRefs(writer)
|
||||||
Buffer.reset(writer)
|
Buffer.reset(writer)
|
||||||
|
|
@ -166,4 +170,11 @@ if RunService:IsClient() then
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
@class Client
|
||||||
|
@schema
|
||||||
|
define a schema for your data and use a strict packing
|
||||||
|
]]
|
||||||
|
Client.Schema = Buffer.Schema
|
||||||
|
|
||||||
return Client :: typeof(Client)
|
return Client :: typeof(Client)
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,16 @@ local queueEvent: {
|
||||||
[Player]: { { any } },
|
[Player]: { { any } },
|
||||||
} = {}
|
} = {}
|
||||||
local eventListeners: { Event } = {}
|
local eventListeners: { Event } = {}
|
||||||
|
local eventSchemas: { [string]: Buffer.SchemaType } = {}
|
||||||
local players_ready: { Player } = {}
|
local players_ready: { Player } = {}
|
||||||
|
|
||||||
local pendingInvokes: { [string]: thread } = {}
|
local pendingInvokes: { [string]: thread } = {}
|
||||||
local invokeHandlers: { [string]: (...any?) -> ...any? } = {}
|
|
||||||
local invokeId = 0
|
local invokeId = 0
|
||||||
|
|
||||||
|
Server.useSchema = function(remoteName: string, schema: Buffer.SchemaType)
|
||||||
|
eventSchemas[remoteName] = schema
|
||||||
|
end
|
||||||
|
|
||||||
Server.Connect = function(remoteName: string, fn: (Player, ...any?) -> (...any?)): Connection
|
Server.Connect = function(remoteName: string, fn: (Player, ...any?) -> (...any?)): Connection
|
||||||
local detail = {
|
local detail = {
|
||||||
remote = remoteName,
|
remote = remoteName,
|
||||||
|
|
@ -99,21 +103,21 @@ end
|
||||||
|
|
||||||
Server.Invoke = function(remoteName: string, player: Player, timeout: number?, ...: any?): ...any?
|
Server.Invoke = function(remoteName: string, player: Player, timeout: number?, ...: any?): ...any?
|
||||||
invokeId += 1
|
invokeId += 1
|
||||||
local invokeId, thread = `{invokeId}`, coroutine.running()
|
local id, thread = `{invokeId}`, coroutine.running()
|
||||||
|
|
||||||
pendingInvokes[invokeId] = thread
|
pendingInvokes[id] = thread
|
||||||
task.delay(timeout or 2, function()
|
task.delay(timeout or 2, function()
|
||||||
local pending = pendingInvokes[invokeId]
|
local pending = pendingInvokes[id]
|
||||||
if not pending then
|
if not pending then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
task.spawn(pending, nil)
|
task.spawn(pending, nil)
|
||||||
pendingInvokes[invokeId] = nil
|
pendingInvokes[id] = nil
|
||||||
end)
|
end)
|
||||||
|
|
||||||
table.insert(queueEvent[player], {
|
table.insert(queueEvent[player], {
|
||||||
"\0",
|
"\0",
|
||||||
{ remoteName, invokeId, { ... } :: any } :: any
|
{ remoteName, id, { ... } :: any } :: any
|
||||||
})
|
})
|
||||||
return coroutine.yield()
|
return coroutine.yield()
|
||||||
end
|
end
|
||||||
|
|
@ -121,24 +125,24 @@ end
|
||||||
if RunService:IsServer() then
|
if RunService:IsServer() then
|
||||||
Event.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance? })
|
Event.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance? })
|
||||||
if type(b) ~= "buffer" then return end
|
if type(b) ~= "buffer" then return end
|
||||||
local contents = Buffer.read(b, ref)
|
local contents = Buffer.readEvents(b, ref, eventSchemas)
|
||||||
for _, content in contents do
|
for _, content in contents do
|
||||||
local remote = content[1]
|
local remote = content[1]
|
||||||
local content = content[2]
|
local content = content[2]
|
||||||
if remote == "\1" then
|
if remote == "\1" then
|
||||||
local invokeId = content[1]
|
local id = content[1]
|
||||||
local results = content[2]
|
local results = content[2]
|
||||||
local pending = pendingInvokes[invokeId]
|
local pending = pendingInvokes[id]
|
||||||
if pending then
|
if pending then
|
||||||
task.spawn(pending :: any, table.unpack(results))
|
task.spawn(pending :: any, table.unpack(results))
|
||||||
pendingInvokes[invokeId] = nil
|
pendingInvokes[id] = nil
|
||||||
end
|
end
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
if #eventListeners == 0 then continue end
|
if #eventListeners == 0 then continue end
|
||||||
if remote == "\0" then
|
if remote == "\0" then
|
||||||
local remoteName = content[1]
|
local remoteName = content[1]
|
||||||
local invokeId = content[2]
|
local id = content[2]
|
||||||
local args = content[3]
|
local args = content[3]
|
||||||
for _, connection in eventListeners do
|
for _, connection in eventListeners do
|
||||||
if connection.remote == remoteName then
|
if connection.remote == remoteName then
|
||||||
|
|
@ -146,7 +150,7 @@ if RunService:IsServer() then
|
||||||
local results = { connection.fn(table.unpack(args)) }
|
local results = { connection.fn(table.unpack(args)) }
|
||||||
table.insert(queueEvent[player], {
|
table.insert(queueEvent[player], {
|
||||||
"\1",
|
"\1",
|
||||||
{ invokeId, results } :: any
|
{ id, results } :: any
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
break
|
break
|
||||||
|
|
@ -166,7 +170,7 @@ if RunService:IsServer() then
|
||||||
deltaT = 0
|
deltaT = 0
|
||||||
for player: Player, content in queueEvent do
|
for player: Player, content in queueEvent do
|
||||||
if #content == 0 or player.Parent ~= Players then continue end
|
if #content == 0 or player.Parent ~= Players then continue end
|
||||||
Buffer.pack(writer, content)
|
Buffer.writeEvents(writer, content, eventSchemas)
|
||||||
do
|
do
|
||||||
local buf, ref = Buffer.buildWithRefs(writer)
|
local buf, ref = Buffer.buildWithRefs(writer)
|
||||||
Buffer.reset(writer)
|
Buffer.reset(writer)
|
||||||
|
|
@ -200,4 +204,11 @@ if RunService:IsServer() then
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
@class Server
|
||||||
|
@schema
|
||||||
|
define a schema for your data and use a strict packing
|
||||||
|
]]
|
||||||
|
Server.Schema = Buffer.Schema
|
||||||
|
|
||||||
return Server :: typeof(Server)
|
return Server :: typeof(Server)
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,29 @@ end
|
||||||
|
|
||||||
local Client = require("@self/Client")
|
local Client = require("@self/Client")
|
||||||
local Server = require("@self/Server")
|
local Server = require("@self/Server")
|
||||||
|
local Buffer = require("@self/Buffer")
|
||||||
|
|
||||||
|
--[[
|
||||||
|
@class Remote
|
||||||
|
@client
|
||||||
|
]]
|
||||||
Remote.Client = function()
|
Remote.Client = function()
|
||||||
return Client
|
return Client
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
@class Remote
|
||||||
|
@server
|
||||||
|
]]
|
||||||
Remote.Server = function()
|
Remote.Server = function()
|
||||||
return Server
|
return Server
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
@class Remote
|
||||||
|
@schema
|
||||||
|
define a schema for your data and use a strict packing
|
||||||
|
]]
|
||||||
|
Remote.Buffer = Buffer
|
||||||
|
|
||||||
return Remote :: typeof(Remote)
|
return Remote :: typeof(Remote)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue