mirror of
https://github.com/imezx/Warp.git
synced 2026-03-18 00:44:16 +00:00
rewrite(phase4): rewrite entire identifier mechanism
This commit is contained in:
parent
0271c0e86d
commit
8b8b29e9a9
7 changed files with 177 additions and 53 deletions
|
|
@ -37,8 +37,8 @@ end)
|
|||
|
||||
```luau [Client]
|
||||
local Players = game:GetService("Players")
|
||||
local Warp = require(game.ReplicatedStorage.WarpNew).Client()
|
||||
local Schemas = require(game.ReplicatedStorage.Schemas)
|
||||
local Warp = require(path.to.warp).Client()
|
||||
local Schemas = require(path.to.schemas)
|
||||
|
||||
-- Use schemas
|
||||
for eventName, schema in Schemas do
|
||||
|
|
|
|||
|
|
@ -1 +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":"Util","className":"Folder","children":[{"name":"Buffer","className":"ModuleScript","filePaths":["src/Util/Buffer/init.luau"]},{"name":"Identifier","className":"ModuleScript","filePaths":["src/Util/Identifier.luau"]},{"name":"Thread","className":"ModuleScript","filePaths":["src/Util/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":"Identifier","className":"ModuleScript","filePaths":["src/Util/Identifier.luau"]},{"name":"Thread","className":"ModuleScript","filePaths":["src/Util/Thread.luau"]}]},{"name":"Replication","className":"ModuleScript","filePaths":["src/Replication/init.luau"]}]}
|
||||
|
|
@ -6,7 +6,7 @@ local Client = {}
|
|||
local RunService = game:GetService("RunService")
|
||||
local Thread = require("./Util/Thread")
|
||||
local Buffer = require("./Util/Buffer")
|
||||
local Identifier = require("./Util/Identifier")
|
||||
local Replication = require("./Replication")
|
||||
local Event: RemoteEvent = script.Parent:WaitForChild("Event")
|
||||
local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent")
|
||||
local deltaT: number, cycle: number = 0, 1 / 61
|
||||
|
|
@ -21,6 +21,8 @@ type Event = {
|
|||
c: (Player, ...any?) -> ...any?,
|
||||
}
|
||||
|
||||
local identifiers: { [string]: number } = {}
|
||||
|
||||
local queueEvent: { { any } } = {}
|
||||
local queueUnreliableEvent: { { any } } = {}
|
||||
local eventListeners: { Event } = {}
|
||||
|
|
@ -30,12 +32,12 @@ local pendingInvokes: { [string]: thread } = {}
|
|||
local invokeId = 0
|
||||
|
||||
Client.useSchema = function(remoteName: string, schema: Buffer.SchemaType)
|
||||
eventSchemas[Identifier(remoteName)] = schema
|
||||
eventSchemas[Replication.get_id(remoteName)] = schema
|
||||
end
|
||||
|
||||
Client.Connect = function(remoteName: string, fn: (Player, ...any?) -> ...any?): Connection
|
||||
local detail = {
|
||||
i = Identifier(remoteName),
|
||||
i = Replication.get_id(remoteName),
|
||||
c = fn,
|
||||
}
|
||||
table.insert(eventListeners, detail)
|
||||
|
|
@ -74,7 +76,7 @@ Client.Wait = function(remoteName: string): (number, ...any?)
|
|||
end
|
||||
|
||||
Client.DisconnectAll = function(remoteName: string)
|
||||
local id = Identifier(remoteName)
|
||||
local id = Replication.get_id(remoteName)
|
||||
for idx = #eventListeners, 1, -1 do
|
||||
if eventListeners[idx].i == id then
|
||||
table.remove(eventListeners, idx)
|
||||
|
|
@ -86,7 +88,7 @@ Client.Destroy = Client.DisconnectAll
|
|||
|
||||
Client.Fire = function(remoteName: string, reliable: boolean, ...: any?)
|
||||
table.insert(reliable and queueEvent or queueUnreliableEvent, {
|
||||
Identifier(remoteName),
|
||||
Replication.get_id(remoteName),
|
||||
{ ... } :: any,
|
||||
})
|
||||
end
|
||||
|
|
@ -106,7 +108,7 @@ Client.Invoke = function(remoteName: string, timeout: number?, ...: any?): ...an
|
|||
end)
|
||||
table.insert(queueEvent, {
|
||||
0,
|
||||
{ Identifier(remoteName), reqid :: any, { ... } :: any } :: any,
|
||||
{ Replication.get_id(remoteName), reqid :: any, { ... } :: any } :: any,
|
||||
})
|
||||
return coroutine.yield()
|
||||
end
|
||||
|
|
|
|||
74
src/Replication/init.luau
Normal file
74
src/Replication/init.luau
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
--!optimize 2
|
||||
local Replication = {}
|
||||
|
||||
local RunService = game:GetService("RunService")
|
||||
local _repl: RemoteEvent = script.Parent:WaitForChild("_repl")
|
||||
|
||||
local Buffer = require("./Util/Buffer")
|
||||
local Identifier = require("./Util/Identifier")
|
||||
|
||||
local identifiers_schema = Buffer.Schema.string
|
||||
local writer: Buffer.Writer = Buffer.createWriter()
|
||||
local warp_identifier_registry = shared.__warp_identifier_registry
|
||||
|
||||
if RunService:IsServer() then
|
||||
local replication_ready: { Player } = {}
|
||||
local replication_id: number = Identifier.get("id_replication") or 1
|
||||
|
||||
if not Identifier.has("id_replication") or not replication_id then
|
||||
replication_id = Identifier.get("id_replication") or 1
|
||||
end
|
||||
|
||||
local function replicateToAll(content: any, id: number?)
|
||||
if #replication_ready == 0 then return end
|
||||
local to_repl: any = type(content) == "string" and { content = id } or content
|
||||
Buffer.writeRepl(writer, to_repl, warp_identifier_registry.counter, identifiers_schema)
|
||||
do
|
||||
local buf = Buffer.build(writer)
|
||||
Buffer.reset(writer)
|
||||
for _, player: Player in replication_ready do
|
||||
_repl:FireClient(player, buf, replication_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function replicateTo(player: Player)
|
||||
Buffer.writeRepl(writer, warp_identifier_registry.cache, warp_identifier_registry.counter, identifiers_schema)
|
||||
do
|
||||
local buf = Buffer.build(writer)
|
||||
Buffer.reset(writer)
|
||||
_repl:FireClient(player, buf, replication_id)
|
||||
end
|
||||
end
|
||||
|
||||
Identifier.on_added(replicateToAll)
|
||||
|
||||
_repl.OnServerEvent:Connect(function(player: Player)
|
||||
if table.find(replication_ready, player) then return end
|
||||
table.insert(replication_ready, player)
|
||||
replicateTo(player)
|
||||
end)
|
||||
|
||||
Replication.remove = function(player: Player)
|
||||
table.remove(replication_ready, table.find(replication_ready, player))
|
||||
end
|
||||
|
||||
elseif RunService:IsClient() then
|
||||
_repl.OnClientEvent:Connect(function(b: buffer, id: number)
|
||||
if type(b) ~= "buffer" then
|
||||
return
|
||||
end
|
||||
local contents = Buffer.readRepl(b, identifiers_schema)
|
||||
if #contents == 0 then return end
|
||||
for _, content in contents do
|
||||
warp_identifier_registry.cache[content[2]] = content[1]
|
||||
end
|
||||
end)
|
||||
_repl:FireServer()
|
||||
|
||||
Replication.get_id = function(name: string): number
|
||||
return warp_identifier_registry.cache[name]
|
||||
end
|
||||
end
|
||||
|
||||
return Replication :: typeof(Replication)
|
||||
|
|
@ -8,7 +8,9 @@ local RunService = game:GetService("RunService")
|
|||
local Thread = require("./Util/Thread")
|
||||
local Buffer = require("./Util/Buffer")
|
||||
local Identifier = require("./Util/Identifier")
|
||||
local Replication = require("./Replication")
|
||||
local Event: RemoteEvent = script.Parent:WaitForChild("Event")
|
||||
local _repl: RemoteEvent = script.Parent:WaitForChild("_repl")
|
||||
local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent")
|
||||
local deltaT: number, cycle: number = 0, 1 / 61
|
||||
local writer: Buffer.Writer = Buffer.createWriter()
|
||||
|
|
@ -36,12 +38,12 @@ local pendingInvokes: { [string]: thread } = {}
|
|||
local invokeId = 0
|
||||
|
||||
Server.useSchema = function(remoteName: string, schema: Buffer.SchemaType)
|
||||
eventSchemas[Identifier(remoteName)] = schema
|
||||
eventSchemas[Identifier.get(remoteName)] = schema
|
||||
end
|
||||
|
||||
Server.Connect = function(remoteName: string, fn: (Player, ...any?) -> ...any?): Connection
|
||||
local detail = {
|
||||
i = Identifier(remoteName),
|
||||
i = Identifier.get(remoteName),
|
||||
c = fn,
|
||||
}
|
||||
table.insert(eventListeners, detail)
|
||||
|
|
@ -80,7 +82,7 @@ Server.Wait = function(remoteName: string): (number, ...any?)
|
|||
end
|
||||
|
||||
Server.DisconnectAll = function(remoteName: string)
|
||||
local id = Identifier(remoteName)
|
||||
local id = Identifier.get(remoteName)
|
||||
if not id then
|
||||
return
|
||||
end
|
||||
|
|
@ -99,7 +101,7 @@ Server.Fire = function(remoteName: string, reliable: boolean, player: Player, ..
|
|||
targetQueue[player] = {} :: any
|
||||
end
|
||||
table.insert(targetQueue[player], {
|
||||
Identifier(remoteName),
|
||||
Identifier.get(remoteName),
|
||||
{ ... } :: any,
|
||||
})
|
||||
end
|
||||
|
|
@ -135,7 +137,7 @@ Server.Invoke = function(remoteName: string, player: Player, timeout: number?, .
|
|||
end
|
||||
table.insert(queueEvent[player], {
|
||||
0,
|
||||
{ Identifier(remoteName), reqId :: any, { ... } :: any } :: any,
|
||||
{ Identifier.get(remoteName), reqId :: any, { ... } :: any } :: any,
|
||||
})
|
||||
return coroutine.yield()
|
||||
end
|
||||
|
|
@ -261,6 +263,7 @@ if RunService:IsServer() then
|
|||
end
|
||||
Players.PlayerAdded:Connect(onAdded)
|
||||
Players.PlayerRemoving:Connect(function(player: Player)
|
||||
Replication.remove(player)
|
||||
table.remove(players_ready, table.find(players_ready, player))
|
||||
if queueEvent[player] then
|
||||
table.clear(queueEvent[player])
|
||||
|
|
|
|||
|
|
@ -633,9 +633,9 @@ local function packColor3(w: Writer, c: Color3)
|
|||
|
||||
if r > 1 or g > 1 or b > 1 then
|
||||
wByte(w, T_COLOR3_F)
|
||||
wF32(w, r)
|
||||
wF32(w, g)
|
||||
wF32(w, b)
|
||||
wF16(w, r)
|
||||
wF16(w, g)
|
||||
wF16(w, b)
|
||||
else
|
||||
wByte(w, T_COLOR3)
|
||||
wByte(w, math.clamp(math.round(r * 255), 0, 255))
|
||||
|
|
@ -974,10 +974,10 @@ unpackValue = function(buf: buffer, pos: number, refs: { any }?): (any, number)
|
|||
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
|
||||
local r = readF16(buf, pos)
|
||||
local g = readF16(buf, pos + 2)
|
||||
local b = readF16(buf, pos + 4)
|
||||
return Color3.new(r, g, b), pos + 6
|
||||
end
|
||||
|
||||
if t == T_BRICKCOLOR then
|
||||
|
|
@ -1647,7 +1647,7 @@ local function writeEvents(w: Writer, events: { { any } }, schemas: { [number]:
|
|||
for _, event in events do
|
||||
local id = event[1]
|
||||
local args = event[2]
|
||||
wU16(w, id)
|
||||
wByte(w, id)
|
||||
local schema = schemas[id]
|
||||
if schema then
|
||||
packStrict(w, schema, args[1])
|
||||
|
|
@ -1662,8 +1662,8 @@ local function readEvents(buf: buffer, refs: { any }?, schemas: { [number]: Sche
|
|||
count, pos = readVarUInt(buf, pos)
|
||||
local events = table.create(count)
|
||||
for i = 1, count do
|
||||
local id: number = buffer.readu16(buf, pos)
|
||||
pos += 2
|
||||
local id: number = buffer.readu8(buf, pos)
|
||||
pos += 1
|
||||
local args: any
|
||||
local schema = schemas[id]
|
||||
if schema then
|
||||
|
|
@ -1678,13 +1678,51 @@ local function readEvents(buf: buffer, refs: { any }?, schemas: { [number]: Sche
|
|||
return events
|
||||
end
|
||||
|
||||
local function writeRepl(w: Writer, content: { [string]: number }, count: number, schema: SchemaType)
|
||||
wVarUInt(w, count)
|
||||
for name, id in content do
|
||||
wByte(w, id)
|
||||
if schema then
|
||||
packStrict(w, schema, name)
|
||||
else
|
||||
packValue(w, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function readRepl(buf: buffer, schema: SchemaType): { { any } }
|
||||
local pos, count = 0, 0
|
||||
count, pos = readVarUInt(buf, pos)
|
||||
local events = table.create(count)
|
||||
for i = 1, count do
|
||||
local id: number = buffer.readu8(buf, pos)
|
||||
pos += 1
|
||||
local args: any
|
||||
if schema then
|
||||
local val
|
||||
val, pos = readStrict(buf, pos, schema)
|
||||
args = val
|
||||
else
|
||||
args, pos = unpackValue(buf, pos)
|
||||
end
|
||||
events[i] = { id, args }
|
||||
end
|
||||
return events
|
||||
end
|
||||
|
||||
local BufferSerdes = {}
|
||||
|
||||
BufferSerdes.Schema = Schema
|
||||
|
||||
BufferSerdes.writeRepl = writeRepl
|
||||
BufferSerdes.readRepl = readRepl
|
||||
|
||||
BufferSerdes.writeEvents = writeEvents
|
||||
BufferSerdes.readEvents = readEvents
|
||||
BufferSerdes.Schema = Schema
|
||||
|
||||
BufferSerdes.compilePacker = compilePacker
|
||||
BufferSerdes.compileReader = compileReader
|
||||
|
||||
BufferSerdes.packStrict = packStrict
|
||||
BufferSerdes.readStrict = readStrict
|
||||
|
||||
|
|
|
|||
|
|
@ -1,42 +1,49 @@
|
|||
--!optimize 2
|
||||
--!native
|
||||
--!strict
|
||||
--@EternityDev
|
||||
if not shared.__identifier_cache then
|
||||
shared.__identifier_cache = {
|
||||
local BITS: number = 8
|
||||
local MAX_VALUE: number = bit32.lshift(1, BITS) - 1
|
||||
local hook_fn: (string, number) -> ()
|
||||
|
||||
if not shared.__warp_identifier_registry then
|
||||
shared.__warp_identifier_registry = {
|
||||
cache = {} :: { [string]: number },
|
||||
used = {} :: { [number]: string },
|
||||
count = 0 :: number,
|
||||
counter = 0,
|
||||
}
|
||||
end
|
||||
|
||||
local registry = shared.__identifier_cache
|
||||
local cache = registry.cache
|
||||
local used = registry.used
|
||||
local count = registry.count
|
||||
local registry = shared.__warp_identifier_registry
|
||||
local function fastAssert(condition: boolean, ...: any)
|
||||
if not condition then
|
||||
error(...)
|
||||
end
|
||||
end
|
||||
|
||||
local BITS: number = 16
|
||||
local MAX_VALUE: number = bit32.lshift(1, BITS) - 1
|
||||
local hash: number = game.PlaceVersion + 5381
|
||||
local Identifier = {}
|
||||
|
||||
return function(name: string): number
|
||||
local cached = cache[name]
|
||||
Identifier.on_added = function(fn: (string, number) -> ())
|
||||
fastAssert(hook_fn == nil, ".on_added can only be called once.")
|
||||
hook_fn = fn
|
||||
end
|
||||
|
||||
Identifier.has = function(name: string): boolean
|
||||
return registry.cache[name] ~= nil
|
||||
end
|
||||
|
||||
Identifier.get = function(name: string): number
|
||||
local cached = registry.cache[name]
|
||||
if cached then
|
||||
return cached
|
||||
end
|
||||
|
||||
local b: number = hash
|
||||
for i = 1, #name do
|
||||
b = bit32.bxor(bit32.lshift(b, 5) + b, string.byte(name, i))
|
||||
local id = registry.counter + 1
|
||||
fastAssert(id <= MAX_VALUE, `Identifier overflow: exceeded {MAX_VALUE + 1} unique names.`)
|
||||
registry.counter = id
|
||||
registry.cache[name] = id
|
||||
if hook_fn then
|
||||
task.defer(hook_fn, name, id)
|
||||
end
|
||||
|
||||
local reduced: number = (b % (MAX_VALUE - 2)) + 2
|
||||
local existing = used[reduced]
|
||||
if existing and existing ~= name then
|
||||
error(`[Warp] Hash collision: "{name}" & "{existing}" both map to ID {reduced}. Rename one.`)
|
||||
end
|
||||
|
||||
used[reduced] = name
|
||||
cache[name] = reduced
|
||||
count += 1
|
||||
return reduced
|
||||
return id
|
||||
end
|
||||
|
||||
return Identifier :: typeof(Identifier)
|
||||
|
|
|
|||
Loading…
Reference in a new issue