@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/kernel/Heap.luau
ADDED
|
@@ -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)
|
package/kernel/Log.luau
ADDED
|
@@ -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)
|