@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,80 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- The one binary min-heap, over parallel key and value arrays.
4
+
5
+ local Heap = {}
6
+
7
+ -- Types ---------------------------------------------------------------
8
+
9
+ --[[
10
+ `count` is the truth, not `#keys`. A pop leaves the arrays at the size they reached, so a heap
11
+ draining at frame rate stops allocating and whatever sits past `count` is gone.
12
+ ]]
13
+ export type Heap = {
14
+ keys: { number },
15
+ values: { number },
16
+ count: number,
17
+ }
18
+
19
+ -- Public --------------------------------------------------------------
20
+
21
+ function Heap.new (): Heap return { keys = {}, values = {}, count = 0 } end
22
+
23
+ -- The parent moves down into the vacated place, so a climb of n levels costs n moves, not three.
24
+ function Heap.push (heap: Heap, key: number, value: number)
25
+ local keys, values = heap.keys, heap.values
26
+
27
+ local at = heap.count + 1
28
+ heap.count = at
29
+
30
+ while at > 1 do
31
+ local parent = at // 2
32
+ if keys[parent] <= key then break end
33
+
34
+ keys[at], values[at] = keys[parent], values[parent]
35
+ at = parent
36
+ end
37
+
38
+ keys[at], values[at] = key, value
39
+ end
40
+
41
+ function Heap.peek (heap: Heap): (number?, number?)
42
+ if heap.count == 0 then return nil, nil end
43
+ return heap.keys[1], heap.values[1]
44
+ end
45
+
46
+ --[[
47
+ Moving rather than swapping for the reason `push` gives climbing. The walk stops at the last
48
+ node with a child, which is half of what is left.
49
+ ]]
50
+ function Heap.pop (heap: Heap): (number?, number?)
51
+ local count = heap.count
52
+ if count == 0 then return nil, nil end
53
+
54
+ local keys, values = heap.keys, heap.values
55
+ local key, value = keys[1], values[1]
56
+
57
+ local size = count - 1
58
+ heap.count = size
59
+ if size == 0 then return key, value end
60
+
61
+ local moved, movedValue = keys[count], values[count]
62
+ local at = 1
63
+ local half = size // 2
64
+
65
+ while at <= half do
66
+ local child = at * 2
67
+ if child < size and keys[child + 1] < keys[child] then
68
+ child += 1
69
+ end
70
+ if keys[child] >= moved then break end
71
+
72
+ keys[at], values[at] = keys[child], values[child]
73
+ at = child
74
+ end
75
+
76
+ keys[at], values[at] = moved, movedValue
77
+ return key, value
78
+ end
79
+
80
+ return table.freeze (Heap)
@@ -0,0 +1,95 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- The connection a registration hands back, and the fan that reaches every one still live.
4
+
5
+ local Listeners = {}
6
+
7
+ -- Types ---------------------------------------------------------------
8
+
9
+ --[[
10
+ `connected` and `disconnect` are exactly what `Types.Connection` names, so a link is handed back
11
+ as the connection itself and the two cannot drift apart.
12
+ ]]
13
+ export type Link<A...> = {
14
+ connected: boolean,
15
+ disconnect: (self: any) -> (),
16
+ fn: (A...) -> (),
17
+ }
18
+
19
+ -- How a listener is reached, which is the one thing about a fan that is not this module's.
20
+ export type Run<A...> = (fn: (A...) -> (),A...) -> ()
21
+
22
+ export type List<A...> = {
23
+ links: { Link<A...> },
24
+ live: number,
25
+ fanning: number,
26
+ run: Run<A...>,
27
+ }
28
+
29
+ -- Private -------------------------------------------------------------
30
+
31
+ -- The links that are no longer connected, taken out in place, once nothing is walking them.
32
+ local function swept (of: List<...any>)
33
+ local links = of.links
34
+ local held = #links
35
+ if of.fanning > 0 or held == of.live then return end
36
+
37
+ local at = 1
38
+ for index = 1, held do
39
+ local link = links[index]
40
+ if link.connected then
41
+ links[at] = link
42
+ at += 1
43
+ end
44
+ end
45
+ for index = held, at, -1 do
46
+ links[index] = nil
47
+ end
48
+ end
49
+
50
+ -- Public --------------------------------------------------------------
51
+
52
+ function Listeners.list <A...>(run: Run<A...>): List<A...>
53
+ return { links = {}, live = 0, fanning = 0, run = run }
54
+ end
55
+
56
+ -- At the end of the order, which is the order a fan walks.
57
+ function Listeners.connect <A...>(of: List<A...>, fn: (A...) -> ()): Link<A...>
58
+ local link: Link<A...> = {
59
+ connected = true,
60
+ fn = fn,
61
+ disconnect = function (self: any)
62
+ if not self.connected then return end
63
+ self.connected = false
64
+
65
+ of.live -= 1
66
+ swept (of)
67
+ end,
68
+ }
69
+
70
+ table.insert (of.links, link)
71
+ of.live += 1
72
+ return link
73
+ end
74
+
75
+ --[[
76
+ Everything still listening, in the order it was registered, each on a thread of its own.
77
+
78
+ The count is read once before the walk, so a listener attached by a listener is reached by the
79
+ next fan and not this one. A walk over a list still growing would have no end it could name.
80
+ ]]
81
+ function Listeners.fan <A...>(of: List<A...>, ...: A...)
82
+ local links, run = of.links, of.run
83
+ local held = #links
84
+
85
+ of.fanning += 1
86
+ for index = 1, held do
87
+ local link = links[index]
88
+ if link.connected then run (link.fn, ...) end
89
+ end
90
+ of.fanning -= 1
91
+
92
+ swept (of)
93
+ end
94
+
95
+ return table.freeze (Listeners)
@@ -0,0 +1,197 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- The one record channel: everything the library says about itself, and who is listening.
4
+
5
+ local Catalog = require (script.Parent.Catalog)
6
+ local Listeners = require (script.Parent.Listeners)
7
+ local Threads = require (script.Parent.Threads)
8
+ local Types = require (script.Parent.Parent.Types)
9
+
10
+ local Log = {}
11
+
12
+ -- Constants -----------------------------------------------------------
13
+
14
+ -- Shared by every record whose site handed over nothing else, since one empty table is enough.
15
+ const NO_FACTS = table.freeze ({} :: Facts)
16
+
17
+ -- A name in braces, which is the whole of what a template can hold.
18
+ const HELD = "{(%w+)}"
19
+
20
+ -- Types ---------------------------------------------------------------
21
+
22
+ --[[
23
+ Every key reaches the record's `data` under its own name, so a site and a caller hold one
24
+ vocabulary and there is no mapping between them to keep in step.
25
+ ]]
26
+ export type Facts = { [string]: unknown }
27
+
28
+ -- A raise on its way to the boundary that catches it.
29
+ export type Rejection = {
30
+ read code: Catalog.Code,
31
+ read facts: Facts,
32
+ }
33
+
34
+ -- State ---------------------------------------------------------------
35
+
36
+ --[[
37
+ One list for the process, so a caller wires `onLog` once and hears everything. Two namespaces in
38
+ one place are not two things anybody watching logs subscribes to separately.
39
+ ]]
40
+ local records: Listeners.List<Types.LogKind, string, Types.LogData> = Listeners.list (Threads.spawn)
41
+
42
+ -- Private -------------------------------------------------------------
43
+
44
+ --[[
45
+ The position comes from whoever calls in, the useful line being a caller's declaration site and
46
+ never this one. Nothing is frozen, so a listener that keeps a record has to copy it.
47
+ ]]
48
+ local function said (kind: Types.LogKind, message: string, facts: Facts, file: string, line: number)
49
+ local data = table.clone (facts) :: any
50
+ data.file = file
51
+ data.line = line
52
+
53
+ Listeners.fan (records, kind, message, data :: Types.LogData)
54
+ end
55
+
56
+ --[[
57
+ Taken where the call raised, since a handler runs on the raising thread with the stack still
58
+ standing under it, which is the only moment a line inside a caller's listener can be read.
59
+ ]]
60
+ local function traced (thrown: any): string return debug.traceback (tostring (thrown), 2) end
61
+
62
+ -- Public --------------------------------------------------------------
63
+
64
+ --[[
65
+ A call run with its answer, and a raise turned into a record rather than the engine's report.
66
+ The trace is the message, the library having no idea what the caller was doing. The answer is
67
+ why this is not `contained`: nobody waits on a listener, and a responder's return is the
68
+ reply.
69
+ ]]
70
+ function Log.caught (call: (...any) -> ...any, ...: any): (boolean, any)
71
+ local ok, held = xpcall (call, traced, ...)
72
+ if ok then return true, held end
73
+
74
+ local file, line = debug.info (call, "sl")
75
+ said ("error", held, NO_FACTS, file, line)
76
+ return false, nil
77
+ end
78
+
79
+ --[[
80
+ A call on a thread of its own, with whatever it raises reported rather than propagated. Every
81
+ listener list is fanned through it, so a mistake in one cannot reach the caller who fired.
82
+ ]]
83
+ function Log.contained <A...>(call: (A...) -> (), ...: A...)
84
+ Threads.spawn (Log.caught :: any, call :: any, ...)
85
+ end
86
+
87
+ --[[
88
+ Something the library dropped and moved past. A caller reads these to learn what their schema or
89
+ somebody else's client is doing, so a warning is never a fault and never a throw to come.
90
+ ]]
91
+ function Log.warn (message: string, facts: Facts?)
92
+ local file, line = debug.info (2, "sl")
93
+ said ("warn", message, facts or NO_FACTS, file, line)
94
+ end
95
+
96
+ --[[
97
+ A fault the library contained, and then the throw. Both, since a caller watching the channel and
98
+ one reading a stack are two people and the record is all the first ever sees.
99
+ ]]
100
+ function Log.error (message: string, facts: Facts?): never
101
+ local file, line = debug.info (2, "sl")
102
+ said ("error", message, facts or NO_FACTS, file, line)
103
+
104
+ return error (message, 0)
105
+ end
106
+
107
+ -- Gated on a global a release build folds away, so a shipped place composes none of this.
108
+ function Log.debug (message: string, facts: Facts?)
109
+ do return end
110
+
111
+ local file, line = debug.info (2, "sl")
112
+ said ("debug", message, facts or NO_FACTS, file, line)
113
+ end
114
+
115
+ -- A caller turned away, raised where they asked and recorded nowhere.
116
+ function Log.refuse (message: string): never return error (message, 0) end
117
+
118
+ --[[
119
+ A rejection for the boundary above. It carries a code and facts and no line, composing one being
120
+ a walk of a template on a path a hostile client decides how often to reach.
121
+ ]]
122
+ function Log.reject (code: Catalog.Code, facts: Facts?): never
123
+ return error (table.freeze ({ code = code, facts = facts or NO_FACTS }) :: Rejection, 0)
124
+ end
125
+
126
+ --[[
127
+ A name with no fact behind it is left in its braces: a line saying `{path}` is a defect a reader
128
+ reports, where one saying nothing at all is one they work around.
129
+ ]]
130
+ function Log.line (code: Catalog.Code, facts: Facts): string
131
+ local out = string.gsub (Catalog.entries[code].template, HELD, function (name: string): string
132
+ local held = facts[name]
133
+ return if held == nil then `{"{"}{name}{"}"}` else tostring (held)
134
+ end)
135
+ return out
136
+ end
137
+
138
+ -- Whatever the boundary caught, as the record a drop is.
139
+ function Log.dropped (thrown: unknown, about: Facts, kind: Types.LogKind?)
140
+ local rejection = if typeof (thrown) == "table" then thrown :: Rejection else nil
141
+ local entry = if rejection == nil then nil else Catalog.entries[rejection.code]
142
+
143
+ local code: Catalog.Code = if entry == nil
144
+ then Catalog.BOUNDS
145
+ else (rejection :: Rejection).code
146
+ local raised: Facts = if entry == nil
147
+ then { said = tostring (thrown) }
148
+ else (rejection :: Rejection).facts
149
+
150
+ --[[
151
+ A copy, since what follows merges the raise in and the facts are the caller's table. A
152
+ record that wrote into its own argument is a promise this cannot make.
153
+ ]]
154
+ local facts = table.clone (about)
155
+ for name, value in raised do
156
+ facts[name] = value
157
+ end
158
+ facts.code = code
159
+
160
+ --[[
161
+ A frame that failed before naming a definition has no declaration site to point at, and a
162
+ record is promised a file and a line, so the library's own name stands there.
163
+ ]]
164
+ local file = if typeof (facts.file) == "string" then facts.file :: string else "Lync"
165
+ local line = if typeof (facts.line) == "number" then facts.line :: number else 1
166
+
167
+ said (kind or "warn", Log.line (code, facts), facts, file, line)
168
+ end
169
+
170
+ --[[
171
+ A listener, and the connection that ends it. Every registration on the channel goes through the
172
+ same list the printer sits in, so a caller who takes the printer keeps the records.
173
+ ]]
174
+ function Log.onLog (
175
+ fn: (kind: Types.LogKind, message: string, data: Types.LogData) -> ()
176
+ ): Types.Connection
177
+ return Listeners.connect (records, fn)
178
+ end
179
+
180
+ --[[
181
+ The printer, handed over rather than registered. It is a listener like any other and is first in
182
+ the order because it is attached as this file loads, and a caller who wants to format records
183
+ themselves disconnects it and attaches their own.
184
+ ]]
185
+ Log.console = Listeners.connect (
186
+ records,
187
+ function (kind: Types.LogKind, message: string, data: Types.LogData)
188
+ local at = `{data.file}:{data.line}`
189
+ if kind == "debug" then
190
+ print (`[lync] {at} {message}`)
191
+ return
192
+ end
193
+ warn (`[lync] {at} {message}`)
194
+ end
195
+ )
196
+
197
+ return table.freeze (Log)
@@ -0,0 +1,128 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- What a namespace has moved, counted where it happens and read as one frozen number set.
4
+
5
+ local Types = require (script.Parent.Parent.Types)
6
+
7
+ local Tally = {}
8
+
9
+ -- Types ---------------------------------------------------------------
10
+
11
+ export type Row = {
12
+ sent: number,
13
+ sentBytes: number,
14
+ received: number,
15
+ receivedBytes: number,
16
+ drops: number,
17
+ }
18
+
19
+ -- Everything counted, which is the four about the namespace and one line a definition.
20
+ export type Tally = {
21
+ flushes: number,
22
+ sentBytes: number,
23
+ receivedBytes: number,
24
+ drops: number,
25
+ definitions: { [string]: Row },
26
+ }
27
+
28
+ -- Private -------------------------------------------------------------
29
+
30
+ local function zeroed (): Row
31
+ return { sent = 0, sentBytes = 0, received = 0, receivedBytes = 0, drops = 0 }
32
+ end
33
+
34
+ -- The fallback line, for a count about a definition the namespace never compiled.
35
+ local function line (of: Tally, name: string): Row
36
+ local held = of.definitions[name]
37
+ if held ~= nil then return held end
38
+
39
+ held = zeroed ()
40
+ of.definitions[name] = held
41
+ return held
42
+ end
43
+
44
+ -- Public --------------------------------------------------------------
45
+
46
+ function Tally.new (names: { string }): Tally
47
+ local definitions: { [string]: Row } = {}
48
+ for _, name in names do
49
+ definitions[name] = zeroed ()
50
+ end
51
+
52
+ return {
53
+ flushes = 0,
54
+ sentBytes = 0,
55
+ receivedBytes = 0,
56
+ drops = 0,
57
+ definitions = definitions,
58
+ }
59
+ end
60
+
61
+ -- Counted where a block is composed, so one bound for two clients still counts once.
62
+ function Tally.sent (of: Tally, name: string, bytes: number)
63
+ local held = line (of, name)
64
+ held.sent += 1
65
+ held.sentBytes += bytes
66
+ end
67
+
68
+ -- More bytes against a definition, without a call to go with them.
69
+ function Tally.carried (of: Tally, name: string, bytes: number)
70
+ line (of, name).sentBytes += bytes
71
+ end
72
+
73
+ -- Counted off the frame, whatever became of what was inside it.
74
+ function Tally.received (of: Tally, name: string, bytes: number)
75
+ local held = line (of, name)
76
+ held.received += 1
77
+ held.receivedBytes += bytes
78
+ end
79
+
80
+ -- One drop, against the definition it was about where there is one.
81
+ function Tally.dropped (of: Tally, name: string?)
82
+ of.drops += 1
83
+ if name == nil then return end
84
+
85
+ line (of, name).drops += 1
86
+ end
87
+
88
+ -- The engine's own figure, above the blocks inside it, since framing rides outside them.
89
+ function Tally.moved (of: Tally, bytes: number)
90
+ of.sentBytes += bytes
91
+ end
92
+
93
+ -- The same figure coming the other way.
94
+ function Tally.took (of: Tally, bytes: number)
95
+ of.receivedBytes += bytes
96
+ end
97
+
98
+ function Tally.flushed (of: Tally)
99
+ of.flushes += 1
100
+ end
101
+
102
+ --[[
103
+ Copied and frozen, since the surface says two readings subtract into an exact rate and a caller
104
+ who kept one is owed the numbers they took.
105
+ ]]
106
+ function Tally.reading (of: Tally): Types.Stats
107
+ local definitions: { [string]: Types.DefinitionStats } = {}
108
+
109
+ for name, held in of.definitions do
110
+ definitions[name] = table.freeze ({
111
+ sent = held.sent,
112
+ sentBytes = held.sentBytes,
113
+ received = held.received,
114
+ receivedBytes = held.receivedBytes,
115
+ drops = held.drops,
116
+ })
117
+ end
118
+
119
+ return table.freeze ({
120
+ flushes = of.flushes,
121
+ sentBytes = of.sentBytes,
122
+ receivedBytes = of.receivedBytes,
123
+ drops = of.drops,
124
+ definitions = table.freeze (definitions),
125
+ })
126
+ end
127
+
128
+ return table.freeze (Tally)
@@ -0,0 +1,44 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- Pooled coroutines: a call runs on one immediately, and cannot reach whoever asked for it.
4
+
5
+ local Threads = {}
6
+
7
+ -- State ---------------------------------------------------------------
8
+
9
+ --[[
10
+ A thread is taken per call and puts itself back when that call returns, so the pile grows to the
11
+ deepest run of calls ever in flight at one moment and never past it.
12
+ ]]
13
+ local idle: { thread } = {}
14
+
15
+ -- Private -------------------------------------------------------------
16
+
17
+ local function ran (call: (...any) -> (), ...: any)
18
+ call (...)
19
+ table.insert (idle, coroutine.running ())
20
+ end
21
+
22
+ --[[
23
+ The first call arrives as this function's own arguments, so a fresh thread is resumed once and
24
+ not twice. Every call after it arrives out of the yield below.
25
+ ]]
26
+ local function cycles (call: (...any) -> (), ...: any)
27
+ ran (call, ...)
28
+ while true do
29
+ ran (coroutine.yield ())
30
+ end
31
+ end
32
+
33
+ -- Public --------------------------------------------------------------
34
+
35
+ function Threads.spawn <A...>(call: (A...) -> (), ...: A...)
36
+ local held = table.remove (idle)
37
+ if held == nil then
38
+ task.spawn (coroutine.create (cycles), call, ...)
39
+ return
40
+ end
41
+ task.spawn (held, call, ...)
42
+ end
43
+
44
+ return table.freeze (Threads)