@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,731 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- One namespace, composed: everything under it wired together and driven from one place.
|
|
4
|
+
|
|
5
|
+
local Blocks = require (script.Parent.Parent.channel.Blocks)
|
|
6
|
+
local Boundary = require (script.Parent.Boundary)
|
|
7
|
+
local Catalog = require (script.Parent.Parent.kernel.Catalog)
|
|
8
|
+
local Catchup = require (script.Parent.Parent.state.Catchup)
|
|
9
|
+
local Clock = require (script.Parent.Parent.wire.Clock)
|
|
10
|
+
local Compile = require (script.Parent.Parent.shape.Compile)
|
|
11
|
+
local Dispatch = require (script.Parent.Dispatch)
|
|
12
|
+
local Frames = require (script.Parent.Parent.shape.Frames)
|
|
13
|
+
local Group = require (script.Parent.Parent.channel.Group)
|
|
14
|
+
local Handles = require (script.Parent.Handles)
|
|
15
|
+
local Handshake = require (script.Parent.Handshake)
|
|
16
|
+
local Link = require (script.Parent.Parent.channel.Link)
|
|
17
|
+
local Log = require (script.Parent.Parent.kernel.Log)
|
|
18
|
+
local Outbox = require (script.Parent.Parent.channel.Outbox)
|
|
19
|
+
local Pending = require (script.Parent.Pending)
|
|
20
|
+
local Plan = require (script.Parent.Parent.state.Plan)
|
|
21
|
+
local Protocol = require (script.Parent.Parent.wire.Protocol)
|
|
22
|
+
local Recipients = require (script.Parent.Parent.channel.Recipients)
|
|
23
|
+
local Router = require (script.Parent.Router)
|
|
24
|
+
local Sections = require (script.Parent.Parent.state.Sections)
|
|
25
|
+
local Sequence = require (script.Parent.Parent.wire.Sequence)
|
|
26
|
+
local Serial = require (script.Parent.Parent.shape.Serial)
|
|
27
|
+
local Sync = require (script.Parent.Parent.state.Sync)
|
|
28
|
+
local Tally = require (script.Parent.Parent.kernel.Tally)
|
|
29
|
+
local Transport = require (script.Parent.Parent.channel.Transport)
|
|
30
|
+
local Types = require (script.Parent.Parent.Types)
|
|
31
|
+
|
|
32
|
+
local Session = {}
|
|
33
|
+
Session.__index = Session
|
|
34
|
+
|
|
35
|
+
-- Constants -----------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
const HANDSHAKE: Transport.Channel = "handshake"
|
|
38
|
+
const RELIABLE: Transport.Channel = "reliable"
|
|
39
|
+
const UNRELIABLE: Transport.Channel = "unreliable"
|
|
40
|
+
|
|
41
|
+
-- What a definition declares about its lane, which decides the class its block takes.
|
|
42
|
+
const ORDERED: Serial.Delivery = "reliable"
|
|
43
|
+
const LATEST: Serial.Delivery = "newest"
|
|
44
|
+
|
|
45
|
+
-- The field a header opens with, which is the definition it is about, in every form there is.
|
|
46
|
+
const WHOSE = 1
|
|
47
|
+
|
|
48
|
+
const FLOOR = Protocol.BUDGET_FLOOR
|
|
49
|
+
const RATE = Protocol.BUDGET_RATE
|
|
50
|
+
const WINDOW = Protocol.DETECTOR_WINDOW
|
|
51
|
+
|
|
52
|
+
-- Types ---------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
-- Who is here, as the three things a namespace asks of the roster and no more.
|
|
55
|
+
export type Roster = {
|
|
56
|
+
read all: (self: Roster) -> { Player },
|
|
57
|
+
read onAdded: (self: Roster, take: (Player) -> ()) -> () -> (),
|
|
58
|
+
read onRemoving: (self: Roster, take: (Player) -> ()) -> () -> (),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type Session = {
|
|
62
|
+
read started: boolean,
|
|
63
|
+
read closed: boolean,
|
|
64
|
+
read start: (self: Session) -> (),
|
|
65
|
+
read queued: (self: Session, said: string) -> (),
|
|
66
|
+
read listen: (self: Session, id: number, fn: (...any) -> ()) -> Types.Connection,
|
|
67
|
+
read respond: (self: Session, id: number, name: string, fn: Dispatch.Responder) -> (),
|
|
68
|
+
read fire: (self: Session, def: Compile.Packet, to: Types.Recipient?, value: any) -> (),
|
|
69
|
+
read ask: (
|
|
70
|
+
self: Session,
|
|
71
|
+
def: Compile.Query,
|
|
72
|
+
to: Player?,
|
|
73
|
+
value: any,
|
|
74
|
+
timeout: number?,
|
|
75
|
+
complete: Types.Outcome<any>
|
|
76
|
+
) -> (),
|
|
77
|
+
read set: (self: Session, id: number) -> Sync.Sync,
|
|
78
|
+
read flush: (self: Session, budget: number?) -> (),
|
|
79
|
+
read close: (self: Session) -> (),
|
|
80
|
+
read stats: (self: Session) -> Types.Stats,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
--[[
|
|
84
|
+
What one namespace is. `order` is the set definitions by id, kept as a list because a table walk
|
|
85
|
+
has no order and two machines planning in different orders would put a frame's blocks in
|
|
86
|
+
different places. `purse` is what the shared stream spent on each client this flush, and
|
|
87
|
+
`fields` is as wide as the widest header.
|
|
88
|
+
]]
|
|
89
|
+
type Self = {
|
|
90
|
+
space: Compile.Compiled,
|
|
91
|
+
isServer: boolean,
|
|
92
|
+
transport: Transport.Transport,
|
|
93
|
+
roster: Roster,
|
|
94
|
+
link: Link.Link,
|
|
95
|
+
boundary: Boundary.Boundary,
|
|
96
|
+
heard: Dispatch.Wiring,
|
|
97
|
+
pending: Pending.Pending,
|
|
98
|
+
router: Router.Wiring,
|
|
99
|
+
sets: { [number]: Sync.Sync },
|
|
100
|
+
order: { number },
|
|
101
|
+
rings: { [number]: number },
|
|
102
|
+
newest: { number },
|
|
103
|
+
latest: { [number]: Latest },
|
|
104
|
+
reserve: number,
|
|
105
|
+
purse: { [Player]: number },
|
|
106
|
+
one: { Player },
|
|
107
|
+
fields: { number },
|
|
108
|
+
unhook: { () -> () },
|
|
109
|
+
started: boolean,
|
|
110
|
+
closed: boolean,
|
|
111
|
+
at: number,
|
|
112
|
+
armed: number?,
|
|
113
|
+
owed: string?,
|
|
114
|
+
counts: Tally.Tally,
|
|
115
|
+
plan: Plan.Budget,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
-- One newest packet, between the fires it took and the one transmission they come to.
|
|
119
|
+
type Latest = {
|
|
120
|
+
held: boolean,
|
|
121
|
+
value: any,
|
|
122
|
+
to: Types.Recipient?,
|
|
123
|
+
sent: string?,
|
|
124
|
+
at: number,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
-- Private -------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
-- The clock a stamp is measured against, which is the one both machines can read.
|
|
130
|
+
local function reading (): number return workspace:GetServerTimeNow () end
|
|
131
|
+
|
|
132
|
+
-- What one flush may spend on state per client.
|
|
133
|
+
local function budgeted (self: Self, asked: number?, now: number): number
|
|
134
|
+
if asked ~= nil then
|
|
135
|
+
if asked < FLOOR then
|
|
136
|
+
Log.error (
|
|
137
|
+
`a flush of {asked} bytes is under the floor of {FLOOR}`,
|
|
138
|
+
{ definition = self.space.space }
|
|
139
|
+
)
|
|
140
|
+
end
|
|
141
|
+
return asked
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
local since = math.max (now - self.at, 0)
|
|
145
|
+
return math.max (math.floor (RATE * since), FLOOR)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
-- Whether anything in this namespace is catching up, which is what the shared plan reserves for.
|
|
149
|
+
local function owing (self: Self): boolean
|
|
150
|
+
for _, id in self.order do
|
|
151
|
+
if Sync.owing (self.sets[id]) then return true end
|
|
152
|
+
end
|
|
153
|
+
return false
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
-- One responder's answer, as the block that carries it.
|
|
157
|
+
local function replying (
|
|
158
|
+
self: Self,
|
|
159
|
+
def: Compile.Query,
|
|
160
|
+
asked: number,
|
|
161
|
+
who: Player?,
|
|
162
|
+
ok: boolean,
|
|
163
|
+
value: any
|
|
164
|
+
)
|
|
165
|
+
local link = self.link
|
|
166
|
+
local scratch = link.scratch
|
|
167
|
+
local head = def.answers
|
|
168
|
+
|
|
169
|
+
head.write (scratch, 0, { def.id + 1, asked, if ok then 0 else 1 })
|
|
170
|
+
|
|
171
|
+
local ends = head.bytes
|
|
172
|
+
if ok then ends = def.response.write (scratch, ends, value) end
|
|
173
|
+
|
|
174
|
+
local side = def.response.sidecar
|
|
175
|
+
local held = if side.bears then side.refs else nil
|
|
176
|
+
local block = Blocks.sealed (scratch, ends, Blocks.RESPONSE, Link.next (link), held)
|
|
177
|
+
Tally.sent (self.counts, def.name, block.len)
|
|
178
|
+
|
|
179
|
+
table.clear (side.refs)
|
|
180
|
+
table.clear (side.slotOf)
|
|
181
|
+
|
|
182
|
+
if who == nil then
|
|
183
|
+
Outbox.shared (link, block)
|
|
184
|
+
return
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
local one = self.one
|
|
188
|
+
one[1] = who
|
|
189
|
+
Outbox.targeted (link, one, 1, block)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
-- What each client is behind on, cut against what the shared stream already spent on it.
|
|
193
|
+
local function behind (self: Self, target: number, purse: { [Player]: number })
|
|
194
|
+
for who in self.link.ready do
|
|
195
|
+
local room = target - (purse[who] or 0)
|
|
196
|
+
if room <= 0 then continue end
|
|
197
|
+
|
|
198
|
+
for _, id in self.order do
|
|
199
|
+
local spent = Sync.behind (self.sets[id], self.link, who, room)
|
|
200
|
+
room -= spent
|
|
201
|
+
|
|
202
|
+
if room <= 0 then break end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
-- A client this endpoint may now send to, and everything it is owed of what already exists.
|
|
208
|
+
local function admitted (self: Self, who: Player)
|
|
209
|
+
Link.admit (self.link, who)
|
|
210
|
+
|
|
211
|
+
for _, id in self.order do
|
|
212
|
+
Sync.joined (self.sets[id], who, self.link.ready)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
-- One departure, and every place this namespace held anything keyed by that player.
|
|
217
|
+
local function departed (self: Self, who: Player)
|
|
218
|
+
local now = reading ()
|
|
219
|
+
|
|
220
|
+
-- Every group this player was in, thinned.
|
|
221
|
+
Group.dropped (who)
|
|
222
|
+
|
|
223
|
+
self.pending:rejectFor (who, now)
|
|
224
|
+
Boundary.left (self.boundary, who)
|
|
225
|
+
self.router.latest[who] = nil
|
|
226
|
+
|
|
227
|
+
for _, id in self.order do
|
|
228
|
+
Sync.left (self.sets[id], who)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
Link.drop (self.link, who)
|
|
232
|
+
self.purse[who] = nil
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
-- Every newest packet held this flush, as the one transmission each comes to.
|
|
236
|
+
local function latest (self: Self, now: number)
|
|
237
|
+
local link = self.link
|
|
238
|
+
local scratch = link.scratch
|
|
239
|
+
|
|
240
|
+
for _, id in self.newest do
|
|
241
|
+
local one = self.latest[id]
|
|
242
|
+
if not one.held then continue end
|
|
243
|
+
|
|
244
|
+
local def = self.space.byId[id] :: Compile.Packet
|
|
245
|
+
local hz = def.hz
|
|
246
|
+
if hz ~= nil and now - one.at < 1 / hz then continue end
|
|
247
|
+
|
|
248
|
+
local head = def.head
|
|
249
|
+
local ends = def.built.write (scratch, head.bytes, one.value)
|
|
250
|
+
local payload = buffer.readstring (scratch, head.bytes, ends - head.bytes)
|
|
251
|
+
|
|
252
|
+
local side = def.built.sidecar
|
|
253
|
+
one.held, one.value = false, nil
|
|
254
|
+
|
|
255
|
+
if payload == one.sent then
|
|
256
|
+
table.clear (side.refs)
|
|
257
|
+
table.clear (side.slotOf)
|
|
258
|
+
continue
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
one.sent, one.at = payload, now
|
|
262
|
+
|
|
263
|
+
local fields = self.fields
|
|
264
|
+
fields[WHOSE] = def.id
|
|
265
|
+
|
|
266
|
+
local at = 1
|
|
267
|
+
if def.timestamped then
|
|
268
|
+
at += 1
|
|
269
|
+
fields[at] = Clock.stamp (Clock.elapsed (now, self.router.epoch))
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
local ring = Sequence.next (self.rings[id] or 0)
|
|
273
|
+
self.rings[id] = ring
|
|
274
|
+
|
|
275
|
+
at += 1
|
|
276
|
+
fields[at] = ring
|
|
277
|
+
|
|
278
|
+
head.write (scratch, 0, fields)
|
|
279
|
+
if head.trailer > 0 then
|
|
280
|
+
Frames.more (scratch, ends, false)
|
|
281
|
+
ends += head.trailer
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
local carried = if side.bears then side.refs else nil
|
|
285
|
+
local block = Blocks.sealed (scratch, ends, Blocks.LOOSE, Link.next (link), carried)
|
|
286
|
+
|
|
287
|
+
table.clear (side.refs)
|
|
288
|
+
table.clear (side.slotOf)
|
|
289
|
+
|
|
290
|
+
local to = one.to
|
|
291
|
+
if to == nil then
|
|
292
|
+
Outbox.loose (link, block)
|
|
293
|
+
Tally.carried (self.counts, def.name, block.len)
|
|
294
|
+
continue
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
local audience = link.audience
|
|
298
|
+
Recipients.resolve (audience, to, link.ready)
|
|
299
|
+
|
|
300
|
+
local reach = #audience
|
|
301
|
+
Outbox.looseTo (link, audience, reach, block)
|
|
302
|
+
if reach > 0 then Tally.carried (self.counts, def.name, block.len) end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
-- The first bytes queued while this endpoint may send, and the thread that says so if none go.
|
|
307
|
+
local function arming (self: Self, said: string)
|
|
308
|
+
if self.armed ~= nil then return end
|
|
309
|
+
|
|
310
|
+
self.armed = reading ()
|
|
311
|
+
self.owed = said
|
|
312
|
+
|
|
313
|
+
--[[
|
|
314
|
+
Delayed rather than pooled. The pool is coroutines run now and put back after, and a thread
|
|
315
|
+
parked for a window is the one thing it is not for: it would hold one out of the pile for as
|
|
316
|
+
long as the window lasts and put it back having done nothing most times.
|
|
317
|
+
]]
|
|
318
|
+
task.delay (WINDOW, function ()
|
|
319
|
+
if self.armed == nil or self.closed then return end
|
|
320
|
+
|
|
321
|
+
Log.error (
|
|
322
|
+
`{self.space.space} queued {self.owed} and never flushed`,
|
|
323
|
+
{ definition = self.owed }
|
|
324
|
+
)
|
|
325
|
+
end)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
-- One frame in, on whichever lane it arrived.
|
|
329
|
+
local function arrived (self: Self, lossy: boolean): Transport.Received
|
|
330
|
+
return function (from: Player?, bytes: buffer, refs: Transport.Sidecar?)
|
|
331
|
+
local counts = self.counts
|
|
332
|
+
|
|
333
|
+
-- A frame this endpoint may not read yet, recorded rather than swallowed.
|
|
334
|
+
if not self.link.sending then
|
|
335
|
+
local facts: Log.Facts = { code = Catalog.UNREADY }
|
|
336
|
+
Log.warn (Log.line (Catalog.UNREADY, facts), facts)
|
|
337
|
+
Tally.dropped (counts, nil)
|
|
338
|
+
return
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
Tally.took (counts, buffer.len (bytes))
|
|
342
|
+
Router.frame (self.router, from, lossy, bytes, refs, reading ())
|
|
343
|
+
|
|
344
|
+
-- The frame over, which a receiver owes as much as a sender does.
|
|
345
|
+
for _, id in self.order do
|
|
346
|
+
Sync.settle (self.sets[id])
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
-- The handshake, from whichever end this is.
|
|
352
|
+
local function greeted (self: Self): Transport.Received
|
|
353
|
+
if self.isServer then
|
|
354
|
+
return function (from: Player?, bytes: buffer)
|
|
355
|
+
local said, ok = Handshake.verdict (self.space.hash, self.router.epoch, bytes)
|
|
356
|
+
if said == nil then return end
|
|
357
|
+
|
|
358
|
+
self.transport:fire (HANDSHAKE, from, said)
|
|
359
|
+
if ok and from ~= nil then admitted (self, from) end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
return function (_: Player?, bytes: buffer)
|
|
364
|
+
local epoch = Handshake.judged (self.space.hash, bytes)
|
|
365
|
+
if epoch == nil then return end
|
|
366
|
+
|
|
367
|
+
self.router.epoch = epoch
|
|
368
|
+
self.link.sending = true
|
|
369
|
+
self.at = reading ()
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
-- Public --------------------------------------------------------------
|
|
374
|
+
|
|
375
|
+
-- One namespace, compiled and wired, before anything is open.
|
|
376
|
+
function Session.new (
|
|
377
|
+
space: string,
|
|
378
|
+
defs: Types.Definitions,
|
|
379
|
+
transport: Transport.Transport,
|
|
380
|
+
roster: Roster,
|
|
381
|
+
side: Transport.Side
|
|
382
|
+
): Session
|
|
383
|
+
local compiled = Compile.build (space, defs)
|
|
384
|
+
|
|
385
|
+
local sets: { [number]: Sync.Sync } = {}
|
|
386
|
+
local order: { number } = {}
|
|
387
|
+
local layouts: { Sections.Layout } = {}
|
|
388
|
+
|
|
389
|
+
-- The widest thing one fire can put on the wire, which is what the scratch has to hold.
|
|
390
|
+
local widest = Protocol.SECTION_BYTES
|
|
391
|
+
local named = 0
|
|
392
|
+
|
|
393
|
+
--[[
|
|
394
|
+
The newest packets, and what each holds between the fires it takes and the one transmission
|
|
395
|
+
they come to. Listed as well as keyed, because a flush walks them in the order the namespace
|
|
396
|
+
named them and a table walk has no order.
|
|
397
|
+
]]
|
|
398
|
+
local newest: { number } = {}
|
|
399
|
+
local latest: { [number]: Latest } = {}
|
|
400
|
+
|
|
401
|
+
for _, def in compiled.list do
|
|
402
|
+
if def.kind == "packet" and def.delivery == LATEST then
|
|
403
|
+
table.insert (newest, def.id)
|
|
404
|
+
latest[def.id] = { held = false, value = nil, to = nil, sent = nil, at = 0 }
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
if def.kind == "packet" then
|
|
408
|
+
named = math.max (named, def.head.fields)
|
|
409
|
+
elseif def.kind == "query" then
|
|
410
|
+
named = math.max (named, def.asks.fields, def.answers.fields)
|
|
411
|
+
else
|
|
412
|
+
named = math.max (named, def.head.fields, def.stream.fields)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
if def.kind == "set" then continue end
|
|
416
|
+
widest = math.max (widest, def.blockBytes)
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
for _, def in compiled.list do
|
|
420
|
+
if def.kind ~= "set" then continue end
|
|
421
|
+
|
|
422
|
+
local sync = Sync.new (def)
|
|
423
|
+
local layout = sync.layout
|
|
424
|
+
|
|
425
|
+
sets[def.id] = sync
|
|
426
|
+
table.insert (order, def.id)
|
|
427
|
+
table.insert (layouts, layout)
|
|
428
|
+
|
|
429
|
+
widest = math.max (widest, layout.opening + layout.entry)
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
-- One tally for the namespace, held by everything that observes a number for it.
|
|
433
|
+
local names = table.create (compiled.count) :: { string }
|
|
434
|
+
for _, def in compiled.list do
|
|
435
|
+
table.insert (names, def.name)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
local counts = Tally.new (names)
|
|
439
|
+
|
|
440
|
+
local link = Link.new (space, side, widest, transport, counts)
|
|
441
|
+
local boundary = Boundary.new (space, counts)
|
|
442
|
+
local heard = Dispatch.wiring ()
|
|
443
|
+
local pending = Pending.new (space)
|
|
444
|
+
local router = Router.wiring (compiled, boundary, heard, pending, counts)
|
|
445
|
+
|
|
446
|
+
for _, id in order do
|
|
447
|
+
local sync = sets[id]
|
|
448
|
+
|
|
449
|
+
router.sections[id] = function (b, pos, ctx) return Sync.taken (sync, b, pos, ctx), nil end
|
|
450
|
+
router.streams[id] = function (b, pos, ctx) return Sync.stream (sync, b, pos, ctx), nil end
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
local self: Self = {
|
|
454
|
+
space = compiled,
|
|
455
|
+
isServer = side == "server",
|
|
456
|
+
transport = transport,
|
|
457
|
+
roster = roster,
|
|
458
|
+
link = link,
|
|
459
|
+
boundary = boundary,
|
|
460
|
+
heard = heard,
|
|
461
|
+
pending = pending,
|
|
462
|
+
router = router,
|
|
463
|
+
sets = sets,
|
|
464
|
+
order = order,
|
|
465
|
+
rings = {},
|
|
466
|
+
newest = newest,
|
|
467
|
+
latest = latest,
|
|
468
|
+
reserve = Catchup.reserve (layouts),
|
|
469
|
+
purse = {},
|
|
470
|
+
one = table.create (1) :: { Player },
|
|
471
|
+
fields = table.create (named, 0) :: { number },
|
|
472
|
+
unhook = {},
|
|
473
|
+
started = false,
|
|
474
|
+
closed = false,
|
|
475
|
+
at = 0,
|
|
476
|
+
armed = nil,
|
|
477
|
+
owed = nil,
|
|
478
|
+
counts = counts,
|
|
479
|
+
plan = Plan.budget (0),
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
--[[
|
|
483
|
+
Where a responder's answer goes, which is here and could not have been said sooner: the
|
|
484
|
+
router is made before the session that queues through it, and the two hold each other.
|
|
485
|
+
]]
|
|
486
|
+
router.answered = function (def, asked, who, ok, value)
|
|
487
|
+
replying (self, def, asked, who, ok, value)
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
local held = (setmetatable (self, Session) :: any) :: Session
|
|
491
|
+
|
|
492
|
+
-- Every handle bound to what this namespace compiled it into.
|
|
493
|
+
for _, def in compiled.list do
|
|
494
|
+
local of = (defs[def.name] :: any).live
|
|
495
|
+
if of ~= nil then Handles.opened (of, def, held, side == "server") end
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
return held
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
-- The namespace opened, and whatever this end of it says first.
|
|
502
|
+
function Session.start (self: Self)
|
|
503
|
+
if self.started then
|
|
504
|
+
Log.error (`{self.space.space} has already started`, { definition = self.space.space })
|
|
505
|
+
end
|
|
506
|
+
self.started = true
|
|
507
|
+
|
|
508
|
+
Link.open (self.link)
|
|
509
|
+
|
|
510
|
+
self.transport:onReceive (HANDSHAKE, greeted (self))
|
|
511
|
+
self.transport:onReceive (RELIABLE, arrived (self, false))
|
|
512
|
+
self.transport:onReceive (UNRELIABLE, arrived (self, true))
|
|
513
|
+
|
|
514
|
+
self.router.epoch = reading ()
|
|
515
|
+
self.at = self.router.epoch
|
|
516
|
+
|
|
517
|
+
if not self.isServer then
|
|
518
|
+
self.transport:fire (HANDSHAKE, nil, Handshake.hello (self.space.hash))
|
|
519
|
+
return
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
table.insert (
|
|
523
|
+
self.unhook,
|
|
524
|
+
self.roster:onRemoving (function (who: Player) departed (self, who) end)
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
for _, who in self.roster:all () do
|
|
528
|
+
admitted (self, who)
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
-- Everything buffered since the last flush, out.
|
|
533
|
+
function Session.flush (self: Self, budget: number?)
|
|
534
|
+
local now = reading ()
|
|
535
|
+
local target = budgeted (self, budget, now)
|
|
536
|
+
|
|
537
|
+
if not self.link.sending then return end
|
|
538
|
+
|
|
539
|
+
self.pending:sweep (now)
|
|
540
|
+
latest (self, now)
|
|
541
|
+
|
|
542
|
+
-- Every set's audiences, before anything is decided about what this flush carries.
|
|
543
|
+
for _, id in self.order do
|
|
544
|
+
Sync.resolved (self.sets[id], self.link, self.link.ready)
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
--[[
|
|
548
|
+
The reserve comes off the shared plan and nothing puts it back. Held to the floor instead,
|
|
549
|
+
the subtraction did nothing wherever the budget was near it, so a client catching up was
|
|
550
|
+
left with whatever happened to be over. The floor is what a flush is given, not what the
|
|
551
|
+
shared half of one is owed.
|
|
552
|
+
]]
|
|
553
|
+
local held = if owing (self) then self.reserve else 0
|
|
554
|
+
local plan = Plan.reset (self.plan, math.max (target - held, 0))
|
|
555
|
+
|
|
556
|
+
local purse: { [Player]: number }? = nil
|
|
557
|
+
if held > 0 then
|
|
558
|
+
table.clear (self.purse)
|
|
559
|
+
purse = self.purse
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
for _, id in self.order do
|
|
563
|
+
Sync.shared (self.sets[id], self.link, plan, purse, self.link.ready, now)
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
if purse ~= nil then behind (self, target, purse) end
|
|
567
|
+
|
|
568
|
+
Outbox.fire (self.link)
|
|
569
|
+
|
|
570
|
+
for _, id in self.order do
|
|
571
|
+
Sync.settle (self.sets[id])
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
Tally.flushed (self.counts)
|
|
575
|
+
self.at = now
|
|
576
|
+
self.armed = nil
|
|
577
|
+
self.owed = nil
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
-- One last flush, everything outstanding told why, and the remotes let go.
|
|
581
|
+
function Session.close (self: Self)
|
|
582
|
+
if self.closed then return end
|
|
583
|
+
|
|
584
|
+
Session.flush (self)
|
|
585
|
+
self.closed = true
|
|
586
|
+
|
|
587
|
+
self.pending:rejectAll (reading ())
|
|
588
|
+
|
|
589
|
+
for _, drop in self.unhook do
|
|
590
|
+
drop ()
|
|
591
|
+
end
|
|
592
|
+
table.clear (self.unhook)
|
|
593
|
+
|
|
594
|
+
-- One call, because a link closes what it speaks through and this handed it the one it has.
|
|
595
|
+
Link.close (self.link)
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
-- Something queued, said by whoever queued it.
|
|
599
|
+
function Session.queued (self: Self, said: string)
|
|
600
|
+
if not self.link.sending then return end
|
|
601
|
+
arming (self, said)
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
-- A listener on a definition, and the connection that ends it.
|
|
605
|
+
function Session.listen (self: Self, id: number, fn: (...any) -> ()): Types.Connection
|
|
606
|
+
return Dispatch.listen (self.heard, id, fn)
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
-- The one responder a query gets, refused at the second rather than at the reply.
|
|
610
|
+
function Session.respond (self: Self, id: number, name: string, fn: Dispatch.Responder)
|
|
611
|
+
Dispatch.respond (self.heard, id, name, fn)
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
-- One set, by the id the wire names it under, which is what a handle mutates through.
|
|
615
|
+
function Session.set (self: Self, id: number): Sync.Sync return self.sets[id] end
|
|
616
|
+
|
|
617
|
+
-- One packet fired, as the block that carries it.
|
|
618
|
+
function Session.fire (self: Self, def: Compile.Packet, to: Types.Recipient?, value: any)
|
|
619
|
+
-- A newest packet is held rather than written.
|
|
620
|
+
if def.delivery == LATEST then
|
|
621
|
+
local one = self.latest[def.id]
|
|
622
|
+
one.held, one.value, one.to = true, value, to
|
|
623
|
+
|
|
624
|
+
Session.queued (self, def.name)
|
|
625
|
+
Tally.sent (self.counts, def.name, 0)
|
|
626
|
+
return
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
local link = self.link
|
|
630
|
+
local scratch = link.scratch
|
|
631
|
+
local head = def.head
|
|
632
|
+
|
|
633
|
+
local fields = self.fields
|
|
634
|
+
fields[WHOSE] = def.id
|
|
635
|
+
|
|
636
|
+
local at = 1
|
|
637
|
+
if def.timestamped then
|
|
638
|
+
at += 1
|
|
639
|
+
fields[at] = Clock.stamp (Clock.elapsed (reading (), self.router.epoch))
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
head.write (scratch, 0, fields)
|
|
643
|
+
|
|
644
|
+
local ends = def.built.write (scratch, head.bytes, value)
|
|
645
|
+
if head.trailer > 0 then
|
|
646
|
+
Frames.more (scratch, ends, false)
|
|
647
|
+
ends += head.trailer
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
local side = def.built.sidecar
|
|
651
|
+
local held = if side.bears then side.refs else nil
|
|
652
|
+
local loose = def.delivery ~= ORDERED
|
|
653
|
+
local class = if loose then Blocks.LOOSE else Blocks.PACKET
|
|
654
|
+
local block = Blocks.sealed (scratch, ends, class, Link.next (link), held)
|
|
655
|
+
|
|
656
|
+
table.clear (side.refs)
|
|
657
|
+
table.clear (side.slotOf)
|
|
658
|
+
|
|
659
|
+
Session.queued (self, def.name)
|
|
660
|
+
|
|
661
|
+
if to == nil then
|
|
662
|
+
if loose then
|
|
663
|
+
Outbox.loose (link, block)
|
|
664
|
+
else
|
|
665
|
+
Outbox.shared (link, block)
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
Tally.sent (self.counts, def.name, block.len)
|
|
669
|
+
return
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
local audience = link.audience
|
|
673
|
+
Recipients.resolve (audience, to, link.ready)
|
|
674
|
+
|
|
675
|
+
local reach = #audience
|
|
676
|
+
if loose then
|
|
677
|
+
Outbox.looseTo (link, audience, reach, block)
|
|
678
|
+
else
|
|
679
|
+
Outbox.targeted (link, audience, reach, block)
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
-- A fire at nobody counted, and its bytes not.
|
|
683
|
+
Tally.sent (self.counts, def.name, if reach > 0 then block.len else 0)
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
-- One question asked, and the answer put where whoever asked it will be told.
|
|
687
|
+
function Session.ask (
|
|
688
|
+
self: Self,
|
|
689
|
+
def: Compile.Query,
|
|
690
|
+
to: Player?,
|
|
691
|
+
value: any,
|
|
692
|
+
timeout: number?,
|
|
693
|
+
complete: Types.Outcome<any>
|
|
694
|
+
)
|
|
695
|
+
local id = self.pending:open (reading (), def.name, to, timeout, complete)
|
|
696
|
+
|
|
697
|
+
local link = self.link
|
|
698
|
+
local scratch = link.scratch
|
|
699
|
+
local head = def.asks
|
|
700
|
+
|
|
701
|
+
local fields = self.fields
|
|
702
|
+
fields[WHOSE] = def.id
|
|
703
|
+
fields[WHOSE + 1] = id
|
|
704
|
+
|
|
705
|
+
head.write (scratch, 0, fields)
|
|
706
|
+
|
|
707
|
+
local ends = def.request.write (scratch, head.bytes, value)
|
|
708
|
+
local side = def.request.sidecar
|
|
709
|
+
local held = if side.bears then side.refs else nil
|
|
710
|
+
local block = Blocks.sealed (scratch, ends, Blocks.REQUEST, Link.next (link), held)
|
|
711
|
+
Tally.sent (self.counts, def.name, block.len)
|
|
712
|
+
|
|
713
|
+
table.clear (side.refs)
|
|
714
|
+
table.clear (side.slotOf)
|
|
715
|
+
|
|
716
|
+
Session.queued (self, def.name)
|
|
717
|
+
|
|
718
|
+
if to == nil then
|
|
719
|
+
Outbox.shared (link, block)
|
|
720
|
+
return
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
local audience = link.audience
|
|
724
|
+
audience[1] = to
|
|
725
|
+
Outbox.targeted (link, audience, 1, block)
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
-- What this namespace has done, as the reading two of which subtract into a rate.
|
|
729
|
+
function Session.stats (self: Self): Types.Stats return Tally.reading (self.counts) end
|
|
730
|
+
|
|
731
|
+
return table.freeze (Session)
|