@axpecter/lync 2.3.2 → 3.0.0

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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +302 -299
  3. package/Types.luau +215 -0
  4. package/api/Define.luau +154 -0
  5. package/api/Lifecycle.luau +119 -0
  6. package/api/Packet.luau +108 -0
  7. package/api/Query.luau +149 -0
  8. package/api/Set.luau +189 -0
  9. package/channel/Blocks.luau +124 -0
  10. package/channel/Group.luau +118 -0
  11. package/channel/Link.luau +135 -0
  12. package/channel/Outbox.luau +261 -0
  13. package/channel/Recipients.luau +129 -0
  14. package/channel/Transport.luau +138 -0
  15. package/codec/Base.luau +236 -0
  16. package/codec/Composites.luau +128 -0
  17. package/codec/Datatypes.luau +162 -0
  18. package/codec/Legality.luau +61 -0
  19. package/codec/Scalars.luau +72 -0
  20. package/codec/Text.luau +89 -0
  21. package/codec/init.luau +39 -0
  22. package/index.d.ts +285 -0
  23. package/init.luau +91 -0
  24. package/kernel/Bits.luau +16 -0
  25. package/kernel/Catalog.luau +121 -0
  26. package/kernel/Heap.luau +80 -0
  27. package/kernel/Listeners.luau +95 -0
  28. package/kernel/Log.luau +197 -0
  29. package/kernel/Tally.luau +128 -0
  30. package/kernel/Threads.luau +44 -0
  31. package/kernel/Wide.luau +267 -0
  32. package/package.json +15 -32
  33. package/runtime/Boundary.luau +180 -0
  34. package/runtime/Dispatch.luau +91 -0
  35. package/runtime/Handles.luau +114 -0
  36. package/runtime/Handshake.luau +90 -0
  37. package/runtime/Pending.luau +196 -0
  38. package/runtime/Router.luau +379 -0
  39. package/runtime/Session.luau +731 -0
  40. package/shape/Compile.luau +340 -0
  41. package/shape/Folds.luau +332 -0
  42. package/shape/Frames.luau +170 -0
  43. package/shape/Grid.luau +169 -0
  44. package/shape/Ir.luau +540 -0
  45. package/shape/Joins.luau +125 -0
  46. package/shape/Legality.luau +273 -0
  47. package/shape/Lower.luau +419 -0
  48. package/shape/Order.luau +64 -0
  49. package/shape/Regions.luau +113 -0
  50. package/shape/Rotation.luau +472 -0
  51. package/shape/Serial.luau +258 -0
  52. package/shape/Words.luau +89 -0
  53. package/shape/emit/Block.luau +453 -0
  54. package/shape/emit/Column.luau +338 -0
  55. package/shape/emit/Leaf.luau +462 -0
  56. package/shape/emit/Word.luau +234 -0
  57. package/shape/emit/init.luau +222 -0
  58. package/shape/init.luau +78 -0
  59. package/state/Apply.luau +330 -0
  60. package/state/Audience.luau +126 -0
  61. package/state/Catchup.luau +218 -0
  62. package/state/Coalesce.luau +43 -0
  63. package/state/Fieldset.luau +177 -0
  64. package/state/Newest.luau +339 -0
  65. package/state/Plan.luau +441 -0
  66. package/state/Sections.luau +468 -0
  67. package/state/Store.luau +188 -0
  68. package/state/Sync.luau +762 -0
  69. package/wire/Bytes.luau +118 -0
  70. package/wire/Clock.luau +37 -0
  71. package/wire/Protocol.luau +110 -0
  72. package/wire/Sequence.luau +40 -0
  73. package/wire/Varint.luau +124 -0
  74. package/src/Types.luau +0 -180
  75. package/src/api/Group.luau +0 -156
  76. package/src/api/Packet.luau +0 -319
  77. package/src/api/Query.luau +0 -338
  78. package/src/api/Scope.luau +0 -90
  79. package/src/api/Signal.luau +0 -188
  80. package/src/codec/Base.luau +0 -136
  81. package/src/codec/composite/Array.luau +0 -456
  82. package/src/codec/composite/Map.luau +0 -469
  83. package/src/codec/composite/Optional.luau +0 -54
  84. package/src/codec/composite/Shared.luau +0 -291
  85. package/src/codec/composite/Struct.luau +0 -494
  86. package/src/codec/composite/Tagged.luau +0 -103
  87. package/src/codec/composite/Tuple.luau +0 -143
  88. package/src/codec/datatype/Buffer.luau +0 -66
  89. package/src/codec/datatype/CFrame.luau +0 -109
  90. package/src/codec/datatype/Color.luau +0 -32
  91. package/src/codec/datatype/Instance.luau +0 -52
  92. package/src/codec/datatype/IntVector.luau +0 -39
  93. package/src/codec/datatype/NumberRange.luau +0 -39
  94. package/src/codec/datatype/Ray.luau +0 -36
  95. package/src/codec/datatype/Rect.luau +0 -31
  96. package/src/codec/datatype/Region.luau +0 -62
  97. package/src/codec/datatype/Sequence.luau +0 -184
  98. package/src/codec/datatype/String.luau +0 -94
  99. package/src/codec/datatype/UDim.luau +0 -46
  100. package/src/codec/datatype/Vector.luau +0 -130
  101. package/src/codec/meta/Auto.luau +0 -318
  102. package/src/codec/meta/Bitfield.luau +0 -147
  103. package/src/codec/meta/Custom.luau +0 -33
  104. package/src/codec/meta/DeltaScalar.luau +0 -390
  105. package/src/codec/meta/Enum.luau +0 -50
  106. package/src/codec/meta/Float.luau +0 -38
  107. package/src/codec/meta/Nothing.luau +0 -13
  108. package/src/codec/meta/Unknown.luau +0 -41
  109. package/src/codec/primitive/Bool.luau +0 -26
  110. package/src/codec/primitive/Float16.luau +0 -99
  111. package/src/codec/primitive/Int.luau +0 -59
  112. package/src/codec/primitive/Number.luau +0 -24
  113. package/src/codec/primitive/Signed.luau +0 -32
  114. package/src/codec/primitive/Varint.luau +0 -165
  115. package/src/codec/primitive/Zint.luau +0 -99
  116. package/src/index.d.ts +0 -423
  117. package/src/init.luau +0 -362
  118. package/src/internal/Baseline.luau +0 -57
  119. package/src/internal/Channel.luau +0 -390
  120. package/src/internal/Middleware.luau +0 -100
  121. package/src/internal/Pool.luau +0 -65
  122. package/src/internal/Registry.luau +0 -119
  123. package/src/internal/Transport.luau +0 -28
  124. package/src/transport/Bridge.luau +0 -110
  125. package/src/transport/Client.luau +0 -97
  126. package/src/transport/Gate.luau +0 -488
  127. package/src/transport/Reader.luau +0 -331
  128. package/src/transport/Server.luau +0 -211
  129. package/src/util/Array.luau +0 -18
  130. package/src/util/Buffer.luau +0 -90
  131. package/src/util/Constants.luau +0 -30
  132. package/src/util/Log.luau +0 -68
  133. package/src/util/Player.luau +0 -14
  134. package/src/util/Quantize.luau +0 -84
  135. package/src/util/Quat.luau +0 -124
