@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,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)