@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.
- package/LICENSE +21 -0
- package/README.md +302 -299
- package/Types.luau +215 -0
- package/api/Define.luau +154 -0
- package/api/Lifecycle.luau +119 -0
- package/api/Packet.luau +108 -0
- package/api/Query.luau +149 -0
- package/api/Set.luau +189 -0
- package/channel/Blocks.luau +124 -0
- package/channel/Group.luau +118 -0
- package/channel/Link.luau +135 -0
- package/channel/Outbox.luau +261 -0
- package/channel/Recipients.luau +129 -0
- package/channel/Transport.luau +138 -0
- package/codec/Base.luau +236 -0
- package/codec/Composites.luau +128 -0
- package/codec/Datatypes.luau +162 -0
- package/codec/Legality.luau +61 -0
- package/codec/Scalars.luau +72 -0
- package/codec/Text.luau +89 -0
- package/codec/init.luau +39 -0
- package/index.d.ts +285 -0
- package/init.luau +91 -0
- package/kernel/Bits.luau +16 -0
- package/kernel/Catalog.luau +121 -0
- package/kernel/Heap.luau +80 -0
- package/kernel/Listeners.luau +95 -0
- package/kernel/Log.luau +197 -0
- package/kernel/Tally.luau +128 -0
- package/kernel/Threads.luau +44 -0
- package/kernel/Wide.luau +267 -0
- package/package.json +15 -32
- package/runtime/Boundary.luau +180 -0
- package/runtime/Dispatch.luau +91 -0
- package/runtime/Handles.luau +114 -0
- package/runtime/Handshake.luau +90 -0
- package/runtime/Pending.luau +196 -0
- package/runtime/Router.luau +379 -0
- package/runtime/Session.luau +731 -0
- package/shape/Compile.luau +340 -0
- package/shape/Folds.luau +332 -0
- package/shape/Frames.luau +170 -0
- package/shape/Grid.luau +169 -0
- package/shape/Ir.luau +540 -0
- package/shape/Joins.luau +125 -0
- package/shape/Legality.luau +273 -0
- package/shape/Lower.luau +419 -0
- package/shape/Order.luau +64 -0
- package/shape/Regions.luau +113 -0
- package/shape/Rotation.luau +472 -0
- package/shape/Serial.luau +258 -0
- package/shape/Words.luau +89 -0
- package/shape/emit/Block.luau +453 -0
- package/shape/emit/Column.luau +338 -0
- package/shape/emit/Leaf.luau +462 -0
- package/shape/emit/Word.luau +234 -0
- package/shape/emit/init.luau +222 -0
- package/shape/init.luau +78 -0
- package/state/Apply.luau +330 -0
- package/state/Audience.luau +126 -0
- package/state/Catchup.luau +218 -0
- package/state/Coalesce.luau +43 -0
- package/state/Fieldset.luau +177 -0
- package/state/Newest.luau +339 -0
- package/state/Plan.luau +441 -0
- package/state/Sections.luau +468 -0
- package/state/Store.luau +188 -0
- package/state/Sync.luau +762 -0
- package/wire/Bytes.luau +118 -0
- package/wire/Clock.luau +37 -0
- package/wire/Protocol.luau +110 -0
- package/wire/Sequence.luau +40 -0
- package/wire/Varint.luau +124 -0
- package/src/Types.luau +0 -180
- package/src/api/Group.luau +0 -156
- package/src/api/Packet.luau +0 -319
- package/src/api/Query.luau +0 -338
- package/src/api/Scope.luau +0 -90
- package/src/api/Signal.luau +0 -188
- package/src/codec/Base.luau +0 -136
- package/src/codec/composite/Array.luau +0 -456
- package/src/codec/composite/Map.luau +0 -469
- package/src/codec/composite/Optional.luau +0 -54
- package/src/codec/composite/Shared.luau +0 -291
- package/src/codec/composite/Struct.luau +0 -494
- package/src/codec/composite/Tagged.luau +0 -103
- package/src/codec/composite/Tuple.luau +0 -143
- package/src/codec/datatype/Buffer.luau +0 -66
- package/src/codec/datatype/CFrame.luau +0 -109
- package/src/codec/datatype/Color.luau +0 -32
- package/src/codec/datatype/Instance.luau +0 -52
- package/src/codec/datatype/IntVector.luau +0 -39
- package/src/codec/datatype/NumberRange.luau +0 -39
- package/src/codec/datatype/Ray.luau +0 -36
- package/src/codec/datatype/Rect.luau +0 -31
- package/src/codec/datatype/Region.luau +0 -62
- package/src/codec/datatype/Sequence.luau +0 -184
- package/src/codec/datatype/String.luau +0 -94
- package/src/codec/datatype/UDim.luau +0 -46
- package/src/codec/datatype/Vector.luau +0 -130
- package/src/codec/meta/Auto.luau +0 -318
- package/src/codec/meta/Bitfield.luau +0 -147
- package/src/codec/meta/Custom.luau +0 -33
- package/src/codec/meta/DeltaScalar.luau +0 -390
- package/src/codec/meta/Enum.luau +0 -50
- package/src/codec/meta/Float.luau +0 -38
- package/src/codec/meta/Nothing.luau +0 -13
- package/src/codec/meta/Unknown.luau +0 -41
- package/src/codec/primitive/Bool.luau +0 -26
- package/src/codec/primitive/Float16.luau +0 -99
- package/src/codec/primitive/Int.luau +0 -59
- package/src/codec/primitive/Number.luau +0 -24
- package/src/codec/primitive/Signed.luau +0 -32
- package/src/codec/primitive/Varint.luau +0 -165
- package/src/codec/primitive/Zint.luau +0 -99
- package/src/index.d.ts +0 -423
- package/src/init.luau +0 -362
- package/src/internal/Baseline.luau +0 -57
- package/src/internal/Channel.luau +0 -390
- package/src/internal/Middleware.luau +0 -100
- package/src/internal/Pool.luau +0 -65
- package/src/internal/Registry.luau +0 -119
- package/src/internal/Transport.luau +0 -28
- package/src/transport/Bridge.luau +0 -110
- package/src/transport/Client.luau +0 -97
- package/src/transport/Gate.luau +0 -488
- package/src/transport/Reader.luau +0 -331
- package/src/transport/Server.luau +0 -211
- package/src/util/Array.luau +0 -18
- package/src/util/Buffer.luau +0 -90
- package/src/util/Constants.luau +0 -30
- package/src/util/Log.luau +0 -68
- package/src/util/Player.luau +0 -14
- package/src/util/Quantize.luau +0 -84
- 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)
|