@axpecter/lync 2.2.1 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -147
- package/package.json +1 -1
- package/src/Types.luau +34 -22
- package/src/api/Group.luau +40 -33
- package/src/api/Packet.luau +140 -66
- package/src/api/Query.luau +103 -65
- package/src/api/Scope.luau +26 -10
- package/src/api/Signal.luau +43 -52
- package/src/codec/Base.luau +28 -14
- package/src/codec/composite/Array.luau +296 -115
- package/src/codec/composite/Map.luau +377 -57
- package/src/codec/composite/Optional.luau +10 -2
- package/src/codec/composite/Shared.luau +134 -64
- package/src/codec/composite/Struct.luau +294 -43
- package/src/codec/composite/Tagged.luau +21 -16
- package/src/codec/composite/Tuple.luau +32 -15
- package/src/codec/datatype/Buffer.luau +7 -7
- package/src/codec/datatype/CFrame.luau +34 -122
- package/src/codec/datatype/Color.luau +10 -10
- package/src/codec/datatype/Instance.luau +15 -12
- package/src/codec/datatype/IntVector.luau +2 -2
- package/src/codec/datatype/NumberRange.luau +15 -8
- package/src/codec/datatype/Ray.luau +13 -14
- package/src/codec/datatype/Rect.luau +12 -12
- package/src/codec/datatype/Region.luau +13 -14
- package/src/codec/datatype/Sequence.luau +87 -65
- package/src/codec/datatype/String.luau +17 -10
- package/src/codec/datatype/UDim.luau +10 -8
- package/src/codec/datatype/Vector.luau +20 -59
- package/src/codec/meta/Auto.luau +94 -126
- package/src/codec/meta/Bitfield.luau +12 -14
- package/src/codec/meta/Custom.luau +3 -1
- package/src/codec/meta/DeltaScalar.luau +390 -0
- package/src/codec/meta/Enum.luau +9 -9
- package/src/codec/meta/Float.luau +6 -28
- package/src/codec/meta/Nothing.luau +1 -1
- package/src/codec/meta/Unknown.luau +10 -7
- package/src/codec/primitive/Bool.luau +6 -4
- package/src/codec/primitive/Float16.luau +5 -2
- package/src/codec/primitive/Int.luau +5 -6
- package/src/codec/primitive/Number.luau +6 -4
- package/src/codec/primitive/Signed.luau +2 -2
- package/src/codec/primitive/Varint.luau +76 -39
- package/src/codec/primitive/Zint.luau +99 -0
- package/src/index.d.ts +207 -53
- package/src/init.luau +116 -103
- package/src/internal/Baseline.luau +9 -1
- package/src/internal/Channel.luau +191 -157
- package/src/internal/Middleware.luau +22 -6
- package/src/internal/Pool.luau +12 -4
- package/src/internal/Registry.luau +25 -16
- package/src/internal/Transport.luau +1 -1
- package/src/transport/Bridge.luau +47 -33
- package/src/transport/Client.luau +25 -21
- package/src/transport/Gate.luau +227 -172
- package/src/transport/Reader.luau +174 -106
- package/src/transport/Server.luau +46 -57
- package/src/util/Array.luau +18 -0
- package/src/util/Buffer.luau +90 -0
- package/src/util/Constants.luau +30 -0
- package/src/util/Log.luau +68 -0
- package/src/util/Player.luau +14 -0
- package/src/util/Quantize.luau +84 -0
- package/src/util/Quat.luau +124 -0
- package/src/internal/Util.luau +0 -26
package/src/api/Group.luau
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!optimize 2
|
|
3
|
-
-- Named player sets with __iter
|
|
3
|
+
-- Named player sets with __iter and auto-cleanup on PlayerRemoving.
|
|
4
4
|
|
|
5
5
|
local Players = game:GetService("Players")
|
|
6
6
|
|
|
7
|
-
local
|
|
7
|
+
local Array = require(script.Parent.Parent.util.Array)
|
|
8
|
+
local Log = require(script.Parent.Parent.util.Log)
|
|
8
9
|
|
|
9
10
|
-- Public types -----------------------------------------------------------
|
|
10
11
|
|
|
@@ -14,22 +15,24 @@ export type GroupHandle = {
|
|
|
14
15
|
has: (self: GroupHandle, player: Player) -> boolean,
|
|
15
16
|
count: (self: GroupHandle) -> number,
|
|
16
17
|
destroy: (self: GroupHandle) -> (),
|
|
17
|
-
|
|
18
|
+
_lyncKind: "group",
|
|
18
19
|
_members: { [Player]: boolean },
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
type
|
|
22
|
+
type GroupInternal = GroupHandle & { _count: number, _name: string }
|
|
22
23
|
|
|
23
24
|
-- State ------------------------------------------------------------------
|
|
24
25
|
|
|
25
|
-
local _groups: { [string]:
|
|
26
|
+
local _groups: { [string]: GroupInternal } = {}
|
|
27
|
+
-- Reverse index used by PlayerRemoving so we don't scan every group on disconnect.
|
|
26
28
|
local _playerCleanup: { [Player]: { Group } } = {}
|
|
27
29
|
local _cleanupConnected = false
|
|
28
30
|
|
|
29
31
|
-- Private ----------------------------------------------------------------
|
|
30
32
|
|
|
31
|
-
local swapRemove =
|
|
33
|
+
local swapRemove = Array.swapRemove
|
|
32
34
|
|
|
35
|
+
-- Connect once on the first Group.create; subsequent creates reuse the same connection.
|
|
33
36
|
local function ensureCleanup(): ()
|
|
34
37
|
if _cleanupConnected then
|
|
35
38
|
return
|
|
@@ -49,6 +52,17 @@ local function ensureCleanup(): ()
|
|
|
49
52
|
end)
|
|
50
53
|
end
|
|
51
54
|
|
|
55
|
+
local function untrackCleanup(player: Player, group: GroupInternal): ()
|
|
56
|
+
local cleanup = _playerCleanup[player]
|
|
57
|
+
if not cleanup then
|
|
58
|
+
return
|
|
59
|
+
end
|
|
60
|
+
local idx = table.find(cleanup, group)
|
|
61
|
+
if idx then
|
|
62
|
+
swapRemove(cleanup, idx)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
52
66
|
-- Public -----------------------------------------------------------------
|
|
53
67
|
|
|
54
68
|
local Group = {}
|
|
@@ -56,15 +70,12 @@ local Group = {}
|
|
|
56
70
|
local GroupMeta = {}
|
|
57
71
|
GroupMeta.__index = GroupMeta
|
|
58
72
|
|
|
59
|
-
--
|
|
60
|
-
|
|
61
|
-
uses Luau's builtin path (luaH_next) with zero closure allocation.
|
|
62
|
-
]]
|
|
63
|
-
function GroupMeta.__iter(self: Group): (any, any, any)
|
|
73
|
+
-- __iter lets `for player in group do` skip the explicit :members() call.
|
|
74
|
+
function GroupMeta.__iter(self: GroupInternal): (any, any, any)
|
|
64
75
|
return next, self._members, nil
|
|
65
76
|
end
|
|
66
77
|
|
|
67
|
-
function GroupMeta.add(self:
|
|
78
|
+
function GroupMeta.add(self: GroupInternal, player: Player): boolean
|
|
68
79
|
if self._members[player] then
|
|
69
80
|
return false
|
|
70
81
|
end
|
|
@@ -81,45 +92,32 @@ function GroupMeta.add(self: Group, player: Player): boolean
|
|
|
81
92
|
return true
|
|
82
93
|
end
|
|
83
94
|
|
|
84
|
-
function GroupMeta.remove(self:
|
|
95
|
+
function GroupMeta.remove(self: GroupInternal, player: Player): boolean
|
|
85
96
|
if not self._members[player] then
|
|
86
97
|
return false
|
|
87
98
|
end
|
|
88
99
|
|
|
89
100
|
self._members[player] = nil
|
|
90
101
|
self._count -= 1
|
|
91
|
-
|
|
92
|
-
local cleanup = _playerCleanup[player]
|
|
93
|
-
if cleanup then
|
|
94
|
-
local idx = table.find(cleanup, self)
|
|
95
|
-
if idx then
|
|
96
|
-
swapRemove(cleanup, idx)
|
|
97
|
-
end
|
|
98
|
-
end
|
|
102
|
+
untrackCleanup(player, self)
|
|
99
103
|
return true
|
|
100
104
|
end
|
|
101
105
|
|
|
102
|
-
function GroupMeta.has(self:
|
|
106
|
+
function GroupMeta.has(self: GroupInternal, player: Player): boolean
|
|
103
107
|
return self._members[player] == true
|
|
104
108
|
end
|
|
105
109
|
|
|
106
|
-
function GroupMeta.count(self:
|
|
110
|
+
function GroupMeta.count(self: GroupInternal): number
|
|
107
111
|
return self._count
|
|
108
112
|
end
|
|
109
113
|
|
|
110
|
-
function GroupMeta.destroy(self:
|
|
114
|
+
function GroupMeta.destroy(self: GroupInternal): ()
|
|
111
115
|
if _groups[self._name] == self then
|
|
112
116
|
_groups[self._name] = nil
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
for player in self._members do
|
|
116
|
-
|
|
117
|
-
if cleanup then
|
|
118
|
-
local idx = table.find(cleanup, self)
|
|
119
|
-
if idx then
|
|
120
|
-
swapRemove(cleanup, idx)
|
|
121
|
-
end
|
|
122
|
-
end
|
|
120
|
+
untrackCleanup(player, self)
|
|
123
121
|
end
|
|
124
122
|
|
|
125
123
|
table.clear(self._members)
|
|
@@ -130,13 +128,13 @@ table.freeze(GroupMeta)
|
|
|
130
128
|
|
|
131
129
|
function Group.create(name: string): GroupHandle
|
|
132
130
|
if _groups[name] then
|
|
133
|
-
error(`
|
|
131
|
+
Log.error(`"{name}" already exists`)
|
|
134
132
|
end
|
|
135
133
|
|
|
136
134
|
ensureCleanup()
|
|
137
135
|
|
|
138
136
|
local group = setmetatable({
|
|
139
|
-
|
|
137
|
+
_lyncKind = "group" :: "group",
|
|
140
138
|
_members = {} :: { [Player]: boolean },
|
|
141
139
|
_count = 0,
|
|
142
140
|
_name = name,
|
|
@@ -146,4 +144,13 @@ function Group.create(name: string): GroupHandle
|
|
|
146
144
|
return group
|
|
147
145
|
end
|
|
148
146
|
|
|
147
|
+
function Group.reset(): ()
|
|
148
|
+
for _, group in _groups do
|
|
149
|
+
table.clear(group._members)
|
|
150
|
+
group._count = 0
|
|
151
|
+
end
|
|
152
|
+
table.clear(_groups)
|
|
153
|
+
table.clear(_playerCleanup)
|
|
154
|
+
end
|
|
155
|
+
|
|
149
156
|
return table.freeze(Group)
|
package/src/api/Packet.luau
CHANGED
|
@@ -7,34 +7,38 @@ local RunService = game:GetService("RunService")
|
|
|
7
7
|
|
|
8
8
|
local Baseline = require(script.Parent.Parent.internal.Baseline)
|
|
9
9
|
local Channel = require(script.Parent.Parent.internal.Channel)
|
|
10
|
+
local Log = require(script.Parent.Parent.util.Log)
|
|
10
11
|
local Middleware = require(script.Parent.Parent.internal.Middleware)
|
|
12
|
+
local Player = require(script.Parent.Parent.util.Player)
|
|
11
13
|
local Registry = require(script.Parent.Parent.internal.Registry)
|
|
14
|
+
local Shared = require(script.Parent.Parent.codec.composite.Shared)
|
|
12
15
|
local Transport = require(script.Parent.Parent.internal.Transport)
|
|
13
16
|
local Types = require(script.Parent.Parent.Types)
|
|
14
|
-
local Util = require(script.Parent.Parent.internal.Util)
|
|
15
17
|
|
|
16
18
|
-- Constants --------------------------------------------------------------
|
|
17
19
|
|
|
18
20
|
local IS_SERVER = RunService:IsServer()
|
|
19
21
|
|
|
22
|
+
-- mode: TS_* tag; headerSize: 1 (frame byte) + timestamp bytes (1, 2, or 8).
|
|
23
|
+
local TS_MODES: { [string]: { mode: number, headerSize: number } } = table.freeze({
|
|
24
|
+
frame = table.freeze({ mode = Channel.TS_FRAME, headerSize = 2 }),
|
|
25
|
+
offset = table.freeze({ mode = Channel.TS_OFFSET, headerSize = 3 }),
|
|
26
|
+
full = table.freeze({ mode = Channel.TS_FULL, headerSize = 9 }),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
local EMPTY_OPTIONS: Types.PacketOptions = table.freeze({})
|
|
30
|
+
|
|
20
31
|
-- Private ----------------------------------------------------------------
|
|
21
32
|
|
|
22
|
-
local isPlayer =
|
|
33
|
+
local isPlayer = Player.is
|
|
23
34
|
|
|
24
35
|
local function resolveTimestampMode(ts: ("frame" | "offset" | "full")?): (number, number)
|
|
25
|
-
if ts ==
|
|
26
|
-
Channel.
|
|
27
|
-
return Channel.TS_FRAME, 2
|
|
28
|
-
end
|
|
29
|
-
if ts == "offset" then
|
|
30
|
-
Channel.enableTimestamps()
|
|
31
|
-
return Channel.TS_OFFSET, 3
|
|
32
|
-
end
|
|
33
|
-
if ts == "full" then
|
|
34
|
-
Channel.enableTimestamps()
|
|
35
|
-
return Channel.TS_FULL, 9
|
|
36
|
+
if ts == nil then
|
|
37
|
+
return Channel.TS_NONE, 1
|
|
36
38
|
end
|
|
37
|
-
|
|
39
|
+
local entry = TS_MODES[ts]
|
|
40
|
+
Channel.enableTimestamps()
|
|
41
|
+
return entry.mode, entry.headerSize
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
-- Public types -----------------------------------------------------------
|
|
@@ -63,17 +67,18 @@ function Packet.define<T>(
|
|
|
63
67
|
codec: Types.InternalCodec<T>,
|
|
64
68
|
options: Types.PacketOptions?
|
|
65
69
|
): PacketHandle<T>
|
|
66
|
-
local opts = options or
|
|
70
|
+
local opts = options or EMPTY_OPTIONS
|
|
67
71
|
local isUnreliable = opts.unreliable or false
|
|
68
72
|
|
|
73
|
+
-- Delta + unreliable is unsafe: a dropped frame permanently desyncs the receiver baseline.
|
|
69
74
|
if isUnreliable and codec._isDelta then
|
|
70
|
-
error(
|
|
75
|
+
Log.error(
|
|
76
|
+
`"{name}" delta codecs need reliable transport (lost packets desync the baseline)`
|
|
77
|
+
)
|
|
71
78
|
end
|
|
72
79
|
|
|
73
80
|
if codec._hasUnknown and not opts.validate then
|
|
74
|
-
warn(
|
|
75
|
-
`[Lync] Lync.packet: "{name}" uses unknown codec without validate; data bypasses schema validation`
|
|
76
|
-
)
|
|
81
|
+
Log.warn(`"{name}" uses unknown codec without validate; data bypasses schema validation`)
|
|
77
82
|
end
|
|
78
83
|
|
|
79
84
|
if codec._isDelta then
|
|
@@ -109,85 +114,154 @@ function Packet.define<T>(
|
|
|
109
114
|
end
|
|
110
115
|
end
|
|
111
116
|
|
|
112
|
-
|
|
117
|
+
--[[
|
|
118
|
+
Run the send middleware chain. Returns (dropped, value). A nil value
|
|
119
|
+
is a legitimate payload (Lync.nothing); only the explicit Lync.DROP
|
|
120
|
+
sentinel or a hook returning DROP triggers the drop branch.
|
|
121
|
+
]]
|
|
122
|
+
local function applySend(data: any, player: Player?): (boolean, any)
|
|
113
123
|
if not Middleware.hasSendHooks() then
|
|
114
|
-
return data
|
|
124
|
+
return false, data
|
|
115
125
|
end
|
|
116
|
-
|
|
117
126
|
local sendData = Middleware.runSend(data, name, player)
|
|
118
|
-
if typeof(sendData) == "table" and sendData.
|
|
119
|
-
return nil
|
|
127
|
+
if typeof(sendData) == "table" and sendData._lyncKind == "drop" then
|
|
128
|
+
return true, nil
|
|
120
129
|
end
|
|
121
|
-
return sendData
|
|
130
|
+
return false, sendData
|
|
122
131
|
end
|
|
123
132
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
133
|
+
-- Non-delta codecs are stateless wire-encoders, so broadcast can encode
|
|
134
|
+
-- once and bufCopy the payload to every player. Falls through if the
|
|
135
|
+
-- encoded payload includes ref indices (the indices would need rewriting
|
|
136
|
+
-- per channel, which no codec exposes).
|
|
137
|
+
local canEncodeOnce = not codec._isDelta and not codec._hasDelta
|
|
128
138
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return
|
|
133
|
-
end
|
|
134
|
-
sendToPlayer(target :: Player, sendData)
|
|
139
|
+
local function broadcast(players: { Player }, data: any): ()
|
|
140
|
+
local count = #players
|
|
141
|
+
if count == 0 then
|
|
135
142
|
bumpFires()
|
|
136
143
|
return
|
|
137
144
|
end
|
|
138
145
|
|
|
139
|
-
if
|
|
140
|
-
local
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
if canEncodeOnce then
|
|
147
|
+
local scratch = Shared.acquireScratch()
|
|
148
|
+
scratch.currentPacket = name
|
|
149
|
+
codec.write(scratch, data)
|
|
150
|
+
scratch.currentPacket = nil
|
|
151
|
+
if scratch.refCount == 0 then
|
|
152
|
+
local payload = scratch.buff
|
|
153
|
+
local payloadLen = scratch.cursor
|
|
154
|
+
local server = Transport.server()
|
|
155
|
+
-- Channel.writeBatchEncoded is hot-swapped by enableStats; re-read per call.
|
|
156
|
+
local writeBatchEncoded = Channel.writeBatchEncoded
|
|
157
|
+
for i = 1, count do
|
|
158
|
+
writeBatchEncoded(
|
|
159
|
+
server.getChannel(players[i], isUnreliable),
|
|
160
|
+
reg,
|
|
161
|
+
payload,
|
|
162
|
+
payloadLen
|
|
163
|
+
)
|
|
148
164
|
end
|
|
165
|
+
Shared.releaseScratch()
|
|
149
166
|
bumpFires()
|
|
150
167
|
return
|
|
151
168
|
end
|
|
169
|
+
Shared.releaseScratch()
|
|
170
|
+
end
|
|
152
171
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
172
|
+
for i = 1, count do
|
|
173
|
+
sendToPlayer(players[i], data)
|
|
174
|
+
end
|
|
175
|
+
bumpFires()
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
--[[
|
|
179
|
+
Resolve a target table into a { Player } list. Returns nil for unknown
|
|
180
|
+
sentinel kinds; the caller errors with the original target type.
|
|
181
|
+
]]
|
|
182
|
+
local function resolveAudience(target: any): { Player }?
|
|
183
|
+
local kind = target._lyncKind
|
|
184
|
+
if kind == "all" then
|
|
185
|
+
return Players:GetPlayers()
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
if kind == "except" then
|
|
189
|
+
local excluded = target._excluded :: { [Player]: boolean }
|
|
190
|
+
local all = Players:GetPlayers()
|
|
191
|
+
local list = table.create(#all)
|
|
192
|
+
local n = 0
|
|
193
|
+
for _, player in all do
|
|
194
|
+
if not excluded[player] then
|
|
195
|
+
n += 1
|
|
196
|
+
list[n] = player
|
|
159
197
|
end
|
|
160
|
-
bumpFires()
|
|
161
|
-
return
|
|
162
198
|
end
|
|
199
|
+
return list
|
|
200
|
+
end
|
|
163
201
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
202
|
+
if kind == "group" then
|
|
203
|
+
local members = target._members :: { [Player]: boolean }
|
|
204
|
+
local list: { Player } = {}
|
|
205
|
+
local n = 0
|
|
206
|
+
for player in members do
|
|
207
|
+
n += 1
|
|
208
|
+
list[n] = player
|
|
170
209
|
end
|
|
210
|
+
return list
|
|
211
|
+
end
|
|
171
212
|
|
|
213
|
+
if kind == nil then
|
|
214
|
+
-- Plain { Player } array. Validate entries on the way to a clean list.
|
|
215
|
+
local list: { Player } = {}
|
|
216
|
+
local n = 0
|
|
172
217
|
for _, player in target do
|
|
173
218
|
if isPlayer(player) then
|
|
174
|
-
|
|
219
|
+
n += 1
|
|
220
|
+
list[n] = player :: Player
|
|
175
221
|
end
|
|
176
222
|
end
|
|
223
|
+
return list
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
return nil
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
local function sendToTarget(data: any, target: any): ()
|
|
230
|
+
if isPlayer(target) then
|
|
231
|
+
local dropped, sendData = applySend(data, target :: Player)
|
|
232
|
+
if dropped then
|
|
233
|
+
return
|
|
234
|
+
end
|
|
235
|
+
sendToPlayer(target :: Player, sendData)
|
|
177
236
|
bumpFires()
|
|
178
237
|
return
|
|
179
238
|
end
|
|
180
239
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
240
|
+
if typeof(target) == "table" then
|
|
241
|
+
local dropped, sendData = applySend(data, nil)
|
|
242
|
+
if dropped then
|
|
243
|
+
return
|
|
244
|
+
end
|
|
245
|
+
local audience = resolveAudience(target)
|
|
246
|
+
if audience then
|
|
247
|
+
broadcast(audience, sendData)
|
|
248
|
+
return
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
Log.error(`"{name}" expected Player, table, Group, or Lync.all, got {typeof(target)}`)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
local function sendFromServer(data: any, target: any): ()
|
|
256
|
+
if target == nil then
|
|
257
|
+
Log.error(`"{name}" server send requires a target`)
|
|
258
|
+
end
|
|
259
|
+
sendToTarget(data, target)
|
|
186
260
|
end
|
|
187
261
|
|
|
188
262
|
local function sendFromClient(data: any): ()
|
|
189
|
-
local sendData = applySend(data, nil)
|
|
190
|
-
if
|
|
263
|
+
local dropped, sendData = applySend(data, nil)
|
|
264
|
+
if dropped then
|
|
191
265
|
return
|
|
192
266
|
end
|
|
193
267
|
local ch = Transport.client().getChannel(isUnreliable)
|