rewrite(phase4): implement Identifier, and small fix

This commit is contained in:
khtsly 2026-02-13 17:37:23 +07:00
parent e810c6e000
commit f6b0e62880
9 changed files with 115 additions and 88 deletions

2
.gitignore vendored
View file

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

View file

@ -7,10 +7,10 @@ Let's try and play something with Warp!
local Schema = require(path.to.warp).Buffer.Schema local Schema = require(path.to.warp).Buffer.Schema
return { return {
Example = Schema.array(Schema.string()), Example = Schema.array(Schema.string),
Ping = Schema.string(), Ping = Schema.string,
Pong = Schema.string(), Pong = Schema.string,
PingAll = Schema.string(), PingAll = Schema.string,
} }
``` ```
```luau [Server] ```luau [Server]
@ -58,17 +58,17 @@ Warp.Connect("PingAll", function(isPing: boolean)
end end
end) end)
task.wait(3) -- lets wait a few seconds, let the server do the things first!
-- Try request a event from server! -- Try request a event from server!
print(Warp.Invoke("Example", 1, { "Hello!", `this is from: @{Players.LocalPlayer.Name}` })) print(Warp.Invoke("Example", 1, { "Hello!", `this is from: @{Players.LocalPlayer.Name}` }))
-- Do a ping & pong to server! -- Do a ping & pong to server!
Warp.Fire("Ping", true, "ping!") -- we send through reliable event Warp.Fire("Ping", true, "ping!") -- we send through reliable event
task.wait(1) -- lets wait 1 seconds!
-- Disconnect All the events -- Disconnect All the events
connection1:DisconnectAll() connection1:Disconnect()
-- or just disconnect spesific connection -- or just disconnect spesific connection
Warp.Disconnect("PingAll") Warp.DisconnectAll("PingAll")
-- Destroying/Deleting a Event? -- Destroying/Deleting a Event?
Warp.Destroy("Pong") Warp.Destroy("Pong")

View file

