@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/codec/Base.luau
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- The codec cell: one frozen value per declaration, and the four modifiers that answer another.
|
|
4
|
+
|
|
5
|
+
local Log = require (script.Parent.Parent.kernel.Log)
|
|
6
|
+
local Types = require (script.Parent.Parent.Types)
|
|
7
|
+
|
|
8
|
+
local Base = {}
|
|
9
|
+
|
|
10
|
+
--[[
|
|
11
|
+
The one metatable every codec shares, so a declaration costs one table and `table.clone`
|
|
12
|
+
carries it to every copy it makes.
|
|
13
|
+
]]
|
|
14
|
+
local Modifiers = {}
|
|
15
|
+
Modifiers.__index = Modifiers
|
|
16
|
+
|
|
17
|
+
-- Constants -----------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
-- Shared by every codec declaring neither, since two frozen empty tables cannot be told apart.
|
|
20
|
+
const NO_PARAMS: Params = table.freeze ({})
|
|
21
|
+
const NO_CHECKS = table.freeze ({} :: { Validate })
|
|
22
|
+
|
|
23
|
+
-- Types ---------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
export type Kind =
|
|
26
|
+
"bool"
|
|
27
|
+
| "int"
|
|
28
|
+
| "quant"
|
|
29
|
+
| "angle"
|
|
30
|
+
| "f32"
|
|
31
|
+
| "f64"
|
|
32
|
+
| "vlq"
|
|
33
|
+
| "vli"
|
|
34
|
+
| "str"
|
|
35
|
+
| "buffer"
|
|
36
|
+
| "struct"
|
|
37
|
+
| "array"
|
|
38
|
+
| "map"
|
|
39
|
+
| "optional"
|
|
40
|
+
| "tagged"
|
|
41
|
+
| "enum"
|
|
42
|
+
| "bitfield"
|
|
43
|
+
| "vec2"
|
|
44
|
+
| "vec3"
|
|
45
|
+
| "cframe"
|
|
46
|
+
| "rotation"
|
|
47
|
+
| "color3"
|
|
48
|
+
| "inst"
|
|
49
|
+
|
|
50
|
+
export type Marker = "none" | "monotonic" | "newest"
|
|
51
|
+
|
|
52
|
+
export type Validate = (value: any, ctx: Types.ValidateContext) -> string?
|
|
53
|
+
|
|
54
|
+
--[[
|
|
55
|
+
Every parameter any constructor records, in one vocabulary. The set is closed and holds nothing
|
|
56
|
+
without exact text: the descriptor writes parameters as decimals, and two machines agreeing on
|
|
57
|
+
a schema is that text agreeing.
|
|
58
|
+
]]
|
|
59
|
+
export type Params = {
|
|
60
|
+
read min: number?,
|
|
61
|
+
read max: number?,
|
|
62
|
+
read step: number?,
|
|
63
|
+
read form: string?,
|
|
64
|
+
read field: string?,
|
|
65
|
+
read symbols: string?,
|
|
66
|
+
read class: string?,
|
|
67
|
+
read names: { string }?,
|
|
68
|
+
read colors: { Color3 }?,
|
|
69
|
+
read axis: Vector3?,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
--[[
|
|
73
|
+
A declaration, frozen, as the compile path reads it. There is one codec type and it is the
|
|
74
|
+
public one, since two over one value cost more than the accessor below and neither an
|
|
75
|
+
intersection nor a flat pair survives the solver. It carries no value type either: a walk over
|
|
76
|
+
children meets a different one at every node.
|
|
77
|
+
]]
|
|
78
|
+
export type Cell = {
|
|
79
|
+
read kind: Kind,
|
|
80
|
+
read params: Params,
|
|
81
|
+
read children: { [string]: Cell }?,
|
|
82
|
+
read validates: { Validate },
|
|
83
|
+
read marker: Marker,
|
|
84
|
+
read newestHz: number?,
|
|
85
|
+
read lift: ((wire: any) -> any)?,
|
|
86
|
+
read lower: ((domain: any) -> any)?,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
--[[
|
|
90
|
+
What the library builds for itself: the one or two children a composite names. A caller's own
|
|
91
|
+
table does not come through here, because a table whose properties are read-only satisfies no
|
|
92
|
+
indexer in this solver and everything in this repository is frozen.
|
|
93
|
+
]]
|
|
94
|
+
export type Children = { [string]: Types.Codec<any> }
|
|
95
|
+
|
|
96
|
+
--[[
|
|
97
|
+
A cell mid-change, with the five facts a modifier may move writable and everything that decides
|
|
98
|
+
a layout still read-only. A modifier that tried to widen a field does not compile, which is the
|
|
99
|
+
claim `codec.modifier-free` makes from the other end.
|
|
100
|
+
]]
|
|
101
|
+
type Open = {
|
|
102
|
+
read kind: Kind,
|
|
103
|
+
read params: Params,
|
|
104
|
+
read children: { [string]: Cell }?,
|
|
105
|
+
validates: { Validate },
|
|
106
|
+
marker: Marker,
|
|
107
|
+
newestHz: number?,
|
|
108
|
+
lift: ((wire: any) -> any)?,
|
|
109
|
+
lower: ((domain: any) -> any)?,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
-- Private -------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
local function opened (of: Types.Codec<any>): Open return table.clone (of :: any) :: any end
|
|
115
|
+
|
|
116
|
+
local function sealed (made: Open): Types.Codec<any> return table.freeze (made :: any) :: any end
|
|
117
|
+
|
|
118
|
+
-- Public --------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
-- A codec as its constructor declares it, before any modifier.
|
|
121
|
+
function Base.make (kind: Kind, params: Params?, children: Children?): Types.Codec<any>
|
|
122
|
+
local held: any = if children == nil then nil else table.freeze (table.clone (children))
|
|
123
|
+
|
|
124
|
+
local taken: any = params
|
|
125
|
+
if taken ~= nil then
|
|
126
|
+
for name, value in taken do
|
|
127
|
+
if type (value) == "table" then taken[name] = table.freeze (table.clone (value)) end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
return table.freeze (setmetatable ({
|
|
132
|
+
kind = kind,
|
|
133
|
+
params = if params == nil then NO_PARAMS else table.freeze (params),
|
|
134
|
+
children = held,
|
|
135
|
+
validates = NO_CHECKS,
|
|
136
|
+
marker = "none" :: Marker,
|
|
137
|
+
}, Modifiers)) :: any
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
--[[
|
|
141
|
+
The same value as the compile path reads it. One call rather than a cast at every field, and the
|
|
142
|
+
one place the two views of a codec meet.
|
|
143
|
+
]]
|
|
144
|
+
function Base.cell (of: Types.Codec<any>): Cell return of :: any end
|
|
145
|
+
|
|
146
|
+
-- The other way round, which is one table read two ways and never two tables.
|
|
147
|
+
function Base.codec (of: Cell): Types.Codec<any> return of :: any end
|
|
148
|
+
|
|
149
|
+
-- Whether a value came from here, which is what a composite asks of every child handed to it.
|
|
150
|
+
function Base.made (value: unknown): boolean return getmetatable (value :: any) == Modifiers end
|
|
151
|
+
|
|
152
|
+
--[[
|
|
153
|
+
Checks run in declaration order and the first reason wins, so a chain appends rather than
|
|
154
|
+
replaces. The array is frozen with the cell: a codec is a value, and a value whose checks could
|
|
155
|
+
be appended to from outside would be two schemas under one hash.
|
|
156
|
+
]]
|
|
157
|
+
function Modifiers.validate <T>(
|
|
158
|
+
self: Types.Codec<T>,
|
|
159
|
+
check: (value: T, ctx: Types.ValidateContext) -> string?
|
|
160
|
+
): Types.Codec<T>
|
|
161
|
+
local checks = table.clone (Base.cell (self).validates)
|
|
162
|
+
table.insert (checks, check :: Validate)
|
|
163
|
+
|
|
164
|
+
local made = opened (self)
|
|
165
|
+
made.validates = table.freeze (checks)
|
|
166
|
+
return sealed (made)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
--[[
|
|
170
|
+
The rate goes with the marker it belonged to, so a chain never leaves one behind a marker that
|
|
171
|
+
has none and a descriptor never writes a rate onto `monotonic`.
|
|
172
|
+
]]
|
|
173
|
+
function Modifiers.monotonic <T>(self: Types.Codec<T>): Types.Codec<T>
|
|
174
|
+
local made = opened (self)
|
|
175
|
+
made.marker = "monotonic"
|
|
176
|
+
made.newestHz = nil
|
|
177
|
+
return sealed (made)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
--[[
|
|
181
|
+
A rate of zero never opens its gate and a negative one always does, so both are a stream that
|
|
182
|
+
silently does not do what it says. Whether the marker belongs on this field at all is a
|
|
183
|
+
question about the shape underneath it, and is asked when the namespace starts.
|
|
184
|
+
]]
|
|
185
|
+
function Modifiers.newest <T>(self: Types.Codec<T>, hz: number?): Types.Codec<T>
|
|
186
|
+
if hz ~= nil and not (hz > 0 and hz < math.huge) then
|
|
187
|
+
Log.refuse (`newest takes a rate above zero, not {hz}`)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
local made = opened (self)
|
|
191
|
+
made.marker = "newest"
|
|
192
|
+
made.newestHz = hz
|
|
193
|
+
return sealed (made)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
--[[
|
|
197
|
+
A pair is adopted whole where there is none beneath it, and composed where there is. A datatype
|
|
198
|
+
is an `as` over its wire form, so the common case is the first one and a wrapper call per value
|
|
199
|
+
would tax every vector in every frame.
|
|
200
|
+
]]
|
|
201
|
+
function Modifiers.as <T, U>(
|
|
202
|
+
self: Types.Codec<T>,
|
|
203
|
+
to: (value: T) -> U,
|
|
204
|
+
from: (value: U) -> T
|
|
205
|
+
): Types.Codec<U>
|
|
206
|
+
local held = Base.cell (self)
|
|
207
|
+
local lift, lower = held.lift, held.lower
|
|
208
|
+
|
|
209
|
+
local lifted = if lift == nil
|
|
210
|
+
then to :: any
|
|
211
|
+
else function (wire: any) return to (lift (wire)) end
|
|
212
|
+
local lowered = if lower == nil
|
|
213
|
+
then from
|
|
214
|
+
else function (domain: any) return lower (from (domain)) end
|
|
215
|
+
|
|
216
|
+
local made = opened (self)
|
|
217
|
+
made.lift = lifted
|
|
218
|
+
made.lower = lowered
|
|
219
|
+
|
|
220
|
+
local checks = held.validates
|
|
221
|
+
local reach = #checks
|
|
222
|
+
if reach == 0 then return sealed (made) end
|
|
223
|
+
|
|
224
|
+
local under = table.create (reach) :: { Validate }
|
|
225
|
+
for index = 1, reach do
|
|
226
|
+
local check = checks[index]
|
|
227
|
+
under[index] = function (value: any, ctx: Types.ValidateContext): string?
|
|
228
|
+
return check (from (value :: any), ctx)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
made.validates = table.freeze (under)
|
|
233
|
+
return sealed (made)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
return table.freeze (Base)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- The seven composites: five that record a codec under a name, and two that record names alone.
|
|
4
|
+
|
|
5
|
+
local Base = require (script.Parent.Base)
|
|
6
|
+
local Legality = require (script.Parent.Legality)
|
|
7
|
+
local Log = require (script.Parent.Parent.kernel.Log)
|
|
8
|
+
local Types = require (script.Parent.Parent.Types)
|
|
9
|
+
|
|
10
|
+
local Composites = {}
|
|
11
|
+
|
|
12
|
+
-- Private -------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
-- A list of distinct names, answered in the one order two machines both arrive at.
|
|
15
|
+
local function labelled (kind: string, names: { string }): { string }
|
|
16
|
+
if type (names) ~= "table" then
|
|
17
|
+
Log.refuse (`{kind} takes a list of names, not a {typeof (names)}`)
|
|
18
|
+
end
|
|
19
|
+
if #names == 0 then Log.refuse (`{kind} takes at least one name`) end
|
|
20
|
+
|
|
21
|
+
local seen = {}
|
|
22
|
+
for index = 1, #names do
|
|
23
|
+
local name = names[index]
|
|
24
|
+
if type (name) ~= "string" then
|
|
25
|
+
Log.refuse (`{kind} takes names, and the one at {index} is a {typeof (name)}`)
|
|
26
|
+
end
|
|
27
|
+
if seen[name] ~= nil then
|
|
28
|
+
Log.refuse (`{kind} takes each name once, and {name} is twice`)
|
|
29
|
+
end
|
|
30
|
+
seen[name] = true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
local held = table.clone (names)
|
|
34
|
+
table.sort (held)
|
|
35
|
+
return held
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
-- Public --------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
--[[
|
|
41
|
+
The caller's own names, which are the only child names not chosen here. The table is taken as
|
|
42
|
+
it comes, since a frozen one satisfies no indexer in this solver, so each field is asked what it
|
|
43
|
+
is rather than the type above. No field at all is a record with nothing in it.
|
|
44
|
+
]]
|
|
45
|
+
function Composites.struct <S>(fields: S): Types.Codec<any>
|
|
46
|
+
if type (fields) ~= "table" then
|
|
47
|
+
Log.refuse (`struct takes a table of fields, not a {typeof (fields)}`)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
for name, of in fields :: any do
|
|
51
|
+
if type (name) ~= "string" then
|
|
52
|
+
Log.refuse (`struct takes named fields, and {tostring (name)} is a {typeof (name)}`)
|
|
53
|
+
end
|
|
54
|
+
Legality.codec ("struct", name, of)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
return Base.make ("struct", nil, fields :: any)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
function Composites.array <T>(element: Types.Codec<T>, min: number, max: number): Types.Codec<{ T }>
|
|
61
|
+
Legality.bare ("array", "element", element)
|
|
62
|
+
Legality.counts ("array", min, max)
|
|
63
|
+
|
|
64
|
+
return Base.make ("array", { min = min, max = max }, { element = element })
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
function Composites.map <K, V>(
|
|
68
|
+
key: Types.Codec<K>,
|
|
69
|
+
value: Types.Codec<V>,
|
|
70
|
+
min: number,
|
|
71
|
+
max: number
|
|
72
|
+
): Types.Codec<{ [K]: V }>
|
|
73
|
+
Legality.bare ("map", "key", key)
|
|
74
|
+
Legality.bare ("map", "value", value)
|
|
75
|
+
Legality.counts ("map", min, max)
|
|
76
|
+
|
|
77
|
+
return Base.make ("map", { min = min, max = max }, { key = key, value = value })
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
function Composites.optional <T>(inner: Types.Codec<T>): Types.Codec<T?>
|
|
81
|
+
Legality.bare ("optional", "inner", inner)
|
|
82
|
+
return Base.make ("optional", nil, { inner = inner })
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
--[[
|
|
86
|
+
The tag rides inside the record rather than beside it, so every variant is a struct and none of
|
|
87
|
+
them may already declare the field it is written into. One that did would have its own value
|
|
88
|
+
overwritten going out and read back as the tag coming in.
|
|
89
|
+
]]
|
|
90
|
+
function Composites.tagged <V>(field: string, variants: V): Types.Codec<any>
|
|
91
|
+
if type (field) ~= "string" or field == "" then
|
|
92
|
+
Log.refuse (`tagged takes a field name, not {tostring (field)}`)
|
|
93
|
+
end
|
|
94
|
+
if type (variants) ~= "table" then
|
|
95
|
+
Log.refuse (`tagged takes a table of variants, not a {typeof (variants)}`)
|
|
96
|
+
end
|
|
97
|
+
if next (variants :: any) == nil then Log.refuse ("tagged takes at least one variant") end
|
|
98
|
+
|
|
99
|
+
for name, of in variants :: any do
|
|
100
|
+
if type (name) ~= "string" then
|
|
101
|
+
Log.refuse (`tagged takes named variants, and {tostring (name)} is a {typeof (name)}`)
|
|
102
|
+
end
|
|
103
|
+
Legality.codec ("tagged", name, of)
|
|
104
|
+
|
|
105
|
+
if Base.cell (of).kind ~= "struct" then
|
|
106
|
+
Log.refuse (`tagged takes a struct for {name}, not a {Base.cell (of).kind}`)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
local held = Base.cell (of).children
|
|
110
|
+
if held ~= nil and held[field] ~= nil then
|
|
111
|
+
Log.refuse (`the variant {name} already declares {field}, which is the tag`)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
return Base.make ("tagged", { field = field }, variants :: any)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
-- One of the names, by its place in the canonical order rather than in the order it was written.
|
|
119
|
+
function Composites.enum (names: { string }): Types.Codec<string>
|
|
120
|
+
return Base.make ("enum", { names = labelled ("enum", names) })
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
-- One bit per name, in the same order, and a record of every one of them either way.
|
|
124
|
+
function Composites.bitfield (names: { string }): Types.Codec<{ [string]: boolean }>
|
|
125
|
+
return Base.make ("bitfield", { names = labelled ("bitfield", names) })
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
return table.freeze (Composites)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- The six Roblox datatypes: what each one declares, and never how it converts.
|
|
4
|
+
|
|
5
|
+
local Base = require (script.Parent.Base)
|
|
6
|
+
local Legality = require (script.Parent.Legality)
|
|
7
|
+
local Log = require (script.Parent.Parent.kernel.Log)
|
|
8
|
+
local Scalars = require (script.Parent.Scalars)
|
|
9
|
+
local Types = require (script.Parent.Parent.Types)
|
|
10
|
+
|
|
11
|
+
local Datatypes = {}
|
|
12
|
+
|
|
13
|
+
-- Private -------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
--[[
|
|
16
|
+
A form is recorded wherever a name carries more than one, because a form is a different wire
|
|
17
|
+
object rather than a different parameter. The lowering then reads one field and branches,
|
|
18
|
+
instead of working out which parameters happen to be absent.
|
|
19
|
+
]]
|
|
20
|
+
local function turning (_: any, component: Types.Codec<any>?): Types.Codec<Vector3>
|
|
21
|
+
if component == nil then return Base.make ("vec3", { form = "components" }) end
|
|
22
|
+
|
|
23
|
+
Legality.bare ("vec3", "component", component)
|
|
24
|
+
return Base.make ("vec3", { form = "components" }, { component = component })
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
local function unit (precision: number): Types.Codec<Vector3>
|
|
28
|
+
Scalars.precise ("vec3.unit", precision)
|
|
29
|
+
return Base.make ("vec3", { form = "unit", step = precision })
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
local function bounded (max: number, step: number): Types.Codec<Vector3>
|
|
33
|
+
if not (Legality.finite (max) and max > 0) then
|
|
34
|
+
Log.refuse (`vec3.bounded takes a radius above zero, not {max}`)
|
|
35
|
+
end
|
|
36
|
+
if not (Legality.finite (step) and step > 0) then
|
|
37
|
+
Log.refuse (`vec3.bounded takes a step above zero, not {step}`)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
return Base.make ("vec3", { form = "bounded", max = max, step = step })
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
local function still (): Types.Codec<CFrame> return Base.make ("rotation", { form = "none" }) end
|
|
44
|
+
|
|
45
|
+
--[[
|
|
46
|
+
The axis is recorded as it was given rather than normalised. Any positive multiple of it is the
|
|
47
|
+
same turn and would lower the same way, so canonicalising it would change what a caller wrote to
|
|
48
|
+
make two descriptors agree that no two builds can disagree about.
|
|
49
|
+
]]
|
|
50
|
+
local function about (axis: Vector3, precision: number): Types.Codec<CFrame>
|
|
51
|
+
if typeof (axis) ~= "Vector3" then
|
|
52
|
+
Log.refuse (`rotation.axis takes a Vector3 to turn about, not a {typeof (axis)}`)
|
|
53
|
+
end
|
|
54
|
+
if not (Legality.finite (axis.Magnitude) and axis.Magnitude > 0) then
|
|
55
|
+
Log.refuse (`rotation.axis takes an axis with a direction, not {tostring (axis)}`)
|
|
56
|
+
end
|
|
57
|
+
Scalars.precise ("rotation.axis", precision)
|
|
58
|
+
|
|
59
|
+
return Base.make ("rotation", { form = "axis", axis = axis, step = precision })
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
local function facing (precision: number): Types.Codec<CFrame>
|
|
63
|
+
Scalars.precise ("rotation.direction", precision)
|
|
64
|
+
return Base.make ("rotation", { form = "direction", step = precision })
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
local function quat (precision: number): Types.Codec<CFrame>
|
|
68
|
+
Scalars.precise ("rotation.quat", precision)
|
|
69
|
+
return Base.make ("rotation", { form = "quat", step = precision })
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
local function packed (): Types.Codec<Color3> return Base.make ("color3", { form = "rgb565" }) end
|
|
73
|
+
|
|
74
|
+
--[[
|
|
75
|
+
Read by their components rather than by identity, because a colour is a userdata and two of them
|
|
76
|
+
equal by value are two keys in a table. A repeat costs the same here as a repeated symbol in
|
|
77
|
+
an alphabet does, which is an index nothing can ever be encoded as.
|
|
78
|
+
]]
|
|
79
|
+
local function palette (colors: { Color3 }): Types.Codec<Color3>
|
|
80
|
+
if type (colors) ~= "table" then
|
|
81
|
+
Log.refuse (`color3.palette takes a list of colours, not a {typeof (colors)}`)
|
|
82
|
+
end
|
|
83
|
+
if #colors == 0 then Log.refuse ("color3.palette takes at least one colour") end
|
|
84
|
+
|
|
85
|
+
local seen = {}
|
|
86
|
+
for index = 1, #colors do
|
|
87
|
+
local color = colors[index]
|
|
88
|
+
if typeof (color) ~= "Color3" then
|
|
89
|
+
Log.refuse (`color3.palette takes a colour at {index}, not a {typeof (color)}`)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
local at = `{color.R} {color.G} {color.B}`
|
|
93
|
+
if seen[at] ~= nil then
|
|
94
|
+
Log.refuse (`color3.palette takes each colour once, and the one at {index} is twice`)
|
|
95
|
+
end
|
|
96
|
+
seen[at] = true
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
return Base.make ("color3", { form = "palette", colors = colors })
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
-- Three floats, which is what a colour is where nothing narrower was asked for.
|
|
103
|
+
local function coloured (_: any): Types.Codec<Color3>
|
|
104
|
+
return Base.make ("color3", { form = "components" })
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
-- Public --------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
-- Two components in the codec given, or two floats where none was.
|
|
110
|
+
function Datatypes.vec2 (component: Types.Codec<any>?): Types.Codec<Vector2>
|
|
111
|
+
if component == nil then return Base.make ("vec2") end
|
|
112
|
+
|
|
113
|
+
Legality.bare ("vec2", "component", component)
|
|
114
|
+
return Base.make ("vec2", nil, { component = component })
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
Datatypes.vec3 = table.freeze (setmetatable ({ unit = unit, bounded = bounded }, {
|
|
118
|
+
__call = turning,
|
|
119
|
+
}))
|
|
120
|
+
|
|
121
|
+
--[[
|
|
122
|
+
A position codec and a rotation codec, and neither is held to a kind. `as` is how a caller
|
|
123
|
+
reaches a datatype this vocabulary does not name, so a position built out of a struct is a
|
|
124
|
+
position, and what it lifts to is the caller's promise rather than something a kind can say.
|
|
125
|
+
]]
|
|
126
|
+
function Datatypes.cframe (
|
|
127
|
+
position: Types.Codec<any>,
|
|
128
|
+
rotation: Types.Codec<any>
|
|
129
|
+
): Types.Codec<CFrame>
|
|
130
|
+
Legality.bare ("cframe", "position", position)
|
|
131
|
+
Legality.bare ("cframe", "rotation", rotation)
|
|
132
|
+
|
|
133
|
+
return Base.make ("cframe", nil, { position = position, rotation = rotation })
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
-- Four ways to spell a turn and no default among them, so the name itself answers nothing.
|
|
137
|
+
Datatypes.rotation = table.freeze ({
|
|
138
|
+
none = still,
|
|
139
|
+
axis = about,
|
|
140
|
+
direction = facing,
|
|
141
|
+
quat = quat,
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
Datatypes.color3 = table.freeze (setmetatable ({ rgb565 = packed, palette = palette }, {
|
|
145
|
+
__call = coloured,
|
|
146
|
+
}))
|
|
147
|
+
|
|
148
|
+
--[[
|
|
149
|
+
Always optional, since a receiver that cannot see the instance is handed nothing and that is a
|
|
150
|
+
normal outcome rather than a drop. The class is a name to check against on the way in and
|
|
151
|
+
nothing here can tell a real one from a typo, so it is only held.
|
|
152
|
+
]]
|
|
153
|
+
function Datatypes.inst (class: string?): Types.Codec<Instance?>
|
|
154
|
+
if class == nil then return Base.make ("inst") end
|
|
155
|
+
if type (class) ~= "string" or class == "" then
|
|
156
|
+
Log.refuse (`inst takes a class name, not {tostring (class)}`)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
return Base.make ("inst", { class = class })
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
return table.freeze (Datatypes)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- What a constructor will take: the tests every one of them makes of a parameter or a child.
|
|
4
|
+
|
|
5
|
+
local Base = require (script.Parent.Base)
|
|
6
|
+
local Log = require (script.Parent.Parent.kernel.Log)
|
|
7
|
+
local Types = require (script.Parent.Parent.Types)
|
|
8
|
+
|
|
9
|
+
local Legality = {}
|
|
10
|
+
|
|
11
|
+
-- Public --------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
--[[
|
|
14
|
+
The annotation is erased at run time, so a numeric string would be coerced downstream and
|
|
15
|
+
recorded as a parameter. A NaN answers false to both comparisons, which is the whole test.
|
|
16
|
+
]]
|
|
17
|
+
function Legality.finite (value: number): boolean
|
|
18
|
+
return type (value) == "number" and value > -math.huge and value < math.huge
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
-- Whole, and on the line with it: an infinity and a NaN both leave a NaN behind in the modulo.
|
|
22
|
+
function Legality.whole (value: number): boolean
|
|
23
|
+
if type (value) ~= "number" then return false end
|
|
24
|
+
return value % 1 == 0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
--[[
|
|
28
|
+
A count range, which every counted kind takes and none of them takes differently. Zero is a
|
|
29
|
+
legal floor and a legal ceiling, so an empty string and an empty list are both declarable, and
|
|
30
|
+
the bounds may meet, which is a count of no bits.
|
|
31
|
+
]]
|
|
32
|
+
function Legality.counts (kind: string, min: number, max: number)
|
|
33
|
+
if not (Legality.whole (min) and Legality.whole (max)) then
|
|
34
|
+
Log.refuse (`{kind} takes whole bounds, not {min} to {max}`)
|
|
35
|
+
end
|
|
36
|
+
if min < 0 then Log.refuse (`{kind} takes bounds from zero up, not {min}`) end
|
|
37
|
+
if min > max then Log.refuse (`{kind} takes its bounds in order, not {min} to {max}`) end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
--[[
|
|
41
|
+
A child, refused where it is anything else. Every composite takes one and the mistake a caller
|
|
42
|
+
makes is handing over the value they meant to describe, so the refusal names what arrived as
|
|
43
|
+
well as where it was wanted.
|
|
44
|
+
]]
|
|
45
|
+
function Legality.codec (kind: string, at: string, of: Types.Codec<any>)
|
|
46
|
+
if not Base.made (of) then Log.refuse (`{kind} takes a codec for {at}, not a {typeof (of)}`) end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
--[[
|
|
50
|
+
A child, and the one place an optional may not be. Two slots cannot tell an absent value from an
|
|
51
|
+
absent slot: a list element and a map entry are read back by their presence, so an optional
|
|
52
|
+
there decodes as a hole, and an optional inside an optional is one absence written twice.
|
|
53
|
+
]]
|
|
54
|
+
function Legality.bare (kind: string, at: string, of: Types.Codec<any>)
|
|
55
|
+
Legality.codec (kind, at, of)
|
|
56
|
+
if Base.cell (of).kind == "optional" then
|
|
57
|
+
Log.refuse (`{kind} takes no optional for {at}, since an absent slot is already absent`)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
return table.freeze (Legality)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- The eight scalar constructors, and the parameters each of them will take.
|
|
4
|
+
|
|
5
|
+
local Base = require (script.Parent.Base)
|
|
6
|
+
local Legality = require (script.Parent.Legality)
|
|
7
|
+
local Log = require (script.Parent.Parent.kernel.Log)
|
|
8
|
+
local Types = require (script.Parent.Parent.Types)
|
|
9
|
+
|
|
10
|
+
local Scalars = {}
|
|
11
|
+
|
|
12
|
+
-- Constants -----------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
-- A whole turn, in the degrees every angle is stated in, and the one home of that number.
|
|
15
|
+
Scalars.TURN = 360
|
|
16
|
+
|
|
17
|
+
-- Public --------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
-- An angular precision, in the degrees every one of them is spelled in.
|
|
20
|
+
function Scalars.precise (kind: string, degrees: number)
|
|
21
|
+
if not (Legality.finite (degrees) and degrees > 0 and degrees <= Scalars.TURN) then
|
|
22
|
+
Log.refuse (`{kind} takes a precision from zero to {Scalars.TURN}, not {degrees}`)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
function Scalars.bool (): Types.Codec<boolean> return Base.make ("bool") end
|
|
27
|
+
|
|
28
|
+
--[[
|
|
29
|
+
Bounds are inclusive and may meet, which is a field of no bits carrying one value. How wide
|
|
30
|
+
the range may be is left alone here for the same reason a count is, since what a frame holds is
|
|
31
|
+
a fact about the wire, and a refusal at start carries a declaration site this cannot name.
|
|
32
|
+
]]
|
|
33
|
+
function Scalars.int (min: number, max: number): Types.Codec<number>
|
|
34
|
+
if not (Legality.whole (min) and Legality.whole (max)) then
|
|
35
|
+
Log.refuse (`int takes whole bounds, not {min} to {max}`)
|
|
36
|
+
end
|
|
37
|
+
if min > max then Log.refuse (`int takes its bounds in order, not {min} to {max}`) end
|
|
38
|
+
|
|
39
|
+
return Base.make ("int", { min = min, max = max })
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
--[[
|
|
43
|
+
A step need not divide the range. The grid keeps the points it has and the nearest one to the
|
|
44
|
+
top is the last, so a range holding two and a half steps is three points and not four.
|
|
45
|
+
]]
|
|
46
|
+
function Scalars.quant (min: number, max: number, step: number): Types.Codec<number>
|
|
47
|
+
if not (Legality.finite (min) and Legality.finite (max)) then
|
|
48
|
+
Log.refuse (`quant takes bounds on the line, not {min} to {max}`)
|
|
49
|
+
end
|
|
50
|
+
if min > max then Log.refuse (`quant takes its bounds in order, not {min} to {max}`) end
|
|
51
|
+
if not (Legality.finite (step) and step > 0) then
|
|
52
|
+
Log.refuse (`quant takes a step above zero, not {step}`)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
return Base.make ("quant", { min = min, max = max, step = step })
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
-- One parameter, since the rest of an angle is what an angle is: zero to a turn, and cyclic.
|
|
59
|
+
function Scalars.angle (precision: number): Types.Codec<number>
|
|
60
|
+
Scalars.precise ("angle", precision)
|
|
61
|
+
return Base.make ("angle", { step = precision })
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
function Scalars.f32 (): Types.Codec<number> return Base.make ("f32") end
|
|
65
|
+
|
|
66
|
+
function Scalars.f64 (): Types.Codec<number> return Base.make ("f64") end
|
|
67
|
+
|
|
68
|
+
function Scalars.vlq (): Types.Codec<number> return Base.make ("vlq") end
|
|
69
|
+
|
|
70
|
+
function Scalars.vli (): Types.Codec<number> return Base.make ("vli") end
|
|
71
|
+
|
|
72
|
+
return table.freeze (Scalars)
|