@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
package/state/Sync.luau
ADDED
|
@@ -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)
|