@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,762 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- One set, coordinated: what a caller does to it, and what a flush turns that into.
4
+
5
+ local Apply = require (script.Parent.Apply)
6
+ local Audience = require (script.Parent.Audience)
7
+ local Blocks = require (script.Parent.Parent.channel.Blocks)
8
+ local Catchup = require (script.Parent.Catchup)
9
+ local Coalesce = require (script.Parent.Coalesce)
10
+ local Compile = require (script.Parent.Parent.shape.Compile)
11
+ local Link = require (script.Parent.Parent.channel.Link)
12
+ local Listeners = require (script.Parent.Parent.kernel.Listeners)
13
+ local Log = require (script.Parent.Parent.kernel.Log)
14
+ local Newest = require (script.Parent.Newest)
15
+ local Outbox = require (script.Parent.Parent.channel.Outbox)
16
+ local Plan = require (script.Parent.Plan)
17
+ local Protocol = require (script.Parent.Parent.wire.Protocol)
18
+ local Recipients = require (script.Parent.Parent.channel.Recipients)
19
+ local Sections = require (script.Parent.Sections)
20
+ local Store = require (script.Parent.Store)
21
+ local Tally = require (script.Parent.Parent.kernel.Tally)
22
+ local Types = require (script.Parent.Parent.Types)
23
+ local Wide = require (script.Parent.Parent.kernel.Wide)
24
+
25
+ local Sync = {}
26
+
27
+ -- Constants -----------------------------------------------------------
28
+
29
+ const KEYFRAME = Protocol.SECTION_KEYFRAME
30
+ const DELTA = Protocol.SECTION_DELTA
31
+ const REMOVAL = Protocol.SECTION_REMOVAL
32
+ const CLEAR = Protocol.SECTION_CLEAR
33
+
34
+ -- A record that has moved no field yet, which is what a first update is taken over.
35
+ const NOTHING = Wide.NONE
36
+
37
+ -- The partition an unkeyed set is the whole of.
38
+ const WHOLE = table.freeze ({})
39
+
40
+ -- What a caller writes into an update to empty a field, which nothing else can mean.
41
+ const CLEARED =
42
+ setmetatable ({}, table.freeze ({ __tostring = function (): string return "Lync.none" end }))
43
+
44
+ -- Types ---------------------------------------------------------------
45
+
46
+ -- One partition: what it holds, what it owes the wire, and where its newest streams have reached.
47
+ export type Partition = {
48
+ read key: any,
49
+ read store: Store.Store,
50
+ read dirt: Plan.Dirt,
51
+ read streams: Newest.Streams,
52
+ }
53
+
54
+ -- One set, as everything that has to agree about it.
55
+ export type Sync = {
56
+ read def: Compile.Set,
57
+ read layout: Sections.Layout,
58
+ read plan: Plan.Plan,
59
+ read lane: Newest.Lane,
60
+ read seen: Audience.Audience,
61
+ read owed: Catchup.Catchup,
62
+ read heard: Apply.Heard,
63
+ read added: Listeners.List<number, any>,
64
+ read changed: Listeners.List<number, any, any>,
65
+ read removed: Listeners.List<number, Types.Cause>,
66
+ read parts: { [any]: Partition },
67
+ read order: { any },
68
+ read partOf: { [number]: any },
69
+ read row: { any },
70
+ read one: { Player },
71
+ at: number,
72
+ }
73
+
74
+ -- Private -------------------------------------------------------------
75
+
76
+ -- What a partition is found under, which is a table key and so is never nothing.
77
+ local function under (key: any): any return if key == nil then WHOLE else key end
78
+
79
+ local function refuse (of: Sync, said: string, id: number): never
80
+ return Log.error (`{of.def.name} {said} record {id}`, { definition = of.def.name })
81
+ end
82
+
83
+ -- An id a record can be held under, which is any whole number a double carries exactly.
84
+ local function identified (of: Sync, id: number)
85
+ if id ~= id or id % 1 ~= 0 or math.abs (id) >= Protocol.EXACT then
86
+ refuse (of, "cannot hold a record under", id)
87
+ end
88
+ end
89
+
90
+ -- The partition a key names, made where this is the first record to name it.
91
+ local function partition (of: Sync, key: any): Partition
92
+ local at = under (key)
93
+ local held = of.parts[at]
94
+ if held ~= nil then return held end
95
+
96
+ local made: Partition = {
97
+ key = key,
98
+ store = Store.new (of.def),
99
+ dirt = Plan.dirt (),
100
+ streams = Newest.streams (of.lane),
101
+ }
102
+
103
+ of.parts[at] = made
104
+ table.insert (of.order, at)
105
+ return made
106
+ end
107
+
108
+ -- Every field of a record into the store, which is the whole of what a bind leaves to say.
109
+ local function written (of: Sync, store: Store.Store, slot: number, record: any)
110
+ local names = of.def.fieldNames
111
+ for field = 1, of.def.fieldCount do
112
+ Store.write (store, slot, field, record[names[field] ])
113
+ end
114
+ end
115
+
116
+ -- A record marked as owing whatever it now owes, which is what the net of a flush is taken over.
117
+ local function marked (part: Partition, id: number, took: Coalesce.Kind)
118
+ local dirt = part.dirt
119
+ dirt.ops[id] = Coalesce.merge (dirt.ops[id], took)
120
+ end
121
+
122
+ -- A record taken out of a partition, and what that leaves the wire owing.
123
+ local function released (part: Partition, id: number, slot: number)
124
+ local dirt = part.dirt
125
+
126
+ dirt.masks[id] = nil
127
+ dirt.ops[id] = Coalesce.merge (dirt.ops[id], "remove")
128
+
129
+ if dirt.ops[id] == nil then
130
+ Store.spare (part.store, slot)
131
+ return
132
+ end
133
+
134
+ dirt.freed[slot] = id
135
+ end
136
+
137
+ -- One record out of the partition its key used to name, and into the one it names now.
138
+ local function migrated (of: Sync, from: Partition, id: number, slot: number)
139
+ local row = of.row
140
+ local columns = from.store.columns
141
+
142
+ for field = 1, of.def.fieldCount do
143
+ row[field] = columns[field][slot]
144
+ end
145
+
146
+ Store.unbind (from.store, id)
147
+ released (from, id, slot)
148
+
149
+ local into = partition (of, row[of.def.keyIndex :: number])
150
+ local held = Store.bind (into.store, id)
151
+
152
+ for field = 1, of.def.fieldCount do
153
+ Store.write (into.store, held, field, row[field])
154
+ end
155
+
156
+ marked (into, id, "add")
157
+ of.partOf[id] = under (into.key)
158
+ end
159
+
160
+ -- Ids a removal or a clear is about to take away, read while they can still be found.
161
+ local function losing (of: Sync, part: Partition, kind: number)
162
+ if kind == CLEAR then
163
+ for id in part.store.slotOf do
164
+ of.partOf[id] = nil
165
+ end
166
+ return
167
+ end
168
+
169
+ if kind ~= REMOVAL then return end
170
+
171
+ local frame = of.layout.frame
172
+ local ids = part.store.ids
173
+
174
+ for index = 1, frame.count do
175
+ local id = ids[frame.slots[index] ]
176
+ if id ~= nil then of.partOf[id] = nil end
177
+ end
178
+ end
179
+
180
+ -- Ids a keyframe has just bound, which is the only section that puts a record anywhere.
181
+ local function gaining (of: Sync, part: Partition, kind: number, at: any)
182
+ if kind ~= KEYFRAME then return end
183
+
184
+ local frame = of.layout.frame
185
+ for index = 1, frame.count do
186
+ of.partOf[frame.ids[index] ] = at
187
+ end
188
+ end
189
+
190
+ -- One block over the section just written, with whatever instances it named beside it.
191
+ local function sealed (of: Sync, link: Link.Link, len: number, class: number): Blocks.Block
192
+ local side = of.def.site.sidecar
193
+
194
+ --[[
195
+ One where the schema can name an instance and nothing where it cannot, since that is the
196
+ fact both machines pair a frame's sidecar by. Whether this payload named one is not it.
197
+ ]]
198
+ local held = if side.bears then side.refs else nil
199
+ local made = Blocks.sealed (link.scratch, len, class, Link.next (link), held)
200
+ Tally.sent (link.tally, of.def.name, len)
201
+
202
+ table.clear (side.refs)
203
+ table.clear (side.slotOf)
204
+ return made
205
+ end
206
+
207
+ -- One section into the queue, everybody's or its audience's.
208
+ local function queued (of: Sync, link: Link.Link, at: any, open: boolean, kind: number, len: number)
209
+ local block = sealed (of, link, len, if kind == DELTA then Blocks.DELTA else Blocks.ESTABLISH)
210
+
211
+ if open then
212
+ Outbox.shared (link, block)
213
+ return
214
+ end
215
+
216
+ local now = Audience.members (of.seen, at)
217
+ Outbox.targeted (link, now, #now, block)
218
+ end
219
+
220
+ -- What a shared section cost every client that is going to receive it.
221
+ local function billed (
222
+ of: Sync,
223
+ purse: { [Player]: number },
224
+ at: any,
225
+ open: boolean,
226
+ ready: Recipients.Ready,
227
+ bytes: number
228
+ )
229
+ if open then
230
+ for who in ready do
231
+ purse[who] = (purse[who] or 0) + bytes
232
+ end
233
+ return
234
+ end
235
+
236
+ for _, who in Audience.members (of.seen, at) do
237
+ purse[who] = (purse[who] or 0) + bytes
238
+ end
239
+ end
240
+
241
+ -- Who sees this partition now, and what has changed about that since the last flush.
242
+ local function diffed (of: Sync, part: Partition, ready: Recipients.Ready): boolean
243
+ local at = under (part.key)
244
+
245
+ Audience.diff (of.seen, at, ready)
246
+
247
+ local gained, lost = of.seen.gained, of.seen.lost
248
+ if #gained > 0 then Catchup.gained (of.owed, at, gained, part.store) end
249
+ if #lost > 0 then Catchup.lost (of.owed, at, lost) end
250
+
251
+ return Audience.everyone (of.seen, at)
252
+ end
253
+
254
+ -- Every record a partition holds, gone, for the clients that stopped being able to see it.
255
+ local function withdrawn (of: Sync, link: Link.Link, part: Partition, lost: { Player })
256
+ local store = part.store
257
+ local frame = of.layout.frame
258
+ local layout = of.layout
259
+
260
+ -- A partition emptied this flush takes its losers with it, and says so in one section.
261
+ if part.dirt.cleared then
262
+ frame.key = part.key
263
+
264
+ local ends = Sections.clear (layout, link.scratch, 0)
265
+ Outbox.targeted (link, lost, #lost, sealed (of, link, ends, Blocks.ESTABLISH))
266
+ return
267
+ end
268
+
269
+ --[[
270
+ Every slot this client was actually holding, which is every one that shipped. A record added
271
+ in the flush that took the key away has not been sent yet, so a removal for it would be a
272
+ removal of something the client never had, and the pending operation says so.
273
+ ]]
274
+ local ops = part.dirt.ops
275
+ local freed = part.dirt.freed
276
+ local held = 0
277
+
278
+ for slot = 1, store.high do
279
+ local id = store.ids[slot]
280
+ if id == nil then id = freed[slot] end
281
+ if id == nil or ops[id] == "add" then continue end
282
+
283
+ held += 1
284
+ frame.slots[held] = slot
285
+ end
286
+ if held == 0 then return end
287
+
288
+ frame.key = part.key
289
+
290
+ local reach = Plan.keyframes (layout, Protocol.SECTION_BYTES)
291
+ local from = 1
292
+
293
+ while from <= held do
294
+ local count = math.min (reach, held - from + 1)
295
+ for index = 1, count do
296
+ frame.slots[index] = frame.slots[from + index - 1]
297
+ end
298
+
299
+ frame.count = count
300
+ local ends = Sections.removal (layout, link.scratch, 0)
301
+
302
+ Outbox.targeted (link, lost, #lost, sealed (of, link, ends, Blocks.ESTABLISH))
303
+ from += count
304
+ end
305
+ end
306
+
307
+ -- Every partition's audience, worked out before the flush decides anything about itself.
308
+ function Sync.resolved (of: Sync, link: Link.Link, ready: Recipients.Ready)
309
+ for _, at in of.order do
310
+ local part = of.parts[at]
311
+ diffed (of, part, ready)
312
+
313
+ local lost = of.seen.lost
314
+ if #lost > 0 then withdrawn (of, link, part, lost) end
315
+ end
316
+ end
317
+
318
+ -- One partition's reliable sections, planned, queued, applied here and retired.
319
+ local function planned (
320
+ of: Sync,
321
+ link: Link.Link,
322
+ budget: Plan.Budget,
323
+ purse: { [Player]: number }?,
324
+ part: Partition,
325
+ open: boolean,
326
+ ready: Recipients.Ready,
327
+ opens: (Plan.Plan, Plan.Budget, Partition) -> boolean
328
+ )
329
+ if not opens (of.plan, budget, part) then return end
330
+
331
+ local frame = of.layout.frame
332
+ local at = under (part.key)
333
+ local scratch = link.scratch
334
+
335
+ while true do
336
+ local kind, ends = Plan.next (of.plan, budget, part, scratch, 0)
337
+ if kind == nil then break end
338
+
339
+ if kind == KEYFRAME or kind == REMOVAL then
340
+ Catchup.retired (of.owed, at, frame.slots, 1, frame.count)
341
+ elseif kind == CLEAR then
342
+ Catchup.cleared (of.owed, at)
343
+ end
344
+
345
+ if kind ~= CLEAR then Apply.section (part.store, of.layout, kind, of.heard) end
346
+
347
+ if kind == REMOVAL then
348
+ for index = 1, frame.count do
349
+ Store.spare (part.store, frame.slots[index])
350
+ end
351
+ end
352
+
353
+ queued (of, link, at, open, kind, ends)
354
+ if kind == KEYFRAME then
355
+ Newest.established (of.lane, part.store, frame.slots, frame.count)
356
+ end
357
+
358
+ if purse ~= nil then billed (of, purse, at, open, ready, ends) end
359
+ end
360
+ end
361
+
362
+ -- One partition's newest fields, each as much of its stream as one fire carries.
363
+ local function streamed (of: Sync, link: Link.Link, part: Partition, open: boolean, now: number)
364
+ local lane = of.lane
365
+ local reach = #lane.fields
366
+ if reach == 0 then return end
367
+
368
+ local streams = part.streams
369
+ local store = part.store
370
+ local scratch = link.scratch
371
+
372
+ lane.key = part.key
373
+
374
+ for at = 1, reach do
375
+ local count = Newest.changed (lane, streams, store, at, now)
376
+ if count == 0 then continue end
377
+
378
+ local ends = Newest.section (lane, streams, store, at, scratch, 0, now, count)
379
+ local block = sealed (of, link, ends, Blocks.LOOSE)
380
+
381
+ if open then
382
+ Outbox.loose (link, block)
383
+ continue
384
+ end
385
+
386
+ local held = Audience.members (of.seen, under (part.key))
387
+ Outbox.looseTo (link, held, #held, block)
388
+ end
389
+ end
390
+
391
+ -- The record an id names, read the way a caller reads one, or nothing where there is none.
392
+ local function rowed (of: Sync, id: number): any
393
+ local at = of.partOf[id]
394
+ if at == nil then return nil end
395
+
396
+ local part = of.parts[at]
397
+ if part == nil then return nil end
398
+
399
+ return part.store.rows[id]
400
+ end
401
+
402
+ -- Public --------------------------------------------------------------
403
+
404
+ Sync.NONE = table.freeze (CLEARED)
405
+
406
+ -- One set, holding nothing.
407
+ function Sync.new (def: Compile.Set): Sync
408
+ local layout = Sections.of (def)
409
+
410
+ return {
411
+ def = def,
412
+ layout = layout,
413
+ plan = Plan.of (layout),
414
+ lane = Newest.of (layout),
415
+ seen = Audience.new (),
416
+ owed = Catchup.new (),
417
+ heard = Apply.heard (),
418
+ added = Listeners.list (Log.contained :: Listeners.Run<number, any>),
419
+ changed = Listeners.list (Log.contained :: Listeners.Run<number, any, any>),
420
+ removed = Listeners.list (Log.contained :: Listeners.Run<number, Types.Cause>),
421
+ parts = {},
422
+ order = {},
423
+ partOf = {},
424
+ row = table.create (def.fieldCount),
425
+ one = table.create (1) :: { Player },
426
+ at = 1,
427
+ }
428
+ end
429
+
430
+ -- A record under an id that nothing else holds.
431
+ function Sync.add (of: Sync, id: number, record: any)
432
+ identified (of, id)
433
+ if of.partOf[id] ~= nil then refuse (of, "already holds", id) end
434
+
435
+ local field = of.def.keyField
436
+ local part = partition (of, if field == nil then nil else record[field])
437
+ local slot = Store.bind (part.store, id)
438
+
439
+ written (of, part.store, slot, record)
440
+ marked (part, id, "add")
441
+ of.partOf[id] = under (part.key)
442
+ end
443
+
444
+ -- Whether two values a field may hold are the same value.
445
+ local function alike (was: any, now: any): boolean
446
+ if was == now then return true end
447
+ if type (was) ~= "table" or type (now) ~= "table" then return false end
448
+ if getmetatable (was :: any) ~= nil or getmetatable (now :: any) ~= nil then return false end
449
+
450
+ for name, held in was do
451
+ if not alike (held, (now :: any)[name]) then return false end
452
+ end
453
+
454
+ for name in now do
455
+ if (was :: any)[name] == nil then return false end
456
+ end
457
+
458
+ return true
459
+ end
460
+
461
+ -- Some fields of a record that is already held.
462
+ function Sync.update (of: Sync, id: number, fields: any)
463
+ local at = of.partOf[id]
464
+ if at == nil then refuse (of, "does not hold", id) end
465
+
466
+ local part = of.parts[at]
467
+ local def = of.def
468
+ local place = def.fieldIndex
469
+ local store = part.store
470
+ local slot = store.slotOf[id]
471
+
472
+ local dirt = part.dirt
473
+ local mask = dirt.masks[id] or NOTHING
474
+
475
+ local moved = false
476
+ for name, value in fields do
477
+ local field = place[name]
478
+ if field == nil then
479
+ Log.error (`{def.name} has no field named {name}`, { definition = def.name })
480
+ end
481
+
482
+ -- The one value that means a field has none, which is a table nothing else can be.
483
+ if value == Sync.NONE and not def.optionalFields[field] then
484
+ Log.error (`{def.name}.{name} is not optional and cannot be cleared`, {
485
+ definition = def.name,
486
+ })
487
+ end
488
+
489
+ local was = store.columns[field][slot]
490
+
491
+ -- A field that may only climb, held to it at the call that would have lowered it.
492
+ if def.climbingFields[field] and was ~= nil and value ~= Sync.NONE and value < was then
493
+ Log.error (
494
+ `{def.name}.{name} only climbs, and {value} is under {was}`,
495
+ { definition = def.name }
496
+ )
497
+ end
498
+
499
+ --[[
500
+ A write of what the field already holds is no write at all. Nothing is dirtied and
501
+ nothing is sent, which is what makes a settled stream cost nothing: a place writing
502
+ every record every frame from a source that stopped moving is the ordinary idle set.
503
+ ]]
504
+ local now = if value == Sync.NONE then nil else value
505
+ if alike (was, now) then continue end
506
+
507
+ Store.write (store, slot, field, now)
508
+
509
+ --[[
510
+ A newest field is written and not marked. The lane that carries one compares what the
511
+ column holds against what it last sent, so a mark here would be a second answer to a
512
+ question the store already answers, and it would put an absolute value in a reliable
513
+ delta, which is the one place it never goes.
514
+ ]]
515
+ if of.lane.place[field] == nil then
516
+ mask = Coalesce.touched (mask, field)
517
+ moved = true
518
+ end
519
+ end
520
+
521
+ -- A call that moved nothing but newest fields owes the reliable lane nothing at all.
522
+ if not moved then return end
523
+
524
+ dirt.masks[id] = mask
525
+ marked (part, id, "update")
526
+
527
+ if Coalesce.migrates (mask, def.keyIndex) then migrated (of, part, id, slot) end
528
+ end
529
+
530
+ -- A record taken away, which is a slot the wire still has to be told about.
531
+ function Sync.remove (of: Sync, id: number)
532
+ local at = of.partOf[id]
533
+ if at == nil then refuse (of, "does not hold", id) end
534
+
535
+ local part = of.parts[at]
536
+ local slot = Store.unbind (part.store, id) :: number
537
+
538
+ released (part, id, slot)
539
+ of.partOf[id] = nil
540
+ end
541
+
542
+ -- Every record of every partition, gone.
543
+ function Sync.clear (of: Sync)
544
+ for _, at in of.order do
545
+ local part = of.parts[at]
546
+ local dirt = part.dirt
547
+
548
+ Store.clear (part.store)
549
+ Newest.sweep (part.streams)
550
+ Catchup.cleared (of.owed, at)
551
+
552
+ table.clear (dirt.ops)
553
+ table.clear (dirt.masks)
554
+ table.clear (dirt.freed)
555
+ dirt.cleared = true
556
+ end
557
+
558
+ table.clear (of.partOf)
559
+ end
560
+
561
+ -- Who a key's records are for, replacing whoever it was for.
562
+ function Sync.audience (of: Sync, key: any, to: Types.Recipient)
563
+ partition (of, key)
564
+ Audience.assign (of.seen, under (key), to)
565
+ end
566
+
567
+ -- The live record under an id, or nothing where this set does not hold one.
568
+ function Sync.get (of: Sync, id: number): any?
569
+ local row = rowed (of, id)
570
+
571
+
572
+ return row
573
+ end
574
+
575
+ -- How many records the set holds, which is every partition's count and not one of them.
576
+ function Sync.size (of: Sync): number
577
+ local held = 0
578
+ for _, at in of.order do
579
+ held += of.parts[at].store.count
580
+ end
581
+ return held
582
+ end
583
+
584
+ -- Every record the set holds, whichever partition it is in, once each.
585
+ function Sync.walk (of: Sync): () -> (number?, any)
586
+ local at = 1
587
+ local id: number? = nil
588
+
589
+ return function (): (number?, any)
590
+ while true do
591
+ local key = of.order[at]
592
+ if key == nil then return nil end
593
+
594
+ local found, row = next (of.parts[key].store.rows, id)
595
+ if found ~= nil then
596
+ id = found
597
+ return found, row
598
+ end
599
+
600
+ at += 1
601
+ id = nil
602
+ end
603
+ end
604
+ end
605
+
606
+ --[[
607
+ A client that has just become ready, owed every record it can already see.
608
+
609
+ Apart from the audience diff, and for the reason a diff is kept apart at all: a diff answers
610
+ what changed, and a join changed nothing about who sees what.
611
+ ]]
612
+ function Sync.joined (of: Sync, who: Player, ready: Recipients.Ready)
613
+ local one = of.one
614
+ one[1] = who
615
+
616
+ for _, at in of.order do
617
+ local part = of.parts[at]
618
+ if part.store.count == 0 then continue end
619
+
620
+ local to = of.seen.of[at]
621
+ if to ~= nil and not Recipients.holds (to, who) then continue end
622
+
623
+ Catchup.gained (of.owed, at, one, part.store)
624
+ end
625
+ end
626
+
627
+ -- A link that has closed, which is every backlog its client was carrying and nothing else.
628
+ function Sync.left (of: Sync, who: Player) Catchup.dropped (of.owed, who) end
629
+
630
+ -- Whether anything at all is catching up, which is what the shared plan holds a reserve for.
631
+ function Sync.owing (of: Sync): boolean return of.owed.any > 0 end
632
+
633
+ --[[
634
+ Whether a partition is open to everyone, read rather than worked out, since `resolved` settled
635
+ it before the flush began.
636
+ ]]
637
+ local function sees (of: Sync, at: any): boolean return Audience.everyone (of.seen, under (at)) end
638
+
639
+ -- One flush of one set: every partition twice, in the order the rotation reached them.
640
+ function Sync.shared (
641
+ of: Sync,
642
+ link: Link.Link,
643
+ budget: Plan.Budget,
644
+ purse: { [Player]: number }?,
645
+ ready: Recipients.Ready,
646
+ now: number
647
+ )
648
+ local order = of.order
649
+ local reach = #order
650
+ if reach == 0 then return end
651
+
652
+ for step = 0, reach - 1 do
653
+ local key = order[(of.at + step - 1) % reach + 1]
654
+ planned (of, link, budget, purse, of.parts[key], sees (of, key), ready, Plan.dropping)
655
+ end
656
+
657
+ for step = 0, reach - 1 do
658
+ local key = order[(of.at + step - 1) % reach + 1]
659
+ local part = of.parts[key]
660
+ local open = sees (of, key)
661
+
662
+ planned (of, link, budget, purse, part, open, ready, Plan.making)
663
+ streamed (of, link, part, open, now)
664
+ end
665
+
666
+ of.at = of.at % reach + 1
667
+ end
668
+
669
+ -- One chunk of what a client is owed of one partition, queued for that client alone.
670
+ function Sync.behind (of: Sync, link: Link.Link, who: Player, room: number): number
671
+ if of.owed.any == 0 then return 0 end
672
+
673
+ local held = of.owed.owed[who]
674
+ if held == nil then return 0 end
675
+
676
+ local spent = 0
677
+ for _, at in of.order do
678
+ if held[at] == nil then continue end
679
+
680
+ local part = of.parts[at]
681
+
682
+ -- Chunks until the backlog is empty or the room is, rather than one and done.
683
+ while true do
684
+ local ends =
685
+ Catchup.next (of.owed, of.layout, part.store, who, at, link.scratch, 0, room)
686
+ if ends == 0 then break end
687
+
688
+ local one = of.one
689
+ one[1] = who
690
+ Outbox.targeted (link, one, 1, sealed (of, link, ends, Blocks.ESTABLISH))
691
+
692
+ room -= ends
693
+ spent += ends
694
+ end
695
+ end
696
+
697
+ return spent
698
+ end
699
+
700
+ -- One reliable section off the wire, into the partition it is about.
701
+ function Sync.taken (of: Sync, b: buffer, pos: number, ctx: Types.ValidateContext): number
702
+ local layout = of.layout
703
+ local
704
+ _, key =Sections.about (layout, b, pos, ctx)
705
+ local part = partition (of, key)
706
+
707
+ local kind, ends = Sections.taken (layout, b, pos, part.store.prev, ctx)
708
+
709
+ losing (of, part, kind)
710
+ Apply.section (part.store, layout, kind, of.heard)
711
+ gaining (of, part, kind, under (key))
712
+
713
+ return ends
714
+ end
715
+
716
+ -- One newest section off the wire, into the partition it is about.
717
+ function Sync.stream (of: Sync, b: buffer, pos: number, ctx: Types.ValidateContext): number
718
+ local lane = of.lane
719
+ local at, seq, count, ends = Newest.taken (lane, b, pos, ctx)
720
+ local part = partition (of, lane.key)
721
+
722
+ Newest.apply (lane, part.streams, part.store, of.heard, at, seq, count)
723
+ return ends
724
+ end
725
+
726
+ -- A caller told when a record arrives, moves or goes.
727
+ function Sync.onAdded (of: Sync, fn: (number, any) -> ()): Types.Connection
728
+ return Listeners.connect (of.added, fn) :: any
729
+ end
730
+
731
+ function Sync.onChanged (of: Sync, fn: (number, any, any?) -> ()): Types.Connection
732
+ of.heard.watching = true
733
+ return Listeners.connect (of.changed, fn) :: any
734
+ end
735
+
736
+ function Sync.onRemoved (of: Sync, fn: (number, Types.Cause) -> ()): Types.Connection
737
+ return Listeners.connect (of.removed, fn) :: any
738
+ end
739
+
740
+ -- The frame over: what its keyframes owe paid, what it did said, and what it did forgotten.
741
+ function Sync.settle (of: Sync)
742
+ local heard = of.heard
743
+ Apply.settle (heard)
744
+
745
+ local gone, added, changed = Apply.parted (heard)
746
+
747
+ for _, id in gone do
748
+ Listeners.fan (of.removed, id, heard.causes[id])
749
+ end
750
+
751
+ for _, id in added do
752
+ Listeners.fan (of.added, id, rowed (of, id))
753
+ end
754
+
755
+ for _, id in changed do
756
+ Listeners.fan (of.changed, id, rowed (of, id), heard.olds[id])
757
+ end
758
+
759
+ Apply.forget (heard)
760
+ end
761
+
762
+ return table.freeze (Sync)