@skating/swiftpacket 0.1.4

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 skaterstudios (skaterstudios.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # SwiftPacket
2
+
3
+ [![CI](https://github.com/skatingii/SwiftPacket/actions/workflows/ci.yml/badge.svg)](https://github.com/skatingii/SwiftPacket/actions/workflows/ci.yml)
4
+ [![License](https://img.shields.io/github/license/skatingii/SwiftPacket)](LICENSE)
5
+
6
+ A fast, fully typed, buffer-batched networking library for Roblox.
7
+
8
+ Describe each packet once, with the types of the values it carries, then fire it
9
+ like a function. SwiftPacket writes every message into one buffer per player,
10
+ sends it once per frame, and checks everything a client sends before your code
11
+ sees it.
12
+
13
+ - **Typed**: `Fire()`, handlers and responses are checked by the Luau type
14
+ checker, without a single cast.
15
+ - **Small**: every value is written in the fewest bytes its type allows, with no
16
+ type tags on the wire.
17
+ - **Batched**: one remote call per player per frame, for reliable and unreliable
18
+ packets.
19
+ - **Ordered**: messages to a player arrive in the order they were fired, events
20
+ and requests alike.
21
+ - **Hardened**: bounds checks, rejection of NaN and infinity, a per-player byte
22
+ budget, rate limits and validators.
23
+
24
+ ## Installation
25
+
26
+ With [Wally](https://wally.run), add SwiftPacket to your `wally.toml`:
27
+
28
+ ```toml
29
+ [dependencies]
30
+ SwiftPacket = "skatingii/swiftpacket@0.1.4"
31
+ ```
32
+
33
+ With [roblox-ts](https://roblox-ts.com), install it from npm:
34
+
35
+ ```sh
36
+ npm install @skating/swiftpacket
37
+ ```
38
+
39
+ Add the `@skating` scope to your project so roblox-ts can find it. In
40
+ `tsconfig.json`:
41
+
42
+ ```json
43
+ "typeRoots": ["node_modules/@rbxts", "node_modules/@skating"]
44
+ ```
45
+
46
+ And in `default.project.json`, next to `@rbxts` under `node_modules`:
47
+
48
+ ```json
49
+ "@skating": { "$path": "node_modules/@skating" }
50
+ ```
51
+
52
+ Then import it:
53
+
54
+ ```ts
55
+ import SwiftPacket from "@skating/swiftpacket";
56
+
57
+ const Chat = SwiftPacket("Chat", SwiftPacket.String);
58
+ const GetCoins = SwiftPacket("GetCoins").Response(SwiftPacket.U32);
59
+ ```
60
+
61
+ Or download `SwiftPacket.rbxm` from the
62
+ [latest release](https://github.com/skatingii/SwiftPacket/releases/latest) and put
63
+ it in `ReplicatedStorage`.
64
+
65
+ ## Example
66
+
67
+ Define your packets in one module that both the server and the client require:
68
+
69
+ ```luau
70
+ const SwiftPacket = require("@game/ReplicatedStorage/Packages/SwiftPacket")
71
+
72
+ return {
73
+ Chat = SwiftPacket("Chat", SwiftPacket.String):RateLimit(5, 1),
74
+ Move = SwiftPacket("Move", SwiftPacket.Vector3F24):Unreliable(),
75
+ GetCoins = SwiftPacket("GetCoins"):Response(SwiftPacket.U32),
76
+ Inventory = SwiftPacket("Inventory", {
77
+ Coins = SwiftPacket.U32,
78
+ Items = { SwiftPacket.String },
79
+ }),
80
+ }
81
+ ```
82
+
83
+ On the server:
84
+
85
+ ```luau
86
+ const Packets = require("@game/ReplicatedStorage/Packets")
87
+
88
+ Packets.Chat.OnServerEvent:Connect(function(Player, Message)
89
+ Packets.Chat:FireExcept(Player, `{Player.Name}: {Message}`)
90
+ end)
91
+
92
+ Packets.GetCoins:SetServerInvoke(function(Player)
93
+ return 100
94
+ end)
95
+ ```
96
+
97
+ On the client:
98
+
99
+ ```luau
100
+ const Packets = require("@game/ReplicatedStorage/Packets")
101
+
102
+ Packets.Chat:Fire("hello")
103
+
104
+ const Coins = Packets.GetCoins:Fire()
105
+ ```
106
+
107
+ ## Documentation
108
+
109
+ - [Crash course](docs/tut/crash-course/1-introduction.md): packets, events,
110
+ requests, types and security, one page each.
111
+ - [API reference](docs/api/swiftpacket.md): every function and type.
112
+ - [Migrating from Packet](docs/tut/crash-course/8-migrating-from-packet.md):
113
+ what changes if you are coming from Packet.
114
+
115
+ ## Security
116
+
117
+ Please do not report a vulnerability in a public issue. See
118
+ [SECURITY.md](SECURITY.md) for how to report one privately.
119
+
120
+ ---
121
+
122
+ SwiftPacket is released under the [MIT License](LICENSE) by
123
+ [skaterstudios](https://skaterstudios.com).
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@skating/swiftpacket",
3
+ "version": "0.1.4",
4
+ "description": "A fast, fully typed, buffer-batched networking library for Roblox",
5
+ "main": "src/init.luau",
6
+ "types": "src/index.d.ts",
7
+ "files": [
8
+ "src"
9
+ ],
10
+ "keywords": [
11
+ "roblox",
12
+ "roblox-ts",
13
+ "luau",
14
+ "networking",
15
+ "remote",
16
+ "buffer"
17
+ ],
18
+ "author": "skaterstudios",
19
+ "license": "MIT",
20
+ "homepage": "https://github.com/skatingii/SwiftPacket#readme",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/skatingii/SwiftPacket.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/skatingii/SwiftPacket/issues"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ }
31
+ }
@@ -0,0 +1,472 @@
1
+ --!strict
2
+ --!optimize 2
3
+
4
+ --[[
5
+ Author: @skaterstudios
6
+ Website: skaterstudios.com
7
+ GitHub: github.com/skatingii/SwiftPacket
8
+ Discord Server: discord.gg/skaterstudios
9
+
10
+ Licensed under the MIT License. You may use, modify, redistribute and sell
11
+ this system freely, as long as this notice and the LICENSE file stay with it.
12
+ ]]
13
+
14
+ const RunService = game:GetService("RunService")
15
+
16
+ const Configuration = require("./configuration")
17
+ const Cursor = require("./cursor")
18
+ const Registry = require("./registry")
19
+ const Schema = require("./schema")
20
+ const ThreadPool = require("./thread_pool")
21
+ const Wire = require("./wire")
22
+
23
+ type Definition = Registry.Definition
24
+ type Arguments = Schema.Arguments
25
+
26
+ type PendingCall = {
27
+ Thread: thread,
28
+ Deadline: number,
29
+ Definition: Definition,
30
+ }
31
+
32
+ type Held = {
33
+ Source: buffer,
34
+ Instances: { any },
35
+ Offset: number,
36
+ InstancesOffset: number,
37
+ MissingId: number,
38
+ ReceivedAt: number,
39
+ }
40
+
41
+ const Client = {}
42
+
43
+ const Reliable = Cursor.New()
44
+ const Unreliable: { Cursor.Cursor } = { Cursor.New(Configuration.UnreliableByteLimit) }
45
+ const PendingCalls: { [number]: PendingCall } = {}
46
+ const Definitions: { [string]: Definition } = {}
47
+ const Queued: { [Definition]: { Cursor.Cursor } } = {}
48
+ const Waiting: { [Definition]: { thread } } = {}
49
+ const HeldBatches: { Held } = {}
50
+ const Scratch = Cursor.New()
51
+ const Incoming = Wire.NewBatch()
52
+ const NoInstances: { any } = {}
53
+ const ResponseFlag = Wire.ResponseFlag
54
+ const EventIndex = Wire.EventIndex
55
+
56
+ local ReliableRemote: RemoteEvent? = nil
57
+ local UnreliableRemote: UnreliableRemoteEvent? = nil
58
+ local UnreliableCount = 1
59
+ local NextCallIndex = 0
60
+ local Elapsed = 0
61
+ local RetryScheduled = false
62
+
63
+ const function Deliver(Definition: Definition, Id: number, Body: Cursor.Cursor)
64
+ if Definition.Reliable then
65
+ Wire.Deliver(Reliable, Id, nil, nil, Body)
66
+
67
+ return
68
+ end
69
+
70
+ const Size = Wire.Size(Id, nil, Body)
71
+
72
+ if Size > Configuration.UnreliableByteLimit then
73
+ error(
74
+ `[swiftpacket] {Definition.Name} is {Size} bytes, over the unreliable limit of {Configuration.UnreliableByteLimit}`,
75
+ 0
76
+ )
77
+ end
78
+
79
+ local Segment = Unreliable[UnreliableCount]
80
+
81
+ if Segment.Offset > 0 and Segment.Offset + Size > Configuration.UnreliableByteLimit then
82
+ UnreliableCount += 1
83
+
84
+ const Next = Unreliable[UnreliableCount]
85
+
86
+ if Next then
87
+ Segment = Next
88
+ else
89
+ Segment = Cursor.New(Configuration.UnreliableByteLimit)
90
+ Unreliable[UnreliableCount] = Segment
91
+ end
92
+ end
93
+
94
+ Wire.Deliver(Segment, Id, nil, nil, Body)
95
+ end
96
+
97
+ function Client.Send(Definition: Definition, ...: any)
98
+ const Id = Definition.Id
99
+
100
+ if Id == nil then
101
+ const Body = Cursor.New()
102
+ Schema.WriteBody(Body, Definition.Name, Definition.Arguments, ...)
103
+
104
+ const Bodies = Queued[Definition] or {}
105
+ table.insert(Bodies, Body)
106
+ Queued[Definition] = Bodies
107
+
108
+ return
109
+ end
110
+
111
+ if Definition.Reliable then
112
+ Schema.WriteEvent(Reliable, Id, Definition.Name, Definition.Arguments, ...)
113
+
114
+ return
115
+ end
116
+
117
+ Schema.WriteBody(Scratch, Definition.Name, Definition.Arguments, ...)
118
+ Deliver(Definition, Id, Scratch)
119
+ end
120
+
121
+ const function FindCallIndex(): number
122
+ local CallIndex = NextCallIndex
123
+
124
+ for Attempt = 1, Configuration.CallCapacity do
125
+ if PendingCalls[CallIndex] == nil then
126
+ return CallIndex
127
+ end
128
+
129
+ CallIndex = (CallIndex + 1) % Configuration.CallCapacity
130
+ end
131
+
132
+ error("[swiftpacket] too many calls are waiting on a response", 0)
133
+ end
134
+
135
+ function Client.Invoke(Definition: Definition, ...: any): ...any
136
+ local CallIndex = 0
137
+
138
+ if Definition.Id == nil then
139
+ const Body = Cursor.New()
140
+ Schema.WriteBody(Body, Definition.Name, Definition.Arguments, ...)
141
+
142
+ const Threads = Waiting[Definition] or {}
143
+ table.insert(Threads, coroutine.running())
144
+ Waiting[Definition] = Threads
145
+
146
+ coroutine.yield()
147
+
148
+ const Id = Definition.Id
149
+
150
+ if Id == nil then
151
+ error(`[swiftpacket] {Definition.Name} has no id`, 0)
152
+ end
153
+
154
+ CallIndex = FindCallIndex()
155
+ Wire.Deliver(Reliable, Id, CallIndex, nil, Body)
156
+ else
157
+ CallIndex = FindCallIndex()
158
+ Schema.WriteRequest(Reliable, Definition.Id, CallIndex, Definition.Name, Definition.Arguments, ...)
159
+ end
160
+
161
+ NextCallIndex = (CallIndex + 1) % Configuration.CallCapacity
162
+ PendingCalls[CallIndex] = {
163
+ Thread = coroutine.running(),
164
+ Deadline = os.clock() + Definition.TimeoutSeconds,
165
+ Definition = Definition,
166
+ }
167
+
168
+ return coroutine.yield()
169
+ end
170
+
171
+ const function Respond(Definition: Definition, CallIndex: number, ...: any)
172
+ const Handler = Definition.OnClientInvoke
173
+ local Results: Arguments = { n = 0 }
174
+
175
+ if Handler then
176
+ Results = table.pack(pcall(Handler, ...))
177
+ else
178
+ Results = { n = 2, false, "no OnClientInvoke was set" }
179
+ end
180
+
181
+ const Id = Definition.Id
182
+
183
+ if Id == nil then
184
+ return
185
+ end
186
+
187
+ const Ok, Problem = Wire.EncodeResults(Scratch, Definition, Results)
188
+
189
+ if not Ok then
190
+ warn(`[swiftpacket] call from the server failed: {Problem}`)
191
+ end
192
+
193
+ Wire.DeliverResponse(Reliable, Id, CallIndex, if Ok then Scratch else nil)
194
+ end
195
+
196
+ const function Resolve(Definition: Definition, CallIndex: number, Success: boolean, ...: any)
197
+ const Call = PendingCalls[CallIndex]
198
+
199
+ if Call == nil or Call.Definition ~= Definition then
200
+ Configuration.Warn(`[swiftpacket] ignored an unexpected response to {Definition.Name}`)
201
+
202
+ return
203
+ end
204
+
205
+ PendingCalls[CallIndex] = nil
206
+
207
+ if Success then
208
+ task.defer(Call.Thread, ...)
209
+ else
210
+ task.defer(Call.Thread, Wire.Unpack(Definition.TimeoutValues))
211
+ end
212
+ end
213
+
214
+ const function Dispatch()
215
+ const Values = Incoming.Values
216
+
217
+ for Index = 1, Incoming.Count do
218
+ const Definition = Incoming.Definitions[Index]
219
+ const CallIndex = Incoming.CallIndices[Index]
220
+ const First = Incoming.Starts[Index]
221
+ const Last = First + Incoming.Counts[Index] - 1
222
+
223
+ if CallIndex >= ResponseFlag then
224
+ Resolve(Definition, CallIndex - ResponseFlag, Incoming.Successes[Index], table.unpack(Values, First, Last))
225
+ elseif CallIndex ~= EventIndex then
226
+ ThreadPool.Defer(Respond, Definition, CallIndex, table.unpack(Values, First, Last))
227
+ elseif First == Last then
228
+ Definition.OnClientEvent:Fire(Values[First])
229
+ else
230
+ Definition.OnClientEvent:Fire(table.unpack(Values, First, Last))
231
+ end
232
+ end
233
+ end
234
+
235
+ const function Process(Source: buffer, Attached: { any }, Offset: number, StartInstances: number): number?
236
+ const Ok, Result = pcall(Wire.Decode, Incoming, Source, Attached, false, Offset, StartInstances)
237
+
238
+ if not Ok then
239
+ Wire.Release(Incoming)
240
+ warn(`[swiftpacket] could not read a batch from the server: {tostring(Result)}`)
241
+
242
+ return nil
243
+ end
244
+
245
+ Dispatch()
246
+ Wire.Release(Incoming)
247
+
248
+ return Result
249
+ end
250
+
251
+ const function Retry()
252
+ RetryScheduled = false
253
+
254
+ local Index = 1
255
+
256
+ while Index <= #HeldBatches do
257
+ const Entry = HeldBatches[Index]
258
+
259
+ if Registry.ById[Entry.MissingId] == nil then
260
+ Index += 1
261
+
262
+ continue
263
+ end
264
+
265
+ const MissingId = Process(Entry.Source, Entry.Instances, Entry.Offset, Entry.InstancesOffset)
266
+
267
+ if MissingId then
268
+ Entry.Offset = Incoming.StopOffset
269
+ Entry.InstancesOffset = Incoming.StopInstances
270
+ Entry.MissingId = MissingId
271
+ Index += 1
272
+ else
273
+ table.remove(HeldBatches, Index)
274
+ end
275
+ end
276
+ end
277
+
278
+ const function ScheduleRetry()
279
+ if RetryScheduled or HeldBatches[1] == nil then
280
+ return
281
+ end
282
+
283
+ RetryScheduled = true
284
+ task.defer(Retry)
285
+ end
286
+
287
+ const function Assign(Definition: Definition, Id: number)
288
+ const Previous = Definition.Id
289
+
290
+ if Previous and Registry.ById[Previous] == Definition then
291
+ Registry.ById[Previous] = nil
292
+ end
293
+
294
+ Definition.Id = Id
295
+ Registry.ById[Id] = Definition
296
+
297
+ const Bodies = Queued[Definition]
298
+ Queued[Definition] = nil
299
+
300
+ if Bodies then
301
+ for Index, Body in Bodies do
302
+ Deliver(Definition, Id, Body)
303
+ end
304
+ end
305
+
306
+ const Threads = Waiting[Definition]
307
+ Waiting[Definition] = nil
308
+
309
+ if Threads then
310
+ for Index, Thread in Threads do
311
+ task.spawn(Thread)
312
+ end
313
+ end
314
+
315
+ ScheduleRetry()
316
+ end
317
+
318
+ function Client.Register(Definition: Definition)
319
+ const Remote = ReliableRemote
320
+
321
+ if Remote == nil then
322
+ error("[swiftpacket] the client transport has not started", 0)
323
+ end
324
+
325
+ Definitions[Definition.Name] = Definition
326
+
327
+ const Id = Remote:GetAttribute(Definition.Name)
328
+
329
+ if type(Id) == "number" then
330
+ Assign(Definition, Id)
331
+
332
+ return
333
+ end
334
+
335
+ task.delay(5, function()
336
+ if Definition.Id == nil then
337
+ warn(`[swiftpacket] {Definition.Name} has no id after 5 seconds, define it on the server too`)
338
+ end
339
+ end)
340
+ end
341
+
342
+ const function Receive(Source: any, Instances: any)
343
+ if typeof(Source) ~= "buffer" then
344
+ return
345
+ end
346
+
347
+ const Attached = if type(Instances) == "table" then Instances else NoInstances
348
+ const MissingId = Process(Source, Attached, 0, 0)
349
+
350
+ if MissingId then
351
+ table.insert(HeldBatches, {
352
+ Source = Source,
353
+ Instances = Attached,
354
+ Offset = Incoming.StopOffset,
355
+ InstancesOffset = Incoming.StopInstances,
356
+ MissingId = MissingId,
357
+ ReceivedAt = os.clock(),
358
+ })
359
+ end
360
+ end
361
+
362
+ const function FireSegment(Remote: RemoteEvent | UnreliableRemoteEvent, Segment: Cursor.Cursor)
363
+ if Segment.Offset == 0 then
364
+ return
365
+ end
366
+
367
+ const Data, Attached = Cursor.Take(Segment)
368
+ Cursor.Reset(Segment)
369
+
370
+ if Remote:IsA("RemoteEvent") then
371
+ if Attached then
372
+ Remote:FireServer(Data, Attached)
373
+ else
374
+ Remote:FireServer(Data)
375
+ end
376
+ elseif Remote:IsA("UnreliableRemoteEvent") then
377
+ if Attached then
378
+ Remote:FireServer(Data, Attached)
379
+ else
380
+ Remote:FireServer(Data)
381
+ end
382
+ end
383
+ end
384
+
385
+ const function Flush()
386
+ const ReliableTarget = ReliableRemote
387
+ const UnreliableTarget = UnreliableRemote
388
+
389
+ if ReliableTarget == nil or UnreliableTarget == nil then
390
+ return
391
+ end
392
+
393
+ FireSegment(ReliableTarget, Reliable)
394
+
395
+ for Index = 1, UnreliableCount do
396
+ FireSegment(UnreliableTarget, Unreliable[Index])
397
+ end
398
+
399
+ UnreliableCount = 1
400
+ end
401
+
402
+ const function Expire()
403
+ const Now = os.clock()
404
+
405
+ for CallIndex, Call in PendingCalls do
406
+ if Now >= Call.Deadline then
407
+ PendingCalls[CallIndex] = nil
408
+ task.defer(Call.Thread, Wire.Unpack(Call.Definition.TimeoutValues))
409
+ end
410
+ end
411
+
412
+ for Index = #HeldBatches, 1, -1 do
413
+ const Entry = HeldBatches[Index]
414
+
415
+ if Now - Entry.ReceivedAt >= Configuration.HoldSeconds then
416
+ table.remove(HeldBatches, Index)
417
+ warn(
418
+ `[swiftpacket] dropped messages after the unknown packet id {Entry.MissingId}, define it on the client too`
419
+ )
420
+ end
421
+ end
422
+ end
423
+
424
+ const function FindRemote(Root: Instance, Name: string): Instance
425
+ const Remote = Root:WaitForChild(Name, 10)
426
+
427
+ if Remote == nil then
428
+ error(`[swiftpacket] could not find {Root:GetFullName()}.{Name}, require swiftpacket on the server first`, 0)
429
+ end
430
+
431
+ return Remote
432
+ end
433
+
434
+ function Client.Start(Root: Instance)
435
+ if ReliableRemote then
436
+ return
437
+ end
438
+
439
+ const ReliableInstance = FindRemote(Root, "Reliable")
440
+ const UnreliableInstance = FindRemote(Root, "Unreliable")
441
+
442
+ if not ReliableInstance:IsA("RemoteEvent") or not UnreliableInstance:IsA("UnreliableRemoteEvent") then
443
+ error(`[swiftpacket] the remotes under {Root:GetFullName()} have the wrong classes`, 0)
444
+ end
445
+
446
+ ReliableRemote = ReliableInstance
447
+ UnreliableRemote = UnreliableInstance
448
+
449
+ ReliableInstance.OnClientEvent:Connect(Receive)
450
+ UnreliableInstance.OnClientEvent:Connect(Receive)
451
+
452
+ ReliableInstance.AttributeChanged:Connect(function(Name: string)
453
+ const Definition = Definitions[Name]
454
+ const Id = ReliableInstance:GetAttribute(Name)
455
+
456
+ if Definition and type(Id) == "number" then
457
+ Assign(Definition, Id)
458
+ end
459
+ end)
460
+
461
+ RunService.Heartbeat:Connect(function(DeltaTime: number)
462
+ Expire()
463
+ Elapsed += DeltaTime
464
+
465
+ if Elapsed >= Configuration.FlushRate then
466
+ Elapsed %= Configuration.FlushRate
467
+ task.defer(Flush)
468
+ end
469
+ end)
470
+ end
471
+
472
+ return Client
@@ -0,0 +1,74 @@
1
+ --!strict
2
+ --!optimize 2
3
+
4
+ --[[
5
+ Author: @skaterstudios
6
+ Website: skaterstudios.com
7
+ GitHub: github.com/skatingii/SwiftPacket
8
+ Discord Server: discord.gg/skaterstudios
9
+
10
+ Licensed under the MIT License. You may use, modify, redistribute and sell
11
+ this system freely, as long as this notice and the LICENSE file stay with it.
12
+ ]]
13
+
14
+ const RunService = game:GetService("RunService")
15
+
16
+ export type Options = {
17
+ Logging: boolean?,
18
+ IncomingBytesPerSecond: number?,
19
+ UnreliableByteLimit: number?,
20
+ }
21
+
22
+ const Configuration = {
23
+ Logging = RunService:IsStudio(),
24
+ IncomingBytesPerSecond = 64000,
25
+ UnreliableByteLimit = 900,
26
+ FlushRate = 1 / 60,
27
+ MessageCost = 8,
28
+ HoldSeconds = 10,
29
+ CallCapacity = 32768,
30
+ }
31
+
32
+ const OptionTypes: { [string]: string } = {
33
+ Logging = "boolean",
34
+ IncomingBytesPerSecond = "number",
35
+ UnreliableByteLimit = "number",
36
+ }
37
+
38
+ function Configuration.Configure(Options: { [string]: any })
39
+ for Key, Value in Options do
40
+ const Expected = OptionTypes[Key]
41
+
42
+ if Expected == nil then
43
+ error(`[swiftpacket] unknown option {Key}`, 2)
44
+ end
45
+
46
+ if type(Value) ~= Expected then
47
+ error(`[swiftpacket] option {Key} must be a {Expected}, got {typeof(Value)}`, 2)
48
+ end
49
+ end
50
+
51
+ const Logging = Options.Logging
52
+ const IncomingBytesPerSecond = Options.IncomingBytesPerSecond
53
+ const UnreliableByteLimit = Options.UnreliableByteLimit
54
+
55
+ if Logging ~= nil then
56
+ Configuration.Logging = Logging
57
+ end
58
+
59
+ if IncomingBytesPerSecond ~= nil then
60
+ Configuration.IncomingBytesPerSecond = math.max(IncomingBytesPerSecond, 1000)
61
+ end
62
+
63
+ if UnreliableByteLimit ~= nil then
64
+ Configuration.UnreliableByteLimit = math.clamp(UnreliableByteLimit, 64, 900)
65
+ end
66
+ end
67
+
68
+ function Configuration.Warn(Message: string)
69
+ if Configuration.Logging then
70
+ warn(Message)
71
+ end
72
+ end
73
+
74
+ return Configuration