@ -1 +1 @@
{"name":"Warp","className":"ModuleScript","filePaths":["src/init.luau","default.project.json"],"children":[{"name":"Buffer","className":"ModuleScript","filePaths":["src/Buffer/init.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":"Util","className":"Folder","children":[{"name":"Buffer","className":"ModuleScript","filePaths":["src/Util/Buffer/init.luau"]},{"name":"Thread","className":"ModuleScript","filePaths":["src/Util/Thread.luau"]},{"name":"Identifier","className":"ModuleScript","filePaths":["src/Util/Identifier.luau"]}]}]}

View file

@ -4,8 +4,9 @@
local Client = {} local Client = {}
local RunService = game:GetService("RunService") local RunService = game:GetService("RunService")
local Thread = require("./Thread") local Thread = require("./Util/Thread")
local Buffer = require("./Buffer") local Buffer = require("./Util/Buffer")
local Identifier = require("./Util/Identifier")
local Event: RemoteEvent = script.Parent:WaitForChild("Event") local Event: RemoteEvent = script.Parent:WaitForChild("Event")
local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent") local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent")
local deltaT: number, cycle: number = 0, 1 / 61 local deltaT: number, cycle: number = 0, 1 / 61
@ -16,26 +17,26 @@ type Connection = {
Disconnect: (self: Connection) -> (), Disconnect: (self: Connection) -> (),
} }
type Event = { type Event = {
remote: string, i: number,
fn: (Player, ...any?) -> ...any?, c: (Player, ...any?) -> ...any?,
} }
local queueEvent: { { any } } = {} local queueEvent: { { any } } = {}
local queueUnreliableEvent: { { any } } = {} local queueUnreliableEvent: { { any } } = {}
local eventListeners: { Event } = {} local eventListeners: { Event } = {}
local eventSchemas: { [string]: Buffer.SchemaType } = {} local eventSchemas: { [number]: Buffer.SchemaType } = {}
local pendingInvokes: { [string]: thread } = {} local pendingInvokes: { [string]: thread } = {}
local invokeId = 0 local invokeId = 0
Client.useSchema = function(remoteName: string, schema: Buffer.SchemaType) Client.useSchema = function(remoteName: string, schema: Buffer.SchemaType)
eventSchemas[remoteName] = schema eventSchemas[Identifier(remoteName)] = schema
end 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, i = Identifier(remoteName),
fn = fn, c = fn,
} }
table.insert(eventListeners, detail) table.insert(eventListeners, detail)
return { return {
@ -73,40 +74,39 @@ Client.Wait = function(remoteName: string): (number, ...any?)
end end
Client.DisconnectAll = function(remoteName: string) Client.DisconnectAll = function(remoteName: string)
local id = Identifier(remoteName)
for idx = #eventListeners, 1, -1 do for idx = #eventListeners, 1, -1 do
if eventListeners[idx].remote == remoteName then if eventListeners[idx].i == id then
table.remove(eventListeners, idx) table.remove(eventListeners, idx)
end end
end end
end end
Client.Destroy = function(remoteName: string) Client.Destroy = Client.DisconnectAll
Client.DisconnectAll(remoteName)
end
Client.Fire = function(remoteName: string, reliable: boolean, ...: any?) Client.Fire = function(remoteName: string, reliable: boolean, ...: any?)
table.insert(reliable and queueEvent or queueUnreliableEvent, { table.insert(reliable and queueEvent or queueUnreliableEvent, {
remoteName, Identifier(remoteName),
{ ... } :: any, { ... } :: any,
}) })
end end
Client.Invoke = function(remoteName: string, timeout: number?, ...: any?): ...any? Client.Invoke = function(remoteName: string, timeout: number?, ...: any?): ...any?
invokeId += 1 invokeId += 1
local id, thread = `{invokeId}`, coroutine.running() local reqid, thread = `{invokeId}`, coroutine.running()
pendingInvokes[id] = thread pendingInvokes[reqid] = thread
task.delay(timeout or 2, function() task.delay(timeout or 2, function()
local pending = pendingInvokes[id] local pending = pendingInvokes[reqid]
if not pending then if not pending then
return return
end end
task.spawn(pending, nil) task.spawn(pending, nil)
pendingInvokes[id] = nil pendingInvokes[reqid] = nil
end) end)
table.insert(queueEvent, { table.insert(queueEvent, {
"\0", 0,
{ remoteName, id, { ... } :: any } :: any, { Identifier(remoteName), reqid :: any, { ... } :: any } :: any,
}) })
return coroutine.yield() return coroutine.yield()
end end
@ -121,7 +121,7 @@ if RunService:IsClient() then
local remote = content[1] local remote = content[1]
local content = content[2] local content = content[2]
if handleInvokes then if handleInvokes then
if remote == "\1" then if remote == 0 then
local id = content[1] local id = content[1]
local results = content[2] local results = content[2]
local pending = pendingInvokes[id] local pending = pendingInvokes[id]
@ -131,7 +131,7 @@ if RunService:IsClient() then
end end
continue continue
end end
if remote == "\0" then if remote == 1 then
if #eventListeners == 0 then if #eventListeners == 0 then
continue continue
end end
@ -139,11 +139,11 @@ if RunService:IsClient() then
local id = 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.i == remoteName then
Thread(function() Thread(function()
local results = { connection.fn(table.unpack(args)) } local results = { connection.c(table.unpack(args)) }
table.insert(queueEvent, { table.insert(queueEvent, {
"\1", 1,
{ id, results } :: any, { id, results } :: any,
}) })
end) end)
@ -157,10 +157,10 @@ if RunService:IsClient() then
continue continue
end end
for _, connection in eventListeners do for _, connection in eventListeners do
if connection.remote ~= remote then if connection.i ~= remote then
continue continue
end end
Thread(connection.fn, table.unpack(content)) Thread(connection.c, table.unpack(content))
end end
end end
end end

View file

@ -5,8 +5,9 @@ local Server = {}
local Players = game:GetService("Players") local Players = game:GetService("Players")
local RunService = game:GetService("RunService") local RunService = game:GetService("RunService")
local Thread = require("./Thread") local Thread = require("./Util/Thread")
local Buffer = require("./Buffer") local Buffer = require("./Util/Buffer")
local Identifier = require("./Util/Identifier")
local Event: RemoteEvent = script.Parent:WaitForChild("Event") local Event: RemoteEvent = script.Parent:WaitForChild("Event")
local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent") local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent")
local deltaT: number, cycle: number = 0, 1 / 61 local deltaT: number, cycle: number = 0, 1 / 61
@ -17,8 +18,8 @@ type Connection = {
Disconnect: (self: Connection) -> (), Disconnect: (self: Connection) -> (),
} }
type Event = { type Event = {
remote: string, i: number,
fn: (Player, ...any?) -> ...any?, c: (Player, ...any?) -> ...any?,
} }
local queueEvent: { local queueEvent: {
@ -28,20 +29,20 @@ local queueUnreliableEvent: {
[Player]: { { any } }, [Player]: { { any } },
} = {} } = {}
local eventListeners: { Event } = {} local eventListeners: { Event } = {}
local eventSchemas: { [string]: Buffer.SchemaType } = {} local eventSchemas: { [number]: Buffer.SchemaType } = {}
local players_ready: { Player } = {} local players_ready: { Player } = {}
local pendingInvokes: { [string]: thread } = {} local pendingInvokes: { [string]: thread } = {}
local invokeId = 0 local invokeId = 0
Server.useSchema = function(remoteName: string, schema: Buffer.SchemaType) Server.useSchema = function(remoteName: string, schema: Buffer.SchemaType)
eventSchemas[remoteName] = schema eventSchemas[Identifier(remoteName)] = schema
end 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, i = Identifier(remoteName),
fn = fn, c = fn,
} }
table.insert(eventListeners, detail) table.insert(eventListeners, detail)
return { return {
@ -79,16 +80,18 @@ Server.Wait = function(remoteName: string): (number, ...any?)
end end
Server.DisconnectAll = function(remoteName: string) Server.DisconnectAll = function(remoteName: string)
local id = Identifier(remoteName)
if not id then
return
end
for idx = #eventListeners, 1, -1 do for idx = #eventListeners, 1, -1 do
if eventListeners[idx].remote == remoteName then if eventListeners[idx].i == id then
table.remove(eventListeners, idx) table.remove(eventListeners, idx)
end end
end end
end end
Server.Destroy = function(remoteName: string) Server.Destroy = Server.DisconnectAll
Server.DisconnectAll(remoteName)
end
Server.Fire = function(remoteName: string, reliable: boolean, player: Player, ...: any?) Server.Fire = function(remoteName: string, reliable: boolean, player: Player, ...: any?)
local targetQueue = reliable and queueEvent or queueUnreliableEvent local targetQueue = reliable and queueEvent or queueUnreliableEvent
@ -96,7 +99,7 @@ Server.Fire = function(remoteName: string, reliable: boolean, player: Player, ..
targetQueue[player] = {} :: any targetQueue[player] = {} :: any
end end
table.insert(targetQueue[player], { table.insert(targetQueue[player], {
remoteName, Identifier(remoteName),
{ ... } :: any, { ... } :: any,
}) })
end end
@ -116,23 +119,23 @@ 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 id, thread = `{invokeId}`, coroutine.running() local reqId, thread = `{invokeId}`, coroutine.running()
pendingInvokes[id] = thread pendingInvokes[reqId] = thread
task.delay(timeout or 2, function() task.delay(timeout or 2, function()
local pending = pendingInvokes[id] local pending = pendingInvokes[reqId]
if not pending then if not pending then
return return
end end
task.spawn(pending, nil) task.spawn(pending, nil)
pendingInvokes[id] = nil pendingInvokes[reqId] = nil
end) end)
if not queueEvent[player] then if not queueEvent[player] then
queueEvent[player] = {} :: any queueEvent[player] = {} :: any
end end
table.insert(queueEvent[player], { table.insert(queueEvent[player], {
"\0", 0,
{ remoteName, id, { ... } :: any } :: any, { Identifier(remoteName), reqId :: any, { ... } :: any } :: any,
}) })
return coroutine.yield() return coroutine.yield()
end end
@ -147,7 +150,7 @@ if RunService:IsServer() then
local remote = content[1] local remote = content[1]
local content = content[2] local content = content[2]
if handleInvokes then if handleInvokes then
if remote == "\1" then if remote == 1 then
local id = content[1] local id = content[1]
local results = content[2] local results = content[2]
local pending = pendingInvokes[id] local pending = pendingInvokes[id]
@ -157,7 +160,7 @@ if RunService:IsServer() then
end end
continue continue
end end
if remote == "\0" then if remote == 0 then
if #eventListeners == 0 then if #eventListeners == 0 then
continue continue
end end
@ -165,14 +168,14 @@ if RunService:IsServer() then
local id = 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.i == remoteName then
Thread(function() Thread(function()
local results = { connection.fn(table.unpack(args)) } local results = { connection.c(player, table.unpack(args)) }
if not queueEvent[player] then if not queueEvent[player] then
queueEvent[player] = {} :: any queueEvent[player] = {} :: any
end end
table.insert(queueEvent[player], { table.insert(queueEvent[player], {
"\1", 0,
{ id, results } :: any, { id, results } :: any,
}) })
end) end)
@ -186,10 +189,10 @@ if RunService:IsServer() then
continue continue
end end
for _, connection in eventListeners do for _, connection in eventListeners do
if connection.remote ~= remote then if connection.i ~= remote then
continue continue
end end
Thread(connection.fn, player, table.unpack(content)) Thread(connection.c, player, table.unpack(content))
end end
end end
end end

View file

@ -1504,14 +1504,14 @@ local function readStrict(buf: buffer, cursor: number, s: SchemaType, refs: { an
return reader(buf, cursor, refs) return reader(buf, cursor, refs)
end end
local function writeEvents(w: Writer, events: { { any } }, schemas: { [string]: SchemaType }) local function writeEvents(w: Writer, events: { { any } }, schemas: { [number]: SchemaType })
local count = #events local count = #events
wVarUInt(w, count) wVarUInt(w, count)
for _, event in events do for _, event in events do
local remote = event[1] local id = event[1]
local args = event[2] local args = event[2]
packValue(w, remote) wByte(w, id)
local schema = schemas[remote] local schema = schemas[id]
if schema then if schema then
packStrict(w, schema, args[1]) packStrict(w, schema, args[1])
else else
@ -1520,15 +1520,15 @@ local function writeEvents(w: Writer, events: { { any } }, schemas: { [string]:
end end
end end
local function readEvents(buf: buffer, refs: { any }?, schemas: { [string]: SchemaType }): { { any } } local function readEvents(buf: buffer, refs: { any }?, schemas: { [number]: SchemaType }): { { any } }
local pos, count = 0, 0 local pos, count = 0, 0
count, pos = readVarUInt(buf, pos) count, pos = readVarUInt(buf, pos)
local events = table.create(count) local events = table.create(count)
for i = 1, count do for i = 1, count do
local remote local id: number = buffer.readu8(buf, pos)
remote, pos = unpackValue(buf, pos, refs) pos += 1
local args local args: any
local schema = schemas[remote] local schema = schemas[id]
if schema then if schema then
local val local val
val, pos = readStrict(buf, pos, schema, refs) val, pos = readStrict(buf, pos, schema, refs)
@ -1536,7 +1536,7 @@ local function readEvents(buf: buffer, refs: { any }?, schemas: { [string]: Sche
else else
args, pos = unpackValue(buf, pos, refs) args, pos = unpackValue(buf, pos, refs)
end end
events[i] = { remote, args } events[i] = { id, args }
end end
return events return events
end end

24
src/Util/Identifier.luau Normal file
View file

@ -0,0 +1,24 @@
--!optimize 2
--!native
--@EternityDev
local BITS: number = 8
local MAX_VALUE: number = bit32.lshift(1, BITS) - 1
local cache: { [string]: number } = {}
local hash: number = game.PlaceVersion + 5381
return function(name: string): number
local cached = cache[name]
if cached then
return cached
end
local b: number = hash
for i = 1, #name do
b = bit32.bxor(bit32.lshift(hash, 5) + b, string.byte(name, i))
end
local reduced = bit32.band(b, MAX_VALUE)
cache[name] = reduced
return reduced
end

View file

@ -16,7 +16,7 @@ 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") local Buffer = require("@self/Util/Buffer")
--[[ --[[
@class Remote @class Remote