@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/api/Query.luau
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- The query handle: one responder a side, and a request that answers rather than throws or hangs.
|
|
4
|
+
|
|
5
|
+
local Base = require (script.Parent.Parent.codec.Base)
|
|
6
|
+
local Define = require (script.Parent.Define)
|
|
7
|
+
local Handles = require (script.Parent.Parent.runtime.Handles)
|
|
8
|
+
local Serial = require (script.Parent.Parent.shape.Serial)
|
|
9
|
+
local Types = require (script.Parent.Parent.Types)
|
|
10
|
+
|
|
11
|
+
local Query = {}
|
|
12
|
+
Query.__index = Query
|
|
13
|
+
|
|
14
|
+
-- Private -------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
-- The one responder this side gets, held until there is a dispatch to hand it to.
|
|
17
|
+
local function answers (self: any, said: string, fn: (...any) -> any)
|
|
18
|
+
local live = self.live
|
|
19
|
+
local under = Define.naming (live, said)
|
|
20
|
+
|
|
21
|
+
local session = live.session
|
|
22
|
+
if session ~= nil and session.started then
|
|
23
|
+
Define.refuse (`{said} after {under} started, and start seals responders`, under)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if self.answering ~= nil then
|
|
27
|
+
Define.refuse (`{under} already has a responder, and a query has one`, under)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
self.answering = fn
|
|
31
|
+
Handles.waiting (live, function (session) session:respond (live.id :: number, under, fn) end)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
--[[
|
|
35
|
+
A client's request, which yields until it is answered or told why it will not be. A third
|
|
36
|
+
argument is the server form's completion and the client form has no third, so a call carrying
|
|
37
|
+
one is the server's made on the wrong machine. The lookup below cannot refuse, the caller
|
|
38
|
+
having branched on the side it tests, and what it is called for is the session.
|
|
39
|
+
]]
|
|
40
|
+
local function asked (
|
|
41
|
+
self: any,
|
|
42
|
+
value: any,
|
|
43
|
+
timeout: number?,
|
|
44
|
+
complete: any
|
|
45
|
+
): (boolean, any, Types.OutcomeData?)
|
|
46
|
+
local session = Define.asking (self.live, "request")
|
|
47
|
+
if complete ~= nil then
|
|
48
|
+
Define.refuse (
|
|
49
|
+
"request with a completion is the server's call, and this is a client",
|
|
50
|
+
Define.naming (self.live, "request")
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
local settled = false
|
|
55
|
+
local parked: thread? = nil
|
|
56
|
+
|
|
57
|
+
local ok: boolean
|
|
58
|
+
local result: any
|
|
59
|
+
local data: Types.OutcomeData?
|
|
60
|
+
|
|
61
|
+
session:ask (self.live.def, nil, value, timeout, function (answered, gave, about)
|
|
62
|
+
ok, result, data = answered, gave, about
|
|
63
|
+
settled = true
|
|
64
|
+
|
|
65
|
+
local held = parked
|
|
66
|
+
if held ~= nil then task.spawn (held) end
|
|
67
|
+
end)
|
|
68
|
+
|
|
69
|
+
if not settled then
|
|
70
|
+
parked = coroutine.running ()
|
|
71
|
+
coroutine.yield ()
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
return ok, result, data
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
-- Public --------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
--[[
|
|
80
|
+
Two builds and one handle, because a request and its reply are one agreement between two
|
|
81
|
+
machines, and they share one definition and two consecutive ids.
|
|
82
|
+
]]
|
|
83
|
+
function Query.new <Q, R>(of: Types.Codec<Q>, reply: Types.Codec<R>): Types.Query<Q, R>
|
|
84
|
+
--[[
|
|
85
|
+
Level two is the caller rather than this frame, so a schema refused at start points at the
|
|
86
|
+
declaration instead of the start that caught it.
|
|
87
|
+
]]
|
|
88
|
+
local file, line = debug.info (2, "sl")
|
|
89
|
+
local self = {
|
|
90
|
+
kind = "query" :: "query",
|
|
91
|
+
of = of,
|
|
92
|
+
reply = reply,
|
|
93
|
+
at = { file = file, line = line },
|
|
94
|
+
live = Handles.live (),
|
|
95
|
+
answering = nil :: ((...any) -> any)?,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return (setmetatable (self, Query) :: any) :: Types.Query<Q, R>
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
--[[
|
|
102
|
+
The responder, on whichever side is answering.
|
|
103
|
+
|
|
104
|
+
Two names for one registration, because what a responder is handed differs between the sides
|
|
105
|
+
and what it is registered as does not.
|
|
106
|
+
]]
|
|
107
|
+
function Query.onServer (self: any, fn: (any, Player) -> any) answers (self, "onServer", fn) end
|
|
108
|
+
|
|
109
|
+
function Query.onClient (self: any, fn: (any) -> any) answers (self, "onClient", fn) end
|
|
110
|
+
|
|
111
|
+
--[[
|
|
112
|
+
One request, in whichever of its two forms this machine has. Which form comes off the machine
|
|
113
|
+
and never off the arguments, so a call before start names the namespace that has not opened
|
|
114
|
+
rather than an argument. What the arguments say afterwards is whether the caller made the other
|
|
115
|
+
side's call, which is the one mistake here that used to answer with somebody else's error.
|
|
116
|
+
]]
|
|
117
|
+
function Query.request (self: any, first: any, second: any, third: any, fourth: any): ...any
|
|
118
|
+
if self.live.isServer then
|
|
119
|
+
--[[
|
|
120
|
+
The server form carries a completion and the client form has no third argument, so a
|
|
121
|
+
call reaching here without one is the client's. The lookup below cannot refuse, the
|
|
122
|
+
branch above having settled the side before it is asked, and what it is called for is
|
|
123
|
+
the session and the namespace it names when there is none.
|
|
124
|
+
]]
|
|
125
|
+
local session = Define.serving (self.live, "request")
|
|
126
|
+
if typeof (third) ~= "function" then
|
|
127
|
+
Define.refuse (
|
|
128
|
+
"request without a completion is a client's call, and this is the server",
|
|
129
|
+
Define.naming (self.live, "request")
|
|
130
|
+
)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
session:ask (self.live.def, first, second, fourth, third)
|
|
134
|
+
return
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
return asked (self, first, second, third)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
-- The schema as text, which is the wording a refused request is answered with.
|
|
141
|
+
function Query.describe (self: any): string
|
|
142
|
+
return Serial.frame ({
|
|
143
|
+
kind = "query",
|
|
144
|
+
of = { cell = Base.cell (self.of) },
|
|
145
|
+
reply = { cell = Base.cell (self.reply) },
|
|
146
|
+
})
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
return table.freeze (Query)
|
package/api/Set.luau
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- The set handle: the server's records, the client's view of them, and the one key that splits.
|
|
4
|
+
|
|
5
|
+
local Base = require (script.Parent.Parent.codec.Base)
|
|
6
|
+
local Define = require (script.Parent.Define)
|
|
7
|
+
local Handles = require (script.Parent.Parent.runtime.Handles)
|
|
8
|
+
local Serial = require (script.Parent.Parent.shape.Serial)
|
|
9
|
+
local Sync = require (script.Parent.Parent.state.Sync)
|
|
10
|
+
local Types = require (script.Parent.Parent.Types)
|
|
11
|
+
|
|
12
|
+
local Set = {}
|
|
13
|
+
Set.__index = Set
|
|
14
|
+
|
|
15
|
+
-- Private -------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
-- The set this handle mutates, which only the server has records in.
|
|
18
|
+
local function owning (self: any, said: string): Sync.Sync
|
|
19
|
+
local live = self.live
|
|
20
|
+
return Define.serving (live, said):set (live.id :: number)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
--[[
|
|
24
|
+
The set this handle reads, or nothing where its namespace has not opened one.
|
|
25
|
+
|
|
26
|
+
Nothing rather than a refusal, because a read is what a caller does to find out whether there is
|
|
27
|
+
anything there, and a set before start holds nothing in exactly the way an empty one does.
|
|
28
|
+
]]
|
|
29
|
+
local function viewing (self: any): Sync.Sync?
|
|
30
|
+
local live = self.live
|
|
31
|
+
local session = live.session
|
|
32
|
+
if session == nil then return nil end
|
|
33
|
+
|
|
34
|
+
return session:set (live.id :: number)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
-- A walk over nothing, which is what a set that has not opened has to walk.
|
|
38
|
+
local function barren (): (number?, any) return nil end
|
|
39
|
+
|
|
40
|
+
--[[
|
|
41
|
+
A callback, attached whenever the caller liked.
|
|
42
|
+
|
|
43
|
+
Set callbacks attach at any time for the reason listeners do, and the one that most needs it is
|
|
44
|
+
`onAdded`: a client attaching after start would miss the seeding its own join asked for.
|
|
45
|
+
]]
|
|
46
|
+
local function watches (
|
|
47
|
+
self: any,
|
|
48
|
+
attach: (Sync.Sync, any) -> Types.Connection,
|
|
49
|
+
fn: any
|
|
50
|
+
): Types.Connection
|
|
51
|
+
local live = self.live
|
|
52
|
+
return Handles.later (
|
|
53
|
+
live,
|
|
54
|
+
function (session) return attach (session:set (live.id :: number), fn) end
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
-- Public --------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
-- What clears an optional field, since nothing else can.
|
|
61
|
+
Set.NONE = Sync.NONE
|
|
62
|
+
|
|
63
|
+
--[[
|
|
64
|
+
The key is not here. A set is split by a field a caller names afterwards, and until they do the
|
|
65
|
+
whole set goes to everybody, which is the declaration this makes on its own.
|
|
66
|
+
]]
|
|
67
|
+
function Set.new <T>(of: Types.Codec<T>): Types.Set<T>
|
|
68
|
+
--[[
|
|
69
|
+
Level two is the caller, so a set refused at start is named against the line that declared
|
|
70
|
+
it rather than against the start that did the refusing.
|
|
71
|
+
]]
|
|
72
|
+
local file, line = debug.info (2, "sl")
|
|
73
|
+
local self = {
|
|
74
|
+
kind = "set" :: "set",
|
|
75
|
+
of = of,
|
|
76
|
+
keyField = nil :: string?,
|
|
77
|
+
at = { file = file, line = line },
|
|
78
|
+
live = Handles.live (),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (setmetatable (self, Set) :: any) :: Types.Set<T>
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
--[[
|
|
85
|
+
The field records are split by, which is what gives a key an audience to be assigned one.
|
|
86
|
+
|
|
87
|
+
Whether the field exists and whether its codec can be a key is settled at start against the
|
|
88
|
+
compiled record rather than here against the declaration, which has no record to look in yet.
|
|
89
|
+
]]
|
|
90
|
+
function Set.keyBy (self: any, field: string): any
|
|
91
|
+
Define.declaring (self.live, "keyBy")
|
|
92
|
+
self.keyField = field
|
|
93
|
+
return self
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
-- The full record, under an id nothing else in this set holds.
|
|
97
|
+
function Set.add (self: any, id: number, record: any)
|
|
98
|
+
local of = owning (self, "add")
|
|
99
|
+
Sync.add (of, id, record)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
-- Only the fields named, and only to whoever can already see the record they are on.
|
|
103
|
+
function Set.update (self: any, id: number, fields: any)
|
|
104
|
+
local of = owning (self, "update")
|
|
105
|
+
Sync.update (of, id, fields)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
function Set.remove (self: any, id: number)
|
|
109
|
+
local of = owning (self, "remove")
|
|
110
|
+
Sync.remove (of, id)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
--[[
|
|
114
|
+
Everything at once, which goes out as one marker per partition rather than a removal per id.
|
|
115
|
+
A listener is still told once per record, under a cause that says a clear was what did it.
|
|
116
|
+
]]
|
|
117
|
+
function Set.clear (self: any)
|
|
118
|
+
local of = owning (self, "clear")
|
|
119
|
+
Sync.clear (of)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
--[[
|
|
123
|
+
Who sees the records under one key, which an unkeyed set has nobody to say it about.
|
|
124
|
+
|
|
125
|
+
Refused rather than ignored: an audience on a set that goes to everybody is a caller who
|
|
126
|
+
believes they have restricted something and has not.
|
|
127
|
+
]]
|
|
128
|
+
function Set.audience (self: any, key: any, to: Types.Recipient)
|
|
129
|
+
local of = owning (self, "audience")
|
|
130
|
+
if self.keyField == nil then
|
|
131
|
+
Define.refuse (
|
|
132
|
+
"audience is for a set split by keyBy, and this one goes to everybody",
|
|
133
|
+
Define.naming (self.live, "audience")
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
Sync.audience (of, key, to)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
--[[
|
|
141
|
+
The live record under an id, borrowed rather than copied.
|
|
142
|
+
|
|
143
|
+
Nothing where this machine holds no record under it, which on a client is every id its audiences
|
|
144
|
+
do not reach as well as every id nobody has added.
|
|
145
|
+
]]
|
|
146
|
+
function Set.get (self: any, id: number): any?
|
|
147
|
+
local of = viewing (self)
|
|
148
|
+
if of == nil then return nil end
|
|
149
|
+
|
|
150
|
+
return Sync.get (of, id)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
function Set.__len (self: any): number
|
|
154
|
+
local of = viewing (self)
|
|
155
|
+
if of == nil then return 0 end
|
|
156
|
+
|
|
157
|
+
return Sync.size (of)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
-- Every record this machine holds, in no order a caller may rely on.
|
|
161
|
+
function Set.__iter (self: any): (any, any, any)
|
|
162
|
+
local of = viewing (self)
|
|
163
|
+
if of == nil then return barren, self, nil end
|
|
164
|
+
|
|
165
|
+
return Sync.walk (of), self, nil
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
function Set.onAdded (self: any, fn: (number, any) -> ()): Types.Connection
|
|
169
|
+
return watches (self, Sync.onAdded, fn)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
function Set.onChanged (self: any, fn: (number, any, any?) -> ()): Types.Connection
|
|
173
|
+
return watches (self, Sync.onChanged, fn)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
function Set.onRemoved (self: any, fn: (number, Types.Cause) -> ()): Types.Connection
|
|
177
|
+
return watches (self, Sync.onRemoved, fn)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
-- The schema as text, which is how a record turned away on arrival is described back.
|
|
181
|
+
function Set.describe (self: any): string
|
|
182
|
+
return Serial.frame ({
|
|
183
|
+
kind = "set",
|
|
184
|
+
of = { cell = Base.cell (self.of) },
|
|
185
|
+
keyField = self.keyField,
|
|
186
|
+
})
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
return table.freeze (Set)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- One block of a frame: the bytes a producer laid down, and how many places still hold them.
|
|
4
|
+
|
|
5
|
+
local Bytes = require (script.Parent.Parent.wire.Bytes)
|
|
6
|
+
|
|
7
|
+
local Blocks = {}
|
|
8
|
+
|
|
9
|
+
-- Constants -----------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
--[[
|
|
12
|
+
The five classes a reliable frame's blocks drain in, which is that drain order as the buckets a
|
|
13
|
+
queue has.
|
|
14
|
+
]]
|
|
15
|
+
const RESPONSE = 1
|
|
16
|
+
const REQUEST = RESPONSE + 1
|
|
17
|
+
const PACKET = REQUEST + 1
|
|
18
|
+
const ESTABLISH = PACKET + 1
|
|
19
|
+
const DELTA = ESTABLISH + 1
|
|
20
|
+
|
|
21
|
+
-- What a block on the lossy lane is classed as, which is nothing.
|
|
22
|
+
const LOOSE = 0
|
|
23
|
+
|
|
24
|
+
-- What a released block points at until its next build, so a pooled record pins no bytes.
|
|
25
|
+
const EMPTY = buffer.create (0)
|
|
26
|
+
|
|
27
|
+
-- Types ---------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
-- One block, queued and copied and eventually let go.
|
|
30
|
+
export type Block = {
|
|
31
|
+
buf: buffer,
|
|
32
|
+
len: number,
|
|
33
|
+
class: number,
|
|
34
|
+
seq: number,
|
|
35
|
+
refs: { Instance }?,
|
|
36
|
+
rc: number,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
-- State ---------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
-- Blocks nobody holds any more, kept for the next build rather than collected.
|
|
42
|
+
local free: { Block } = {}
|
|
43
|
+
|
|
44
|
+
-- Public --------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
Blocks.RESPONSE = RESPONSE
|
|
47
|
+
Blocks.REQUEST = REQUEST
|
|
48
|
+
Blocks.PACKET = PACKET
|
|
49
|
+
Blocks.ESTABLISH = ESTABLISH
|
|
50
|
+
Blocks.DELTA = DELTA
|
|
51
|
+
Blocks.LOOSE = LOOSE
|
|
52
|
+
|
|
53
|
+
-- How many classes there are, which is the number of buckets a reliable queue keeps.
|
|
54
|
+
Blocks.CLASSES = DELTA
|
|
55
|
+
|
|
56
|
+
-- What a caller runs to fill one block, which is everything this layer refuses to know.
|
|
57
|
+
export type Produce = (b: buffer, pos: number) -> number
|
|
58
|
+
|
|
59
|
+
--[[
|
|
60
|
+
One block over bytes already in scratch, sealed at a length rather than run from a producer,
|
|
61
|
+
because a planner writes a section to decide whether it fits and the frame has moved on by the
|
|
62
|
+
time the block is wanted. What is sealed is exactly the bytes written, so a frame is the sum of
|
|
63
|
+
its blocks, and `refs` is copied because the array it came from is about to be refilled.
|
|
64
|
+
]]
|
|
65
|
+
function Blocks.sealed (
|
|
66
|
+
into: buffer,
|
|
67
|
+
len: number,
|
|
68
|
+
class: number,
|
|
69
|
+
seq: number,
|
|
70
|
+
refs: { Instance }?
|
|
71
|
+
): Block
|
|
72
|
+
local held = if refs == nil then nil else table.clone (refs)
|
|
73
|
+
local sealed = Bytes.seal (into, len)
|
|
74
|
+
|
|
75
|
+
local of = table.remove (free)
|
|
76
|
+
if of == nil then
|
|
77
|
+
return { buf = sealed, len = len, class = class, seq = seq, refs = held, rc = 1 }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
of.buf = sealed
|
|
81
|
+
of.len = len
|
|
82
|
+
of.class = class
|
|
83
|
+
of.seq = seq
|
|
84
|
+
of.refs = held
|
|
85
|
+
of.rc = 1
|
|
86
|
+
return of
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
-- One block, built by running a producer into scratch and sealing what it wrote.
|
|
90
|
+
function Blocks.build (
|
|
91
|
+
into: buffer,
|
|
92
|
+
class: number,
|
|
93
|
+
seq: number,
|
|
94
|
+
refs: { Instance }?,
|
|
95
|
+
produce: Produce
|
|
96
|
+
): Block
|
|
97
|
+
return Blocks.sealed (into, produce (into, 0), class, seq, refs)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
-- One more hold, taken by whatever is about to keep a block past the call that handed it over.
|
|
101
|
+
function Blocks.retain (of: Block)
|
|
102
|
+
of.rc += 1
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
-- One hold fewer, and the pool where the last one leaves it.
|
|
106
|
+
function Blocks.release (of: Block)
|
|
107
|
+
of.rc -= 1
|
|
108
|
+
if of.rc > 0 then return end
|
|
109
|
+
|
|
110
|
+
of.buf = EMPTY
|
|
111
|
+
of.len = 0
|
|
112
|
+
of.refs = nil
|
|
113
|
+
table.insert (free, of)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
-- Every block in a list let go, and the list emptied.
|
|
117
|
+
function Blocks.drain (held: { Block })
|
|
118
|
+
for _, of in held do
|
|
119
|
+
Blocks.release (of)
|
|
120
|
+
end
|
|
121
|
+
table.clear (held)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
return table.freeze (Blocks)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- A membership set a caller mutates and an audience holds, and the sweep that thins one.
|
|
4
|
+
|
|
5
|
+
local Log = require (script.Parent.Parent.kernel.Log)
|
|
6
|
+
local Types = require (script.Parent.Parent.Types)
|
|
7
|
+
|
|
8
|
+
local Group = {}
|
|
9
|
+
|
|
10
|
+
-- Types ---------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
--[[
|
|
13
|
+
A set kept as both an array and the place each member sits in it. The array is what a walk and a
|
|
14
|
+
count read, and the index is what a membership test and a removal read, and neither is derivable
|
|
15
|
+
from the other in constant time.
|
|
16
|
+
]]
|
|
17
|
+
type Held = {
|
|
18
|
+
members: { Player },
|
|
19
|
+
at: { [Player]: number },
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
-- Private -------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
local Members = {}
|
|
25
|
+
Members.__index = Members
|
|
26
|
+
|
|
27
|
+
--[[
|
|
28
|
+
What a group becomes when it is released. Every read and every write goes through a metamethod
|
|
29
|
+
that raises, so a caller holding a stale reference is told rather than answered: a destroyed
|
|
30
|
+
group that reported an empty membership would let them fire at nobody forever and never learn
|
|
31
|
+
why.
|
|
32
|
+
]]
|
|
33
|
+
local Dead = {}
|
|
34
|
+
|
|
35
|
+
local function gone (): any return Log.refuse ("this group was destroyed") end
|
|
36
|
+
|
|
37
|
+
Dead.__index = gone
|
|
38
|
+
Dead.__len = gone
|
|
39
|
+
Dead.__iter = gone
|
|
40
|
+
|
|
41
|
+
-- State ---------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
-- Every group that has not been destroyed, which is where a leave sweep finds them.
|
|
44
|
+
local live: { [Held]: true } = {}
|
|
45
|
+
|
|
46
|
+
-- Public --------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
--[[
|
|
49
|
+
Adding a member twice leaves one and removing an absent one is nothing. A caller adds on every
|
|
50
|
+
join and removes on every leave from code that has no idea what the group held, so counting
|
|
51
|
+
duplicates or refusing an absent removal would make each of those sites read it first.
|
|
52
|
+
]]
|
|
53
|
+
function Members.add (self: Held, player: Player)
|
|
54
|
+
if self.at[player] ~= nil then return end
|
|
55
|
+
|
|
56
|
+
table.insert (self.members, player)
|
|
57
|
+
self.at[player] = #self.members
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
-- The last member fills the gap, so a removal costs nothing and the order is not one to rely on.
|
|
61
|
+
function Members.remove (self: Held, player: Player)
|
|
62
|
+
local index = self.at[player]
|
|
63
|
+
if index == nil then return end
|
|
64
|
+
|
|
65
|
+
local last = #self.members
|
|
66
|
+
local moved = self.members[last]
|
|
67
|
+
|
|
68
|
+
self.members[index] = moved
|
|
69
|
+
self.at[moved] = index
|
|
70
|
+
|
|
71
|
+
self.members[last] = nil
|
|
72
|
+
self.at[player] = nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
function Members.has (self: Held, player: Player): boolean return self.at[player] ~= nil end
|
|
76
|
+
|
|
77
|
+
--[[
|
|
78
|
+
Emptied and let go, in that order. The emptying is what releases the members: a group is the
|
|
79
|
+
only thing holding some of them by the time a caller is done with it, and waiting for the
|
|
80
|
+
collector to notice is exactly the timing this library never rides.
|
|
81
|
+
]]
|
|
82
|
+
function Members.destroy (self: Held)
|
|
83
|
+
live[self] = nil
|
|
84
|
+
|
|
85
|
+
table.clear (self.members)
|
|
86
|
+
table.clear (self.at)
|
|
87
|
+
setmetatable (self :: any, Dead)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
function Members.__len (self: Held): number return #self.members end
|
|
91
|
+
|
|
92
|
+
function Members.__iter (self: Held) return ipairs (self.members) end
|
|
93
|
+
|
|
94
|
+
function Group.new (): Types.Group
|
|
95
|
+
local self: Held = setmetatable ({ members = {}, at = {} }, Members) :: any
|
|
96
|
+
live[self] = true
|
|
97
|
+
|
|
98
|
+
return (self :: any) :: Types.Group
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
--[[
|
|
102
|
+
Whether a value is a group, which is what a recipient asks before it walks one. A destroyed one
|
|
103
|
+
never reaches here: every recipient form is told apart by reading a field off it first, and
|
|
104
|
+
reading any field off a destroyed group is already the raise that says so.
|
|
105
|
+
]]
|
|
106
|
+
function Group.is (value: unknown): boolean return getmetatable (value :: any) == Members end
|
|
107
|
+
|
|
108
|
+
--[[
|
|
109
|
+
Every group a departed player was in, thinned. Called once per leave from wherever the removal
|
|
110
|
+
is heard, which is the same sweep that releases every other structure keyed by them.
|
|
111
|
+
]]
|
|
112
|
+
function Group.dropped (player: Player)
|
|
113
|
+
for of in live do
|
|
114
|
+
(of :: any):remove (player)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
return table.freeze (Group)
|