@axpecter/lync 2.3.3 → 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 -317
  77. package/src/api/Query.luau +0 -338
  78. package/src/api/Scope.luau +0 -90
  79. package/src/api/Signal.luau +0 -193
  80. package/src/codec/Base.luau +0 -136
  81. package/src/codec/composite/Array.luau +0 -473
  82. package/src/codec/composite/Map.luau +0 -485
  83. package/src/codec/composite/Optional.luau +0 -54
  84. package/src/codec/composite/Shared.luau +0 -311
  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 -160
  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 -484
  120. package/src/internal/Middleware.luau +0 -100
  121. package/src/internal/Pool.luau +0 -66
  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 -233
  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,126 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- Who sees a partition, and what has changed about that since the last flush.
4
+
5
+ local Recipients = require (script.Parent.Parent.channel.Recipients)
6
+ local Types = require (script.Parent.Parent.Types)
7
+
8
+ local Audience = {}
9
+
10
+ -- Constants -----------------------------------------------------------
11
+
12
+ -- What a player seeded into a key is marked with, which is a flush no diff ever runs under.
13
+ const BEFORE = 0
14
+
15
+ -- Types ---------------------------------------------------------------
16
+
17
+ -- Who held a key at its last diff, under the number of the diff that put them there.
18
+ export type Seen = { [Player]: number }
19
+
20
+ -- One set's audiences: who each key is for, and who was holding it last time anyone asked.
21
+ export type Audience = {
22
+ read of: { [any]: Types.Recipient },
23
+ read held: { [any]: Seen },
24
+ read wide: { [any]: boolean },
25
+ read moved: { [any]: boolean },
26
+ read now: { [any]: { Player } },
27
+ read gained: { Player },
28
+ read lost: { Player },
29
+ at: number,
30
+ }
31
+
32
+ -- Public --------------------------------------------------------------
33
+
34
+ function Audience.new (): Audience
35
+ return {
36
+ of = {},
37
+ held = {},
38
+ wide = {},
39
+ moved = {},
40
+ now = {},
41
+ gained = {},
42
+ lost = {},
43
+ at = BEFORE,
44
+ }
45
+ end
46
+
47
+ -- A key's viewers, replacing whoever it had.
48
+ function Audience.assign (of: Audience, key: any, to: Types.Recipient)
49
+ of.of[key] = to
50
+ of.moved[key] = true
51
+ end
52
+
53
+ -- Whether a key goes to everybody, which is what puts its sections on the shared list.
54
+ function Audience.everyone (of: Audience, key: any): boolean
55
+ local to = of.of[key]
56
+ return to == nil or to == Recipients.ALL
57
+ end
58
+
59
+ -- Who holds a key, as the diff that ran this flush worked it out.
60
+ function Audience.members (of: Audience, key: any): { Player }
61
+ local now = of.now[key]
62
+ if now ~= nil then return now end
63
+
64
+ now = {}
65
+ of.now[key] = now
66
+ return now
67
+ end
68
+
69
+ -- Who holds a key now, and who gained or lost it since the last time this asked.
70
+ function Audience.diff (of: Audience, key: any, ready: Recipients.Ready)
71
+ table.clear (of.gained)
72
+ table.clear (of.lost)
73
+
74
+ local now = of.now[key]
75
+ if now == nil then
76
+ now = {}
77
+ of.now[key] = now
78
+ end
79
+
80
+ local to = of.of[key]
81
+ local open = to == nil or to == Recipients.ALL
82
+
83
+ if open and of.moved[key] ~= true then
84
+ table.clear (now)
85
+ return
86
+ end
87
+
88
+ local held = of.held[key]
89
+ if held == nil then
90
+ held = {}
91
+ of.held[key] = held
92
+ end
93
+
94
+ if of.wide[key] ~= false then
95
+ for player in ready do
96
+ held[player] = BEFORE
97
+ end
98
+ end
99
+
100
+ of.moved[key] = nil
101
+ of.wide[key] = open
102
+
103
+ local at = of.at + 1
104
+ of.at = at
105
+
106
+ Recipients.resolve (now, if open then Recipients.ALL else to :: Types.Recipient, ready)
107
+
108
+ for _, player in now do
109
+ if held[player] == nil then table.insert (of.gained, player) end
110
+ held[player] = at
111
+ end
112
+
113
+ -- Who stopped holding this key, which is who is owed a removal for everything under it.
114
+ for player, when in held do
115
+ if when == at then continue end
116
+
117
+ held[player] = nil
118
+ if ready[player] == nil then continue end
119
+
120
+ table.insert (of.lost, player)
121
+ end
122
+
123
+ if open then table.clear (held) end
124
+ end
125
+
126
+ return table.freeze (Audience)
@@ -0,0 +1,218 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- What a client is owed of a partition it has only just been given, and what a flush pays of it.
4
+
5
+ local Plan = require (script.Parent.Plan)
6
+ local Sections = require (script.Parent.Sections)
7
+ local Store = require (script.Parent.Store)
8
+
9
+ local Catchup = {}
10
+
11
+ -- Types ---------------------------------------------------------------
12
+
13
+ -- One client's backlog on one partition: the slots it is owed, in the order they will go.
14
+ export type Pending = {
15
+ read slots: { number },
16
+ read live: { [number]: boolean },
17
+ at: number,
18
+ count: number,
19
+ }
20
+
21
+ -- Every backlog a namespace is carrying, and how many of them are not yet empty.
22
+ export type Catchup = {
23
+ read owed: { [Player]: { [any]: Pending } },
24
+ any: number,
25
+ }
26
+
27
+ -- Private -------------------------------------------------------------
28
+
29
+ -- A partition's held slots, ascending, which is the whole of what a client meeting it is owed.
30
+ local function holding (store: Store.Store): { number }
31
+ local ids = store.ids
32
+ local slots = {}
33
+ local count = 0
34
+
35
+ for slot = 1, store.high do
36
+ if ids[slot] == nil then continue end
37
+
38
+ count += 1
39
+ slots[count] = slot
40
+ end
41
+
42
+ return slots
43
+ end
44
+
45
+ -- A backlog finished with, taken off its client and off the count the reserve is owed under.
46
+ local function finished (of: Catchup, owed: { [any]: Pending }, who: Player, key: any)
47
+ owed[key] = nil
48
+ of.any -= 1
49
+
50
+ if next (owed) == nil then of.owed[who] = nil end
51
+ end
52
+
53
+ -- Public --------------------------------------------------------------
54
+
55
+ -- Every backlog a namespace is carrying, empty, which a session holds one of while it is open.
56
+ function Catchup.new (): Catchup return { owed = {}, any = 0 } end
57
+
58
+ -- The bytes a flush holds back from the shared plan while anything is catching up.
59
+ function Catchup.reserve (over: { Sections.Layout }): number
60
+ local most = 0
61
+
62
+ for _, layout in over do
63
+ local one = layout.opening + layout.entry
64
+ if one > most then most = one end
65
+ end
66
+
67
+ return most
68
+ end
69
+
70
+ -- Players that have just been given a key, each seeded with everything the partition holds.
71
+ function Catchup.gained (of: Catchup, key: any, players: { Player }, store: Store.Store)
72
+ if #players == 0 then return end
73
+
74
+ local slots = holding (store)
75
+ local total = #slots
76
+ if total == 0 then return end
77
+
78
+ for _, who in players do
79
+ local owed = of.owed[who]
80
+ if owed ~= nil and owed[key] ~= nil then continue end
81
+
82
+ if owed == nil then
83
+ owed = {}
84
+ of.owed[who] = owed
85
+ end
86
+
87
+ local live = {}
88
+ for _, slot in slots do
89
+ live[slot] = true
90
+ end
91
+
92
+ owed[key] = { slots = slots, live = live, at = 1, count = total }
93
+ of.any += 1
94
+ end
95
+ end
96
+
97
+ -- Slots a section has just carried to everybody, taken off every backlog that was owed them.
98
+ function Catchup.retired (of: Catchup, key: any, slots: { number }, from: number, count: number)
99
+ if of.any == 0 then return end
100
+
101
+ for who, owed in of.owed do
102
+ local pending = owed[key]
103
+ if pending == nil then continue end
104
+
105
+ local live = pending.live
106
+ local left = pending.count
107
+
108
+ for index = from, from + count - 1 do
109
+ local slot = slots[index]
110
+ if live[slot] == nil then continue end
111
+
112
+ live[slot] = nil
113
+ left -= 1
114
+ end
115
+
116
+ pending.count = left
117
+ if left == 0 then finished (of, owed, who, key) end
118
+ end
119
+ end
120
+
121
+ -- A partition emptied, which takes every backlog on it away with it.
122
+ function Catchup.cleared (of: Catchup, key: any)
123
+ if of.any == 0 then return end
124
+
125
+ for who, owed in of.owed do
126
+ if owed[key] ~= nil then finished (of, owed, who, key) end
127
+ end
128
+ end
129
+
130
+ -- Players that have just had a key taken away, which owes them nothing further of it.
131
+ function Catchup.lost (of: Catchup, key: any, players: { Player })
132
+ if of.any == 0 then return end
133
+
134
+ for _, who in players do
135
+ local owed = of.owed[who]
136
+ if owed ~= nil and owed[key] ~= nil then finished (of, owed, who, key) end
137
+ end
138
+ end
139
+
140
+ -- A link that has closed, which is every backlog its client was carrying and no other client's.
141
+ function Catchup.dropped (of: Catchup, who: Player)
142
+ local owed = of.owed[who]
143
+ if owed == nil then return end
144
+
145
+ for _ in owed do
146
+ of.any -= 1
147
+ end
148
+
149
+ of.owed[who] = nil
150
+ end
151
+
152
+ -- One chunk of what a client is owed of a partition, written where it was handed.
153
+ function Catchup.next (
154
+ of: Catchup,
155
+ layout: Sections.Layout,
156
+ store: Store.Store,
157
+ who: Player,
158
+ key: any,
159
+ b: buffer,
160
+ pos: number,
161
+ room: number
162
+ ): number
163
+ local owed = of.owed[who]
164
+ if owed == nil then return pos end
165
+
166
+ local pending = owed[key]
167
+ if pending == nil then return pos end
168
+
169
+ local cut = Plan.keyframes (layout, room)
170
+
171
+ local frame = layout.frame
172
+ local slots = pending.slots
173
+ local live = pending.live
174
+ local ids = store.ids
175
+ local gens = store.gens
176
+
177
+ local total = #slots
178
+ local index = pending.at
179
+ local taken = 0
180
+ local gone = 0
181
+
182
+ while index <= total and taken < cut do
183
+ local slot = slots[index]
184
+ index += 1
185
+
186
+ if live[slot] == nil then continue end
187
+
188
+ --[[
189
+ A slot the store no longer holds, which is a record removed since the grant. `remove`
190
+ unbinds at the call and the slot retires when the removal is planned, so a backlog
191
+ outlives the record it names. Nobody is owed a keyframe for a record that is gone.
192
+ ]]
193
+ if ids[slot] == nil then
194
+ live[slot] = nil
195
+ gone += 1
196
+ continue
197
+ end
198
+
199
+ taken += 1
200
+ frame.slots[taken] = slot
201
+ frame.ids[taken] = ids[slot]
202
+ if gens ~= nil then frame.gens[taken] = gens[slot] end
203
+ live[slot] = nil
204
+ end
205
+
206
+ pending.at = index
207
+ pending.count -= taken + gone
208
+ if pending.count == 0 then finished (of, owed, who, key) end
209
+
210
+ if taken == 0 then return pos end
211
+
212
+ frame.key = key
213
+ frame.count = taken
214
+
215
+ return Sections.keyframe (layout, b, pos, store.columns)
216
+ end
217
+
218
+ return table.freeze (Catchup)
@@ -0,0 +1,43 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- One record's mutations across a flush: the net effect that ships, and the fields it named.
4
+
5
+ local Wide = require (script.Parent.Parent.kernel.Wide)
6
+
7
+ local Coalesce = {}
8
+
9
+ -- Types ---------------------------------------------------------------
10
+
11
+ --[[
12
+ What an entry has pending. `replace` is produced and never taken: an add onto a removal is a new
13
+ record under an old id, and a receiver has to be told the one it holds is gone before it is
14
+ handed another.
15
+ ]]
16
+ export type Kind = "add" | "update" | "remove" | "replace"
17
+
18
+ -- Public --------------------------------------------------------------
19
+
20
+ --[[
21
+ Nothing pending takes whatever came. An update never changes what an entry is, since a keyframe
22
+ already states every field and a delta is still a delta. An add can only follow a removal, which
23
+ is a replace. A remove ends the entry, and ends it as nothing where the add it cancels never
24
+ reached anybody.
25
+ ]]
26
+ function Coalesce.merge (was: Kind?, took: Kind): Kind?
27
+ if was == nil then return took end
28
+ if took == "update" then return was end
29
+ if took == "add" then return "replace" end
30
+ return if was == "add" then nil else "remove"
31
+ end
32
+
33
+ function Coalesce.touched (mask: Wide.Word, field: number): Wide.Word
34
+ return Wide.union (mask, Wide.bit (field))
35
+ end
36
+
37
+ -- Tested bit by bit and never read as a number, because the widest field is the sign.
38
+ function Coalesce.migrates (mask: Wide.Word, key: number?): boolean
39
+ if key == nil then return false end
40
+ return Wide.meets (mask, Wide.bit (key))
41
+ end
42
+
43
+ return table.freeze (Coalesce)
@@ -0,0 +1,177 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- Which fields a delta section carries, and which of them changed on every slot it lists.
4
+
5
+ local Bits = require (script.Parent.Parent.kernel.Bits)
6
+ local Bytes = require (script.Parent.Parent.wire.Bytes)
7
+ local Catalog = require (script.Parent.Parent.kernel.Catalog)
8
+ local Log = require (script.Parent.Parent.kernel.Log)
9
+ local Protocol = require (script.Parent.Parent.wire.Protocol)
10
+
11
+ local Fieldset = {}
12
+
13
+ -- Constants -----------------------------------------------------------
14
+
15
+ const LIST = Protocol.FIELDSET_LIST
16
+ const MASK = Protocol.FIELDSET_MASK
17
+
18
+ -- The two one-bit fields here, named apart because they say different things at different places.
19
+ const MODE = 1
20
+ const FLAG = 1
21
+
22
+ const BYTE = Protocol.BYTE_BITS
23
+
24
+ const NO_FIELD = "a fieldset naming a field this set has not got"
25
+ const NO_FIELDS = "a fieldset naming no fields at all"
26
+ const OUT_OF_ORDER = "a fieldset naming its fields out of order"
27
+ const PADDED = "a fieldset leaving something in the bits above it"
28
+
29
+ -- Types ---------------------------------------------------------------
30
+
31
+ -- The fields a delta selected, and how each of them is covered.
32
+ export type Chosen = {
33
+ mode: number,
34
+ count: number,
35
+ fields: { number },
36
+ full: { number },
37
+ }
38
+
39
+ export type Write = (b: buffer, at: number, of: Chosen) -> number
40
+ export type Read = (b: buffer, at: number, into: Chosen) -> number
41
+
42
+ -- One set's fieldset, compiled: the widths its two forms take and the pair that writes them.
43
+ export type Fieldset = {
44
+ read over: number,
45
+ read width: number,
46
+ read widest: number,
47
+ read write: Write,
48
+ read read: Read,
49
+ }
50
+
51
+ -- Private -------------------------------------------------------------
52
+
53
+ --[[
54
+ A section this endpoint could not have written, refused under the one code every such refusal
55
+ carries and the phrase the site that found it holds.
56
+ ]]
57
+ function Fieldset.broken (said: string): never
58
+ local facts: Log.Facts = { said = said }
59
+ return Log.reject (Catalog.SECTION, facts)
60
+ end
61
+
62
+ -- Public --------------------------------------------------------------
63
+
64
+ -- A set's fieldset, as the pair that lays one down and takes it back up.
65
+ function Fieldset.of (over: number): Fieldset
66
+ local width = Bits.width (over - 1)
67
+ local listed = width * (over + 1)
68
+
69
+ local function write (b: buffer, at: number, of: Chosen): number
70
+ local count = of.count
71
+ local mode = if width * (count + 1) <= over then LIST else MASK
72
+ of.mode = mode
73
+
74
+ local held = Bytes.put (b, at * BYTE, MODE, mode)
75
+
76
+ if mode == LIST then
77
+ --[[
78
+ One less than the count, because a delta selecting nothing is not a delta. That is
79
+ what lets a set of one field spend nothing at all on its fieldset past the mode bit:
80
+ the count is a ranged int over one possibility and so is the field index, and both
81
+ are no bits.
82
+ ]]
83
+ held = Bytes.put (b, held, width, count - 1)
84
+ for index = 1, count do
85
+ held = Bytes.put (b, held, width, of.fields[index] - 1)
86
+ end
87
+ else
88
+ --[[
89
+ The cursor is held to the count, because what sits past it is the section before
90
+ this one's. A leftover equal to the field being asked about would set a bit for a
91
+ field this delta does not carry, and the reader would then read every field after it
92
+ at the wrong offset.
93
+ ]]
94
+ local reached = 1
95
+ for field = 1, over do
96
+ local on = if reached <= count and of.fields[reached] == field then 1 else 0
97
+ held = Bytes.put (b, held, FLAG, on)
98
+ reached += on
99
+ end
100
+ end
101
+
102
+ --[[
103
+ The coverage flags ride the same bit run rather than sitting beside the values they
104
+ speak for. Every boundary in a section is on a byte, so a flag there would cost a byte
105
+ per selected field where this costs a bit, and nothing else moves: a reader knows how
106
+ many follow the moment it has read the fieldset.
107
+ ]]
108
+ for index = 1, count do
109
+ held = Bytes.put (b, held, FLAG, of.full[index])
110
+ end
111
+
112
+ local byte = Bytes.aligned (b, held)
113
+ return byte
114
+ end
115
+
116
+ --[[
117
+ A reader turns away a fieldset an encoder could not have written, and stops there. Out of
118
+ order and empty are one comparison each and convict a repeat as well. A count past the
119
+ field count needs no test: that many ascending indices inside it do not exist.
120
+ ]]
121
+ local function read (b: buffer, at: number, into: Chosen): number
122
+ local mode, held = Bytes.take (b, at * BYTE, MODE)
123
+ into.mode = mode
124
+
125
+ local count = 0
126
+ if mode == LIST then
127
+ count, held = Bytes.take (b, held, width)
128
+ count += 1
129
+
130
+ local last = 0
131
+ for index = 1, count do
132
+ local one
133
+ one, held = Bytes.take (b, held, width)
134
+ one += 1
135
+
136
+ if one > over then Fieldset.broken (NO_FIELD) end
137
+ if one <= last then Fieldset.broken (OUT_OF_ORDER) end
138
+
139
+ into.fields[index] = one
140
+ last = one
141
+ end
142
+ else
143
+ for field = 1, over do
144
+ local on
145
+ on, held = Bytes.take (b, held, FLAG)
146
+ if on == 0 then continue end
147
+
148
+ count += 1
149
+ into.fields[count] = field
150
+ end
151
+ if count == 0 then Fieldset.broken (NO_FIELDS) end
152
+ end
153
+
154
+ into.count = count
155
+
156
+ for index = 1, count do
157
+ into.full[index], held = Bytes.take (b, held, FLAG)
158
+ end
159
+
160
+ local byte, clean = Bytes.aligned (b, held)
161
+ if not clean then Fieldset.broken (PADDED) end
162
+ return byte
163
+ end
164
+
165
+ return table.freeze ({
166
+ over = over,
167
+ width = width,
168
+ widest = math.ceil ((MODE + math.min (listed, over) + over) / BYTE),
169
+ write = write,
170
+ read = read,
171
+ })
172
+ end
173
+
174
+ -- The record a caller fills and a reader fills back, empty, which is one per set that has a delta.
175
+ function Fieldset.chosen (): Chosen return { mode = LIST, count = 0, fields = {}, full = {} } end
176
+
177
+ return table.freeze (Fieldset)