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]
@ -19,19 +19,19 @@ local Schemas = require(path.to.schemas)
-- Use schemas -- Use schemas
for eventName, schema in Schemas do for eventName, schema in Schemas do
Warp.useSchema(eventName, schema) Warp.useSchema(eventName, schema)
end end
Warp.Connect("Example", function(player, arg) Warp.Connect("Example", function(player, arg)
print(table.unpack(arg)) print(table.unpack(arg))
return "Hey!" return "Hey!"
end) end)
Warp.Connect("Ping", function(player, ping) Warp.Connect("Ping", function(player, ping)
if ping then if ping then
print("PING!") print("PING!")
Warp.Fire("Pong", true, player, "pong!") -- Fire to spesific player through reliable event Warp.Fire("Pong", true, player, "pong!") -- Fire to spesific player through reliable event
Warp.Fire("PingAll", true, "ey!") -- Fire to all clients through reliable event Warp.Fire("PingAll", true, "ey!") -- Fire to all clients through reliable event
end end
end) end)
``` ```
@ -42,33 +42,33 @@ local Schemas = require(path.to.schemas)
-- Use schemas -- Use schemas
for eventName, schema in Schemas do for eventName, schema in Schemas do
Warp.useSchema(eventName, schema) Warp.useSchema(eventName, schema)
end end
-- Connect the events -- Connect the events
local connection1 local connection1
connection1 = Warp.Connect("Pong", function(pong: boolean) -- we store the connection so we can disconnect it later connection1 = Warp.Connect("Pong", function(pong: boolean) -- we store the connection so we can disconnect it later
if pong then if pong then
print("PONG!") print("PONG!")
end end
end) end)
Warp.Connect("PingAll", function(isPing: boolean) Warp.Connect("PingAll", function(isPing: boolean)
if isPing then if isPing then
print("I GET PINGED!") print("I GET PINGED!")
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