@axpecter/lync 2.3.3 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +302 -299
  3. package/Types.luau +215 -0
  4. package/api/Define.luau +154 -0
  5. package/api/Lifecycle.luau +119 -0
  6. package/api/Packet.luau +108 -0
  7. package/api/Query.luau +149 -0
  8. package/api/Set.luau +189 -0
  9. package/channel/Blocks.luau +124 -0
  10. package/channel/Group.luau +118 -0
  11. package/channel/Link.luau +135 -0
  12. package/channel/Outbox.luau +261 -0
  13. package/channel/Recipients.luau +129 -0
  14. package/channel/Transport.luau +138 -0
  15. package/codec/Base.luau +236 -0
  16. package/codec/Composites.luau +128 -0
  17. package/codec/Datatypes.luau +162 -0
  18. package/codec/Legality.luau +61 -0
  19. package/codec/Scalars.luau +72 -0
  20. package/codec/Text.luau +89 -0
  21. package/codec/init.luau +39 -0
  22. package/index.d.ts +285 -0
  23. package/init.luau +91 -0
  24. package/kernel/Bits.luau +16 -0
  25. package/kernel/Catalog.luau +121 -0
  26. package/kernel/Heap.luau +80 -0
  27. package/kernel/Listeners.luau +95 -0
  28. package/kernel/Log.luau +197 -0
  29. package/kernel/Tally.luau +128 -0
  30. package/kernel/Threads.luau +44 -0
  31. package/kernel/Wide.luau +267 -0
  32. package/package.json +15 -32
  33. package/runtime/Boundary.luau +180 -0
  34. package/runtime/Dispatch.luau +91 -0
  35. package/runtime/Handles.luau +114 -0
  36. package/runtime/Handshake.luau +90 -0
  37. package/runtime/Pending.luau +196 -0
  38. package/runtime/Router.luau +379 -0
  39. package/runtime/Session.luau +731 -0
  40. package/shape/Compile.luau +340 -0
  41. package/shape/Folds.luau +332 -0
  42. package/shape/Frames.luau +170 -0
  43. package/shape/Grid.luau +169 -0
  44. package/shape/Ir.luau +540 -0
  45. package/shape/Joins.luau +125 -0
  46. package/shape/Legality.luau +273 -0
  47. package/shape/Lower.luau +419 -0
  48. package/shape/Order.luau +64 -0
  49. package/shape/Regions.luau +113 -0
  50. package/shape/Rotation.luau +472 -0
  51. package/shape/Serial.luau +258 -0
  52. package/shape/Words.luau +89 -0
  53. package/shape/emit/Block.luau +453 -0
  54. package/shape/emit/Column.luau +338 -0
  55. package/shape/emit/Leaf.luau +462 -0
  56. package/shape/emit/Word.luau +234 -0
  57. package/shape/emit/init.luau +222 -0
  58. package/shape/init.luau +78 -0
  59. package/state/Apply.luau +330 -0
  60. package/state/Audience.luau +126 -0
  61. package/state/Catchup.luau +218 -0
  62. package/state/Coalesce.luau +43 -0
  63. package/state/Fieldset.luau +177 -0
  64. package/state/Newest.luau +339 -0
  65. package/state/Plan.luau +441 -0
  66. package/state/Sections.luau +468 -0
  67. package/state/Store.luau +188 -0
  68. package/state/Sync.luau +762 -0
  69. package/wire/Bytes.luau +118 -0
  70. package/wire/Clock.luau +37 -0
  71. package/wire/Protocol.luau +110 -0
  72. package/wire/Sequence.luau +40 -0
  73. package/wire/Varint.luau +124 -0
  74. package/src/Types.luau +0 -180
  75. package/src/api/Group.luau +0 -156
  76. package/src/api/Packet.luau +0 -317
  77. package/src/api/Query.luau +0 -338
  78. package/src/api/Scope.luau +0 -90
  79. package/src/api/Signal.luau +0 -193
  80. package/src/codec/Base.luau +0 -136
  81. package/src/codec/composite/Array.luau +0 -473
  82. package/src/codec/composite/Map.luau +0 -485
  83. package/src/codec/composite/Optional.luau +0 -54
  84. package/src/codec/composite/Shared.luau +0 -311
  85. package/src/codec/composite/Struct.luau +0 -494
  86. package/src/codec/composite/Tagged.luau +0 -103
  87. package/src/codec/composite/Tuple.luau +0 -143
  88. package/src/codec/datatype/Buffer.luau +0 -66
  89. package/src/codec/datatype/CFrame.luau +0 -109
  90. package/src/codec/datatype/Color.luau +0 -32
  91. package/src/codec/datatype/Instance.luau +0 -52
  92. package/src/codec/datatype/IntVector.luau +0 -39
  93. package/src/codec/datatype/NumberRange.luau +0 -39
  94. package/src/codec/datatype/Ray.luau +0 -36
  95. package/src/codec/datatype/Rect.luau +0 -31
  96. package/src/codec/datatype/Region.luau +0 -62
  97. package/src/codec/datatype/Sequence.luau +0 -184
  98. package/src/codec/datatype/String.luau +0 -94
  99. package/src/codec/datatype/UDim.luau +0 -46
  100. package/src/codec/datatype/Vector.luau +0 -130
  101. package/src/codec/meta/Auto.luau +0 -318
  102. package/src/codec/meta/Bitfield.luau +0 -160
  103. package/src/codec/meta/Custom.luau +0 -33
  104. package/src/codec/meta/DeltaScalar.luau +0 -390
  105. package/src/codec/meta/Enum.luau +0 -50
  106. package/src/codec/meta/Float.luau +0 -38
  107. package/src/codec/meta/Nothing.luau +0 -13
  108. package/src/codec/meta/Unknown.luau +0 -41
  109. package/src/codec/primitive/Bool.luau +0 -26
  110. package/src/codec/primitive/Float16.luau +0 -99
  111. package/src/codec/primitive/Int.luau +0 -59
  112. package/src/codec/primitive/Number.luau +0 -24
  113. package/src/codec/primitive/Signed.luau +0 -32
  114. package/src/codec/primitive/Varint.luau +0 -165
  115. package/src/codec/primitive/Zint.luau +0 -99
  116. package/src/index.d.ts +0 -423
  117. package/src/init.luau +0 -362
  118. package/src/internal/Baseline.luau +0 -57
  119. package/src/internal/Channel.luau +0 -484
  120. package/src/internal/Middleware.luau +0 -100
  121. package/src/internal/Pool.luau +0 -66
  122. package/src/internal/Registry.luau +0 -119
  123. package/src/internal/Transport.luau +0 -28
  124. package/src/transport/Bridge.luau +0 -110
  125. package/src/transport/Client.luau +0 -97
  126. package/src/transport/Gate.luau +0 -488
  127. package/src/transport/Reader.luau +0 -331
  128. package/src/transport/Server.luau +0 -233
  129. package/src/util/Array.luau +0 -18
  130. package/src/util/Buffer.luau +0 -90
  131. package/src/util/Constants.luau +0 -30
  132. package/src/util/Log.luau +0 -68
  133. package/src/util/Player.luau +0 -14
  134. package/src/util/Quantize.luau +0 -84
  135. package/src/util/Quat.luau +0 -124
@@ -0,0 +1,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)