@@ -0,0 +1,135 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- One namespace's endpoint: the buffers it reuses, the clients it may reach, and what is queued.
4
+
5
+ local Blocks = require (script.Parent.Blocks)
6
+ local Recipients = require (script.Parent.Recipients)
7
+ local Tally = require (script.Parent.Parent.kernel.Tally)
8
+ local Transport = require (script.Parent.Transport)
9
+
10
+ local Link = {}
11
+
12
+ -- Types ---------------------------------------------------------------
13
+
14
+ --[[
15
+ A namespace's outbound state, as a record rather than a handle, because everything here is read
16
+ by an assembler that walks the buckets and sums the lengths and a class would put a method in
17
+ front of every one of those reads. Four lists, since a frame is assembled per client and two of
18
+ them are the part that is the same for all, and the lossy pair has no classes.
19
+ ]]
20
+ export type Link = {
21
+ read name: string,
22
+ read isServer: boolean,
23
+ read transport: Transport.Transport,
24
+ read tally: Tally.Tally,
25
+ read scratch: buffer,
26
+ seq: number,
27
+ epoch: number,
28
+ sending: boolean,
29
+ read audience: { Player },
30
+ read ready: Recipients.Ready,
31
+ read shared: { { Blocks.Block } },
32
+ read divergent: { [Player]: { { Blocks.Block } } },
33
+ read sharedLossy: { Blocks.Block },
34
+ read divergentLossy: { [Player]: { Blocks.Block } },
35
+ }
36
+
37
+ -- Private -------------------------------------------------------------
38
+
39
+ -- One bucket per class, which is what a reliable queue is on either side of the shared line.
40
+ local function buckets (): { { Blocks.Block } }
41
+ local out = table.create (Blocks.CLASSES) :: { { Blocks.Block } }
42
+ for index = 1, Blocks.CLASSES do
43
+ out[index] = {}
44
+ end
45
+ return out
46
+ end
47
+
48
+ -- Public --------------------------------------------------------------
49
+
50
+ -- An endpoint, before its remotes exist.
51
+ function Link.new (
52
+ name: string,
53
+ side: Transport.Side,
54
+ room: number,
55
+ transport: Transport.Transport,
56
+ tally: Tally.Tally
57
+ ): Link
58
+ return {
59
+ name = name,
60
+ isServer = side == "server",
61
+ transport = transport,
62
+ tally = tally,
63
+ scratch = buffer.create (room),
64
+ seq = 0,
65
+ epoch = 0,
66
+ sending = side == "server",
67
+ audience = {},
68
+ ready = {},
69
+ shared = buckets (),
70
+ divergent = {},
71
+ sharedLossy = {},
72
+ divergentLossy = {},
73
+ }
74
+ end
75
+
76
+ function Link.open (of: Link) of.transport:open () end
77
+
78
+ -- A client this endpoint may now send to, and the two lists its targeted blocks queue in.
79
+ function Link.admit (of: Link, player: Player)
80
+ of.ready[player] = true
81
+ of.divergent[player] = buckets ()
82
+ of.divergentLossy[player] = {}
83
+ end
84
+
85
+ -- A client gone, and everything queued for them let go.
86
+ function Link.drop (of: Link, player: Player)
87
+ of.ready[player] = nil
88
+
89
+ local held = of.divergent[player]
90
+ if held ~= nil then
91
+ for _, one in held do
92
+ Blocks.drain (one)
93
+ end
94
+ of.divergent[player] = nil
95
+ end
96
+
97
+ local lossy = of.divergentLossy[player]
98
+ if lossy == nil then return end
99
+
100
+ Blocks.drain (lossy)
101
+ of.divergentLossy[player] = nil
102
+ end
103
+
104
+ -- The next block's place in its bucket, which is the half of a merge key a class does not decide.
105
+ function Link.next (of: Link): number
106
+ local at = of.seq
107
+ of.seq = at + 1
108
+ return at
109
+ end
110
+
111
+ -- The sequence back to nothing, which a flush does once every bucket it filled has drained.
112
+ function Link.rewind (of: Link) of.seq = 0 end
113
+
114
+ -- An endpoint let go: nothing queued, nobody ready, and the remotes heard no longer.
115
+ function Link.close (of: Link)
116
+ of.sending = false
117
+
118
+ for _, one in of.shared do
119
+ Blocks.drain (one)
120
+ end
121
+ Blocks.drain (of.sharedLossy)
122
+
123
+ --[[
124
+ Over the readiness set, because a client is admitted and given its buckets in one call, so
125
+ that set is the list of everyone holding any. Clearing a key mid-walk is what `next` allows.
126
+ ]]
127
+ for player in of.ready do
128
+ Link.drop (of, player)
129
+ end
130
+
131
+ table.clear (of.audience)
132
+ of.transport:close ()
133
+ end
134
+
135
+ return table.freeze (Link)
@@ -0,0 +1,261 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- Everything queued, as the frames it becomes: one lane merged and ordered, one split and lost.
4
+
5
+ local Blocks = require (script.Parent.Blocks)
6
+ local Link = require (script.Parent.Link)
7
+ local Log = require (script.Parent.Parent.kernel.Log)
8
+ local Protocol = require (script.Parent.Parent.wire.Protocol)
9
+ local Tally = require (script.Parent.Parent.kernel.Tally)
10
+ local Transport = require (script.Parent.Transport)
11
+
12
+ local Outbox = {}
13
+
14
+ -- Constants -----------------------------------------------------------
15
+
16
+ -- The one lane a class ever reaches, since the other never defers and so never needs ordering.
17
+ const SURE: Transport.Channel = "reliable"
18
+
19
+ -- The lane whose frames may vanish, and what one fire of it carries.
20
+ const LOSSY: Transport.Channel = "unreliable"
21
+ const CAP = Protocol.UNRELIABLE_CAP
22
+
23
+ --[[
24
+ The hard ceiling on one fire, which is a tripwire and not a budget.
25
+
26
+ A frame this wide is a place with a loop that forgot to flush, and the throw names the size so
27
+ whoever wrote the loop reads the number rather than an engine's refusal to send.
28
+ ]]
29
+ const CEILING = Protocol.MAX_FIRE
30
+
31
+ -- What a client with nothing of their own is merged against.
32
+ const NONE = table.freeze ({} :: { Blocks.Block })
33
+
34
+ -- Types ---------------------------------------------------------------
35
+
36
+ -- One client's own buckets, or nothing where the frame being assembled is everybody's.
37
+ type Mine = { { Blocks.Block } }?
38
+
39
+ -- Private -------------------------------------------------------------
40
+
41
+ -- What a frame comes to, and whether anything in it carries an instance.
42
+ local function summed (of: Link.Link, mine: Mine): (number, boolean)
43
+ local bytes = 0
44
+ local carried = false
45
+
46
+ for class = 1, Blocks.CLASSES do
47
+ for _, block in of.shared[class] do
48
+ bytes += block.len
49
+ if block.refs ~= nil then carried = true end
50
+ end
51
+
52
+ if mine == nil then continue end
53
+ for _, block in mine[class] do
54
+ bytes += block.len
55
+ if block.refs ~= nil then carried = true end
56
+ end
57
+ end
58
+
59
+ return bytes, carried
60
+ end
61
+
62
+ -- One frame, in the order the classes put its blocks in.
63
+ local function assembled (
64
+ of: Link.Link,
65
+ mine: Mine,
66
+ bytes: number,
67
+ carried: boolean
68
+ ): (buffer, Transport.Sidecar?)
69
+ local frame = buffer.create (bytes)
70
+ local side: Transport.Sidecar? = if carried then {} else nil
71
+ local at = 0
72
+
73
+ for class = 1, Blocks.CLASSES do
74
+ local ours = of.shared[class]
75
+ local theirs = if mine == nil then NONE else mine[class]
76
+ local reach, alsoReach = #ours, #theirs
77
+ local one, also = 1, 1
78
+
79
+ while one <= reach or also <= alsoReach do
80
+ local block
81
+ if also > alsoReach or (one <= reach and ours[one].seq < theirs[also].seq) then
82
+ block = ours[one]
83
+ one += 1
84
+ else
85
+ block = theirs[also]
86
+ also += 1
87
+ end
88
+
89
+ buffer.copy (frame, at, block.buf, 0, block.len)
90
+ at += block.len
91
+
92
+ local refs = block.refs
93
+ if side ~= nil and refs ~= nil then table.insert (side, refs) end
94
+ end
95
+ end
96
+
97
+ return frame, side
98
+ end
99
+
100
+ -- Whether any client has anything of their own queued.
101
+ local function diverges (of: Link.Link): boolean
102
+ for _, buckets in of.divergent do
103
+ for class = 1, Blocks.CLASSES do
104
+ if #buckets[class] > 0 then return true end
105
+ end
106
+ end
107
+ return false
108
+ end
109
+
110
+ -- One frame, assembled and gone.
111
+ local function sent (of: Link.Link, mine: Mine, to: Player?)
112
+ local bytes, carried = summed (of, mine)
113
+ if bytes == 0 then return end
114
+
115
+ if bytes > CEILING then
116
+ Log.error (
117
+ `{of.name} assembled a frame of {bytes} bytes, past the {CEILING} one fire takes`,
118
+ { namespace = of.name }
119
+ )
120
+ end
121
+
122
+ local frame, side = assembled (of, mine, bytes, carried)
123
+ of.transport:fire (SURE, to, frame, side)
124
+ Tally.moved (of.tally, bytes)
125
+ end
126
+
127
+ -- The holds a block needs for an audience of that many, and whether there is anyone at all.
128
+ local function reaches (block: Blocks.Block, count: number): boolean
129
+ if count == 0 then
130
+ Blocks.release (block)
131
+ return false
132
+ end
133
+
134
+ for _ = 2, count do
135
+ Blocks.retain (block)
136
+ end
137
+ return true
138
+ end
139
+
140
+ -- Everything queued on the reliable lane, out, and every bucket it drew from emptied.
141
+ local function reliable (of: Link.Link)
142
+ if of.isServer and diverges (of) then
143
+ for player in of.ready do
144
+ sent (of, of.divergent[player], player)
145
+ end
146
+ else
147
+ sent (of, nil, nil)
148
+ end
149
+
150
+ for class = 1, Blocks.CLASSES do
151
+ Blocks.drain (of.shared[class])
152
+ end
153
+ for _, buckets in of.divergent do
154
+ for class = 1, Blocks.CLASSES do
155
+ Blocks.drain (buckets[class])
156
+ end
157
+ end
158
+ end
159
+
160
+ -- One lossy frame, from a run of blocks that fit inside the cap.
161
+ local function posted (
162
+ of: Link.Link,
163
+ held: { Blocks.Block },
164
+ from: number,
165
+ upto: number,
166
+ bytes: number,
167
+ at: Player?
168
+ )
169
+ if bytes == 0 then return end
170
+
171
+ local frame = buffer.create (bytes)
172
+ local side: Transport.Sidecar? = nil
173
+ local pos = 0
174
+
175
+ for index = from, upto do
176
+ local block = held[index]
177
+ buffer.copy (frame, pos, block.buf, 0, block.len)
178
+ pos += block.len
179
+
180
+ local refs = block.refs
181
+ if refs == nil then continue end
182
+
183
+ if side == nil then
184
+ side = { refs }
185
+ continue
186
+ end
187
+ table.insert (side, refs)
188
+ end
189
+
190
+ of.transport:fire (LOSSY, at, frame, side)
191
+ Tally.moved (of.tally, bytes)
192
+ end
193
+
194
+ -- One queue of lossy blocks, as the fires it splits into.
195
+ local function looses (of: Link.Link, held: { Blocks.Block }, at: Player?)
196
+ local reach = #held
197
+ local from = 1
198
+ local bytes = 0
199
+
200
+ for index = 1, reach do
201
+ local len = held[index].len
202
+ if bytes + len > CAP then
203
+ posted (of, held, from, index - 1, bytes, at)
204
+ from = index
205
+ bytes = 0
206
+ end
207
+ bytes += len
208
+ end
209
+
210
+ posted (of, held, from, reach, bytes, at)
211
+ end
212
+
213
+ -- Everything queued on the lossy lane, out, and the queues left empty.
214
+ local function lossy (of: Link.Link)
215
+ looses (of, of.sharedLossy, nil)
216
+ Blocks.drain (of.sharedLossy)
217
+
218
+ for player, held in of.divergentLossy do
219
+ looses (of, held, player)
220
+ Blocks.drain (held)
221
+ end
222
+ end
223
+
224
+ -- Public --------------------------------------------------------------
225
+
226
+ -- A block everybody gets, in the bucket its class drains in.
227
+ function Outbox.shared (of: Link.Link, block: Blocks.Block)
228
+ table.insert (of.shared[block.class], block)
229
+ end
230
+
231
+ -- A block some of them get, in each of their own buckets.
232
+ function Outbox.targeted (of: Link.Link, to: { Player }, count: number, block: Blocks.Block)
233
+ if not reaches (block, count) then return end
234
+
235
+ for index = 1, count do
236
+ table.insert (of.divergent[to[index] ][block.class], block)
237
+ end
238
+ end
239
+
240
+ -- A block everybody gets on the lane that may lose it, in the one list that lane keeps.
241
+ function Outbox.loose (of: Link.Link, block: Blocks.Block) table.insert (of.sharedLossy, block) end
242
+
243
+ -- A block some of them get on the lane that may lose it, in each of their own lists.
244
+ function Outbox.looseTo (of: Link.Link, to: { Player }, count: number, block: Blocks.Block)
245
+ if not reaches (block, count) then return end
246
+
247
+ for index = 1, count do
248
+ table.insert (of.divergentLossy[to[index] ], block)
249
+ end
250
+ end
251
+
252
+ -- Both lanes out, and the flush over.
253
+ function Outbox.fire (of: Link.Link)
254
+ if not of.sending then return end
255
+
256
+ reliable (of)
257
+ lossy (of)
258
+ Link.rewind (of)
259
+ end
260
+
261
+ return table.freeze (Outbox)
@@ -0,0 +1,129 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- The five ways to name an audience, and the one walk that turns any of them into players.
4
+
5
+ local Group = require (script.Parent.Group)
6
+ local Types = require (script.Parent.Parent.Types)
7
+
8
+ local Recipients = {}
9
+
10
+ -- Types ---------------------------------------------------------------
11
+
12
+ --[[
13
+ The clients a namespace may send to, which is the ones that have been told they may receive. A
14
+ player who has joined and not finished the handshake discards a data frame on arrival, so naming
15
+ one costs the bytes and buys nothing, and a player who has left is not in here at all.
16
+ ]]
17
+ export type Ready = { [Player]: boolean }
18
+
19
+ -- Private -------------------------------------------------------------
20
+
21
+ --[[
22
+ The members of one recipient, written from the front, and how many there are. The tail is left
23
+ for the caller above to cut, because a resolve that is one form deep and one that recursed have
24
+ different ideas about where the end is and only one of them is right.
25
+ ]]
26
+ local function filled (into: { Player }, of: Types.Recipient, ready: Ready): number
27
+ if typeof (of) == "Instance" then
28
+ local player = of :: Player
29
+ if not ready[player] then return 0 end
30
+
31
+ into[1] = player
32
+ return 1
33
+ end
34
+
35
+ local tagged = of :: any
36
+ local count = 0
37
+
38
+ if tagged._all == true then
39
+ for player, allowed in ready do
40
+ if not allowed then continue end
41
+
42
+ count += 1
43
+ into[count] = player
44
+ end
45
+ return count
46
+ end
47
+
48
+ if tagged._except == true then
49
+ for player, allowed in ready do
50
+ if not allowed or Recipients.holds (tagged.from, player) then continue end
51
+
52
+ count += 1
53
+ into[count] = player
54
+ end
55
+ return count
56
+ end
57
+
58
+ if Group.is (of) then
59
+ for _, player in of :: Types.Group do
60
+ if not ready[player] then continue end
61
+
62
+ count += 1
63
+ into[count] = player
64
+ end
65
+ return count
66
+ end
67
+
68
+ --[[
69
+ A caller's own list, which is the one form that can name somebody twice. Two of a player is
70
+ two frames to them, so the ones already written are read back before each is taken. Nothing
71
+ else pays for that: the other four forms are sets already, and the scan here is over what a
72
+ caller wrote rather than over everybody connected.
73
+ ]]
74
+ for _, player in of :: { Player } do
75
+ if not ready[player] then continue end
76
+
77
+ -- Past the count is whatever the last resolve left, which is nobody this one named.
78
+ local seen = table.find (into, player)
79
+ if seen ~= nil and seen <= count then continue end
80
+
81
+ count += 1
82
+ into[count] = player
83
+ end
84
+ return count
85
+ end
86
+
87
+ -- Public --------------------------------------------------------------
88
+
89
+ --[[
90
+ Tagged with the phantom field its type declares, because the router tells the five recipient
91
+ forms apart at run time and an untagged table would read as a list of players. Frozen, since one
92
+ value is shared by every caller of the surface.
93
+ ]]
94
+ Recipients.ALL = table.freeze ({ _all = true }) :: Types.All
95
+
96
+ --[[
97
+ Everyone but this one, whatever form it takes. What it excludes is held rather than resolved,
98
+ because a group's members and the connected set are both read when the flush goes out.
99
+ ]]
100
+ function Recipients.except (from: Types.Recipient): Types.Except
101
+ return table.freeze ({ _except = true, from = from }) :: any
102
+ end
103
+
104
+ --[[
105
+ Whether a recipient names this player, which is the question an exclusion asks of everyone. A
106
+ test, so nothing has to be built to answer it: an exclusion is asked once per ready client, and
107
+ what it excludes is usually one player.
108
+ ]]
109
+ function Recipients.holds (of: Types.Recipient, player: Player): boolean
110
+ if typeof (of) == "Instance" then return of == player end
111
+
112
+ local tagged = of :: any
113
+ if tagged._all == true then return true end
114
+ if tagged._except == true then return not Recipients.holds (tagged.from, player) end
115
+ if Group.is (of) then return (of :: Types.Group):has (player) end
116
+
117
+ return table.find (of :: { Player }, player) ~= nil
118
+ end
119
+
120
+ -- A recipient as the players it comes to, in an array the caller keeps and hands back next time.
121
+ function Recipients.resolve (into: { Player }, of: Types.Recipient, ready: Ready)
122
+ local count = filled (into, of, ready)
123
+
124
+ for index = #into, count + 1, -1 do
125
+ into[index] = nil
126
+ end
127
+ end
128
+
129
+ return table.freeze (Recipients)
@@ -0,0 +1,138 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- The three remote instances, and the only module that touches them.
4
+
5
+ local Transport = {}
6
+ Transport.__index = Transport
7
+
8
+ -- Constants -----------------------------------------------------------
9
+
10
+ const FOLDER = "Lync"
11
+ const CHANNELS: { Channel } = { "handshake", "reliable", "unreliable" }
12
+ const LOSSY: Channel = "unreliable"
13
+
14
+ -- Types ---------------------------------------------------------------
15
+
16
+ export type Channel = "handshake" | "reliable" | "unreliable"
17
+
18
+ -- Which machine this is, taken rather than read, since one place reads the engine for both ends.
19
+ export type Side = "server" | "client"
20
+
21
+ export type Sidecar = { { Instance } }
22
+
23
+ export type Received = (from: Player?, bytes: buffer, refs: Sidecar?) -> ()
24
+
25
+ export type Transport = {
26
+ read open: (self: Transport) -> (),
27
+ read fire: (self: Transport, at: Channel, to: Player?, bytes: buffer, refs: Sidecar?) -> (),
28
+ read onReceive: (self: Transport, at: Channel, take: Received) -> (),
29
+ read close: (self: Transport) -> (),
30
+ }
31
+
32
+ --[[
33
+ The unreliable lane is an UnreliableRemoteEvent and is held as a RemoteEvent anyway. The two
34
+ classes declare the same three fire methods and the same two signals, so one table over both
35
+ keeps the lane out of every signature below, and a client casts all three regardless because
36
+ discovery answers an Instance.
37
+ ]]
38
+ type Self = {
39
+ namespace: string,
40
+ isServer: boolean,
41
+ remotes: { [Channel]: RemoteEvent },
42
+ heard: { RBXScriptConnection },
43
+ }
44
+
45
+ -- Private -------------------------------------------------------------
46
+
47
+ -- One folder for every namespace, so the first to open makes it and the rest find it.
48
+ local function rooted (storage: Instance): Instance
49
+ local found = storage:FindFirstChild (FOLDER)
50
+ if found ~= nil then return found end
51
+
52
+ local made = Instance.new ("Folder")
53
+ made.Name = FOLDER
54
+ made.Parent = storage
55
+ return made
56
+ end
57
+
58
+ -- Public --------------------------------------------------------------
59
+
60
+ function Transport.new (namespace: string, side: Side): Transport
61
+ local self: Self = {
62
+ namespace = namespace,
63
+ isServer = side == "server",
64
+ remotes = {},
65
+ heard = {},
66
+ }
67
+ return (setmetatable (self, Transport) :: any) :: Transport
68
+ end
69
+
70
+ --[[
71
+ A client waits without a deadline. The engine already warns and names the path it is waiting on,
72
+ and a namespace slow to replicate is a slow join, where a deadline would make it a failure with
73
+ nothing to recover to.
74
+ ]]
75
+ function Transport.open (self: Self)
76
+ local storage = game:GetService ("ReplicatedStorage")
77
+
78
+ if not self.isServer then
79
+ local folder = storage:WaitForChild (FOLDER):WaitForChild (self.namespace)
80
+ for _, at in CHANNELS do
81
+ self.remotes[at] = folder:WaitForChild (at) :: RemoteEvent
82
+ end
83
+ return
84
+ end
85
+
86
+ local folder = Instance.new ("Folder")
87
+ folder.Name = self.namespace
88
+
89
+ for _, at in CHANNELS do
90
+ local remote: RemoteEvent = if at == LOSSY
91
+ then Instance.new ("UnreliableRemoteEvent") :: any
92
+ else Instance.new ("RemoteEvent")
93
+ remote.Name = at
94
+ remote.Parent = folder
95
+ self.remotes[at] = remote
96
+ end
97
+
98
+ folder.Parent = rooted (storage)
99
+ end
100
+
101
+ --[[
102
+ One frame handed to the engine, on the lane and to whoever the caller named. A client names
103
+ nobody and reaches the server, and a server naming nobody reaches everyone, which is the one
104
+ place the three engine calls a remote offers are told apart.
105
+ ]]
106
+ function Transport.fire (self: Self, at: Channel, to: Player?, bytes: buffer, refs: Sidecar?)
107
+ local remote = self.remotes[at]
108
+ if not self.isServer then return remote:FireServer (bytes, refs) end
109
+ if to ~= nil then return remote:FireClient (to, bytes, refs) end
110
+ remote:FireAllClients (bytes, refs)
111
+ end
112
+
113
+ -- A server receiver is the engine's own, since the player it names is the authenticated sender.
114
+ function Transport.onReceive (self: Self, at: Channel, take: Received)
115
+ local remote = self.remotes[at]
116
+
117
+ if self.isServer then
118
+ table.insert (self.heard, remote.OnServerEvent:Connect (take))
119
+ return
120
+ end
121
+
122
+ local function relay (bytes: buffer, refs: Sidecar?) take (nil, bytes, refs) end
123
+ table.insert (self.heard, remote.OnClientEvent:Connect (relay))
124
+ end
125
+
126
+ --[[
127
+ Released rather than destroyed. Destroying the remotes would take the namespace out from under a
128
+ counterparty still running on it, and an endpoint that hears nothing is what a caller who shut
129
+ one down asked for.
130
+ ]]
131
+ function Transport.close (self: Self)
132
+ for _, made in self.heard do
133
+ made:Disconnect ()
134
+ end
135
+ table.clear (self.heard)
136
+ end
137
+
138
+ return table.freeze (Transport)